X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/041bf37266e8b97f457b78401ee7006429c69495..d4ff6d2a25234f7558baa99a54ca138c3f004b06:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 02eb7e8a0..3e8ec6d84 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 - 2023 */ /* 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-or-later */ /* 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 @@ -125,6 +122,10 @@ require current GnuTLS, then we'll drop support for the ancient libraries). # endif #endif +#if GNUTLS_VERSION_NUMBER >= 0x030702 +# define HAVE_GNUTLS_EXPORTER +#endif + #ifndef DISABLE_OCSP # include #endif @@ -266,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 */ @@ -339,6 +338,9 @@ before, for now. */ # endif /* AVOID_GNUTLS_PKCS11 */ #endif +#if GNUTLS_VERSION_NUMBER >= 0x030404 +# define HAVE_GNUTLS_PRF_RFC5705 +#endif @@ -377,7 +379,7 @@ Argument: the connected host if setting up a client errstr pointer to returned error string -Returns: OK/DEFER/FAIL +Returns: DEFER/FAIL */ static int @@ -390,13 +392,15 @@ return host ? FAIL : DEFER; } +/* Returns: DEFER/FAIL */ 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)) + ? string_sprintf("rxd alert: %s", + US gnutls_alert_get_name(gnutls_alert_get(state->session))) : US gnutls_strerror(err), state ? state->host : NULL, errstr); @@ -630,11 +634,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; @@ -652,21 +651,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 @@ -689,7 +713,6 @@ if (!state->host) -#ifndef GNUTLS_AUTO_DHPARAMS /************************************************* * Setup up DH parameters * *************************************************/ @@ -704,7 +727,7 @@ file is never present. If two processes both compute some new parameters, you waste a bit of effort, but it doesn't seem worth messing around with locking to prevent this. -Returns: OK/DEFER/FAIL +Returns: OK/DEFER (expansion issue)/FAIL (requested none) */ static int @@ -712,7 +735,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; @@ -725,6 +748,9 @@ DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n"); 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; @@ -739,7 +765,7 @@ else if (Ustrcmp(exp_tls_dhparam, "historic") == 0) else if (Ustrcmp(exp_tls_dhparam, "none") == 0) { DEBUG(D_tls) debug_printf("Requested no DH parameters\n"); - return OK; + return FAIL; } else if (exp_tls_dhparam[0] != '/') { @@ -874,12 +900,14 @@ if (rc < 0) 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; @@ -942,7 +970,6 @@ if (rc < 0) DEBUG(D_tls) debug_printf("initialized server D-H parameters\n"); return OK; } -#endif @@ -989,7 +1016,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, (long)2 * 60 * 60)) /* 2 hour */ + || (rc = gnutls_x509_crt_set_expiration_time(cert, now + (long)2 * 60 * 60)) /* 2 hour */ || (rc = gnutls_x509_crt_set_key(cert, pkey)) || (rc = gnutls_x509_crt_set_dn_by_oid(cert, @@ -1092,21 +1119,28 @@ switch (tls_id) /* 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. */ + latter is as described in OpenSSL documentation. + Note that we do not get called for a match_fail, making it hard to log + a single bad ALPN being offered (the common case). */ + { + gstring * g = NULL; 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++; + g = string_append_listele_n(g, ':', s+1, *s); DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1); } DEBUG(D_tls) debug_printf("\n"); if (server_seen_alpn > 1) { + log_write(0, LOG_MAIN, "TLS ALPN (%Y) rejected", g); DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n"); return GNUTLS_E_NO_APPLICATION_PROTOCOL; } break; + } #endif } return 0; @@ -1118,8 +1152,9 @@ 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; } @@ -1150,6 +1185,8 @@ tls_server_servercerts_cb(gnutls_session_t session, unsigned int htype, # ifdef notdef_crashes /*XXX crashes */ return gnutls_ext_raw_parse(NULL, tls_server_servercerts_ext, msg, 0); +# else +return GNUTLS_E_SUCCESS; # endif } #endif /*SUPPORT_GNUTLS_EXT_RAW_PARSE*/ @@ -1198,7 +1235,7 @@ switch (htype) return tls_server_ticket_cb(sess, htype, when, incoming, msg); # endif default: - return 0; + return GNUTLS_E_SUCCESS; } } #endif @@ -1244,6 +1281,7 @@ DEBUG(D_tls) debug_printf("TLS: basic cred init, %s\n", server ? "server" : "client"); } +/* Returns OK/DEFER/FAIL */ static int creds_load_server_certs(exim_gnutls_state_st * state, const uschar * cert, const uschar * pkey, const uschar * ocsp, uschar ** errstr) @@ -1267,7 +1305,7 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0))) return tls_error(US"cert/key setup: out of keys", NULL, NULL, errstr); - else if ((rc = tls_add_certfile(state, NULL, cfile, kfile, errstr)) > 0) + else if ((rc = tls_add_certfile(state, NULL, cfile, kfile, errstr)) > OK) return rc; else { @@ -1345,7 +1383,7 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) } #endif /* DISABLE_OCSP */ } -return 0; +return OK; } static int @@ -1355,7 +1393,7 @@ creds_load_client_certs(exim_gnutls_state_st * state, const host_item * host, int rc = tls_add_certfile(state, host, cert, pkey, errstr); if (rc > 0) return rc; DEBUG(D_tls) debug_printf("TLS: cert/key registered\n"); -return 0; +return OK; } static int @@ -1586,6 +1624,9 @@ 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) { @@ -1781,8 +1822,13 @@ D-H generation. */ if (!state->lib_state.conn_certs) { - if (!Expand_check_tlsvar(tls_certificate, errstr)) + if ( !Expand_check_tlsvar(tls_certificate, errstr) + || f.expand_string_forcedfail) + { + if (f.expand_string_forcedfail) + *errstr = US"expansion of tls_certificate failed"; return DEFER; + } /* certificate is mandatory in server, optional in client */ @@ -1794,8 +1840,14 @@ if (!state->lib_state.conn_certs) else DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n"); - if (state->tls_privatekey && !Expand_check_tlsvar(tls_privatekey, errstr)) + if ( state->tls_privatekey && !Expand_check_tlsvar(tls_privatekey, errstr) + || f.expand_string_forcedfail + ) + { + if (f.expand_string_forcedfail) + *errstr = US"expansion of tls_privatekey failed"; return DEFER; + } /* tls_privatekey is optional, defaulting to same file as certificate */ @@ -1837,7 +1889,11 @@ if (!state->lib_state.conn_certs) tls_ocsp_file, #endif errstr) - ) ) return rc; + ) ) + { + DEBUG(D_tls) debug_printf("load-cert: '%s'\n", *errstr); + return rc; + } } } else @@ -1948,12 +2004,11 @@ Returns: OK/DEFER/FAIL */ static int -tls_set_remaining_x509(exim_gnutls_state_st *state, uschar ** errstr) +tls_set_remaining_x509(exim_gnutls_state_st * state, uschar ** errstr) { -int rc; -const host_item *host = state->host; /* macro should be reconsidered? */ +int rc = OK; +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. */ @@ -1961,15 +2016,14 @@ client-side params. */ if (!state->host) { if (!dh_server_params) - if ((rc = init_server_dh(errstr)) != OK) return rc; + if ((rc = init_server_dh(errstr)) == DEFER) return rc; + + /* Unnecessary & discouraged with 3.6.0 or later, according to docs. But without it, + no DHE- ciphers are advertised. */ - /* Unnecessary & discouraged with 3.6.0 or later */ - gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params); + if (rc == OK) + gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params); } -#else -DEBUG(D_tls) if (tls_dhparam) - debug_printf("Ignoring tls_dhparam (recent version GnuTLS)\n"); -#endif /* Link the credentials to the session. */ @@ -2022,7 +2076,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)); @@ -2252,7 +2306,7 @@ old_pool = store_pool; for (s++; (c = *s) && c != ')'; s++) g = string_catn(g, s, 1); - tlsp->ver = string_copyn(g->s, g->ptr); + tlsp->ver = string_copy_from_gstring(g); for (uschar * p = US tlsp->ver; *p; p++) if (*p == '-') { *p = '\0'; break; } /* TLS1.0-PKIX -> TLS1.0 */ @@ -2342,7 +2396,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)]"); @@ -2422,8 +2476,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))) @@ -2570,7 +2624,7 @@ else ) { DEBUG(D_tls) - debug_printf("TLS certificate verification failed: cert name mismatch\n"); + debug_printf("TLS certificate verification failed: cert name mismatch (per GnuTLS)\n"); if (state->verify_requirement >= VERIFY_REQUIRED) goto badcert; return TRUE; @@ -2669,7 +2723,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 */ @@ -2685,11 +2739,12 @@ if ((rc = tls_expand_session_files(state, &dummy_errstr)) != OK) { /* If the setup of certs/etc failed before handshake, TLS would not have been offered. The best we can do now is abort. */ - return GNUTLS_E_APPLICATION_ERROR_MIN; + DEBUG(D_tls) debug_printf("expansion for SNI-dependent session files failed\n"); + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; } rc = tls_set_remaining_x509(state, &dummy_errstr); -if (rc != OK) return GNUTLS_E_APPLICATION_ERROR_MIN; +if (rc != OK) return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; return 0; } @@ -2718,25 +2773,25 @@ exim_gnutls_state_st * state = gnutls_session_get_ptr(session); if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size))) while (cert_list_size--) - { - if ((rc = import_cert(&cert_list[cert_list_size], &crt)) != GNUTLS_E_SUCCESS) { - DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n", - cert_list_size, gnutls_strerror(rc)); - break; - } + if ((rc = import_cert(&cert_list[cert_list_size], &crt)) != GNUTLS_E_SUCCESS) + { + DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n", + cert_list_size, gnutls_strerror(rc)); + break; + } - state->tlsp->peercert = crt; - if ((yield = event_raise(state->event_action, - US"tls:cert", string_sprintf("%d", cert_list_size)))) - { - log_write(0, LOG_MAIN, - "SSL verify denied by event-action: depth=%d: %s", - cert_list_size, yield); - return 1; /* reject */ + state->tlsp->peercert = crt; + if ((yield = event_raise(state->event_action, + US"tls:cert", string_sprintf("%d", cert_list_size), &errno))) + { + log_write(0, LOG_MAIN, + "SSL verify denied by event-action: depth=%d: %s", + cert_list_size, yield); + return 1; /* reject */ + } + state->tlsp->peercert = NULL; } - state->tlsp->peercert = NULL; - } return 0; } @@ -2798,7 +2853,7 @@ static int tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, unsigned incoming, const gnutls_datum_t * msg) { -DEBUG(D_tls) debug_printf("newticket cb\n"); +DEBUG(D_tls) debug_printf("newticket cb (on server)\n"); tls_in.resumption |= RESUME_CLIENT_REQUESTED; return 0; } @@ -2835,9 +2890,12 @@ tls_server_resume_posthandshake(exim_gnutls_state_st * state) { if (gnutls_session_resumption_requested(state->session)) { - /* This tells us the client sent a full ticket. We use a + /* This tells us the client sent a full (?) ticket. We use a callback on session-ticket request, elsewhere, to tell - if a client asked for a ticket. */ + if a client asked for a ticket. + XXX As of GnuTLS 3.0.1 it seems to be returning true even for + a pure ticket-req (a zero-length Session Ticket extension + in the Client Hello, for 1.2) which mucks up our logic. */ tls_in.resumption |= RESUME_CLIENT_SUGGESTED; DEBUG(D_tls) debug_printf("client requested resumption\n"); @@ -2857,12 +2915,12 @@ NULL plist return for silent no-ALPN. */ 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) @@ -2880,7 +2938,7 @@ else 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); } @@ -2892,11 +2950,12 @@ 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(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. */ @@ -2946,7 +3005,7 @@ exim_gnutls_state_st * state = NULL; if (tls_in.active.sock >= 0) { tls_error(US"STARTTLS received after TLS started", US "", NULL, errstr); - smtp_printf("554 Already in TLS\r\n", FALSE); + smtp_printf("554 Already in TLS\r\n", SP_NO_MORE); return FAIL; } @@ -3025,7 +3084,7 @@ mode, the fflush() happens when smtp_getc() is called. */ if (!state->tlsp->on_connect) { - smtp_printf("220 TLS go ahead\r\n", FALSE); + smtp_printf("220 TLS go ahead\r\n", SP_NO_MORE); fflush(smtp_out); } @@ -3050,6 +3109,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. */ @@ -3057,15 +3119,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(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 */ @@ -3145,7 +3211,6 @@ receive_hasc = tls_hasc; receive_ungetc = tls_ungetc; receive_feof = tls_feof; receive_ferror = tls_ferror; -receive_smtp_buffered = tls_smtp_buffered; return OK; } @@ -3195,8 +3260,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; @@ -3254,25 +3319,29 @@ 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: no LB detection done (continued-conn?)\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)))) { @@ -3284,6 +3353,7 @@ if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK) dbfn_close(dbm_file); } } +else DEBUG(D_tls) debug_printf("no resumption for this host\n"); } @@ -3309,25 +3379,28 @@ 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); + DEBUG(D_tls) debug_printf(" session data size %u\n", (unsigned)tkt.size); memcpy(dt->session, tkt.data, tkt.size); gnutls_free(tkt.data); 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) - debug_printf("wrote session db (len %u)\n", (unsigned)dlen); + debug_printf(" wrote session db (len %u)\n", (unsigned)dlen); } } - else DEBUG(D_tls) - debug_printf("extract session data: %s\n", US gnutls_strerror(rc)); + else + { DEBUG(D_tls) + debug_printf(" extract session data: %s\n", US gnutls_strerror(rc)); + } + else DEBUG(D_tls) + debug_printf(" host not resmable; not saving ticket\n"); } } @@ -3344,7 +3417,7 @@ tls_client_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, exim_gnutls_state_st * state = gnutls_session_get_ptr(sess); tls_support * tlsp = state->tlsp; -DEBUG(D_tls) debug_printf("newticket cb\n"); +DEBUG(D_tls) debug_printf("newticket cb (on client)\n"); if (!tlsp->ticket_received) tls_save_session(tlsp, sess, state->host); @@ -3354,14 +3427,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 @@ -3459,7 +3532,7 @@ if (ob->tls_alpn) 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) @@ -3551,7 +3624,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 @@ -3730,12 +3803,21 @@ if (!tlsp || tlsp->active.sock < 0) return; /* TLS was not active */ 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); } @@ -3748,13 +3830,9 @@ if (!ct_ctx) /* server */ 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 */ @@ -3906,7 +3984,7 @@ 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; @@ -4233,17 +4311,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*/