* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Copyright (c) Phil Pennock 2012 */
#if GNUTLS_VERSION_NUMBER >= 0x030109
# define SUPPORT_CORK
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
+# define SUPPORT_SRV_OCSP_STACK
+#endif
+
+#ifdef SUPPORT_DANE
+# if GNUTLS_VERSION_NUMBER >= 0x030000
+# define DANESSL_USAGE_DANE_TA 2
+# define DANESSL_USAGE_DANE_EE 3
+# else
+# error GnuTLS version too early for DANE
+# endif
+# if GNUTLS_VERSION_NUMBER < 0x999999
+# define GNUTLS_BROKEN_DANE_VALIDATION
+# endif
+#endif
#ifndef DISABLE_OCSP
# include <gnutls/ocsp.h>
#endif
+#ifdef SUPPORT_DANE
+# include <gnutls/dane.h>
+#endif
/* GnuTLS 2 vs 3
/* Values for verify_requirement */
enum peer_verify_requirement
- { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
+ { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED, VERIFY_DANE };
/* This holds most state for server or client; with this, we can set up an
outbound TLS-enabled connection in an ACL callout, while not stomping all
int fd_in;
int fd_out;
BOOL peer_cert_verified;
+ BOOL peer_dane_verified;
BOOL trigger_sni_changes;
BOOL have_set_peerdn;
const struct host_item *host;
- gnutls_x509_crt_t peercert;
+ gnutls_x509_crt_t peercert;
uschar *peerdn;
uschar *ciphersuite;
uschar *received_sni;
uschar *exp_tls_verify_certificates;
uschar *exp_tls_crl;
uschar *exp_tls_require_ciphers;
- uschar *exp_tls_ocsp_file;
const uschar *exp_tls_verify_cert_hostnames;
#ifndef DISABLE_EVENT
uschar *event_action;
#endif
+#ifdef SUPPORT_DANE
+ char * const * dane_data;
+ const int * dane_data_len;
+#endif
tls_support *tlsp; /* set in tls_init() */
uschar *xfer_buffer;
int xfer_buffer_lwm;
int xfer_buffer_hwm;
- int xfer_eof;
- int xfer_error;
+ BOOL xfer_eof; /*XXX never gets set! */
+ BOOL xfer_error;
} exim_gnutls_state_st;
static const exim_gnutls_state_st exim_gnutls_state_init = {
.fd_in = -1,
.fd_out = -1,
.peer_cert_verified = FALSE,
+ .peer_dane_verified = FALSE,
.trigger_sni_changes =FALSE,
.have_set_peerdn = FALSE,
.host = NULL,
.exp_tls_verify_certificates = NULL,
.exp_tls_crl = NULL,
.exp_tls_require_ciphers = NULL,
- .exp_tls_ocsp_file = NULL,
.exp_tls_verify_cert_hostnames = NULL,
#ifndef DISABLE_EVENT
.event_action = NULL,
.xfer_buffer = NULL,
.xfer_buffer_lwm = 0,
.xfer_buffer_hwm = 0,
- .xfer_eof = 0,
- .xfer_error = 0,
+ .xfer_eof = FALSE,
+ .xfer_error = FALSE,
};
/* Not only do we have our own APIs which don't pass around state, assuming
XXX But see gnutls_session_get_ptr()
*/
-static exim_gnutls_state_st state_server, state_client;
+static exim_gnutls_state_st state_server;
/* dh_params are initialised once within the lifetime of a process using TLS;
if we used TLS in a long-lived daemon, we'd have to reconsider this. But we
/* Set this to control gnutls_global_set_log_level(); values 0 to 9 will setup
the library logging; a value less than 0 disables the calls to set up logging
-callbacks. */
+callbacks. Possibly GNuTLS also looks for an environment variable
+"GNUTLS_DEBUG_LEVEL". */
#ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL
# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
#endif
# define EXIM_SERVER_DH_BITS_PRE2_12 1024
#endif
-#define exim_gnutls_err_check(Label) do { \
- if (rc != GNUTLS_E_SUCCESS) \
+#define exim_gnutls_err_check(rc, Label) do { \
+ if ((rc) != GNUTLS_E_SUCCESS) \
return tls_error((Label), gnutls_strerror(rc), host, errstr); \
} while (0)
#endif
tls_support * tlsp = state->tlsp;
-tlsp->active = state->fd_out;
+tlsp->active.sock = state->fd_out;
+tlsp->active.tls_ctx = state;
cipher = gnutls_cipher_get(state->session);
/* returns size in "bytes" */
DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);
tlsp->certificate_verified = state->peer_cert_verified;
+#ifdef SUPPORT_DANE
+tlsp->dane_verified = state->peer_dane_verified;
+#endif
/* note that tls_channelbinding_b64 is not saved to the spool file, since it's
only available for use for authenticators while this TLS session is running. */
DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n");
rc = gnutls_dh_params_init(&dh_server_params);
-exim_gnutls_err_check(US"gnutls_dh_params_init");
+exim_gnutls_err_check(rc, US"gnutls_dh_params_init");
m.data = NULL;
m.size = 0;
if (m.data)
{
rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
- exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3");
DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n");
return OK;
}
rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
free(m.data);
- exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3");
DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
}
debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
dh_bits_gen);
rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen);
- exim_gnutls_err_check(US"gnutls_dh_params_generate2");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_generate2");
/* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,
and I confirmed that a NULL call to get the size first is how the GnuTLS
rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
m.data, &sz);
if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
- exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3(NULL) sizing");
m.size = sz;
if (!(m.data = malloc(m.size)))
return tls_error(US"memory allocation failed", strerror(errno), NULL, errstr);
if (rc != GNUTLS_E_SUCCESS)
{
free(m.data);
- exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3() real");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3() real");
}
m.size = sz; /* shrink by 1, probably */
goto err;
where = US"configuring cert";
-now = 0;
+now = 1;
if ( (rc = gnutls_x509_crt_set_version(cert, 3))
|| (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now)))
|| (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL)))
+/* Add certificate and key, from files.
+
+Return:
+ Zero or negative: good. Negate value for certificate index if < 0.
+ Greater than zero: FAIL or DEFER code.
+*/
+
static int
tls_add_certfile(exim_gnutls_state_st * state, const host_item * host,
uschar * certfile, uschar * keyfile, uschar ** errstr)
{
int rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM);
-exim_gnutls_err_check(
- string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile));
-return OK;
+if (rc < 0)
+ return tls_error(
+ string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile),
+ gnutls_strerror(rc), host, errstr);
+return -rc;
}
}
rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
-exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
+exim_gnutls_err_check(rc, US"gnutls_certificate_allocate_credentials");
+
+#ifdef SUPPORT_SRV_OCSP_STACK
+gnutls_certificate_set_flags(state->x509_cred, GNUTLS_CERTIFICATE_API_V2);
+#endif
/* remember: expand_check_tlsvar() is expand_check() but fiddling with
state members, assuming consistent naming; and expand_check() returns
{
const uschar * clist = state->exp_tls_certificate;
const uschar * klist = state->exp_tls_privatekey;
- int csep = 0, ksep = 0;
- uschar * cfile, * kfile;
+ const uschar * olist;
+ int csep = 0, ksep = 0, osep = 0, cnt = 0;
+ uschar * cfile, * kfile, * ofile;
+
+#ifndef DISABLE_OCSP
+ if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &ofile, errstr))
+ return DEFER;
+ olist = ofile;
+#endif
while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
+
if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0)))
return tls_error(US"cert/key setup: out of keys", NULL, host, errstr);
- else if ((rc = tls_add_certfile(state, host, cfile, kfile, errstr)))
+ else if (0 < (rc = tls_add_certfile(state, host, cfile, kfile, errstr)))
return rc;
else
+ {
+ int gnutls_cert_index = -rc;
DEBUG(D_tls) debug_printf("TLS: cert/key %s registered\n", cfile);
+
+ /* Set the OCSP stapling server info */
+
+#ifndef DISABLE_OCSP
+ if (tls_ocsp_file)
+ if (gnutls_buggy_ocsp)
+ {
+ DEBUG(D_tls)
+ debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
+ }
+ else if ((ofile = string_nextinlist(&olist, &osep, NULL, 0)))
+ {
+ /* Use the full callback method for stapling just to get
+ observability. More efficient would be to read the file once only,
+ if it never changed (due to SNI). Would need restart on file update,
+ or watch datestamp. */
+
+# ifdef SUPPORT_SRV_OCSP_STACK
+ rc = gnutls_certificate_set_ocsp_status_request_function2(
+ state->x509_cred, gnutls_cert_index,
+ server_ocsp_stapling_cb, ofile);
+
+ exim_gnutls_err_check(rc,
+ US"gnutls_certificate_set_ocsp_status_request_function2");
+# else
+ if (cnt++ > 0)
+ {
+ DEBUG(D_tls)
+ debug_printf("oops; multiple OCSP files not supported\n");
+ break;
+ }
+ gnutls_certificate_set_ocsp_status_request_function(
+ state->x509_cred, server_ocsp_stapling_cb, ofile);
+# endif
+
+ DEBUG(D_tls) debug_printf("OCSP response file = %s\n", ofile);
+ }
+ else
+ DEBUG(D_tls) debug_printf("ran out of OCSP response files in list\n");
+#endif
+ }
}
else
{
- if ((rc = tls_add_certfile(state, host,
+ if (0 < (rc = tls_add_certfile(state, host,
state->exp_tls_certificate, state->exp_tls_privatekey, errstr)))
return rc;
DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
} /* tls_certificate */
-/* Set the OCSP stapling server info */
-
-#ifndef DISABLE_OCSP
-if ( !host /* server */
- && tls_ocsp_file
- )
- {
- if (gnutls_buggy_ocsp)
- {
- DEBUG(D_tls) debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
- }
- else
- {
- if (!expand_check(tls_ocsp_file, US"tls_ocsp_file",
- &state->exp_tls_ocsp_file, errstr))
- return DEFER;
-
- /* Use the full callback method for stapling just to get observability.
- More efficient would be to read the file once only, if it never changed
- (due to SNI). Would need restart on file update, or watch datestamp. */
-
- gnutls_certificate_set_ocsp_status_request_function(state->x509_cred,
- server_ocsp_stapling_cb, state->exp_tls_ocsp_file);
-
- DEBUG(D_tls) debug_printf("OCSP response file = %s\n", state->exp_tls_ocsp_file);
- }
- }
-#endif
-
-
/* Set the trusted CAs file if one is provided, and then add the CRL if one is
provided. Experiment shows that, if the certificate file is empty, an unhelpful
error message is provided. However, if we just refrain from setting anything up
if (cert_count < 0)
{
rc = cert_count;
- exim_gnutls_err_check(US"setting certificate trust");
+ exim_gnutls_err_check(rc, US"setting certificate trust");
}
DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
if (cert_count < 0)
{
rc = cert_count;
- exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file");
+ exim_gnutls_err_check(rc, US"gnutls_certificate_set_x509_crl_file");
}
DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count);
}
/* Link the credentials to the session. */
rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
-exim_gnutls_err_check(US"gnutls_credentials_set");
+exim_gnutls_err_check(rc, US"gnutls_credentials_set");
return OK;
}
const uschar *crl,
const uschar *require_ciphers,
exim_gnutls_state_st **caller_state,
+ tls_support * tlsp,
uschar ** errstr)
{
exim_gnutls_state_st *state;
if (!gnutls_allow_auto_pkcs11)
{
rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
- exim_gnutls_err_check(US"gnutls_pkcs11_init");
+ exim_gnutls_err_check(rc, US"gnutls_pkcs11_init");
}
#endif
rc = gnutls_global_init();
- exim_gnutls_err_check(US"gnutls_global_init");
+ exim_gnutls_err_check(rc, US"gnutls_global_init");
#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
DEBUG(D_tls)
if (host)
{
- state = &state_client;
+ /* For client-side sessions we allocate a context. This lets us run
+ several in parallel. */
+ int old_pool = store_pool;
+ store_pool = POOL_PERM;
+ state = store_get(sizeof(exim_gnutls_state_st));
+ store_pool = old_pool;
+
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
- state->tlsp = &tls_out;
+ state->tlsp = tlsp;
DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
rc = gnutls_init(&state->session, GNUTLS_CLIENT);
}
{
state = &state_server;
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
- state->tlsp = &tls_in;
+ state->tlsp = tlsp;
DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
rc = gnutls_init(&state->session, GNUTLS_SERVER);
}
-exim_gnutls_err_check(US"gnutls_init");
+exim_gnutls_err_check(rc, US"gnutls_init");
state->host = host;
sz = Ustrlen(state->tlsp->sni);
rc = gnutls_server_name_set(state->session,
GNUTLS_NAME_DNS, state->tlsp->sni, sz);
- exim_gnutls_err_check(US"gnutls_server_name_set");
+ exim_gnutls_err_check(rc, US"gnutls_server_name_set");
}
}
else if (state->tls_sni)
p = US exim_default_gnutls_priority;
}
-exim_gnutls_err_check(string_sprintf(
+exim_gnutls_err_check(rc, string_sprintf(
"gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
p, errpos - CS p, errpos));
rc = gnutls_priority_set(state->session, state->priority_cache);
-exim_gnutls_err_check(US"gnutls_priority_set");
+exim_gnutls_err_check(rc, US"gnutls_priority_set");
gnutls_db_set_cache_expiration(state->session, ssl_session_timeout);
the peer information, but that's too new for some OSes.
Arguments:
- state exim_gnutls_state_st *
- errstr where to put an error message
+ state exim_gnutls_state_st *
+ errstr where to put an error message
Returns:
FALSE if the session should be rejected
*/
static BOOL
-verify_certificate(exim_gnutls_state_st *state, uschar ** errstr)
+verify_certificate(exim_gnutls_state_st * state, uschar ** errstr)
{
int rc;
-unsigned int verify;
+uint verify;
+
+if (state->verify_requirement == VERIFY_NONE)
+ return TRUE;
*errstr = NULL;
*errstr = US"certificate not supplied";
}
else
+
+ {
+#ifdef SUPPORT_DANE
+ if (state->verify_requirement == VERIFY_DANE && state->host)
+ {
+ /* Using dane_verify_session_crt() would be easy, as it does it all for us
+ including talking to a DNS resolver. But we want to do that bit ourselves
+ as the testsuite intercepts and fakes its own DNS environment. */
+
+ dane_state_t s;
+ dane_query_t r;
+ uint lsize;
+ const gnutls_datum_t * certlist =
+ gnutls_certificate_get_peers(state->session, &lsize);
+ int usage = tls_out.tlsa_usage;
+
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ /* Split the TLSA records into two sets, TA and EE selectors. Run the
+ dane-verification separately so that we know which selector verified;
+ then we know whether to do CA-chain-verification and name-verification
+ (needed for TA but not EE). */
+
+ if (usage == ((1<<DANESSL_USAGE_DANE_TA) | (1<<DANESSL_USAGE_DANE_EE)))
+ { /* a mixed-usage bundle */
+ int i, j, nrec;
+ const char ** dd;
+ int * ddl;
+
+ for(nrec = 0; state->dane_data_len[nrec]; ) nrec++;
+ nrec++;
+
+ dd = store_get(nrec * sizeof(uschar *));
+ ddl = store_get(nrec * sizeof(int));
+ nrec--;
+
+ if ((rc = dane_state_init(&s, 0)))
+ goto tlsa_prob;
+
+ for (usage = DANESSL_USAGE_DANE_EE;
+ usage >= DANESSL_USAGE_DANE_TA; usage--)
+ { /* take records with this usage */
+ for (j = i = 0; i < nrec; i++)
+ if (state->dane_data[i][0] == usage)
+ {
+ dd[j] = state->dane_data[i];
+ ddl[j++] = state->dane_data_len[i];
+ }
+ if (j)
+ {
+ dd[j] = NULL;
+ ddl[j] = 0;
+
+ if ((rc = dane_raw_tlsa(s, &r, (char * const *)dd, ddl, 1, 0)))
+ goto tlsa_prob;
+
+ if ((rc = dane_verify_crt_raw(s, certlist, lsize,
+ gnutls_certificate_type_get(state->session),
+ r, 0,
+ usage == DANESSL_USAGE_DANE_EE
+ ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+ &verify)))
+ {
+ DEBUG(D_tls)
+ debug_printf("TLSA record problem: %s\n", dane_strerror(rc));
+ }
+ else if (verify == 0) /* verification passed */
+ {
+ usage = 1 << usage;
+ break;
+ }
+ }
+ }
+
+ if (rc) goto tlsa_prob;
+ }
+ else
+# endif
+ {
+ if ( (rc = dane_state_init(&s, 0))
+ || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len,
+ 1, 0))
+ || (rc = dane_verify_crt_raw(s, certlist, lsize,
+ gnutls_certificate_type_get(state->session),
+ r, 0,
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ usage == (1 << DANESSL_USAGE_DANE_EE)
+ ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+# else
+ 0,
+# endif
+ &verify))
+ )
+ goto tlsa_prob;
+ }
+
+ if (verify != 0) /* verification failed */
+ {
+ gnutls_datum_t str;
+ (void) dane_verification_status_print(verify, &str, 0);
+ *errstr = US str.data; /* don't bother to free */
+ goto badcert;
+ }
+ state->peer_dane_verified = TRUE;
+
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ /* If a TA-mode TLSA record was used for verification we must additionally
+ verify the CA chain and the cert name. For EE-mode, skip it. */
+
+ if (usage & (1 << DANESSL_USAGE_DANE_EE))
+# endif
+ {
+ state->peer_cert_verified = TRUE;
+ goto goodcert;
+ }
+ }
+#endif
+
rc = gnutls_certificate_verify_peers2(state->session, &verify);
+ }
-/* Handle the result of verification. INVALID seems to be set as well
-as REVOKED, but leave the test for both. */
+/* Handle the result of verification. INVALID is set if any others are. */
-if (rc < 0 ||
- verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)
- )
+if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
{
state->peer_cert_verified = FALSE;
if (!*errstr)
*errstr, state->peerdn ? state->peerdn : US"<unset>");
if (state->verify_requirement >= VERIFY_REQUIRED)
- {
- gnutls_alert_send(state->session,
- GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
- return FALSE;
- }
+ goto badcert;
DEBUG(D_tls)
debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n");
}
DEBUG(D_tls)
debug_printf("TLS certificate verification failed: cert name mismatch\n");
if (state->verify_requirement >= VERIFY_REQUIRED)
- {
- gnutls_alert_send(state->session,
- GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
- return FALSE;
- }
+ goto badcert;
return TRUE;
}
}
state->peerdn ? state->peerdn : US"<unset>");
}
-state->tlsp->peerdn = state->peerdn;
+goodcert:
+ state->tlsp->peerdn = state->peerdn;
+ return TRUE;
-return TRUE;
+#ifdef SUPPORT_DANE
+tlsa_prob:
+ *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc));
+#endif
+
+badcert:
+ gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+ return FALSE;
}
gnutls_datum_t * ocsp_response)
{
int ret;
+DEBUG(D_tls) debug_printf("OCSP stapling callback: %s\n", US ptr);
if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0)
{
uschar * yield;
exim_gnutls_state_st * state = gnutls_session_get_ptr(session);
-cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
-if (cert_list)
+if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size)))
while (cert_list_size--)
{
- rc = import_cert(&cert_list[cert_list_size], &crt);
- if (rc != GNUTLS_E_SUCCESS)
+ if ((rc = import_cert(&cert_list[cert_list_size], &crt)) != GNUTLS_E_SUCCESS)
{
DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
cert_list_size, gnutls_strerror(rc));
exim_gnutls_state_st * state = NULL;
/* Check for previous activation */
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
{
tls_error(US"STARTTLS received after TLS started", "", NULL, errstr);
smtp_printf("554 Already in TLS\r\n", FALSE);
if ((rc = tls_init(NULL, tls_certificate, tls_privatekey,
NULL, tls_verify_certificates, tls_crl,
- require_ciphers, &state, errstr)) != OK) return rc;
+ require_ciphers, &state, &tls_in, errstr)) != OK) return rc;
/* If this is a host for which certificate verification is mandatory or
optional, set up appropriately. */
/* Verify after the fact */
-if ( state->verify_requirement != VERIFY_NONE
- && !verify_certificate(state, errstr))
+if (!verify_certificate(state, errstr))
{
if (state->verify_requirement != VERIFY_OPTIONAL)
{
}
+
+
+#ifdef SUPPORT_DANE
+/* Given our list of RRs from the TLSA lookup, build a lookup block in
+GnuTLS-DANE's preferred format. Hang it on the state str for later
+use in DANE verification.
+
+We point at the dnsa data not copy it, so it must remain valid until
+after verification is done.*/
+
+static BOOL
+dane_tlsa_load(exim_gnutls_state_st * state, dns_answer * dnsa)
+{
+dns_record * rr;
+dns_scan dnss;
+int i;
+const char ** dane_data;
+int * dane_data_len;
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 1;
+ rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+ ) if (rr->type == T_TLSA) i++;
+
+dane_data = store_get(i * sizeof(uschar *));
+dane_data_len = store_get(i * sizeof(int));
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0;
+ rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+ ) if (rr->type == T_TLSA)
+ {
+ const uschar * p = rr->data;
+ uint8_t usage = p[0], sel = p[1], type = p[2];
+
+ DEBUG(D_tls)
+ debug_printf("TLSA: %d %d %d size %d\n", usage, sel, type, rr->size);
+
+ if ( (usage != DANESSL_USAGE_DANE_TA && usage != DANESSL_USAGE_DANE_EE)
+ || (sel != 0 && sel != 1)
+ )
+ continue;
+ switch(type)
+ {
+ case 0: /* Full: cannot check at present */
+ break;
+ case 1: if (rr->size != 3 + 256/8) continue; /* sha2-256 */
+ break;
+ case 2: if (rr->size != 3 + 512/8) continue; /* sha2-512 */
+ break;
+ default: continue;
+ }
+
+ tls_out.tlsa_usage |= 1<<usage;
+ dane_data[i] = p;
+ dane_data_len[i++] = rr->size;
+ }
+
+if (!i) return FALSE;
+
+dane_data[i] = NULL;
+dane_data_len[i] = 0;
+
+state->dane_data = (char * const *)dane_data;
+state->dane_data_len = dane_data_len;
+return TRUE;
+}
+#endif
+
+
+
/*************************************************
* Start a TLS session in a client *
*************************************************/
Arguments:
fd the fd of the connection
- host connected host (for messages)
+ host connected host (for messages and option-tests)
addr the first address (not used)
tb transport (always smtp)
-
+ tlsa_dnsa non-NULL, either request or require dane for this host, and
+ a TLSA record found. Therefore, dane verify required.
+ Which implies cert must be requested and supplied, dane
+ verify must pass, and cert verify irrelevant (incl.
+ hostnames), and (caller handled) require_tls
+ tlsp record details of channel configuration
errstr error string pointer
-Returns: OK/DEFER/FAIL (because using common functions),
- but for a client, DEFER and FAIL have the same meaning
+Returns: Pointer to TLS session context, or NULL on error
*/
-int
+void *
tls_client_start(int fd, host_item *host,
address_item *addr ARG_UNUSED,
transport_instance * tb,
-#ifdef EXPERIMENTAL_DANE
- dns_answer * tlsa_dnsa ARG_UNUSED,
+#ifdef SUPPORT_DANE
+ dns_answer * tlsa_dnsa,
#endif
- uschar ** errstr)
+ 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;
int rc;
-exim_gnutls_state_st *state = NULL;
+exim_gnutls_state_st * state = NULL;
+uschar *cipher_list = NULL;
+
#ifndef DISABLE_OCSP
BOOL require_ocsp =
verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
-if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa && ob->dane_require_tls_ciphers)
+ {
+ /* not using expand_check_tlsvar because not yet in state */
+ if (!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
+ &cipher_list, errstr))
+ return NULL;
+ cipher_list = cipher_list && *cipher_list
+ ? ob->dane_require_tls_ciphers : ob->tls_require_ciphers;
+ }
+#endif
+
+if (!cipher_list)
+ cipher_list = ob->tls_require_ciphers;
+
+if (tls_init(host, ob->tls_certificate, ob->tls_privatekey,
ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
- ob->tls_require_ciphers, &state, errstr)) != OK)
- return rc;
+ cipher_list, &state, tlsp, errstr) != OK)
+ return NULL;
{
int dh_min_bits = ob->tls_dh_min_bits;
set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only
the specified host patterns if one of them is defined */
-if ( ( state->exp_tls_verify_certificates
- && !ob->tls_verify_hosts
- && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
- )
- || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
- )
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa && dane_tlsa_load(state, tlsa_dnsa))
+ {
+ DEBUG(D_tls)
+ debug_printf("TLS: server certificate DANE required.\n");
+ state->verify_requirement = VERIFY_DANE;
+ gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
+ }
+else
+#endif
+ if ( ( state->exp_tls_verify_certificates
+ && !ob->tls_verify_hosts
+ && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
+ )
+ || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
+ )
{
tls_client_setup_hostname_checks(host, state, ob);
DEBUG(D_tls)
DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
NULL, 0, NULL)) != OK)
- return tls_error(US"cert-status-req",
- gnutls_strerror(rc), state->host, errstr);
- tls_out.ocsp = OCSP_NOT_RESP;
+ {
+ tls_error(US"cert-status-req", gnutls_strerror(rc), state->host, errstr);
+ return NULL;
+ }
+ tlsp->ocsp = OCSP_NOT_RESP;
}
#endif
#ifndef DISABLE_EVENT
-if (tb->event_action)
+if (tb && tb->event_action)
{
state->event_action = tb->event_action;
gnutls_session_set_ptr(state->session, state);
sigalrm_seen = FALSE;
alarm(ob->command_timeout);
do
- {
rc = gnutls_handshake(state->session);
- } while ((rc == GNUTLS_E_AGAIN) ||
- (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
+while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
alarm(0);
if (rc != GNUTLS_E_SUCCESS)
+ {
if (sigalrm_seen)
{
gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED);
- return tls_error(US"gnutls_handshake", "timed out", state->host, errstr);
+ tls_error(US"gnutls_handshake", "timed out", state->host, errstr);
}
else
- return tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host, errstr);
+ tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host, errstr);
+ return NULL;
+ }
DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
/* Verify late */
-if (state->verify_requirement != VERIFY_NONE &&
- !verify_certificate(state, errstr))
- return tls_error(US"certificate verification failed", *errstr, state->host, errstr);
+if (!verify_certificate(state, errstr))
+ {
+ tls_error(US"certificate verification failed", *errstr, state->host, errstr);
+ return NULL;
+ }
#ifndef DISABLE_OCSP
if (require_ocsp)
if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
{
- tls_out.ocsp = OCSP_FAILED;
- return tls_error(US"certificate status check failed", NULL, state->host, errstr);
+ tlsp->ocsp = OCSP_FAILED;
+ tls_error(US"certificate status check failed", NULL, state->host, errstr);
+ return NULL;
}
DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
- tls_out.ocsp = OCSP_VFIED;
+ tlsp->ocsp = OCSP_VFIED;
}
#endif
/* Figure out peer DN, and if authenticated, etc. */
-if ((rc = peer_status(state, errstr)) != OK)
- return rc;
+if (peer_status(state, errstr) != OK)
+ return NULL;
/* Sets various Exim expansion variables; may need to adjust for ACL callouts */
extract_exim_vars_from_tls_state(state);
-return OK;
+return state;
}
daemon, to shut down the TLS library, without actually doing a shutdown (which
would tamper with the TLS session in the parent process).
-Arguments: TRUE if gnutls_bye is to be called
+Arguments:
+ ct_ctx client 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
*/
void
-tls_close(BOOL is_server, BOOL shutdown)
+tls_close(void * ct_ctx, int shutdown)
{
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
-if (!state->tlsp || state->tlsp->active < 0) return; /* TLS was not active */
+if (!state->tlsp || state->tlsp->active.sock < 0) return; /* TLS was not active */
if (shutdown)
{
- DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n");
- gnutls_bye(state->session, GNUTLS_SHUT_WR);
+ DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+ shutdown > 1 ? " (with response-wait)" : "");
+
+ alarm(2);
+ gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+ alarm(0);
}
gnutls_deinit(state->session);
gnutls_certificate_free_credentials(state->x509_cred);
-state->tlsp->active = -1;
+state->tlsp->active.sock = -1;
+state->tlsp->active.tls_ctx = NULL;
+if (state->xfer_buffer) store_free(state->xfer_buffer);
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
-
-if ((state_server.session == NULL) && (state_client.session == NULL))
- {
- gnutls_global_deinit();
- exim_gnutls_base_init_done = FALSE;
- }
}
DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
state->session, state->xfer_buffer, ssl_xfer_buffer_size);
+sigalrm_seen = FALSE;
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
MIN(ssl_xfer_buffer_size, lim));
-alarm(0);
+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();
-/* Timeouts do not get this far; see command_timeout_handler().
- A zero-byte return appears to mean that the TLS session has been
- closed down, not that the socket itself has been closed down. Revert to
- non-TLS handling. */
+/* Timeouts do not get this far. A zero-byte return appears to mean that the
+TLS session has been closed down, not that the socket itself has been closed
+down. Revert to non-TLS handling. */
if (sigalrm_seen)
{
DEBUG(D_tls) debug_printf("Got tls read timeout\n");
- state->xfer_error = 1;
+ state->xfer_error = TRUE;
return FALSE;
}
gnutls_certificate_free_credentials(state->x509_cred);
state->session = NULL;
- state->tlsp->active = -1;
+ state->tlsp->active.sock = -1;
+ state->tlsp->active.tls_ctx = NULL;
state->tlsp->bits = 0;
state->tlsp->certificate_verified = FALSE;
tls_channelbinding_b64 = NULL;
else if (inbytes < 0)
{
record_io_error(state, (int) inbytes, US"recv", NULL);
- state->xfer_error = 1;
+ state->xfer_error = TRUE;
return FALSE;
}
#ifndef DISABLE_DKIM
then the caller must feed DKIM.
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
*/
int
-tls_read(BOOL is_server, uschar *buff, size_t len)
+tls_read(void * ct_ctx, uschar *buff, size_t len)
{
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
ssize_t inbytes;
if (len > INT_MAX)
/*
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 more data expected soon
*/
int
-tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more)
+tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more)
{
ssize_t outbytes;
size_t left = len;
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
#ifdef SUPPORT_CORK
static BOOL corked = FALSE;