*************************************************/
/* 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
#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 */
# endif /* AVOID_GNUTLS_PKCS11 */
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030404
+# define HAVE_GNUTLS_PRF_RFC5705
+#endif
#ifndef GNUTLS_AUTO_GLOBAL_INIT
if ((rc = gnutls_global_init()))
- return tls_error_gnu(UNULL, S"gnutls_global_init", rc, errstr);
+ return tls_error_gnu(NULL, US"gnutls_global_init", rc, errstr);
#endif
#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
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};
+ uschar * buf;
+ 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_PRF_RFC5705
+ /* Older libraries may not have GNUTLS_TLS1_3 defined! */
+ if (gnutls_protocol_get_version(state->session) > GNUTLS_TLS1_2)
+ {
+ 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 - being the Finish msg from the server. */
+
+ store_pool = POOL_PERM;
+ tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size,
+ 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
tls_server_clienthello_ext(void * ctx, unsigned tls_id,
const uschar * data, unsigned size)
{
-/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
+/* The values for tls_id are documented here:
+https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
switch (tls_id)
{
case 5: /* Status Request */
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))
{
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. */
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));
if ((rc = creds_load_pristring(state, p, &errpos)))
return tls_error_gnu(state, string_sprintf(
"gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
- p, errpos - CS p, errpos),
+ p, (long)(errpos - CS p), errpos),
rc, errstr);
}
else
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",
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); }
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(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);
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;
{
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 (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);
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
/* 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*/