X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/6a9cf7f890226aa085842cd3d94b13e78ea31637..a85c067ba6c6940512cf57ec213277a370d87e70:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index f5c6a8bd6..c98760202 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -2,11 +2,11 @@ * 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. */ +/* SPDX-License-Identifier: GPL-2.0-only */ /* This file provides TLS/SSL support for Exim using the GnuTLS library, one of the available supported implementations. This file is #included into @@ -90,9 +90,6 @@ require current GnuTLS, then we'll drop support for the ancient libraries). #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 @@ -119,6 +116,16 @@ require current GnuTLS, then we'll drop support for the ancient libraries). # endif #endif +#if GNUTLS_VERSION_NUMBER >= 0x030200 +# ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE +# define EXIM_HAVE_ALPN +# endif +#endif + +#if GNUTLS_VERSION_NUMBER >= 0x030702 +# define HAVE_GNUTLS_EXPORTER +#endif + #ifndef DISABLE_OCSP # include #endif @@ -145,9 +152,12 @@ builtin_macro_create(US"_HAVE_TLS_OCSP"); # 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 @@ -257,13 +267,11 @@ static exim_gnutls_state_st state_server = { .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 */ @@ -278,10 +286,14 @@ static BOOL gnutls_buggy_ocsp = FALSE; 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 */ @@ -326,6 +338,9 @@ before, for now. */ # endif /* AVOID_GNUTLS_PKCS11 */ #endif +#if GNUTLS_VERSION_NUMBER >= 0x030404 +# define HAVE_GNUTLS_PRF_RFC5705 +#endif @@ -345,6 +360,58 @@ tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, #endif +/************************************************* +* Handle TLS error * +*************************************************/ + +/* Called from lots of places when errors occur before actually starting to do +the TLS handshake, that is, while the session is still in clear. Always returns +DEFER for a server and FAIL for a client so that most calls can use "return +tls_error(...)" to do this processing and then give an appropriate return. A +single function is used for both server and client, because it is called from +some shared functions. + +Argument: + prefix text to include in the logged error + msg additional error string (may be NULL) + usually obtained from gnutls_strerror() + host NULL if setting up a server; + the connected host if setting up a client + errstr pointer to returned error string + +Returns: OK/DEFER/FAIL +*/ + +static int +tls_error(const uschar *prefix, const uschar *msg, const host_item *host, + uschar ** errstr) +{ +if (errstr) + *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : US""); +return host ? FAIL : DEFER; +} + + +static int +tls_error_gnu(exim_gnutls_state_st * state, const uschar *prefix, int err, + uschar ** 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 +tls_error_sys(const uschar *prefix, int err, const host_item *host, + uschar ** errstr) +{ +return tls_error(prefix, US strerror(err), host, errstr); +} + + /* ------------------------------------------------------------------------ */ /* Initialisation */ @@ -379,9 +446,10 @@ return FALSE; #endif -static void -tls_g_init(void) +static int +tls_g_init(uschar ** errstr) { +int rc; DEBUG(D_tls) debug_printf("GnuTLS global init required\n"); #if defined(HAVE_GNUTLS_PKCS11) && !defined(GNUTLS_AUTO_PKCS11_MANUAL) @@ -393,12 +461,12 @@ To prevent this, we init PKCS11 first, which is the documented approach. */ if (!gnutls_allow_auto_pkcs11) if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL))) - return tls_error_gnu(US"gnutls_pkcs11_init", rc, host, 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, host, errstr); + return tls_error_gnu(NULL, US"gnutls_global_init", rc, errstr); #endif #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0 @@ -416,6 +484,7 @@ if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp())) #endif exim_gnutls_base_init_done = TRUE; +return OK; } @@ -432,10 +501,11 @@ tls_per_lib_daemon_tick(void) static void tls_per_lib_daemon_init(void) { +uschar * dummy_errstr; static BOOL once = FALSE; if (!exim_gnutls_base_init_done) - tls_g_init(); + tls_g_init(&dummy_errstr); if (!once) { @@ -456,54 +526,6 @@ if (!once) } /* ------------------------------------------------------------------------ */ -/* Static functions */ - -/************************************************* -* Handle TLS error * -*************************************************/ - -/* Called from lots of places when errors occur before actually starting to do -the TLS handshake, that is, while the session is still in clear. Always returns -DEFER for a server and FAIL for a client so that most calls can use "return -tls_error(...)" to do this processing and then give an appropriate return. A -single function is used for both server and client, because it is called from -some shared functions. - -Argument: - prefix text to include in the logged error - msg additional error string (may be NULL) - usually obtained from gnutls_strerror() - host NULL if setting up a server; - the connected host if setting up a client - errstr pointer to returned error string - -Returns: OK/DEFER/FAIL -*/ - -static int -tls_error(const uschar *prefix, const uschar *msg, const host_item *host, - uschar ** errstr) -{ -if (errstr) - *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : US""); -return host ? FAIL : DEFER; -} - - -static int -tls_error_gnu(const uschar *prefix, int err, const host_item *host, - uschar ** errstr) -{ -return tls_error(prefix, US gnutls_strerror(err), host, errstr); -} - -static int -tls_error_sys(const uschar *prefix, int err, const host_item *host, - uschar ** errstr) -{ -return tls_error(prefix, US strerror(err), host, errstr); -} - /************************************************* * Deal with logging errors during I/O * @@ -610,11 +632,6 @@ Argument: 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; @@ -632,21 +649,46 @@ only available for use for authenticators while this TLS session is running. */ 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 @@ -669,7 +711,6 @@ if (!state->host) -#ifndef GNUTLS_AUTO_DHPARAMS /************************************************* * Setup up DH parameters * *************************************************/ @@ -692,7 +733,7 @@ init_server_dh(uschar ** errstr) { 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; @@ -703,7 +744,10 @@ host_item *host = NULL; /* dummy for macros */ 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; @@ -733,7 +777,7 @@ else 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; } @@ -817,7 +861,7 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0) 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); } @@ -849,17 +893,19 @@ if (rc < 0) 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; @@ -872,7 +918,7 @@ if (rc < 0) 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 @@ -883,8 +929,8 @@ if (rc < 0) 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); @@ -894,7 +940,7 @@ if (rc < 0) 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 */ @@ -922,12 +968,11 @@ if (rc < 0) DEBUG(D_tls) debug_printf("initialized server D-H parameters\n"); return OK; } -#endif -/* 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) @@ -944,6 +989,7 @@ rc = GNUTLS_E_NO_CERTIFICATE_FOUND; 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; @@ -968,7 +1014,7 @@ 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))) - || (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, @@ -998,7 +1044,7 @@ out: return rc; err: - rc = tls_error_gnu(where, rc, NULL, errstr); + rc = tls_error_gnu(state, where, rc, errstr); goto out; } @@ -1019,9 +1065,9 @@ tls_add_certfile(exim_gnutls_state_st * state, const host_item * host, 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; } @@ -1056,13 +1102,37 @@ return 0; /* 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; } @@ -1073,11 +1143,13 @@ tls_server_clienthello_cb(gnutls_session_t session, unsigned int htype, unsigned when, unsigned int incoming, const gnutls_datum_t * msg) { /* Call fn for each extension seen. 3.6.3 onwards */ -return gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg, +int rc = gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg, GNUTLS_EXT_RAW_FLAG_TLS_CLIENT_HELLO); +return rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE ? 0 : rc; } +# ifdef notdef_crashes /* Make a note that we saw a status-response */ static int tls_server_servercerts_ext(void * ctx, unsigned tls_id, @@ -1093,6 +1165,7 @@ if (FALSE && tls_id == 5) /* status_request */ } return 0; } +# endif /* Callback for certificates packet, on server, if we think we might serve stapled-OCSP */ static int @@ -1100,12 +1173,12 @@ tls_server_servercerts_cb(gnutls_session_t session, unsigned int htype, unsigned when, unsigned int incoming, const gnutls_datum_t * msg) { /* Call fn for each extension seen. 3.6.3 onwards */ -#ifdef notdef -/*XXX crashes */ +# ifdef notdef_crashes + /*XXX crashes */ return gnutls_ext_raw_parse(NULL, tls_server_servercerts_ext, msg, 0); -#endif +# endif } -#endif +#endif /*SUPPORT_GNUTLS_EXT_RAW_PARSE*/ /*XXX in tls1.3 the cert-status travel as an extension next to the cert, in the "Handshake Protocol: Certificate" record. @@ -1121,12 +1194,12 @@ tls_server_certstatus_cb(gnutls_session_t session, unsigned int htype, 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; } @@ -1256,9 +1329,9 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 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":""); @@ -1276,9 +1349,9 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) 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 { @@ -1380,7 +1453,7 @@ else } 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); @@ -1395,8 +1468,8 @@ int 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; @@ -1417,60 +1490,84 @@ return gnutls_priority_init( (gnutls_priority_t *) &state->lib_state.pri_cache, 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) - && opt_unset_or_noexpand(tls_privatekey) - && opt_unset_or_noexpand(tls_ocsp_file)) +# ifndef DISABLE_OCSP + && opt_unset_or_noexpand(tls_ocsp_file) +# endif + && opt_unset_or_noexpand(tls_privatekey)) { /* Set watches on the filenames. The implementation does de-duplication so we can just blindly do them all. */ if ( tls_set_watch(tls_certificate, TRUE) - && tls_set_watch(tls_privatekey, TRUE) +# ifndef DISABLE_OCSP && tls_set_watch(tls_ocsp_file, TRUE) - ) +# endif + && tls_set_watch(tls_privatekey, TRUE)) { DEBUG(D_tls) debug_printf("TLS: preloading server certs\n"); if (creds_load_server_certs(&state_server, tls_certificate, tls_privatekey && *tls_privatekey ? tls_privatekey : tls_certificate, - tls_ocsp_file, &dummy_errstr) == 0) +# ifdef DISABLE_OCSP + NULL, +# else + tls_ocsp_file, +# endif + &dummy_errstr) == 0) 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"); -/* 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: 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 */ @@ -1481,7 +1578,7 @@ if (opt_set_and_noexpand(tls_verify_certificates)) { 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; } } @@ -1508,12 +1605,16 @@ if (!tls_require_ciphers || opt_set_and_noexpand(tls_require_ciphers)) } else DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n"); +return lifetime; } /* 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) { @@ -1522,8 +1623,9 @@ exim_gnutls_state_st tpt_dummy_state; host_item * dummy_host = (host_item *)1; uschar * dummy_errstr; -if (!exim_gnutls_base_init_done) - tls_g_init(); +if ( !exim_gnutls_base_init_done + && tls_g_init(&dummy_errstr) != OK) + return; ob->tls_preload = null_tls_preload; if (gnutls_certificate_allocate_credentials( @@ -1537,7 +1639,7 @@ creds_basic_init(ob->tls_preload.x509_cred, FALSE); 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)) { @@ -1565,7 +1667,14 @@ else 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)) { @@ -1601,7 +1710,7 @@ depends on DANE or plain usage. */ } -#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. */ @@ -1686,8 +1795,8 @@ if (!state->lib_state.x509_cred) { 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); } @@ -1750,7 +1859,13 @@ if (!state->lib_state.conn_certs) ? creds_load_client_certs(state, host, state->exp_tls_certificate, state->exp_tls_privatekey, errstr) : creds_load_server_certs(state, state->exp_tls_certificate, - state->exp_tls_privatekey, tls_ocsp_file, errstr) + state->exp_tls_privatekey, +#ifdef DISABLE_OCSP + NULL, +#else + tls_ocsp_file, +#endif + errstr) ) ) return rc; } } @@ -1763,9 +1878,11 @@ else state->exp_tls_certificate = US state->tls_certificate; state->exp_tls_privatekey = US state->tls_privatekey; +#ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE if (state->lib_state.ocsp_hook) gnutls_handshake_set_hook_function(state->session, GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb); +#endif } @@ -1773,7 +1890,8 @@ else 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) { @@ -1864,27 +1982,25 @@ tls_set_remaining_x509(exim_gnutls_state_st *state, uschar ** errstr) 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 (!state->host) { -/*XXX DDD done-bit */ if (!dh_server_params) if ((rc = init_server_dh(errstr)) != OK) return rc; - /* Unnecessary & discouraged with 3.6.0 or later */ - gnutls_certificate_set_dh_params(state->.lib_statex509_cred, dh_server_params); + /* 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; } @@ -1920,8 +2036,9 @@ exim_gnutls_state_st * state; int rc; size_t sz; -if (!exim_gnutls_base_init_done) - tls_g_init(); +if ( !exim_gnutls_base_init_done + && (rc = tls_g_init(errstr)) != OK) + return rc; if (host) { @@ -1930,7 +2047,7 @@ if (host) 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)); @@ -1963,13 +2080,13 @@ else 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"); @@ -1992,7 +2109,7 @@ if (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) @@ -2022,10 +2139,10 @@ if (!state->lib_state.pri_string) } 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 { @@ -2035,7 +2152,7 @@ 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. */ @@ -2233,7 +2350,7 @@ if ((ct = gnutls_certificate_type_get(session)) != GNUTLS_CRT_X509) 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) @@ -2250,7 +2367,7 @@ if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) 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)]"); @@ -2330,8 +2447,8 @@ else 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))) @@ -2577,7 +2694,7 @@ if (sni_type != GNUTLS_NAME_DNS) /* 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 */ @@ -2636,7 +2753,7 @@ if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size))) 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", @@ -2756,7 +2873,72 @@ if (gnutls_session_is_resumed(state->session)) 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(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, 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) +{ +uschar * local_alpn = string_copy(tls_alpn); +int rc; +const gnutls_datum_t * plist; +unsigned plen; + +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. */ + + 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 */ @@ -2813,6 +2995,10 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n"); #endif } +#ifdef EXIM_HAVE_ALPN +tls_server_set_acceptable_alpns(state, errstr); +#endif + #ifdef EXIM_HAVE_TLS_RESUME tls_server_resume_prehandshake(state); #endif @@ -2890,6 +3076,9 @@ ALARM_CLR(0); 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. */ @@ -2897,15 +3086,19 @@ if (rc != GNUTLS_E_SUCCESS) 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(US"gnutls_handshake", rc, NULL, errstr); + 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 */ @@ -2928,6 +3121,33 @@ tls_server_resume_posthandshake(state); 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)) @@ -2954,10 +3174,10 @@ state->xfer_buffer = store_malloc(ssl_xfer_buffer_size); 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; } @@ -3007,8 +3227,8 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; 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; @@ -3066,25 +3286,28 @@ however avoid storing and retrieving session information. */ 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)))) { @@ -3121,7 +3344,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET) { 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); @@ -3130,8 +3353,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET) 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) @@ -3166,14 +3388,14 @@ return 0; 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 @@ -3265,6 +3487,28 @@ if (!cipher_list) #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) @@ -3333,7 +3577,7 @@ if (request_ocsp) 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; @@ -3341,7 +3585,7 @@ if (request_ocsp) #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 @@ -3375,7 +3619,7 @@ if (rc != GNUTLS_E_SUCCESS) 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; } @@ -3420,9 +3664,9 @@ if (request_ocsp) 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) @@ -3444,6 +3688,24 @@ if (request_ocsp) 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); @@ -3455,6 +3717,25 @@ return TRUE; +/* +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 * *************************************************/ @@ -3465,27 +3746,39 @@ would tamper with the TLS session in the parent process). 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 > 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, 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); } @@ -3494,16 +3787,13 @@ if (!ct_ctx) /* server */ 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 */ @@ -3608,6 +3898,13 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm) 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) { @@ -3632,12 +3929,15 @@ return buf; } +/* Get up to the given number of bytes from any cached data, and feed to dkim. */ void -tls_get_cache() +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 @@ -3645,15 +3945,13 @@ if (n > 0) 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; } - - /************************************************* * Read bytes from TLS channel * *************************************************/ @@ -3954,7 +4252,7 @@ DEBUG(D_tls) 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 @@ -3974,17 +4272,18 @@ return NULL; /* 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*/