* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
/* 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 */
+/* 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 >= 0x030702
+# define HAVE_GNUTLS_EXPORTER
+#endif
+
#ifndef DISABLE_OCSP
# include <gnutls/ocsp.h>
#endif
.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 */
# endif /* AVOID_GNUTLS_PKCS11 */
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030404
+# define HAVE_GNUTLS_PRF_RFC5705
+#endif
static void
extract_exim_vars_from_tls_state(exim_gnutls_state_st * state)
{
-#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
-int old_pool;
-int rc;
-gnutls_datum_t channel;
-#endif
tls_support * tlsp = state->tlsp;
tlsp->active.sock = state->fd_out;
tlsp->channelbinding = NULL;
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
-channel.data = NULL;
-channel.size = 0;
-if ((rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel)))
- { DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc)); }
-else
{
- /* Declare the taintedness of the binding info. On server, untainted; on
- client, tainted - being the Finish msg from the server. */
+ gnutls_datum_t channel = {.data = NULL, .size = 0};
+ int rc;
- old_pool = store_pool;
- store_pool = POOL_PERM;
- tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size,
- !!state->host);
- store_pool = old_pool;
- DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage\n");
+# ifdef HAVE_GNUTLS_EXPORTER
+ if (gnutls_protocol_get_version(state->session) >= GNUTLS_TLS1_3)
+ {
+ rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_EXPORTER, &channel);
+ tlsp->channelbind_exporter = TRUE;
+ }
+ else
+# elif defined(HAVE_GNUTLS_PRF_RFC5705)
+ /* Older libraries may not have GNUTLS_TLS1_3 defined! */
+ if (gnutls_protocol_get_version(state->session) > GNUTLS_TLS1_2)
+ {
+ uschar * buf = store_get(32, state->host ? GET_TAINTED : GET_UNTAINTED);
+ rc = gnutls_prf_rfc5705(state->session,
+ (size_t)24, "EXPORTER-Channel-Binding", (size_t)0, "",
+ 32, CS buf);
+ channel.data = buf;
+ channel.size = 32;
+ }
+ else
+# endif
+ rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel);
+
+ if (rc)
+ { DEBUG(D_tls) debug_printf("extracting channel binding: %s\n", gnutls_strerror(rc)); }
+ else
+ {
+ int old_pool = store_pool;
+ /* Declare the taintedness of the binding info. On server, untainted; on
+ client, tainted if we used the Finish msg from the server. */
+
+ store_pool = POOL_PERM;
+ tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size,
+ !tlsp->channelbind_exporter && state->host ? GET_TAINTED : GET_UNTAINTED);
+ store_pool = old_pool;
+ DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage\n");
+ }
}
#endif
-#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;
if ((rc = gnutls_dh_params_init(&dh_server_params)))
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;
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(D_tls) debug_printf("initialized server D-H parameters\n");
return OK;
}
-#endif
/* Preload whatever creds are static, onto a transport. The client can then
just copy the pointer as it starts up. */
+/*XXX this is not called for a cmdline send. But one needing to use >1 conn would benefit,
+and there seems little downside. */
+
static void
tls_client_creds_init(transport_instance * t, BOOL watch)
{
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. */
int old_pool = store_pool;
store_pool = POOL_PERM;
- state = store_get(sizeof(exim_gnutls_state_st), FALSE);
+ state = store_get(sizeof(exim_gnutls_state_st), GET_UNTAINTED);
store_pool = old_pool;
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
exim_gnutls_peer_err(US"getting size for cert DN failed");
return FAIL; /* should not happen */
}
-dn_buf = store_get_perm(sz, TRUE); /* tainted */
+dn_buf = store_get_perm(sz, GET_TAINTED);
rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
for (nrec = 0; state->dane_data_len[nrec]; ) nrec++;
nrec++;
- dd = store_get(nrec * sizeof(uschar *), FALSE);
- ddl = store_get(nrec * sizeof(int), FALSE);
+ dd = store_get(nrec * sizeof(uschar *), GET_UNTAINTED);
+ ddl = store_get(nrec * sizeof(int), GET_UNTAINTED);
nrec--;
if ((rc = dane_state_init(&s, 0)))
/* We now have a UTF-8 string in sni_name */
old_pool = store_pool;
store_pool = POOL_PERM;
-state->received_sni = string_copy_taint(US sni_name, TRUE);
+state->received_sni = string_copy_taint(US sni_name, GET_TAINTED);
store_pool = old_pool;
/* We set this one now so that variable expansions below will work */
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",
*/
static BOOL
-tls_alpn_plist(const uschar * tls_alpn, const gnutls_datum_t ** plist, unsigned * plen,
+tls_alpn_plist(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))
+if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr))
return FALSE;
if (!exp_alpn)
while (string_nextinlist(&list, &sep, NULL, 0)) cnt++;
- p = store_get(sizeof(gnutls_datum_t) * cnt, is_tainted(exp_alpn));
+ p = store_get(sizeof(gnutls_datum_t) * cnt, 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); }
static void
tls_server_set_acceptable_alpns(exim_gnutls_state_st * state, uschar ** errstr)
{
+uschar * local_alpn = string_copy(tls_alpn);
int rc;
const gnutls_datum_t * plist;
unsigned plen;
-if (tls_alpn_plist(tls_alpn, &plist, &plen, errstr) && plist)
+if (tls_alpn_plist(&local_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. */
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);
+#ifndef DISABLE_EVENT
+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
gnutls_db_remove_session(state->session);
}
else
{
tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
+#ifndef DISABLE_EVENT
+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
(void) gnutls_alert_send_appropriate(state->session, rc);
gnutls_deinit(state->session);
- gnutls_certificate_free_credentials(state->lib_state.x509_cred);
- state->lib_state = null_tls_preload;
millisleep(500);
shutdown(state->fd_out, SHUT_WR);
for (int i = 1024; fgetc(smtp_in) != EOF && i > 0; ) i--; /* drain skt */
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;
}
rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
) if (rr->type == T_TLSA) i++;
-dane_data = store_get(i * sizeof(uschar *), FALSE);
-dane_data_len = store_get(i * sizeof(int), FALSE);
+dane_data = store_get(i * sizeof(uschar *), GET_UNTAINTED);
+dane_data_len = store_get(i * sizeof(int), GET_UNTAINTED);
i = 0;
for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
static void
tls_retrieve_session(tls_support * tlsp, gnutls_session_t session,
- host_item * host, smtp_transport_options_block * ob)
+ smtp_connect_args * conn_args, smtp_transport_options_block * ob)
{
tlsp->resumption = RESUME_SUPPORTED;
-if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
+
+if (!conn_args->have_lbserver)
+ { DEBUG(D_tls) debug_printf("resumption not supported on continued-connection\n"); }
+else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host) == OK)
{
dbdata_tls_session * dt;
int len, rc;
open_db dbblock, * dbm_file;
- DEBUG(D_tls)
- debug_printf("check for resumable session for %s\n", host->address);
tlsp->host_resumable = TRUE;
+ tls_client_resmption_key(tlsp, conn_args, ob);
+
tlsp->resumption |= RESUME_CLIENT_REQUESTED;
if ((dbm_file = dbfn_open(US"tls", O_RDONLY, &dbblock, FALSE, FALSE)))
{
- /* Key for the db is the IP. We'd like to filter the retrieved session
- for ticket advisory expiry, but 3.6.1 seems to give no access to that */
+ /* We'd like to filter the retrieved session for ticket advisory expiry,
+ but 3.6.1 seems to give no access to that */
- if ((dt = dbfn_read_with_length(dbm_file, host->address, &len)))
+ if ((dt = dbfn_read_with_length(dbm_file, tlsp->resume_index, &len)))
if (!(rc = gnutls_session_set_data(session,
CUS dt->session, (size_t)len - sizeof(dbdata_tls_session))))
{
{
open_db dbblock, * dbm_file;
int dlen = sizeof(dbdata_tls_session) + tkt.size;
- dbdata_tls_session * dt = store_get(dlen, TRUE);
+ dbdata_tls_session * dt = store_get(dlen, GET_TAINTED);
DEBUG(D_tls) debug_printf("session data size %u\n", (unsigned)tkt.size);
memcpy(dt->session, tkt.data, tkt.size);
if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE)))
{
/* key for the db is the IP */
- dbfn_delete(dbm_file, host->address);
- dbfn_write(dbm_file, host->address, dt, dlen);
+ dbfn_write(dbm_file, tlsp->resume_index, dt, dlen);
dbfn_close(dbm_file);
DEBUG(D_tls)
static void
tls_client_resume_prehandshake(exim_gnutls_state_st * state,
- tls_support * tlsp, host_item * host,
+ tls_support * tlsp, smtp_connect_args * conn_args,
smtp_transport_options_block * ob)
{
gnutls_session_set_ptr(state->session, state);
gnutls_handshake_set_hook_function(state->session,
GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, GNUTLS_HOOK_POST, tls_client_ticket_cb);
-tls_retrieve_session(tlsp, state->session, host, ob);
+tls_retrieve_session(tlsp, state->session, conn_args, ob);
}
static void
const gnutls_datum_t * plist;
unsigned plen;
- if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr))
+ 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)
#endif
#ifdef EXIM_HAVE_TLS_RESUME
-tls_client_resume_prehandshake(state, tlsp, host, ob);
+tls_client_resume_prehandshake(state, tlsp, conn_args, ob);
#endif
#ifndef DISABLE_EVENT
if (do_shutdown)
{
DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
- do_shutdown > 1 ? " (with response-wait)" : "");
+ do_shutdown > TLS_SHUTDOWN_NOWAIT ? " (with response-wait)" : "");
tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */
+#ifdef EXIM_TCP_CORK
+ if (do_shutdown == TLS_SHUTDOWN_WAIT)
+ (void) setsockopt(tlsp->active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &off, sizeof(off));
+#endif
+
+ /* The library seems to have no way to only wait for a peer's
+ shutdown, so handle the same as TLS_SHUTDOWN_WAIT */
+
ALARM(2);
- gnutls_bye(state->session, do_shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+ gnutls_bye(state->session,
+ do_shutdown > TLS_SHUTDOWN_NOWAIT ? 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);
-gnutls_certificate_free_credentials(state->lib_state.x509_cred);
-state->lib_state = null_tls_preload;
-
tlsp->active.sock = -1;
tlsp->active.tls_ctx = NULL;
/* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */
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)
{
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;
/* See a description in tls-openssl.c for an explanation of why this exists.
-Arguments: a FILE* to print the results to
-Returns: nothing
+Arguments: string to append to
+Returns: string
*/
-void
-tls_version_report(FILE *f)
+gstring *
+tls_version_report(gstring * g)
{
-fprintf(f, "Library version: GnuTLS: Compile: %s\n"
- " Runtime: %s\n",
- LIBGNUTLS_VERSION,
- gnutls_check_version(NULL));
+return string_fmt_append(g,
+ "Library version: GnuTLS: Compile: %s\n"
+ " Runtime: %s\n",
+ LIBGNUTLS_VERSION,
+ gnutls_check_version(NULL));
}
#endif /*!MACRO_PREDEF*/