/* Local static variables */
-static BOOL verify_callback_called = FALSE;
+static BOOL client_verify_callback_called = FALSE;
+static BOOL server_verify_callback_called = FALSE;
static const uschar *sid_ctx = US"exim";
+/* We have three different contexts to care about.
+
+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.
+
+Server:
+ There are two cases: with and without ServerNameIndication from the client.
+ Given TLS SNI, we can be using different keys, certs and various other
+ configuration settings, because they're re-expanded with $tls_sni set. This
+ allows vhosting with TLS. This SNI is sent in the handshake.
+ A client might not send SNI, so we need a fallback, and an initial setup too.
+ So as a server, we start out using `server_ctx`.
+ If SNI is sent by the client, then we as server, mid-negotiation, try to clone
+ `server_sni` from `server_ctx` and then initialise settings by re-expanding
+ configuration.
+*/
+
static SSL_CTX *client_ctx = NULL;
static SSL_CTX *server_ctx = NULL;
static SSL *client_ssl = NULL;
static SSL *server_ssl = NULL;
+
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
-static SSL_CTX *client_sni = NULL;
static SSL_CTX *server_sni = NULL;
#endif
static char ssl_errstring[256];
static int ssl_session_timeout = 200;
-static BOOL verify_optional = FALSE;
+static BOOL client_verify_optional = FALSE;
+static BOOL server_verify_optional = FALSE;
static BOOL reexpand_tls_files_for_sni = FALSE;
tls_ext_ctx_cb *server_static_cbinfo = NULL;
static int
-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional);
+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, BOOL client);
/* Callbacks */
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
Arguments:
state current yes/no state as 1/0
x509ctx certificate information.
+ client TRUE for client startup, FALSE for server startup
Returns: 1 if verified, 0 if not
*/
static int
-verify_callback(int state, X509_STORE_CTX *x509ctx)
+verify_callback(int state, X509_STORE_CTX *x509ctx, BOOL client)
{
static uschar txt[256];
+tls_support * tlsp;
+BOOL * calledp;
+BOOL * optionalp;
+
+if (client)
+ {
+ tlsp= &tls_out;
+ calledp= &client_verify_callback_called;
+ optionalp= &client_verify_optional;
+ }
+else
+ {
+ tlsp= &tls_in;
+ calledp= &server_verify_callback_called;
+ optionalp= &server_verify_optional;
+ }
X509_NAME_oneline(X509_get_subject_name(x509ctx->current_cert),
CS txt, sizeof(txt));
x509ctx->error_depth,
X509_verify_cert_error_string(x509ctx->error),
txt);
- tls_in.certificate_verified = FALSE;
- verify_callback_called = TRUE;
- if (!verify_optional) return 0; /* reject */
+ tlsp->certificate_verified = FALSE;
+ *calledp = TRUE;
+ if (!*optionalp) return 0; /* reject */
DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
"tls_try_verify_hosts)\n");
return 1; /* accept */
else
{
DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
- verify_callback_called? "" : " authenticated", txt);
- tls_in.peerdn = txt;
+ *calledp ? "" : " authenticated", txt);
+ tlsp->peerdn = txt;
}
-if (!verify_callback_called) tls_in.certificate_verified = TRUE;
-verify_callback_called = TRUE;
+if (!*calledp) tlsp->certificate_verified = TRUE;
+*calledp = TRUE;
return 1; /* accept */
}
+static int
+verify_callback_client(int state, X509_STORE_CTX *x509ctx)
+{
+return verify_callback(state, x509ctx, TRUE);
+}
+
+static int
+verify_callback_server(int state, X509_STORE_CTX *x509ctx)
+{
+return verify_callback(state, x509ctx, FALSE);
+}
+
/*************************************************
*/
static BOOL
-<<<<<<< HEAD
init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host)
-=======
-init_dh(SSL_CTX *ctx, uschar *dhparam, host_item *host)
->>>>>>> Dual-tls - split management of TLS into in- and out-bound connection-handling.
{
BIO *bio;
DH *dh;
if (cbinfo->certificate == NULL)
return OK;
-if (Ustrstr(cbinfo->certificate, US"tls_sni"))
+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 (cbinfo->ocsp_file)
{
SSL_CTX_set_tlsext_status_cb(server_sni, tls_stapling_cb);
- SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
+ SSL_CTX_set_tlsext_status_arg(server_sni, cbinfo);
}
#endif
-rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE);
+rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, FALSE);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
/* do this after setup_certs, because this can require the certs for verifying
rc = tls_expand_session_files(server_sni, cbinfo);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
-rc = init_dh(ctx_sni, cbinfo->dhparam, NULL);
+rc = init_dh(server_sni, cbinfo->dhparam, NULL);
if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
DEBUG(D_tls) debug_printf("Switching SSL context.\n");
if (response_der_len <= 0)
return SSL_TLSEXT_ERR_NOACK;
-SSL_set_tlsext_status_ocsp_resp(ssl, response_der, response_der_len);
+SSL_set_tlsext_status_ocsp_resp(server_ssl, response_der, response_der_len);
return SSL_TLSEXT_ERR_OK;
}
cbinfo->privatekey = privatekey;
#ifdef EXPERIMENTAL_OCSP
cbinfo->ocsp_file = ocsp_file;
+cbinfo->ocsp_file_expanded = NULL;
+cbinfo->ocsp_response = NULL;
#endif
cbinfo->dhparam = dhparam;
cbinfo->host = host;
/* Initialize with DH parameters if supplied */
-<<<<<<< HEAD
-if (!init_dh(ctx, dhparam, host)) return DEFER;
-=======
if (!init_dh(*ctxp, dhparam, host)) return DEFER;
->>>>>>> Dual-tls - split management of TLS into in- and out-bound connection-handling.
/* Set up certificate and key (and perhaps OCSP info) */
callback is invoked. */
if (cbinfo->ocsp_file)
{
- SSL_CTX_set_tlsext_status_cb(ctx, tls_stapling_cb);
- SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
+ SSL_CTX_set_tlsext_status_cb(server_ctx, tls_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(server_ctx, cbinfo);
}
#endif
/* We always do this, so that $tls_sni is available even if not used in
host NULL in a server; the remote host in a client
optional TRUE if called from a server for a host in tls_try_verify_hosts;
otherwise passed as FALSE
+ client TRUE if called for client startup, FALSE for server startup
Returns: OK/DEFER/FAIL
*/
static int
-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional)
+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, BOOL client)
{
uschar *expcerts, *expcrl;
if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
return DEFER;
-if (expcerts != NULL)
+if (expcerts != NULL && *expcerts != '\0')
{
struct stat statbuf;
if (!SSL_CTX_set_default_verify_paths(sctx))
SSL_CTX_set_verify(sctx,
SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
- verify_callback);
+ client ? verify_callback_client : verify_callback_server);
}
return OK;
optional, set up appropriately. */
tls_in.certificate_verified = FALSE;
-verify_callback_called = FALSE;
+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);
+ rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, FALSE, FALSE);
if (rc != OK) return rc;
- verify_optional = FALSE;
+ 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);
+ rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, TRUE, FALSE);
if (rc != OK) return rc;
- verify_optional = TRUE;
+ server_verify_optional = TRUE;
}
/* Prepare for new connection */
if (rc != OK) return rc;
tls_out.certificate_verified = FALSE;
-verify_callback_called = FALSE;
+client_verify_callback_called = FALSE;
if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
return FAIL;
return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
}
-rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE);
+rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE, TRUE);
if (rc != OK) return rc;
if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
{
if (!expand_check(sni, US"tls_sni", &tls_out.sni))
return FAIL;
- if (!Ustrlen(tls_out.sni))
+ if (tls_out.sni == NULL)
+ {
+ 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
{
#else
DEBUG(D_tls)
debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n",
- tls_sni);
+ tls_out.sni);
#endif
}
}
*/
int
-tls_read(uschar *buff, size_t len)
+tls_read(BOOL is_server, uschar *buff, size_t len)
{
+SSL *ssl = is_server ? server_ssl : client_ssl;
int inbytes;
int error;
-DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", client_ssl,
+DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
buff, (unsigned int)len);
-inbytes = SSL_read(client_ssl, CS buff, len);
-error = SSL_get_error(client_ssl, inbytes);
+inbytes = SSL_read(ssl, CS buff, len);
+error = SSL_get_error(ssl, inbytes);
if (error == SSL_ERROR_ZERO_RETURN)
{
tls_close(BOOL is_server, BOOL shutdown)
{
SSL **sslp = is_server ? &server_ssl : &client_ssl;
+int *fdp = is_server ? &tls_in.active : &tls_out.active;
if (*fdp < 0) return; /* TLS was not active */
{
unsigned int r;
int i, needed_len;
+static pid_t pidlast = 0;
+pid_t pidnow;
uschar *p;
uschar smallbuf[sizeof(r)];
if (max <= 1)
return 0;
+pidnow = getpid();
+if (pidnow != pidlast)
+ {
+ /* Although OpenSSL documents that "OpenSSL makes sure that the PRNG state
+ is unique for each thread", this doesn't apparently apply across processes,
+ so our own warning from vaguely_random_number_fallback() applies here too.
+ Fix per PostgreSQL. */
+ if (pidlast != 0)
+ RAND_cleanup();
+ pidlast = pidnow;
+ }
+
/* OpenSSL auto-seeds from /dev/random, etc, but this a double-check. */
if (!RAND_status())
{