*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
-/* See the file NOTICE for conditions of use and distribution. */
-
/* Copyright (c) Phil Pennock 2012 */
+/* Copyright (c) The Exim Maintainers 2020 - 2021 */
+/* See the file NOTICE for conditions of use and distribution. */
/* This file provides TLS/SSL support for Exim using the GnuTLS library,
one of the available supported implementations. This file is #included into
#if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
# define SUPPORT_SRV_OCSP_STACK
#endif
-#if GNUTLS_VERSION_NUMBER >= 0x030600
-# define GNUTLS_AUTO_DHPARAMS
-#endif
#if GNUTLS_VERSION_NUMBER >= 0x030603
# define EXIM_HAVE_TLS1_3
# define SUPPORT_GNUTLS_EXT_RAW_PARSE
# endif
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030200
+# ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE
+# define EXIM_HAVE_ALPN
+# endif
+#endif
+
#ifndef DISABLE_OCSP
# include <gnutls/ocsp.h>
#endif
#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
builtin_macro_create(US"_HAVE_TLS_CA_CACHE");
# endif
+# ifdef EXIM_HAVE_ALPN
+builtin_macro_create(US"_HAVE_TLS_ALPN");
+# endif
}
#else
.fd_out = -1,
};
-#ifndef GNUTLS_AUTO_DHPARAMS
/* 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
don't want to repeat this. */
static gnutls_dh_params_t dh_server_params = NULL;
-#endif
static int ssl_session_timeout = 7200; /* Two hours */
static BOOL exim_testharness_disable_ocsp_validity_check = FALSE;
#endif
+#ifdef EXIM_HAVE_ALPN
+static int server_seen_alpn = -1; /* count of names */
+#endif
#ifdef EXIM_HAVE_TLS_RESUME
static gnutls_datum_t server_sessticket_key;
#endif
+
/* ------------------------------------------------------------------------ */
/* macros */
static int
-tls_error_gnu(const uschar *prefix, int err, const host_item *host,
+tls_error_gnu(exim_gnutls_state_st * state, const uschar *prefix, int err,
uschar ** errstr)
{
-return tls_error(prefix, US gnutls_strerror(err), host, errstr);
+return tls_error(prefix,
+ state && err == GNUTLS_E_FATAL_ALERT_RECEIVED
+ ? US gnutls_alert_get_name(gnutls_alert_get(state->session))
+ : US gnutls_strerror(err),
+ state ? state->host : NULL,
+ errstr);
}
static int
if (!gnutls_allow_auto_pkcs11)
if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL)))
- return tls_error_gnu(US"gnutls_pkcs11_init", rc, NULL, errstr);
+ return tls_error_gnu(NULL, US"gnutls_pkcs11_init", rc, errstr);
#endif
#ifndef GNUTLS_AUTO_GLOBAL_INIT
if ((rc = gnutls_global_init()))
- return tls_error_gnu(US"gnutls_global_init", rc, NULL, errstr);
+ return tls_error_gnu(NULL, US"gnutls_global_init", rc, errstr);
#endif
#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
-#ifndef GNUTLS_AUTO_DHPARAMS
/*************************************************
* Setup up DH parameters *
*************************************************/
{
int fd, rc;
unsigned int dh_bits;
-gnutls_datum_t m = {.data = NULL, .size = 0};
+gnutls_datum_t m;
uschar filename_buf[PATH_MAX];
uschar *filename = NULL;
size_t sz;
DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n");
if ((rc = gnutls_dh_params_init(&dh_server_params)))
- return tls_error_gnu(US"gnutls_dh_params_init", rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_init", rc, errstr);
+
+m.data = NULL;
+m.size = 0;
if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr))
return DEFER;
if (m.data)
{
if ((rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM)))
- return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr);
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);
store_free(m.data);
if (rc)
- return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr);
DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
}
return tls_error(US"Filename too long to generate replacement",
filename, NULL, errstr);
- temp_fn = string_copy(US"%s.XXXXXXX");
+ temp_fn = string_copy(US"exim-dh.XXXXXXX");
if ((fd = mkstemp(CS temp_fn)) < 0) /* modifies temp_fn */
return tls_error_sys(US"Unable to open temp file", errno, NULL, errstr);
(void)exim_chown(temp_fn, exim_uid, exim_gid); /* Probably not necessary */
- /* GnuTLS overshoots! If we ask for 2236, we might get 2237 or more. But
- there's no way to ask GnuTLS how many bits there really are. We can ask
- how many bits were used in a TLS session, but that's it! The prime itself
- is hidden behind too much abstraction. So we ask for less, and proceed on
- a wing and a prayer. First attempt, subtracted 3 for 2233 and got 2240. */
-
+ /* GnuTLS overshoots!
+ * If we ask for 2236, we might get 2237 or more.
+ * But there's no way to ask GnuTLS how many bits there really are.
+ * We can ask how many bits were used in a TLS session, but that's it!
+ * The prime itself is hidden behind too much abstraction.
+ * So we ask for less, and proceed on a wing and a prayer.
+ * First attempt, subtracted 3 for 2233 and got 2240.
+ */
if (dh_bits >= EXIM_CLIENT_DH_MIN_BITS + 10)
{
dh_bits_gen = dh_bits - 10;
debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
dh_bits_gen);
if ((rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen)))
- return tls_error_gnu(US"gnutls_dh_params_generate2", rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_generate2", rc, errstr);
/* 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
if ( (rc = gnutls_dh_params_export_pkcs3(dh_server_params,
GNUTLS_X509_FMT_PEM, m.data, &sz))
&& rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
- return tls_error_gnu(US"gnutls_dh_params_export_pkcs3(NULL) sizing",
- rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3(NULL) sizing",
+ rc, errstr);
m.size = sz;
if (!(m.data = store_malloc(m.size)))
return tls_error_sys(US"memory allocation failed", errno, NULL, errstr);
m.data, &sz)))
{
store_free(m.data);
- return tls_error_gnu(US"gnutls_dh_params_export_pkcs3() real", rc, host, errstr);
+ return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3() real", rc, errstr);
}
m.size = sz; /* shrink by 1, probably */
DEBUG(D_tls) debug_printf("initialized server D-H parameters\n");
return OK;
}
-#endif
return rc;
err:
- rc = tls_error_gnu(where, rc, NULL, errstr);
+ rc = tls_error_gnu(state, where, rc, errstr);
goto out;
}
int rc = gnutls_certificate_set_x509_key_file(state->lib_state.x509_cred,
CCS certfile, CCS keyfile, GNUTLS_X509_FMT_PEM);
if (rc < 0)
- return tls_error_gnu(
+ return tls_error_gnu(state,
string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile),
- rc, host, errstr);
+ rc, errstr);
return -rc;
}
/* Make a note that we saw a status-request */
static int
tls_server_clienthello_ext(void * ctx, unsigned tls_id,
- const unsigned char *data, unsigned size)
+ const uschar * data, unsigned size)
{
-/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
-if (tls_id == 5) /* status_request */
+/* The values for tls_id are documented here:
+https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
+switch (tls_id)
{
- DEBUG(D_tls) debug_printf("Seen status_request extension from client\n");
- tls_in.ocsp = OCSP_NOT_RESP;
+ case 5: /* Status Request */
+ DEBUG(D_tls) debug_printf("Seen status_request extension from client\n");
+ tls_in.ocsp = OCSP_NOT_RESP;
+ break;
+#ifdef EXIM_HAVE_ALPN
+ case 16: /* Application Layer Protocol Notification */
+ /* The format of "data" here doesn't seem to be documented, but appears
+ to be a 2-byte field with a (redundant, given the "size" arg) total length
+ then a sequence of one-byte size then string (not nul-term) names. The
+ latter is as described in OpenSSL documentation. */
+
+ DEBUG(D_tls) debug_printf("Seen ALPN extension from client (s=%u):", size);
+ for (const uschar * s = data+2; s-data < size-1; s += *s + 1)
+ {
+ server_seen_alpn++;
+ DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1);
+ }
+ DEBUG(D_tls) debug_printf("\n");
+ if (server_seen_alpn > 1)
+ {
+ DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n");
+ return GNUTLS_E_NO_APPLICATION_PROTOCOL;
+ }
+ break;
+#endif
}
return 0;
}
unsigned when, unsigned int incoming, const gnutls_datum_t * msg)
{
DEBUG(D_tls) debug_printf("Sending certificate-status\n"); /*XXX we get this for tls1.2 but not for 1.3 */
-#ifdef SUPPORT_SRV_OCSP_STACK
+# ifdef SUPPORT_SRV_OCSP_STACK
tls_in.ocsp = exim_testharness_disable_ocsp_validity_check
? OCSP_VFY_NOT_TRIED : OCSP_VFIED; /* We know that GnuTLS verifies responses */
-#else
+# else
tls_in.ocsp = OCSP_VFY_NOT_TRIED;
-#endif
+# endif
return 0;
}
if ((rc = gnutls_certificate_set_ocsp_status_request_file2(
state->lib_state.x509_cred, CCS ofile, gnutls_cert_index,
ocsp_fmt)) < 0)
- return tls_error_gnu(
+ return tls_error_gnu(state,
US"gnutls_certificate_set_ocsp_status_request_file2",
- rc, NULL, errstr);
+ rc, errstr);
DEBUG(D_tls)
debug_printf(" %d response%s loaded\n", rc, rc>1 ? "s":"");
if ((rc = gnutls_certificate_set_ocsp_status_request_function2(
state->lib_state.x509_cred, gnutls_cert_index,
server_ocsp_stapling_cb, ofile)))
- return tls_error_gnu(
+ return tls_error_gnu(state,
US"gnutls_certificate_set_ocsp_status_request_function2",
- rc, NULL, errstr);
+ rc, errstr);
else
# endif
{
}
if (cert_count < 0)
- return tls_error_gnu(US"setting certificate trust", cert_count, host, errstr);
+ return tls_error_gnu(state, US"setting certificate trust", cert_count, errstr);
DEBUG(D_tls)
debug_printf("Added %d certificate authorities\n", cert_count);
DEBUG(D_tls) debug_printf("loading CRL file = %s\n", crl);
if ((cert_count = gnutls_certificate_set_x509_crl_file(state->lib_state.x509_cred,
CS crl, GNUTLS_X509_FMT_PEM)) < 0)
- return tls_error_gnu(US"gnutls_certificate_set_x509_crl_file",
- cert_count, state->host, errstr);
+ return tls_error_gnu(state, US"gnutls_certificate_set_x509_crl_file",
+ cert_count, errstr);
DEBUG(D_tls) debug_printf("Processed %d CRLs\n", cert_count);
return OK;
else
DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n");
-/* If tls_verify_certificates is non-empty and has no $, load CAs */
+/* If tls_verify_certificates is non-empty and has no $, load CAs.
+If none was configured and we can't handle "system", treat as empty. */
-if (opt_set_and_noexpand(tls_verify_certificates))
+if ( opt_set_and_noexpand(tls_verify_certificates)
+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE
+ && Ustrcmp(tls_verify_certificates, "system") != 0
+#endif
+ )
{
if (tls_set_watch(tls_verify_certificates, FALSE))
{
DEBUG(D_tls)
debug_printf("TLS: not preloading client certs, for transport '%s'\n", t->name);
-if (opt_set_and_noexpand(ob->tls_verify_certificates))
+/* If tls_verify_certificates is non-empty and has no $, load CAs.
+If none was configured and we can't handle "system", treat as empty. */
+
+if ( opt_set_and_noexpand(ob->tls_verify_certificates)
+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE
+ && Ustrcmp(ob->tls_verify_certificates, "system") != 0
+#endif
+ )
{
if (!watch || tls_set_watch(ob->tls_verify_certificates, FALSE))
{
{
if ((rc = gnutls_certificate_allocate_credentials(
(gnutls_certificate_credentials_t *) &state->lib_state.x509_cred)))
- return tls_error_gnu(US"gnutls_certificate_allocate_credentials",
- rc, host, errstr);
+ return tls_error_gnu(state, US"gnutls_certificate_allocate_credentials",
+ rc, errstr);
creds_basic_init(state->lib_state.x509_cred, !host);
}
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
in that case, certificate verification fails, which seems to be the correct
-behaviour. */
+behaviour.
+If none was configured and we can't handle "system", treat as empty. */
if (!state->lib_state.cabundle)
{
int rc;
const host_item *host = state->host; /* macro should be reconsidered? */
-#ifndef GNUTLS_AUTO_DHPARAMS
/* Create D-H parameters, or read them from the cache file. This function does
its own SMTP error messaging. This only happens for the server, TLS D-H ignores
client-side params. */
if (!dh_server_params)
if ((rc = init_server_dh(errstr)) != OK) return rc;
- /* Unnecessary & discouraged with 3.6.0 or later */
+ /* Unnecessary & discouraged with 3.6.0 or later, according to docs. But without it,
+ no DHE- ciphers are advertised. */
gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params);
}
-#endif
/* Link the credentials to the session. */
if ((rc = gnutls_credentials_set(state->session,
GNUTLS_CRD_CERTIFICATE, state->lib_state.x509_cred)))
- return tls_error_gnu(US"gnutls_credentials_set", rc, host, errstr);
+ return tls_error_gnu(state, US"gnutls_credentials_set", rc, errstr);
return OK;
}
state->tls_crl = tls_crl;
}
if (rc)
- return tls_error_gnu(US"gnutls_init", rc, host, errstr);
+ return tls_error_gnu(state, US"gnutls_init", rc, errstr);
state->tls_require_ciphers = require_ciphers;
state->host = host;
sz = Ustrlen(state->tlsp->sni);
if ((rc = gnutls_server_name_set(state->session,
GNUTLS_NAME_DNS, state->tlsp->sni, sz)))
- return tls_error_gnu(US"gnutls_server_name_set", rc, host, errstr);
+ return tls_error_gnu(state, US"gnutls_server_name_set", rc, errstr);
}
}
else if (state->tls_sni)
}
if ((rc = creds_load_pristring(state, p, &errpos)))
- return tls_error_gnu(string_sprintf(
+ return tls_error_gnu(state, string_sprintf(
"gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
- p, errpos - CS p, errpos),
- rc, host, errstr);
+ p, (long)(errpos - CS p), errpos),
+ rc, errstr);
}
else
{
if ((rc = gnutls_priority_set(state->session, state->lib_state.pri_cache)))
- return tls_error_gnu(US"gnutls_priority_set", rc, host, errstr);
+ return tls_error_gnu(state, US"gnutls_priority_set", rc, errstr);
/* This also sets the server ticket expiration time to the same, and
the STEK rotation time to 3x. */
DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
(Label), gnutls_strerror(rc)); \
if (state->verify_requirement >= VERIFY_REQUIRED) \
- return tls_error_gnu((Label), rc, state->host, errstr); \
+ return tls_error_gnu(state, (Label), rc, errstr); \
return OK; \
} \
} while (0)
state->tlsp->peercert = crt;
if ((yield = event_raise(state->event_action,
- US"tls:cert", string_sprintf("%d", cert_list_size))))
+ US"tls:cert", string_sprintf("%d", cert_list_size), &errno)))
{
log_write(0, LOG_MAIN,
"SSL verify denied by event-action: depth=%d: %s",
DEBUG(D_tls) debug_printf("Session resumed\n");
}
}
-#endif
+#endif /* EXIM_HAVE_TLS_RESUME */
+
+
+#ifdef EXIM_HAVE_ALPN
+/* Expand and convert an Exim list to a gnutls_datum list. False return for fail.
+NULL plist return for silent no-ALPN.
+*/
+
+static BOOL
+tls_alpn_plist(const uschar * tls_alpn, const gnutls_datum_t ** plist, unsigned * plen,
+ uschar ** errstr)
+{
+uschar * exp_alpn;
+
+if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr))
+ return FALSE;
+
+if (!exp_alpn)
+ {
+ DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n");
+ *plist = NULL;
+ }
+else
+ {
+ const uschar * list = exp_alpn;
+ int sep = 0;
+ unsigned cnt = 0;
+ gnutls_datum_t * p;
+ uschar * s;
+
+ while (string_nextinlist(&list, &sep, NULL, 0)) cnt++;
+
+ p = store_get(sizeof(gnutls_datum_t) * cnt, is_tainted(exp_alpn));
+ list = exp_alpn;
+ for (int i = 0; s = string_nextinlist(&list, &sep, NULL, 0); i++)
+ { p[i].data = s; p[i].size = Ustrlen(s); }
+ *plist = (*plen = cnt) ? p : NULL;
+ }
+return TRUE;
+}
+
+static void
+tls_server_set_acceptable_alpns(exim_gnutls_state_st * state, uschar ** errstr)
+{
+int rc;
+const gnutls_datum_t * plist;
+unsigned plen;
+
+if (tls_alpn_plist(tls_alpn, &plist, &plen, errstr) && plist)
+ {
+ /* This seems to be only mandatory if the client sends an ALPN extension;
+ not trying ALPN is ok. Need to decide how to support server-side must-alpn. */
+
+ server_seen_alpn = 0;
+ if (!(rc = gnutls_alpn_set_protocols(state->session, plist, plen,
+ GNUTLS_ALPN_MANDATORY)))
+ gnutls_handshake_set_hook_function(state->session,
+ GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb);
+ else
+ DEBUG(D_tls)
+ debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc));
+ }
+}
+#endif /* EXIM_HAVE_ALPN */
+
/* ------------------------------------------------------------------------ */
/* Exported functions */
#endif
}
+#ifdef EXIM_HAVE_ALPN
+tls_server_set_acceptable_alpns(state, errstr);
+#endif
+
#ifdef EXIM_HAVE_TLS_RESUME
tls_server_resume_prehandshake(state);
#endif
if (rc != GNUTLS_E_SUCCESS)
{
+ DEBUG(D_tls) debug_printf(" error %d from gnutls_handshake: %s\n",
+ rc, gnutls_strerror(rc));
+
/* It seems that, except in the case of a timeout, we have to close the
connection right here; otherwise if the other end is running OpenSSL it hangs
until the server times out. */
if (sigalrm_seen)
{
tls_error(US"gnutls_handshake", US"timed out", NULL, errstr);
+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
gnutls_db_remove_session(state->session);
}
else
{
- tls_error_gnu(US"gnutls_handshake", rc, NULL, errstr);
+ tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
(void) gnutls_alert_send_appropriate(state->session, rc);
gnutls_deinit(state->session);
gnutls_certificate_free_credentials(state->lib_state.x509_cred);
DEBUG(D_tls) post_handshake_debug(state);
+#ifdef EXIM_HAVE_ALPN
+if (server_seen_alpn > 0)
+ {
+ DEBUG(D_tls)
+ { /* The client offered ALPN. See what was negotiated. */
+ gnutls_datum_t p = {.size = 0};
+ int rc = gnutls_alpn_get_selected_protocol(state->session, &p);
+ if (!rc)
+ debug_printf("ALPN negotiated: %.*s\n", (int)p.size, p.data);
+ else
+ debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc));
+
+ }
+ }
+else if (server_seen_alpn == 0)
+ if (verify_check_host(&hosts_require_alpn) == OK)
+ {
+ gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL);
+ tls_error(US"handshake", US"ALPN required but not negotiated", NULL, errstr);
+ return FAIL;
+ }
+ else
+ DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n");
+else
+ DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n");
+#endif
+
/* Verify after the fact */
if (!verify_certificate(state, errstr))
receive_getc = tls_getc;
receive_getbuf = tls_getbuf;
receive_get_cache = tls_get_cache;
+receive_hasc = tls_hasc;
receive_ungetc = tls_ungetc;
receive_feof = tls_feof;
receive_ferror = tls_ferror;
-receive_smtp_buffered = tls_smtp_buffered;
return OK;
}
#endif
}
+if (ob->tls_alpn)
+#ifdef EXIM_HAVE_ALPN
+ {
+ const gnutls_datum_t * plist;
+ unsigned plen;
+
+ if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr))
+ return FALSE;
+ if (plist)
+ if (gnutls_alpn_set_protocols(state->session, plist, plen, 0) != 0)
+ {
+ tls_error(US"alpn init", NULL, state->host, errstr);
+ return FALSE;
+ }
+ else
+ DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn);
+ }
+#else
+ log_write(0, LOG_MAIN, "ALPN unusable with this GnuTLS library version; ignoring \"%s\"\n",
+ ob->tls_alpn);
+#endif
+
{
int dh_min_bits = ob->tls_dh_min_bits;
if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS)
if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
NULL, 0, NULL)) != OK)
{
- tls_error_gnu(US"cert-status-req", rc, state->host, errstr);
+ tls_error_gnu(state, US"cert-status-req", rc, errstr);
return FALSE;
}
tlsp->ocsp = OCSP_NOT_RESP;
tls_error(US"gnutls_handshake", US"timed out", state->host, errstr);
}
else
- tls_error_gnu(US"gnutls_handshake", rc, state->host, errstr);
+ tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
return FALSE;
}
gnutls_free(printed.data);
}
else
- (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr);
+ (void) tls_error_gnu(state, US"ocsp decode", rc, errstr);
if (idx == 0 && rc)
- (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr);
+ (void) tls_error_gnu(state, US"ocsp decode", rc, errstr);
}
if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
tls_client_resume_posthandshake(state, tlsp, host);
#endif
+#ifdef EXIM_HAVE_ALPN
+if (ob->tls_alpn) /* We requested. See what was negotiated. */
+ {
+ gnutls_datum_t p = {.size = 0};
+
+ if (gnutls_alpn_get_selected_protocol(state->session, &p) == 0)
+ { DEBUG(D_tls) debug_printf("ALPN negotiated: '%.*s'\n", (int)p.size, p.data); }
+ else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK)
+ {
+ gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL);
+ tls_error(US"handshake", US"ALPN required but not negotiated", state->host, errstr);
+ return FALSE;
+ }
+ else
+ DEBUG(D_tls) debug_printf("No ALPN negotiated");
+ }
+#endif
+
/* Sets various Exim expansion variables; may need to adjust for ACL callouts */
extract_exim_vars_from_tls_state(state);
tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */
+#ifdef EXIM_TCP_CORK
+ if (do_shutdown > 1)
+ (void) setsockopt(tlsp->active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &off, sizeof(off));
+#endif
+
ALARM(2);
gnutls_bye(state->session, do_shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
ALARM_CLR(0);
receive_getc = smtp_getc;
receive_getbuf = smtp_getbuf;
receive_get_cache = smtp_get_cache;
+ receive_hasc = smtp_hasc;
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
receive_ferror = smtp_ferror;
- receive_smtp_buffered = smtp_buffered;
}
gnutls_deinit(state->session);
return state->xfer_buffer[state->xfer_buffer_lwm++];
}
+BOOL
+tls_hasc(void)
+{
+exim_gnutls_state_st * state = &state_server;
+return state->xfer_buffer_lwm < state->xfer_buffer_hwm;
+}
+
uschar *
tls_getbuf(unsigned * len)
{
}
+/* Get up to the given number of bytes from any cached data, and feed to dkim. */
void
-tls_get_cache(void)
+tls_get_cache(unsigned lim)
{
#ifndef DISABLE_DKIM
exim_gnutls_state_st * state = &state_server;
int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm;
+if (n > lim)
+ n = lim;
if (n > 0)
dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n);
#endif
BOOL
-tls_could_read(void)
+tls_could_getc(void)
{
return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
|| gnutls_record_check_pending(state_server.session) > 0;
rc = gnutls_priority_init(&priority_cache, CS expciphers, &errpos);
validate_check_rc(string_sprintf(
"gnutls_priority_init(%s) failed at offset %ld, \"%.8s..\"",
- expciphers, errpos - CS expciphers, errpos));
+ expciphers, (long)(errpos - CS expciphers), errpos));
#undef return_deinit
#undef validate_check_rc