X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/50a3f20592c79da4acd409b59803dd5ff31b9781..b6054898ace169a0e5143117397a4f666a5e7283:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 2e1b9e4d3..bfe40b205 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -3,6 +3,7 @@ *************************************************/ /* 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 */ @@ -53,6 +54,9 @@ require current GnuTLS, then we'll drop support for the ancient libraries). # warning "GnuTLS library version too old; tls:cert event unsupported" # define DISABLE_EVENT #endif +#if GNUTLS_VERSION_NUMBER >= 0x030000 +# define SUPPORT_SELFSIGN /* Uncertain what version is first usable but 2.12.23 is not */ +#endif #if GNUTLS_VERSION_NUMBER >= 0x030306 # define SUPPORT_CA_DIR #else @@ -107,9 +111,11 @@ require current GnuTLS, then we'll drop support for the ancient libraries). # endif #endif -#ifdef EXPERIMENTAL_TLS_RESUME -# if GNUTLS_VERSION_NUMBER < 0x030603 -# error GNUTLS version too early for session-resumption +#ifndef DISABLE_TLS_RESUME +# if GNUTLS_VERSION_NUMBER >= 0x030603 +# define EXIM_HAVE_TLS_RESUME +# else +# warning "GnuTLS library version too old; resumption unsupported" # endif #endif @@ -127,7 +133,7 @@ require current GnuTLS, then we'll drop support for the ancient libraries). void options_tls(void) { -# ifdef EXPERIMENTAL_TLS_RESUME +# ifndef DISABLE_TLS_RESUME builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING ); # endif # ifdef EXIM_HAVE_TLS1_3 @@ -262,7 +268,7 @@ static BOOL gnutls_buggy_ocsp = FALSE; static BOOL exim_testharness_disable_ocsp_validity_check = FALSE; #endif -#ifdef EXPERIMENTAL_TLS_RESUME +#ifdef EXIM_HAVE_TLS_RESUME static gnutls_datum_t server_sessticket_key; #endif @@ -322,7 +328,7 @@ static void exim_gnutls_logger_cb(int level, const char *message); static int exim_sni_handling_cb(gnutls_session_t session); -#ifdef EXPERIMENTAL_TLS_RESUME +#ifdef EXIM_HAVE_TLS_RESUME static int tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, unsigned incoming, const gnutls_datum_t * msg); @@ -333,7 +339,7 @@ tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, void tls_daemon_init(void) { -#ifdef EXPERIMENTAL_TLS_RESUME +#ifdef EXIM_HAVE_TLS_RESUME /* We are dependent on the GnuTLS implementation of the Session Ticket encryption; both the strength and the key rotation period. We hope that the strength at least matches that of the ciphersuite (but GnuTLS does not @@ -418,11 +424,14 @@ record_io_error(exim_gnutls_state_st *state, int rc, uschar *when, uschar *text) const uschar * msg; uschar * errstr; -if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED) - msg = string_sprintf("A TLS fatal alert has been received: %s", - US gnutls_alert_get_name(gnutls_alert_get(state->session))); -else - msg = US gnutls_strerror(rc); +msg = rc == GNUTLS_E_FATAL_ALERT_RECEIVED + ? string_sprintf("A TLS fatal alert has been received: %s", + US gnutls_alert_get_name(gnutls_alert_get(state->session))) +#ifdef GNUTLS_E_PREMATURE_TERMINATION + : rc == GNUTLS_E_PREMATURE_TERMINATION && errno + ? string_sprintf("%s: syscall: %s", US gnutls_strerror(rc), strerror(errno)) +#endif + : US gnutls_strerror(rc); (void) tls_error(when, msg, state->host, &errstr); @@ -541,7 +550,10 @@ else /* peercert is set in peer_status() */ tlsp->peerdn = state->peerdn; -tlsp->sni = state->received_sni; + +/* do not corrupt sni sent by client; record sni rxd by server */ +if (!state->host) + tlsp->sni = state->received_sni; /* record our certificate */ { @@ -717,7 +729,7 @@ else if (errno == ENOENT) debug_printf("D-H parameter cache file \"%s\" does not exist\n", filename); } else - return tls_error(string_open_failed(errno, "\"%s\" for reading", filename), + return tls_error(string_open_failed("\"%s\" for reading", filename), NULL, NULL, errstr); /* If ret < 0, either the cache file does not exist, or the data it contains @@ -824,13 +836,19 @@ gnutls_x509_privkey_t pkey = NULL; const uschar * where; int rc; +#ifndef SUPPORT_SELFSIGN +where = US"library too old"; +rc = GNUTLS_E_NO_CERTIFICATE_FOUND; +if (TRUE) goto err; +#endif + where = US"initialising pkey"; if ((rc = gnutls_x509_privkey_init(&pkey))) goto err; where = US"initialising cert"; if ((rc = gnutls_x509_crt_init(&cert))) goto err; -where = US"generating pkey"; +where = US"generating pkey"; /* Hangs on 2.12.23 */ if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA, #ifdef SUPPORT_PARAM_TO_PK_BITS # ifndef GNUTLS_SEC_PARAM_MEDIUM @@ -990,10 +1008,10 @@ return gnutls_ext_raw_parse(NULL, tls_server_servercerts_ext, msg, 0); "Handshake Protocol: Certificate" record. So we need to spot the Certificate handshake message, parse it and spot any status_request extension(s) -This is different to tls1.2 - where it is a separate record (wireshake term) / handshake message (gnutls term). +This is different to tls1.2 - where it is a separate record (wireshark term) / handshake message (gnutls term). */ -#if defined(EXPERIMENTAL_TLS_RESUME) || defined(SUPPORT_GNUTLS_EXT_RAW_PARSE) +#if defined(EXIM_HAVE_TLS_RESUME) || defined(SUPPORT_GNUTLS_EXT_RAW_PARSE) /* Callback for certificate-status, on server. We sent stapled OCSP. */ static int tls_server_certstatus_cb(gnutls_session_t session, unsigned int htype, @@ -1025,7 +1043,7 @@ switch (htype) # endif case GNUTLS_HANDSHAKE_CERTIFICATE_STATUS: return tls_server_certstatus_cb(sess, htype, when, incoming, msg); -# ifdef EXPERIMENTAL_TLS_RESUME +# ifdef EXIM_HAVE_TLS_RESUME case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: return tls_server_ticket_cb(sess, htype, when, incoming, msg); # endif @@ -2318,7 +2336,7 @@ else } -#ifdef EXPERIMENTAL_TLS_RESUME +#ifdef EXIM_HAVE_TLS_RESUME static int tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, unsigned incoming, const gnutls_datum_t * msg) @@ -2432,7 +2450,7 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n"); #endif } -#ifdef EXPERIMENTAL_TLS_RESUME +#ifdef EXIM_HAVE_TLS_RESUME tls_server_resume_prehandshake(state); #endif @@ -2540,7 +2558,7 @@ if (gnutls_session_get_flags(state->session) & GNUTLS_SFLAGS_EXT_MASTER_SECRET) tls_in.ext_master_secret = TRUE; #endif -#ifdef EXPERIMENTAL_TLS_RESUME +#ifdef EXIM_HAVE_TLS_RESUME tls_server_resume_posthandshake(state); #endif @@ -2591,9 +2609,9 @@ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK) { state->exp_tls_verify_cert_hostnames = #ifdef SUPPORT_I18N - string_domain_utf8_to_alabel(host->name, NULL); + string_domain_utf8_to_alabel(host->certname, NULL); #else - host->name; + host->certname; #endif DEBUG(D_tls) debug_printf("TLS: server cert verification includes hostname: \"%s\".\n", @@ -2673,7 +2691,7 @@ return TRUE; -#ifdef EXPERIMENTAL_TLS_RESUME +#ifdef EXIM_HAVE_TLS_RESUME /* On the client, get any stashed session for the given IP from hints db and apply it to the ssl-connection for attempted resumption. Although there is a gnutls_session_ticket_enable_client() interface it is @@ -2806,7 +2824,7 @@ if (gnutls_session_is_resumed(state->session)) tls_save_session(tlsp, state->session, host); } -#endif /* EXPERIMENTAL_TLS_RESUME */ +#endif /* !DISABLE_TLS_RESUME */ /************************************************* @@ -2853,7 +2871,7 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", cctx->so /* If dane is flagged, have either request or require dane for this host, and a TLSA record found. Therefore, dane verify required. Which implies cert must be requested and supplied, dane verify must pass, and cert verify irrelevant -(incl. hostnames), and (caller handled) require_tls */ +(incl. hostnames), and (caller handled) require_tls and sni=$domain */ if (conn_args->dane && ob->dane_require_tls_ciphers) { @@ -2880,6 +2898,7 @@ if (!cipher_list) cipher_list, &state, tlsp, errstr) != OK) return FALSE; + #ifdef MEASURE_TIMING report_time_since(&t0, US"client tls_init (delta)"); #endif @@ -2960,7 +2979,7 @@ if (request_ocsp) } #endif -#ifdef EXPERIMENTAL_TLS_RESUME +#ifdef EXIM_HAVE_TLS_RESUME tls_client_resume_prehandshake(state, tlsp, host, ob); #endif @@ -3060,7 +3079,7 @@ if (request_ocsp) } #endif -#ifdef EXPERIMENTAL_TLS_RESUME +#ifdef EXIM_HAVE_TLS_RESUME tls_client_resume_posthandshake(state, tlsp, host); #endif @@ -3353,9 +3372,14 @@ tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more) ssize_t outbytes; size_t left = len; exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; -#ifdef SUPPORT_CORK -if (more && !state->corked) gnutls_record_cork(state->session); +#ifdef SUPPORT_CORK +if (more && !state->corked) + { + DEBUG(D_tls) debug_printf("gnutls_record_cork(session=%p)\n", state->session); + gnutls_record_cork(state->session); + state->corked = TRUE; + } #endif DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__, @@ -3371,10 +3395,27 @@ while (left > 0) while (outbytes == GNUTLS_E_AGAIN); DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes); + if (outbytes < 0) { - DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__); - record_io_error(state, outbytes, US"send", NULL); +#ifdef GNUTLS_E_PREMATURE_TERMINATION + if ( outbytes == GNUTLS_E_PREMATURE_TERMINATION && errno == ECONNRESET + && !ct_ctx && f.smtp_in_quit + ) + { /* Outlook, dammit */ + if (LOGGING(protocol_detail)) + log_write(0, LOG_MAIN, "[%s] after QUIT, client reset TCP before" + " SMTP response and TLS close\n", sender_host_address); + else + DEBUG(D_tls) debug_printf("[%s] SSL_write: after QUIT," + " client reset TCP before TLS close\n", sender_host_address); + } + else +#endif + { + DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__); + record_io_error(state, outbytes, US"send", NULL); + } return -1; } if (outbytes == 0) @@ -3396,10 +3437,25 @@ if (len > INT_MAX) } #ifdef SUPPORT_CORK -if (more != state->corked) +if (!more && state->corked) { - if (!more) (void) gnutls_record_uncork(state->session, 0); - state->corked = more; + DEBUG(D_tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session); + do + /* We can't use GNUTLS_RECORD_WAIT here, as it retries on + GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm(). + The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway. + But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN + match the EINTR and EAGAIN errno values.) */ + outbytes = gnutls_record_uncork(state->session, 0); + while (outbytes == GNUTLS_E_AGAIN); + + if (outbytes < 0) + { + record_io_error(state, len, US"uncork", NULL); + return -1; + } + + state->corked = FALSE; } #endif