* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Portions Copyright (c) The OpenSSL Project 1999 */
#ifndef DISABLE_OCSP
# include <openssl/ocsp.h>
#endif
-#ifdef EXPERIMENTAL_DANE
-# include <danessl.h>
+#ifdef SUPPORT_DANE
+# include "danessl.h"
#endif
# define EXIM_HAVE_RAND_PSEUDO
#endif
#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
-# define EXIM_HAVE_SHA256
+# define EXIM_HAVE_SHA256 /*MMMM*/
#endif
/*
#ifndef LIBRESSL_VERSION_NUMBER
# if OPENSSL_VERSION_NUMBER >= 0x010100000L
# define EXIM_HAVE_OPENSSL_CHECKHOST
+# define EXIM_HAVE_OPENSSL_DH_BITS
+# define EXIM_HAVE_OPENSSL_TLS_METHOD
# endif
# if OPENSSL_VERSION_NUMBER >= 0x010000000L \
&& (OPENSSL_VERSION_NUMBER & 0x0000ff000L) >= 0x000002000L
|| LIBRESSL_VERSION_NUMBER >= 0x20010000L
# if !defined(OPENSSL_NO_ECDH)
# if OPENSSL_VERSION_NUMBER >= 0x0090800fL
-# define EXIM_HAVE_ECDH
+# define EXIM_HAVE_ECDH /*MMMM*/
# endif
# if OPENSSL_VERSION_NUMBER >= 0x10002000L
-# if OPENSSL_VERSION_NUMBER < 0x10100000L
-# define EXIM_HAVE_OPENSSL_ECDH_AUTO
-# endif
# define EXIM_HAVE_OPENSSL_EC_NIST2NID
# endif
# endif
# define DISABLE_OCSP
#endif
+#ifdef EXIM_HAVE_OPENSSL_CHECKHOST
+# include <openssl/x509v3.h>
+#endif
+
/* Structure for collecting random data for seeding. */
typedef struct randstuff {
Simple case: client, `client_ctx`
As a client, we can be doing a callout or cut-through delivery while receiving
a message. So we have a client context, which should have options initialised
- from the SMTP Transport.
+ from the SMTP Transport. We may also concurrently want to make TLS connections
+ to utility daemons, so client-contexts are allocated and passed around in call
+ args rather than using a gobal.
Server:
There are two cases: with and without ServerNameIndication from the client.
configuration.
*/
-static SSL_CTX *client_ctx = NULL;
+typedef struct {
+ SSL_CTX * ctx;
+ SSL * ssl;
+} exim_openssl_client_tls_ctx;
+
static SSL_CTX *server_ctx = NULL;
-static SSL *client_ssl = NULL;
static SSL *server_ssl = NULL;
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
typedef struct tls_ext_ctx_cb {
uschar *certificate;
uschar *privatekey;
-#ifndef DISABLE_OCSP
BOOL is_server;
+#ifndef DISABLE_OCSP
STACK_OF(X509) *verify_stack; /* chain for verifying the proof */
union {
struct {
static int
setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
- int (*cert_vfy_cb)(int, X509_STORE_CTX *) );
+ int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr );
/* Callbacks */
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
host NULL if setting up a server;
the connected host if setting up a client
msg error message or NULL if we should ask OpenSSL
+ errstr pointer to output error message
Returns: OK/DEFER/FAIL
*/
static int
-tls_error(uschar * prefix, const host_item * host, uschar * msg)
+tls_error(uschar * prefix, const host_item * host, uschar * msg, uschar ** errstr)
{
if (!msg)
{
ERR_error_string(ERR_get_error(), ssl_errstring);
- msg = (uschar *)ssl_errstring;
+ msg = US ssl_errstring;
}
-if (host)
- {
- log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s): %s",
- host->name, host->address, prefix, msg);
- return FAIL;
- }
-else
- {
- uschar *conn_info = smtp_get_connection_info();
- if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
- conn_info += 5;
- /* I'd like to get separated H= here, but too hard for now */
- log_write(0, LOG_MAIN, "TLS error on %s (%s): %s",
- conn_info, prefix, msg);
- return DEFER;
- }
+if (errstr) *errstr = string_sprintf("(%s): %s", prefix, msg);
+return host ? FAIL : DEFER;
}
-#ifdef EXIM_HAVE_EPHEM_RSA_KEX
/*************************************************
* Callback to generate RSA key *
*************************************************/
/*
Arguments:
- s SSL connection
+ s SSL connection (not used)
export not used
keylength keylength
}
return rsa_key;
}
-#endif
if (preverify_ok == 0)
{
- log_write(0, LOG_MAIN, "[%s] SSL verify error: depth=%d error=%s cert=%s",
- tlsp == &tls_out ? deliver_host_address : sender_host_address,
- depth,
- X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)),
- dn);
+ uschar * extra = verify_mode ? string_sprintf(" (during %c-verify for [%s])",
+ *verify_mode, sender_host_address)
+ : US"";
+ log_write(0, LOG_MAIN, "[%s] SSL verify error%s: depth=%d error=%s cert=%s",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address,
+ extra, depth,
+ X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)), dn);
*calledp = TRUE;
if (!*optionalp)
{
if ( tlsp == &tls_out
&& ((verify_cert_hostnames = client_static_cbinfo->verify_cert_hostnames)))
- /* client, wanting hostname check */
+ /* client, wanting hostname check */
{
#ifdef EXIM_HAVE_OPENSSL_CHECKHOST
if (rc < 0)
{
log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
- tlsp == &tls_out ? deliver_host_address : sender_host_address);
+ tlsp == &tls_out ? deliver_host_address : sender_host_address);
name = NULL;
}
break;
if (!tls_is_name_for_cert(verify_cert_hostnames, cert))
#endif
{
+ uschar * extra = verify_mode
+ ? string_sprintf(" (during %c-verify for [%s])",
+ *verify_mode, sender_host_address)
+ : US"";
log_write(0, LOG_MAIN,
- "[%s] SSL verify error: certificate name mismatch: \"%s\"",
- tlsp == &tls_out ? deliver_host_address : sender_host_address,
- dn);
+ "[%s] SSL verify error%s: certificate name mismatch: DN=\"%s\" H=\"%s\"",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address,
+ extra, dn, verify_cert_hostnames);
*calledp = TRUE;
if (!*optionalp)
{
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
/* This gets called *by* the dane library verify callback, which interposes
itself.
{
X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
uschar dn[256];
-#ifndef DISABLE_EVENT
int depth = X509_STORE_CTX_get_error_depth(x509ctx);
+#ifndef DISABLE_EVENT
BOOL dummy_called, optional = FALSE;
#endif
#endif
if (preverify_ok == 1)
- tls_out.dane_verified =
- tls_out.certificate_verified = TRUE;
+ {
+ tls_out.dane_verified = tls_out.certificate_verified = TRUE;
+#ifndef DISABLE_OCSP
+ if (client_static_cbinfo->u_ocsp.client.verify_store)
+ { /* client, wanting stapling */
+ /* Add the server cert's signing chain as the one
+ for the verification of the OCSP stapled information. */
+
+ if (!X509_STORE_add_cert(client_static_cbinfo->u_ocsp.client.verify_store,
+ cert))
+ ERR_clear_error();
+ sk_X509_push(client_static_cbinfo->verify_stack, cert);
+ }
+#endif
+ }
else
{
int err = X509_STORE_CTX_get_error(x509ctx);
return preverify_ok;
}
-#endif /*EXPERIMENTAL_DANE*/
+#endif /*SUPPORT_DANE*/
/*************************************************
sctx The current SSL CTX (inbound or outbound)
dhparam DH parameter file or fixed parameter identity string
host connected host, if client; NULL if server
+ errstr error string pointer
Returns: TRUE if OK (nothing to set up, or setup worked)
*/
static BOOL
-init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host)
+init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host, uschar ** errstr)
{
BIO *bio;
DH *dh;
uschar *dhexpanded;
const char *pem;
+int dh_bitsize;
-if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded))
+if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr))
return FALSE;
if (!dhexpanded || !*dhexpanded)
if (!(bio = BIO_new_file(CS dhexpanded, "r")))
{
tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
- host, US strerror(errno));
+ host, US strerror(errno), errstr);
return FALSE;
}
}
if (!(pem = std_dh_prime_named(dhexpanded)))
{
tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
- host, US strerror(errno));
+ host, US strerror(errno), errstr);
return FALSE;
}
bio = BIO_new_mem_buf(CS pem, -1);
{
BIO_free(bio);
tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
- host, NULL);
+ host, NULL, errstr);
return FALSE;
}
+/* note: our default limit of 2236 is not a multiple of 8; the limit comes from
+ * an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with
+ * 2236. But older OpenSSL can only report in bytes (octets), not bits.
+ * If someone wants to dance at the edge, then they can raise the limit or use
+ * current libraries. */
+#ifdef EXIM_HAVE_OPENSSL_DH_BITS
+/* Added in commit 26c79d5641d; `git describe --contains` says OpenSSL_1_1_0-pre1~1022
+ * This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */
+dh_bitsize = DH_bits(dh);
+#else
+dh_bitsize = 8 * DH_size(dh);
+#endif
+
/* Even if it is larger, we silently return success rather than cause things
* to fail out, so that a too-large DH will not knock out all TLS; it's a
* debatable choice. */
-if ((8*DH_size(dh)) > tls_dh_max_bits)
+if (dh_bitsize > tls_dh_max_bits)
{
DEBUG(D_tls)
- debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d",
- 8*DH_size(dh), tls_dh_max_bits);
+ debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d\n",
+ dh_bitsize, tls_dh_max_bits);
}
else
{
SSL_CTX_set_tmp_dh(sctx, dh);
DEBUG(D_tls)
debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
- dhexpanded ? dhexpanded : US"default", 8*DH_size(dh));
+ dhexpanded ? dhexpanded : US"default", dh_bitsize);
}
DH_free(dh);
Arguments:
sctx The current SSL CTX (inbound or outbound)
host connected host, if client; NULL if server
+ errstr error string pointer
Returns: TRUE if OK (nothing to set up, or setup worked)
*/
static BOOL
-init_ecdh(SSL_CTX * sctx, host_item * host)
+init_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr)
{
#ifdef OPENSSL_NO_ECDH
return TRUE;
return TRUE;
# else
-if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve))
+if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr))
return FALSE;
if (!exp_curve || !*exp_curve)
return TRUE;
-# ifdef EXIM_HAVE_OPENSSL_ECDH_AUTO
-/* check if new enough library to support auto ECDH temp key parameter selection */
+/* "auto" needs to be handled carefully.
+ * OpenSSL < 1.0.2: we do not select anything, but fallback to prime256v1
+ * OpenSSL < 1.1.0: we have to call SSL_CTX_set_ecdh_auto
+ * (openssl/ssl.h defines SSL_CTRL_SET_ECDH_AUTO)
+ * OpenSSL >= 1.1.0: we do not set anything, the libray does autoselection
+ * https://github.com/openssl/openssl/commit/fe6ef2472db933f01b59cad82aa925736935984b
+ */
if (Ustrcmp(exp_curve, "auto") == 0)
{
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+ DEBUG(D_tls) debug_printf(
+ "ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n");
+ exp_curve = US"prime256v1";
+#else
+# if defined SSL_CTRL_SET_ECDH_AUTO
DEBUG(D_tls) debug_printf(
- "ECDH temp key parameter settings: OpenSSL 1.2+ autoselection\n");
+ "ECDH OpenSSL 1.0.2+ temp key parameter settings: autoselection\n");
SSL_CTX_set_ecdh_auto(sctx, 1);
return TRUE;
+# else
+ DEBUG(D_tls) debug_printf(
+ "ECDH OpenSSL 1.1.0+ temp key parameter settings: default selection\n");
+ return TRUE;
+# endif
+#endif
}
-# endif
DEBUG(D_tls) debug_printf("ECDH: curve '%s'\n", exp_curve);
if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef
# endif
)
{
- tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'",
- exp_curve),
- host, NULL);
+ tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve),
+ host, NULL, errstr);
return FALSE;
}
if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
{
- tls_error(US"Unable to create ec curve", host, NULL);
+ tls_error(US"Unable to create ec curve", host, NULL, errstr);
return FALSE;
}
not to the stability of the interface. */
if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0))
- tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL);
+ tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL, errstr);
else
DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve);
OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT
OCSP_TRUSTOTHER OCSP_NOINTERN */
-/* This does a full verify on the OCSP proof before we load it for serviing
+/* This does a full verify on the OCSP proof before we load it for serving
up; possibly overkill - just date-checks might be nice enough.
OCSP_basic_verify takes a "store" arg, but does not
We do not free the stack since it could be needed a second time for
SNI handling.
-Seperately we might try to replace using OCSP_basic_verify() - which seems to not
-be a public interface into the OpenSSL library (there's no manual entry) -
+Separately we might try to replace using OCSP_basic_verify() - which seems to not
+be a public interface into the OpenSSL library (there's no manual entry) -
But what with? We also use OCSP_basic_verify in the client stapling callback.
-And there we NEED it; we miust verify that status... unless the
+And there we NEED it; we must verify that status... unless the
library does it for us anyway? */
if ((i = OCSP_basic_verify(basic_response, sk, NULL, verify_flags)) < 0)
}
supply_response:
- cbinfo->u_ocsp.server.response = resp;
+ cbinfo->u_ocsp.server.response = resp; /*XXX stack?*/
return;
bad:
{
extern char ** environ;
uschar ** p;
- if (environ) for (p = USS environ; *p != NULL; p++)
+ if (environ) for (p = USS environ; *p; p++)
if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
{
DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
/* Create and install a selfsigned certificate, for use in server mode */
static int
-tls_install_selfsign(SSL_CTX * sctx)
+tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr)
{
X509 * x509 = NULL;
EVP_PKEY * pkey;
goto err;
where = US"generating pkey";
- /* deprecated, use RSA_generate_key_ex() */
-if (!(rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL)))
+if (!(rsa = rsa_callback(NULL, 0, 1024)))
goto err;
-where = US"assiging pkey";
+where = US"assigning pkey";
if (!EVP_PKEY_assign_RSA(pkey, rsa))
goto err;
X509_set_version(x509, 2); /* N+1 - version 3 */
-ASN1_INTEGER_set(X509_get_serialNumber(x509), 0);
+ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60); /* 1 hour */
X509_set_pubkey(x509, pkey);
return OK;
err:
- (void) tls_error(where, NULL, NULL);
+ (void) tls_error(where, NULL, NULL, errstr);
if (x509) X509_free(x509);
if (pkey) EVP_PKEY_free(pkey);
return DEFER;
+static int
+tls_add_certfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+ uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_certificate file %s\n", file);
+if (!SSL_CTX_use_certificate_chain_file(sctx, CS file))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_certificate_chain_file file=%s", file),
+ cbinfo->host, NULL, errstr);
+return 0;
+}
+
+static int
+tls_add_pkeyfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+ uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", file);
+if (!SSL_CTX_use_PrivateKey_file(sctx, CS file, SSL_FILETYPE_PEM))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_PrivateKey_file file=%s", file), cbinfo->host, NULL, errstr);
+return 0;
+}
+
+
/*************************************************
* Expand key and cert file specs *
*************************************************/
Arguments:
sctx the SSL_CTX* to update
cbinfo various parts of session state
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
static int
-tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo)
+tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo,
+ uschar ** errstr)
{
uschar *expanded;
if (!cbinfo->certificate)
{
- if (cbinfo->host) /* client */
+ if (!cbinfo->is_server) /* client */
return OK;
- /* server */
- if (tls_install_selfsign(sctx) != OK)
+ /* server */
+ if (tls_install_selfsign(sctx, errstr) != OK)
return DEFER;
}
else
{
+ int err;
+
if (Ustrstr(cbinfo->certificate, US"tls_sni") ||
Ustrstr(cbinfo->certificate, US"tls_in_sni") ||
Ustrstr(cbinfo->certificate, US"tls_out_sni")
)
reexpand_tls_files_for_sni = TRUE;
- if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded))
+ if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded, errstr))
return DEFER;
- if (expanded != NULL)
- {
- DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded);
- if (!SSL_CTX_use_certificate_chain_file(sctx, CS expanded))
- return tls_error(string_sprintf(
- "SSL_CTX_use_certificate_chain_file file=%s", expanded),
- cbinfo->host, NULL);
- }
+ if (expanded)
+ if (cbinfo->is_server)
+ {
+ const uschar * file_list = expanded;
+ int sep = 0;
+ uschar * file;
+
+ while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+ if ((err = tls_add_certfile(sctx, cbinfo, file, errstr)))
+ return err;
+ }
+ else /* would there ever be a need for multiple client certs? */
+ if ((err = tls_add_certfile(sctx, cbinfo, expanded, errstr)))
+ return err;
if (cbinfo->privatekey != NULL &&
- !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded))
+ !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr))
return DEFER;
/* If expansion was forced to fail, key_expanded will be NULL. If the result
key is in the same file as the certificate. */
if (expanded && *expanded)
- {
- DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded);
- if (!SSL_CTX_use_PrivateKey_file(sctx, CS expanded, SSL_FILETYPE_PEM))
- return tls_error(string_sprintf(
- "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL);
- }
+ if (cbinfo->is_server)
+ {
+ const uschar * file_list = expanded;
+ int sep = 0;
+ uschar * file;
+
+ while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+ if ((err = tls_add_pkeyfile(sctx, cbinfo, file, errstr)))
+ return err;
+ }
+ else /* would there ever be a need for multiple client certs? */
+ if ((err = tls_add_pkeyfile(sctx, cbinfo, expanded, errstr)))
+ return err;
}
#ifndef DISABLE_OCSP
if (cbinfo->is_server && cbinfo->u_ocsp.server.file)
{
- if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded))
+ /*XXX stack*/
+ if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded, errstr))
return DEFER;
if (expanded && *expanded)
tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
int rc;
int old_pool = store_pool;
+uschar * dummy_errstr;
if (!servername)
return SSL_TLSEXT_ERR_OK;
not confident that memcpy wouldn't break some internal reference counting.
Especially since there's a references struct member, which would be off. */
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(server_sni = SSL_CTX_new(TLS_server_method())))
+#else
if (!(server_sni = SSL_CTX_new(SSLv23_server_method())))
+#endif
{
ERR_error_string(ERR_get_error(), ssl_errstring);
DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb);
SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
-if ( !init_dh(server_sni, cbinfo->dhparam, NULL)
- || !init_ecdh(server_sni, NULL)
+if ( !init_dh(server_sni, cbinfo->dhparam, NULL, &dummy_errstr)
+ || !init_ecdh(server_sni, NULL, &dummy_errstr)
)
return SSL_TLSEXT_ERR_NOACK;
#endif
if ((rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE,
- verify_callback_server)) != OK)
+ verify_callback_server, &dummy_errstr)) != OK)
return SSL_TLSEXT_ERR_NOACK;
/* do this after setup_certs, because this can require the certs for verifying
OCSP information. */
-if ((rc = tls_expand_session_files(server_sni, cbinfo)) != OK)
+if ((rc = tls_expand_session_files(server_sni, cbinfo, &dummy_errstr)) != OK)
return SSL_TLSEXT_ERR_NOACK;
DEBUG(D_tls) debug_printf("Switching SSL context.\n");
tls_server_stapling_cb(SSL *s, void *arg)
{
const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
-uschar *response_der;
+uschar *response_der; /*XXX blob */
int response_der_len;
+/*XXX stack: use SSL_get_certificate() to see which cert; from that work
+out which ocsp blob to send. Unfortunately, SSL_get_certificate is known
+buggy in current OpenSSL; it returns the last cert loaded always rather than
+the one actually presented. So we can't support a stack of OCSP proofs at
+this time. */
+
DEBUG(D_tls)
debug_printf("Received TLS status request (OCSP stapling); %s response\n",
cbinfo->u_ocsp.server.response ? "have" : "lack");
return SSL_TLSEXT_ERR_NOACK;
response_der = NULL;
-response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,
+response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, /*XXX stack*/
&response_der);
if (response_der_len <= 0)
return SSL_TLSEXT_ERR_NOACK;
int status, reason;
ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
- DEBUG(D_tls) bp = BIO_new_fp(stderr, BIO_NOCLOSE);
+ DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
/*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */
cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
{
tls_out.ocsp = OCSP_FAILED;
- if (LOGGING(tls_cipher))
- log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable");
+ if (LOGGING(tls_cipher)) log_write(0, LOG_MAIN,
+ "Received TLS cert status response, itself unverifiable: %s",
+ ERR_reason_error_string(ERR_peek_error()));
BIO_printf(bp, "OCSP response verify failure\n");
ERR_print_errors(bp);
+ OCSP_RESPONSE_print(bp, rsp, 0);
goto failed;
}
ocsp_file file of stapling info (server); flag for require ocsp (client)
addr address if client; NULL if server (for some randomness)
cbp place to put allocated callback context
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
uschar *privatekey,
#ifndef DISABLE_OCSP
- uschar *ocsp_file,
+ uschar *ocsp_file, /*XXX stack, in server*/
#endif
- address_item *addr, tls_ext_ctx_cb ** cbp)
+ address_item *addr, tls_ext_ctx_cb ** cbp, uschar ** errstr)
{
+SSL_CTX * ctx;
long init_options;
int rc;
-BOOL okay;
tls_ext_ctx_cb * cbinfo;
cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
cbinfo->certificate = certificate;
cbinfo->privatekey = privatekey;
+cbinfo->is_server = host==NULL;
#ifndef DISABLE_OCSP
cbinfo->verify_stack = NULL;
-if ((cbinfo->is_server = host==NULL))
+if (!host)
{
cbinfo->u_ocsp.server.file = ocsp_file;
cbinfo->u_ocsp.server.file_expanded = NULL;
By disabling with openssl_options, we can let admins re-enable with the
existing knob. */
-*ctxp = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method());
-
-if (!*ctxp) return tls_error(US"SSL_CTX_new", host, NULL);
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(ctx = SSL_CTX_new(host ? TLS_client_method() : TLS_server_method())))
+#else
+if (!(ctx = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method())))
+#endif
+ return tls_error(US"SSL_CTX_new", host, NULL, errstr);
/* It turns out that we need to seed the random number generator this early in
order to get the full complement of ciphers to work. It took me roughly a day
gettimeofday(&r.tv, NULL);
r.p = getpid();
- RAND_seed((uschar *)(&r), sizeof(r));
- RAND_seed((uschar *)big_buffer, big_buffer_size);
- if (addr != NULL) RAND_seed((uschar *)addr, sizeof(addr));
+ RAND_seed(US (&r), sizeof(r));
+ RAND_seed(US big_buffer, big_buffer_size);
+ if (addr != NULL) RAND_seed(US addr, sizeof(addr));
if (!RAND_status())
return tls_error(US"RAND_status", host,
- US"unable to seed random number generator");
+ US"unable to seed random number generator", errstr);
}
/* Set up the information callback, which outputs if debugging is at a suitable
level. */
-DEBUG(D_tls) SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
+DEBUG(D_tls) SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
/* Automatically re-try reads/writes after renegotiation. */
-(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);
+(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
/* Apply administrator-supplied work-arounds.
Historically we applied just one requested option,
No OpenSSL version number checks: the options we accept depend upon the
availability of the option value macros from OpenSSL. */
-okay = tls_openssl_options_parse(openssl_options, &init_options);
-if (!okay)
- return tls_error(US"openssl_options parsing failed", host, NULL);
+if (!tls_openssl_options_parse(openssl_options, &init_options))
+ return tls_error(US"openssl_options parsing failed", host, NULL, errstr);
if (init_options)
{
DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
- if (!(SSL_CTX_set_options(*ctxp, init_options)))
+ if (!(SSL_CTX_set_options(ctx, init_options)))
return tls_error(string_sprintf(
- "SSL_CTX_set_option(%#lx)", init_options), host, NULL);
+ "SSL_CTX_set_option(%#lx)", init_options), host, NULL, errstr);
}
else
DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
+/* We'd like to disable session cache unconditionally, but foolish Outlook
+Express clients then give up the first TLS connection and make a second one
+(which works). Only when there is an IMAP service on the same machine.
+Presumably OE is trying to use the cache for A on B. Leave it enabled for
+now, until we work out a decent way of presenting control to the config. It
+will never be used because we use a new context every time. */
+#ifdef notdef
+(void) SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
+#endif
+
/* Initialize with DH parameters if supplied */
/* Initialize ECDH temp key parameter selection */
-if ( !init_dh(*ctxp, dhparam, host)
- || !init_ecdh(*ctxp, host)
+if ( !init_dh(ctx, dhparam, host, errstr)
+ || !init_ecdh(ctx, host, errstr)
)
return DEFER;
/* Set up certificate and key (and perhaps OCSP info) */
-if ((rc = tls_expand_session_files(*ctxp, cbinfo)) != OK)
+if ((rc = tls_expand_session_files(ctx, cbinfo, errstr)) != OK)
return rc;
/* If we need to handle SNI or OCSP, do so */
}
# endif
-if (host == NULL) /* server */
+if (!host) /* server */
{
# ifndef DISABLE_OCSP
/* We check u_ocsp.server.file, not server.response, because we care about if
callback is invoked. */
if (cbinfo->u_ocsp.server.file)
{
- SSL_CTX_set_tlsext_status_cb(server_ctx, tls_server_stapling_cb);
- SSL_CTX_set_tlsext_status_arg(server_ctx, cbinfo);
+ SSL_CTX_set_tlsext_status_cb(ctx, tls_server_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
}
# endif
/* We always do this, so that $tls_sni is available even if not used in
tls_certificate */
- SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
- SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
+ SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
+ SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
}
# ifndef DISABLE_OCSP
else /* client */
DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n");
return FAIL;
}
- SSL_CTX_set_tlsext_status_cb(*ctxp, tls_client_stapling_cb);
- SSL_CTX_set_tlsext_status_arg(*ctxp, cbinfo);
+ SSL_CTX_set_tlsext_status_cb(ctx, tls_client_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
}
# endif
#endif
#ifdef EXIM_HAVE_EPHEM_RSA_KEX
/* Set up the RSA callback */
-SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
+SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
#endif
/* Finally, set the timeout, and we are done */
-SSL_CTX_set_timeout(*ctxp, ssl_session_timeout);
+SSL_CTX_set_timeout(ctx, ssl_session_timeout);
DEBUG(D_tls) debug_printf("Initialized TLS\n");
*cbp = cbinfo;
+*ctxp = ctx;
return OK;
}
static void
construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
{
-/* With OpenSSL 1.0.0a, this needs to be const but the documentation doesn't
+/* With OpenSSL 1.0.0a, 'c' needs to be const but the documentation doesn't
yet reflect that. It should be a safe change anyway, even 0.9.8 versions have
the accessor functions use const in the prototype. */
-const SSL_CIPHER *c;
-const uschar *ver;
-ver = (const uschar *)SSL_get_version(ssl);
+const uschar * ver = CUS SSL_get_version(ssl);
+const SSL_CIPHER * c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
-c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
SSL_CIPHER_get_bits(c, bits);
string_format(cipherbuf, bsize, "%s:%s:%u", ver,
* Set up for verifying certificates *
*************************************************/
+#ifndef DISABLE_OCSP
/* Load certs from file, return TRUE on success */
static BOOL
BIO * bp;
X509 * x;
+while (sk_X509_num(verify_stack) > 0)
+ X509_free(sk_X509_pop(verify_stack));
+
if (!(bp = BIO_new_file(CS file, "r"))) return FALSE;
while ((x = PEM_read_bio_X509(bp, NULL, 0, NULL)))
sk_X509_push(verify_stack, x);
BIO_free(bp);
return TRUE;
}
+#endif
-/* Called by both client and server startup
+/* Called by both client and server startup; on the server possibly
+repeated after a Server Name Indication.
Arguments:
sctx SSL_CTX* to initialise
optional TRUE if called from a server for a host in tls_try_verify_hosts;
otherwise passed as FALSE
cert_vfy_cb Callback function for certificate verification
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
static int
setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
- int (*cert_vfy_cb)(int, X509_STORE_CTX *) )
+ int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr)
{
uschar *expcerts, *expcrl;
-if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
+if (!expand_check(certs, US"tls_verify_certificates", &expcerts, errstr))
return DEFER;
+DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts);
if (expcerts && *expcerts)
{
CA bundle. Then add the ones specified in the config, if any. */
if (!SSL_CTX_set_default_verify_paths(sctx))
- return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
+ return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL, errstr);
if (Ustrcmp(expcerts, "system") != 0)
{
)
{
log_write(0, LOG_MAIN|LOG_PANIC,
- "failed to load cert hain from %s", file);
+ "failed to load cert chain from %s", file);
return DEFER;
}
#endif
if ( (!file || statbuf.st_size > 0)
&& !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
- return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
+ return tls_error(US"SSL_CTX_load_verify_locations", host, NULL, errstr);
/* Load the list of CAs for which we will accept certs, for sending
to the client. This is only for the one-file tls_verify_certificates
variant.
If a list isn't loaded into the server, but
some verify locations are set, the server end appears to make
- a wildcard reqest for client certs.
+ a wildcard request for client certs.
Meanwhile, the client library as default behaviour *ignores* the list
we send over the wire - see man SSL_CTX_set_client_cert_cb.
Because of this, and that the dir variant is likely only used for
{
STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file);
+ SSL_CTX_set_client_CA_list(sctx, names);
DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n",
sk_X509_NAME_num(names));
- SSL_CTX_set_client_CA_list(sctx, names);
}
}
}
OpenSSL will then handle the verify against CA certs and CRLs by
itself in the verify callback." */
- if (!expand_check(crl, US"tls_crl", &expcrl)) return DEFER;
+ if (!expand_check(crl, US"tls_crl", &expcrl, errstr)) return DEFER;
if (expcrl && *expcrl)
{
struct stat statbufcrl;
DEBUG(D_tls) debug_printf("SSL CRL value is a file %s\n", file);
}
if (X509_STORE_load_locations(cvstore, CS file, CS dir) == 0)
- return tls_error(US"X509_STORE_load_locations", host, NULL);
+ return tls_error(US"X509_STORE_load_locations", host, NULL, errstr);
/* setting the flags to check against the complete crl chain */
Arguments:
require_ciphers allowed ciphers
+ errstr pointer to error message
Returns: OK on success
DEFER for errors before the start of the negotiation
- FAIL for errors during the negotation; the server can't
+ FAIL for errors during the negotiation; the server can't
continue running.
*/
int
-tls_server_start(const uschar *require_ciphers)
+tls_server_start(const uschar * require_ciphers, uschar ** errstr)
{
int rc;
-uschar *expciphers;
-tls_ext_ctx_cb *cbinfo;
+uschar * expciphers;
+tls_ext_ctx_cb * cbinfo;
static uschar peerdn[256];
static uschar cipherbuf[256];
/* Check for previous activation */
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
{
- tls_error(US"STARTTLS received after TLS started", NULL, US"");
- smtp_printf("554 Already in TLS\r\n");
+ tls_error(US"STARTTLS received after TLS started", NULL, US"", errstr);
+ smtp_printf("554 Already in TLS\r\n", FALSE);
return FAIL;
}
rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
#ifndef DISABLE_OCSP
- tls_ocsp_file,
+ tls_ocsp_file, /*XXX stack*/
#endif
- NULL, &server_static_cbinfo);
+ NULL, &server_static_cbinfo, errstr);
if (rc != OK) return rc;
cbinfo = server_static_cbinfo;
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers, errstr))
return FAIL;
/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
were historically separated by underscores. So that I can use either form in my
tests, and also for general convenience, we turn underscores into hyphens here.
+
+XXX SSL_CTX_set_cipher_list() is replaced by SSL_CTX_set_ciphersuites()
+for TLS 1.3 . Since we do not call it at present we get the default list:
+TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
*/
if (expciphers)
while (*s != 0) { if (*s == '_') *s = '-'; s++; }
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers))
- return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL);
+ return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr);
cbinfo->server_cipher_list = expciphers;
}
optional, set up appropriately. */
tls_in.certificate_verified = FALSE;
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
tls_in.dane_verified = FALSE;
#endif
server_verify_callback_called = FALSE;
if (verify_check_host(&tls_verify_hosts) == OK)
{
rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
- FALSE, verify_callback_server);
+ FALSE, verify_callback_server, errstr);
if (rc != OK) return rc;
server_verify_optional = FALSE;
}
else if (verify_check_host(&tls_try_verify_hosts) == OK)
{
rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
- TRUE, verify_callback_server);
+ TRUE, verify_callback_server, errstr);
if (rc != OK) return rc;
server_verify_optional = TRUE;
}
/* Prepare for new connection */
-if (!(server_ssl = SSL_new(server_ctx))) return tls_error(US"SSL_new", NULL, NULL);
+if (!(server_ssl = SSL_new(server_ctx)))
+ return tls_error(US"SSL_new", NULL, NULL, errstr);
/* Warning: we used to SSL_clear(ssl) here, it was removed.
*
SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
if (!tls_in.on_connect)
{
- smtp_printf("220 TLS go ahead\r\n");
+ smtp_printf("220 TLS go ahead\r\n", FALSE);
fflush(smtp_out);
}
if (rc <= 0)
{
- tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL);
- if (ERR_get_error() == 0)
- log_write(0, LOG_MAIN,
- "TLS client disconnected cleanly (rejected our certificate?)");
+ (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
return FAIL;
}
smtp_read_response()/ip_recv().
Hence no need to duplicate for _in and _out.
*/
-ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
+if (!ssl_xfer_buffer) ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
-ssl_xfer_eof = ssl_xfer_error = 0;
+ssl_xfer_eof = ssl_xfer_error = FALSE;
receive_getc = tls_getc;
+receive_getbuf = tls_getbuf;
receive_get_cache = tls_get_cache;
receive_ungetc = tls_ungetc;
receive_feof = tls_feof;
receive_ferror = tls_ferror;
receive_smtp_buffered = tls_smtp_buffered;
-tls_in.active = fileno(smtp_out);
+tls_in.active.sock = fileno(smtp_out);
+tls_in.active.tls_ctx = NULL; /* not using explicit ctx for server-side */
return OK;
}
static int
tls_client_basic_ctx_init(SSL_CTX * ctx,
- host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo
- )
+ host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo,
+ uschar ** errstr)
{
int rc;
/* stick to the old behaviour for compatibility if tls_verify_certificates is
if ( ( !ob->tls_verify_hosts
&& (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
)
- || (verify_check_given_host(&ob->tls_verify_hosts, host) == OK)
+ || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
)
client_verify_optional = FALSE;
else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
return OK;
if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
- ob->tls_crl, host, client_verify_optional, verify_callback_client)) != OK)
+ ob->tls_crl, host, client_verify_optional, verify_callback_client,
+ errstr)) != OK)
return rc;
if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
static int
-dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa)
+dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa, uschar ** errstr)
{
dns_record * rr;
dns_scan dnss;
int found = 0;
if (DANESSL_init(ssl, NULL, hostnames) != 1)
- return tls_error(US"hostnames load", host, NULL);
+ return tls_error(US"hostnames load", host, NULL, errstr);
for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
rr;
switch (DANESSL_add_tlsa(ssl, usage, selector, mdname, p, rr->size - 3))
{
default:
- return tls_error(US"tlsa load", host, NULL);
+ return tls_error(US"tlsa load", host, NULL, errstr);
case 0: /* action not taken */
case 1: break;
}
log_write(0, LOG_MAIN, "DANE error: No usable TLSA records");
return DEFER;
}
-#endif /*EXPERIMENTAL_DANE*/
+#endif /*SUPPORT_DANE*/
Argument:
fd the fd of the connection
- host connected host (for messages)
- addr the first address
+ host connected host (for messages and option-tests)
+ addr the first address (for some randomness; can be NULL)
tb transport (always smtp)
tlsa_dnsa tlsa lookup, if DANE, else null
+ tlsp record details of channel configuration here; must be non-NULL
+ errstr error string pointer
-Returns: OK on success
- FAIL otherwise - note that tls_error() will not give DEFER
- because this is not a server
+Returns: Pointer to TLS session context, or NULL on error
*/
-int
+void *
tls_client_start(int fd, host_item *host, address_item *addr,
- transport_instance *tb
-#ifdef EXPERIMENTAL_DANE
- , dns_answer * tlsa_dnsa
+ transport_instance * tb,
+#ifdef SUPPORT_DANE
+ dns_answer * tlsa_dnsa,
#endif
- )
+ tls_support * tlsp, uschar ** errstr)
{
-smtp_transport_options_block * ob =
- (smtp_transport_options_block *)tb->options_block;
+smtp_transport_options_block * ob = tb
+ ? (smtp_transport_options_block *)tb->options_block
+ : &smtp_transport_option_defaults;
+exim_openssl_client_tls_ctx * exim_client_ctx;
static uschar peerdn[256];
uschar * expciphers;
int rc;
BOOL require_ocsp = FALSE;
#endif
-#ifdef EXPERIMENTAL_DANE
-tls_out.tlsa_usage = 0;
+rc = store_pool;
+store_pool = POOL_PERM;
+exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx));
+store_pool = rc;
+
+#ifdef SUPPORT_DANE
+tlsp->tlsa_usage = 0;
#endif
#ifndef DISABLE_OCSP
{
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
if ( tlsa_dnsa
&& ob->hosts_request_ocsp[0] == '*'
&& ob->hosts_request_ocsp[1] == '\0'
verify_check_given_host(&ob->hosts_require_ocsp, host) == OK))
request_ocsp = TRUE;
else
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
if (!request_ocsp)
# endif
request_ocsp =
}
#endif
-rc = tls_init(&client_ctx, host, NULL,
+rc = tls_init(&exim_client_ctx->ctx, host, NULL,
ob->tls_certificate, ob->tls_privatekey,
#ifndef DISABLE_OCSP
(void *)(long)request_ocsp,
#endif
- addr, &client_static_cbinfo);
-if (rc != OK) return rc;
+ addr, &client_static_cbinfo, errstr);
+if (rc != OK) return NULL;
-tls_out.certificate_verified = FALSE;
+tlsp->certificate_verified = FALSE;
client_verify_callback_called = FALSE;
-if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
- &expciphers))
- return FAIL;
+expciphers = NULL;
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa)
+ {
+ /* We fall back to tls_require_ciphers if unset, empty or forced failure, but
+ other failures should be treated as problems. */
+ if (ob->dane_require_tls_ciphers &&
+ !expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
+ &expciphers, errstr))
+ return NULL;
+ if (expciphers && *expciphers == '\0')
+ expciphers = NULL;
+ }
+#endif
+if (!expciphers &&
+ !expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
+ &expciphers, errstr))
+ return NULL;
/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
are separated by underscores. So that I can use either form in my tests, and
also for general convenience, we turn underscores into hyphens here. */
-if (expciphers != NULL)
+if (expciphers)
{
uschar *s = expciphers;
- while (*s != 0) { if (*s == '_') *s = '-'; s++; }
+ while (*s) { if (*s == '_') *s = '-'; s++; }
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
- if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers))
- return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
+ if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers))
+ {
+ tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr);
+ return NULL;
+ }
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
if (tlsa_dnsa)
{
- SSL_CTX_set_verify(client_ctx,
+ SSL_CTX_set_verify(exim_client_ctx->ctx,
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
verify_callback_client_dane);
if (!DANESSL_library_init())
- return tls_error(US"library init", host, NULL);
- if (DANESSL_CTX_init(client_ctx) <= 0)
- return tls_error(US"context init", host, NULL);
+ {
+ tls_error(US"library init", host, NULL, errstr);
+ return NULL;
+ }
+ if (DANESSL_CTX_init(exim_client_ctx->ctx) <= 0)
+ {
+ tls_error(US"context init", host, NULL, errstr);
+ return NULL;
+ }
}
else
#endif
- if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob, client_static_cbinfo))
- != OK)
- return rc;
+ if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob,
+ client_static_cbinfo, errstr) != OK)
+ return NULL;
-if ((client_ssl = SSL_new(client_ctx)) == NULL)
- return tls_error(US"SSL_new", host, NULL);
-SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx));
-SSL_set_fd(client_ssl, fd);
-SSL_set_connect_state(client_ssl);
+if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
+ {
+ tls_error(US"SSL_new", host, NULL, errstr);
+ return NULL;
+ }
+SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
+SSL_set_fd(exim_client_ctx->ssl, fd);
+SSL_set_connect_state(exim_client_ctx->ssl);
if (ob->tls_sni)
{
- if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni))
- return FAIL;
- if (tls_out.sni == NULL)
+ if (!expand_check(ob->tls_sni, US"tls_sni", &tlsp->sni, errstr))
+ return NULL;
+ if (!tlsp->sni)
{
DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
}
- else if (!Ustrlen(tls_out.sni))
- tls_out.sni = NULL;
+ else if (!Ustrlen(tlsp->sni))
+ tlsp->sni = NULL;
else
{
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
- DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni);
- SSL_set_tlsext_host_name(client_ssl, tls_out.sni);
+ DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni);
+ SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni);
#else
log_write(0, LOG_MAIN, "SNI unusable with this OpenSSL library version; ignoring \"%s\"\n",
- tls_out.sni);
+ tlsp->sni);
#endif
}
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
if (tlsa_dnsa)
- if ((rc = dane_tlsa_load(client_ssl, host, tlsa_dnsa)) != OK)
- return rc;
+ if (dane_tlsa_load(exim_client_ctx->ssl, host, tlsa_dnsa, errstr) != OK)
+ return NULL;
#endif
#ifndef DISABLE_OCSP
/* Request certificate status at connection-time. If the server
does OCSP stapling we will get the callback (set in tls_init()) */
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
if (request_ocsp)
{
const uschar * s;
if (request_ocsp)
{
- SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
+ SSL_set_tlsext_status_type(exim_client_ctx->ssl, TLSEXT_STATUSTYPE_ocsp);
client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
- tls_out.ocsp = OCSP_NOT_RESP;
+ tlsp->ocsp = OCSP_NOT_RESP;
}
#endif
#ifndef DISABLE_EVENT
-client_static_cbinfo->event_action = tb->event_action;
+client_static_cbinfo->event_action = tb ? tb->event_action : NULL;
#endif
/* There doesn't seem to be a built-in timeout on connection. */
DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
sigalrm_seen = FALSE;
alarm(ob->command_timeout);
-rc = SSL_connect(client_ssl);
+rc = SSL_connect(exim_client_ctx->ssl);
alarm(0);
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
if (tlsa_dnsa)
- DANESSL_cleanup(client_ssl);
+ DANESSL_cleanup(exim_client_ctx->ssl);
#endif
if (rc <= 0)
- return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL);
+ {
+ tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr);
+ return NULL;
+ }
DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");
-peer_cert(client_ssl, &tls_out, peerdn, sizeof(peerdn));
+peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn));
-construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
-tls_out.cipher = cipherbuf;
+construct_cipher_name(exim_client_ctx->ssl, cipherbuf, sizeof(cipherbuf), &tlsp->bits);
+tlsp->cipher = cipherbuf;
/* Record the certificate we presented */
{
- X509 * crt = SSL_get_certificate(client_ssl);
- tls_out.ourcert = crt ? X509_dup(crt) : NULL;
+ X509 * crt = SSL_get_certificate(exim_client_ctx->ssl);
+ tlsp->ourcert = crt ? X509_dup(crt) : NULL;
}
-tls_out.active = fd;
-return OK;
+tlsp->active.sock = fd;
+tlsp->active.tls_ctx = exim_client_ctx;
+return exim_client_ctx;
}
-/*************************************************
-* TLS version of getc *
-*************************************************/
-
-/* This gets the next byte from the TLS input buffer. If the buffer is empty,
-it refills the buffer via the SSL reading function.
-
-Arguments: none
-Returns: the next character or EOF
-
-Only used by the server-side TLS.
-*/
-
-int
-tls_getc(void)
+static BOOL
+tls_refill(unsigned lim)
{
-if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
- {
- int error;
- int inbytes;
-
- DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
- ssl_xfer_buffer, ssl_xfer_buffer_size);
+int error;
+int inbytes;
- if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
- inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
- error = SSL_get_error(server_ssl, inbytes);
- alarm(0);
+DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
+ ssl_xfer_buffer, ssl_xfer_buffer_size);
- /* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
- closed down, not that the socket itself has been closed down. Revert to
- non-SSL handling. */
+if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer,
+ MIN(ssl_xfer_buffer_size, lim));
+error = SSL_get_error(server_ssl, inbytes);
+if (smtp_receive_timeout > 0) alarm(0);
+
+if (had_command_timeout) /* set by signal handler */
+ smtp_command_timeout_exit(); /* does not return */
+if (had_command_sigterm)
+ smtp_command_sigterm_exit();
+if (had_data_timeout)
+ smtp_data_timeout_exit();
+if (had_data_sigint)
+ smtp_data_sigint_exit();
+
+/* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
+closed down, not that the socket itself has been closed down. Revert to
+non-SSL handling. */
+
+switch(error)
+ {
+ case SSL_ERROR_NONE:
+ break;
- if (error == SSL_ERROR_ZERO_RETURN)
- {
+ case SSL_ERROR_ZERO_RETURN:
DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
receive_getc = smtp_getc;
+ receive_getbuf = smtp_getbuf;
receive_get_cache = smtp_get_cache;
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
receive_ferror = smtp_ferror;
receive_smtp_buffered = smtp_buffered;
+ if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN)
+ SSL_shutdown(server_ssl);
+
+#ifndef DISABLE_OCSP
+ sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
+ server_static_cbinfo->verify_stack = NULL;
+#endif
SSL_free(server_ssl);
+ SSL_CTX_free(server_ctx);
+ server_ctx = NULL;
server_ssl = NULL;
- tls_in.active = -1;
+ tls_in.active.sock = -1;
+ tls_in.active.tls_ctx = NULL;
tls_in.bits = 0;
tls_in.cipher = NULL;
tls_in.peerdn = NULL;
tls_in.sni = NULL;
- return smtp_getc();
- }
+ return FALSE;
/* Handle genuine errors */
-
- else if (error == SSL_ERROR_SSL)
- {
+ case SSL_ERROR_SSL:
ERR_error_string(ERR_get_error(), ssl_errstring);
log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring);
- ssl_xfer_error = 1;
- return EOF;
- }
+ ssl_xfer_error = TRUE;
+ return FALSE;
- else if (error != SSL_ERROR_NONE)
- {
+ default:
DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
- ssl_xfer_error = 1;
- return EOF;
- }
+ DEBUG(D_tls) if (error == SSL_ERROR_SYSCALL)
+ debug_printf(" - syscall %s\n", strerror(errno));
+ ssl_xfer_error = TRUE;
+ return FALSE;
+ }
#ifndef DISABLE_DKIM
- dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
+dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
#endif
- ssl_xfer_buffer_hwm = inbytes;
- ssl_xfer_buffer_lwm = 0;
- }
+ssl_xfer_buffer_hwm = inbytes;
+ssl_xfer_buffer_lwm = 0;
+return TRUE;
+}
+
+
+/*************************************************
+* TLS version of getc *
+*************************************************/
+
+/* This gets the next byte from the TLS input buffer. If the buffer is empty,
+it refills the buffer via the SSL reading function.
+
+Arguments: lim Maximum amount to read/buffer
+Returns: the next character or EOF
+
+Only used by the server-side TLS.
+*/
+
+int
+tls_getc(unsigned lim)
+{
+if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
+ if (!tls_refill(lim))
+ return ssl_xfer_error ? EOF : smtp_getc(lim);
/* Something in the buffer; return next uschar */
return ssl_xfer_buffer[ssl_xfer_buffer_lwm++];
}
+uschar *
+tls_getbuf(unsigned * len)
+{
+unsigned size;
+uschar * buf;
+
+if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
+ if (!tls_refill(*len))
+ {
+ if (!ssl_xfer_error) return smtp_getbuf(len);
+ *len = 0;
+ return NULL;
+ }
+
+if ((size = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm) > *len)
+ size = *len;
+buf = &ssl_xfer_buffer[ssl_xfer_buffer_lwm];
+ssl_xfer_buffer_lwm += size;
+*len = size;
+return buf;
+}
+
+
void
tls_get_cache()
{
}
+BOOL
+tls_could_read(void)
+{
+return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm || SSL_pending(server_ssl) > 0;
+}
+
/*************************************************
* Read bytes from TLS channel *
/*
Arguments:
+ ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data
len size of buffer
Returns: the number of bytes read
- -1 after a failed read
+ -1 after a failed read, including EOF
Only used by the client-side TLS.
*/
int
-tls_read(BOOL is_server, uschar *buff, size_t len)
+tls_read(void * ct_ctx, uschar *buff, size_t len)
{
-SSL *ssl = is_server ? server_ssl : client_ssl;
+SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
int inbytes;
int error;
return -1;
}
else if (error != SSL_ERROR_NONE)
- {
return -1;
- }
return inbytes;
}
/*
Arguments:
- is_server channel specifier
+ ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data
len number of bytes
+ more further data expected soon
Returns: the number of bytes after a successful write,
-1 after a failed write
*/
int
-tls_write(BOOL is_server, const uschar *buff, size_t len)
+tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
{
-int outbytes;
-int error;
-int left = len;
-SSL *ssl = is_server ? server_ssl : client_ssl;
+int outbytes, error, left;
+SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
+static gstring * corked = NULL;
+
+DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
+ buff, (unsigned long)len, more ? ", more" : "");
-DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left);
-while (left > 0)
+/* Lacking a CORK or MSG_MORE facility (such as GnuTLS has) we copy data when
+"more" is notified. This hack is only ok if small amounts are involved AND only
+one stream does it, in one context (i.e. no store reset). Currently it is used
+for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */
+
+if (!ct_ctx && (more || corked))
{
- DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left);
+ corked = string_catn(corked, buff, len);
+ if (more)
+ return len;
+ buff = CUS corked->s;
+ len = corked->ptr;
+ corked = NULL;
+ }
+
+for (left = len; left > 0;)
+ {
+ DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
outbytes = SSL_write(ssl, CS buff, left);
error = SSL_get_error(ssl, outbytes);
DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
daemon, to shut down the TLS library, without actually doing a shutdown (which
would tamper with the SSL session in the parent process).
-Arguments: TRUE if SSL_shutdown is to be called
+Arguments:
+ ct_ctx client TLS context pointer, or NULL for the one global server context
+ shutdown 1 if TLS close-alert is to be sent,
+ 2 if also response to be waited for
+
Returns: nothing
Used by both server-side and client-side TLS.
*/
void
-tls_close(BOOL is_server, BOOL shutdown)
+tls_close(void * ct_ctx, int shutdown)
{
-SSL **sslp = is_server ? &server_ssl : &client_ssl;
-int *fdp = is_server ? &tls_in.active : &tls_out.active;
+exim_openssl_client_tls_ctx * o_ctx = ct_ctx;
+SSL_CTX **ctxp = o_ctx ? &o_ctx->ctx : &server_ctx;
+SSL **sslp = o_ctx ? &o_ctx->ssl : &server_ssl;
+int *fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock;
if (*fdp < 0) return; /* TLS was not active */
if (shutdown)
{
- DEBUG(D_tls) debug_printf("tls_close(): shutting down SSL\n");
- SSL_shutdown(*sslp);
+ int rc;
+ DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+ shutdown > 1 ? " (with response-wait)" : "");
+
+ if ( (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */
+ && shutdown > 1)
+ {
+ alarm(2);
+ rc = SSL_shutdown(*sslp); /* wait for response */
+ alarm(0);
+ }
+
+ if (rc < 0) DEBUG(D_tls)
+ {
+ ERR_error_string(ERR_get_error(), ssl_errstring);
+ debug_printf("SSL_shutdown: %s\n", ssl_errstring);
+ }
+ }
+
+#ifndef DISABLE_OCSP
+if (!o_ctx) /* server side */
+ {
+ sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
+ server_static_cbinfo->verify_stack = NULL;
}
+#endif
+SSL_CTX_free(*ctxp);
SSL_free(*sslp);
+*ctxp = NULL;
*sslp = NULL;
-
*fdp = -1;
}
if (!(tls_require_ciphers && *tls_require_ciphers))
return NULL;
-if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers,
+ &err))
return US"failed to expand tls_require_ciphers";
if (!(expciphers && *expciphers))
err = NULL;
-ctx = SSL_CTX_new(SSLv23_server_method());
-if (!ctx)
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(ctx = SSL_CTX_new(TLS_server_method())))
+#else
+if (!(ctx = SSL_CTX_new(SSLv23_server_method())))
+#endif
{
ERR_error_string(ERR_get_error(), ssl_errstring);
return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring);
if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
{
ERR_error_string(ERR_get_error(), ssl_errstring);
- err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed", expciphers);
+ err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed: %s",
+ expciphers, ssl_errstring);
}
SSL_CTX_free(ctx);
gettimeofday(&r.tv, NULL);
r.p = getpid();
- RAND_seed((uschar *)(&r), sizeof(r));
+ RAND_seed(US (&r), sizeof(r));
}
/* We're after pseudo-random, not random; if we still don't have enough data
in the internal PRNG then our options are limited. We could sleep and hope
uschar keep_c;
BOOL adding, item_parsed;
-result = 0L;
+result = SSL_OP_NO_TICKET;
/* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
* from default because it increases BEAST susceptibility. */
#ifdef SSL_OP_NO_SSLv2
result |= SSL_OP_SINGLE_DH_USE;
#endif
-if (option_spec == NULL)
+if (!option_spec)
{
*results = result;
return TRUE;