# 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
# ifdef SUPPORT_SRV_OCSP_STACK
builtin_macro_create(US"_HAVE_TLS_OCSP_LIST");
# endif
-#ifdef EXIM_HAVE_INOTIFY
+#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
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
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);
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 */
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 */
-/* Create and install a selfsigned certificate, for use in server mode */
+/* Create and install a selfsigned certificate, for use in server mode. */
static int
tls_install_selfsign(exim_gnutls_state_st * state, uschar ** errstr)
if (TRUE) goto err;
#endif
+DEBUG(D_tls) debug_printf("TLS: generating selfsigned server cert\n");
where = US"initialising pkey";
if ((rc = gnutls_x509_privkey_init(&pkey))) goto err;
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)))
- || (rc = gnutls_x509_crt_set_expiration_time(cert, now + 60 * 60)) /* 1 hr */
+ || (rc = gnutls_x509_crt_set_expiration_time(cert, (long)2 * 60 * 60)) /* 2 hour */
|| (rc = gnutls_x509_crt_set_key(cert, pkey))
|| (rc = gnutls_x509_crt_set_dn_by_oid(cert,
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 */
+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;
CCS p, errpos);
}
-static void
+static unsigned
tls_server_creds_init(void)
{
uschar * dummy_errstr;
+unsigned lifetime = 0;
state_server.lib_state = null_tls_preload;
if (gnutls_certificate_allocate_credentials(
(gnutls_certificate_credentials_t *) &state_server.lib_state.x509_cred))
{
state_server.lib_state.x509_cred = NULL;
- return;
+ return lifetime;
}
creds_basic_init(state_server.lib_state.x509_cred, TRUE);
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
/* If tls_certificate has any $ indicating expansions, it is not good.
If tls_privatekey is set but has $, not good. Likewise for tls_ocsp_file.
-If all good (and tls_certificate set), load the cert(s). Do not try
-to handle selfsign generation for now (tls_certificate null/empty;
-XXX will want to do that later though) due to the lifetime/expiry issue. */
+If all good (and tls_certificate set), load the cert(s). */
if ( opt_set_and_noexpand(tls_certificate)
# ifndef DISABLE_OCSP
state_server.lib_state.conn_certs = TRUE;
}
}
+else if ( !tls_certificate && !tls_privatekey
+# ifndef DISABLE_OCSP
+ && !tls_ocsp_file
+# endif
+ )
+ { /* Generate & preload a selfsigned cert. No files to watch. */
+ if ((tls_install_selfsign(&state_server, &dummy_errstr)) == OK)
+ {
+ state_server.lib_state.conn_certs = TRUE;
+ lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */
+ }
+ }
else
DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n");
DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n");
if (creds_load_cabundle(&state_server, tls_verify_certificates,
NULL, &dummy_errstr) != OK)
- return;
+ return lifetime;
state_server.lib_state.cabundle = TRUE;
/* If CAs loaded and tls_crl is non-empty and has no $, load it */
{
DEBUG(D_tls) debug_printf("TLS: preloading CRL for server\n");
if (creds_load_crl(&state_server, tls_crl, &dummy_errstr) != OK)
- return;
+ return lifetime;
state_server.lib_state.crl = TRUE;
}
}
}
else
DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n");
+return lifetime;
}
tpt_dummy_state.session = NULL;
tpt_dummy_state.lib_state = ob->tls_preload;
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
if ( opt_set_and_noexpand(ob->tls_certificate)
&& opt_unset_or_noexpand(ob->tls_privatekey))
{
}
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
/* Invalidate the creds cached, by dropping the current ones.
Call when we notice one of the source files has changed. */
{
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);
}
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;
/* This handles the variables that might get re-expanded after TLS SNI;
-that's tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */
+tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */
DEBUG(D_tls)
debug_printf("Expanding various TLS configuration options for session credentials\n");
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);
+ 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)
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
}
else
{
- tls_error_gnu(US"gnutls_handshake", rc, NULL, errstr);
+ tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
(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))
#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);
+/*
+Arguments:
+ ct_ctx client TLS context pointer, or NULL for the one global server context
+*/
+
+void
+tls_shutdown_wr(void * ct_ctx)
+{
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
+tls_support * tlsp = state->tlsp;
+
+if (!tlsp || tlsp->active.sock < 0) return; /* TLS was not active */
+
+tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */
+
+HDEBUG(D_transport|D_tls|D_acl|D_v) debug_printf_indent(" SMTP(TLS shutdown)>>\n");
+gnutls_bye(state->session, GNUTLS_SHUT_WR);
+}
+
/*************************************************
* Close down a TLS session *
*************************************************/
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
+ do_shutdown 0 no data-flush or TLS close-alert
+ 1 if TLS close-alert is to be sent,
+ 2 if also response to be waited for (2s timeout)
Returns: nothing
*/
void
-tls_close(void * ct_ctx, int shutdown)
+tls_close(void * ct_ctx, int do_shutdown)
{
exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
tls_support * tlsp = state->tlsp;
if (!tlsp || tlsp->active.sock < 0) return; /* TLS was not active */
-if (shutdown)
+if (do_shutdown)
{
DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
- shutdown > 1 ? " (with response-wait)" : "");
+ do_shutdown > 1 ? " (with response-wait)" : "");
+
+ tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */
ALARM(2);
- gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+ gnutls_bye(state->session, do_shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
ALARM_CLR(0);
}
void
-tls_get_cache()
+tls_get_cache(void)
{
#ifndef DISABLE_DKIM
exim_gnutls_state_st * state = &state_server;
}
-
-
/*************************************************
* Read bytes from TLS channel *
*************************************************/