X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/94c1328507098238ae5ec784150c1ae58f3b3118..74f1a42304ce056cf979d22fb970faae161e3ab2:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 8cf28fca3..12c9fdb38 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2017 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* Copyright (c) Phil Pennock 2012 */ @@ -66,13 +66,17 @@ 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 >= 0x030000 && defined(EXPERIMENTAL_DANE) -# define SUPPORT_DANE -# define DANESSL_USAGE_DANE_TA 2 -# define DANESSL_USAGE_DANE_EE 3 -#endif -#if GNUTLS_VERSION_NUMBER < 0x999999 && defined(EXPERIMENTAL_DANE) -# define GNUTLS_BROKEN_DANE_VALIDATION + +#ifdef SUPPORT_DANE +# if GNUTLS_VERSION_NUMBER >= 0x030000 +# define DANESSL_USAGE_DANE_TA 2 +# define DANESSL_USAGE_DANE_EE 3 +# else +# error GnuTLS version too early for DANE +# endif +# if GNUTLS_VERSION_NUMBER < 0x999999 +# define GNUTLS_BROKEN_DANE_VALIDATION +# endif #endif #ifndef DISABLE_OCSP @@ -152,8 +156,8 @@ typedef struct exim_gnutls_state { uschar *xfer_buffer; int xfer_buffer_lwm; int xfer_buffer_hwm; - int xfer_eof; - int xfer_error; + BOOL xfer_eof; /*XXX never gets set! */ + BOOL xfer_error; } exim_gnutls_state_st; static const exim_gnutls_state_st exim_gnutls_state_init = { @@ -194,8 +198,8 @@ static const exim_gnutls_state_st exim_gnutls_state_init = { .xfer_buffer = NULL, .xfer_buffer_lwm = 0, .xfer_buffer_hwm = 0, - .xfer_eof = 0, - .xfer_error = 0, + .xfer_eof = FALSE, + .xfer_error = FALSE, }; /* Not only do we have our own APIs which don't pass around state, assuming @@ -209,7 +213,7 @@ second connection. XXX But see gnutls_session_get_ptr() */ -static exim_gnutls_state_st state_server, state_client; +static exim_gnutls_state_st state_server; /* 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 @@ -444,7 +448,8 @@ gnutls_datum_t channel; #endif tls_support * tlsp = state->tlsp; -tlsp->active = state->fd_out; +tlsp->active.sock = state->fd_out; +tlsp->active.tls_ctx = state; cipher = gnutls_cipher_get(state->session); /* returns size in "bytes" */ @@ -786,7 +791,7 @@ if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA, goto err; where = US"configuring cert"; -now = 0; +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))) @@ -1258,6 +1263,7 @@ tls_init( const uschar *crl, const uschar *require_ciphers, exim_gnutls_state_st **caller_state, + tls_support * tlsp, uschar ** errstr) { exim_gnutls_state_st *state; @@ -1306,9 +1312,15 @@ if (!exim_gnutls_base_init_done) if (host) { - state = &state_client; + /* For client-side sessions we allocate a context. This lets us run + several in parallel. */ + int old_pool = store_pool; + store_pool = POOL_PERM; + state = store_get(sizeof(exim_gnutls_state_st)); + store_pool = old_pool; + memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); - state->tlsp = &tls_out; + state->tlsp = tlsp; DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n"); rc = gnutls_init(&state->session, GNUTLS_CLIENT); } @@ -1316,7 +1328,7 @@ else { state = &state_server; memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); - state->tlsp = &tls_in; + state->tlsp = tlsp; DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n"); rc = gnutls_init(&state->session, GNUTLS_SERVER); } @@ -1617,7 +1629,7 @@ else (needed for TA but not EE). */ if (usage == ((1<session), - r, 0, + r, 0, # ifdef GNUTLS_BROKEN_DANE_VALIDATION usage == (1 << DANESSL_USAGE_DANE_EE) ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0, @@ -1761,8 +1773,11 @@ goodcert: state->tlsp->peerdn = state->peerdn; return TRUE; +#ifdef SUPPORT_DANE tlsa_prob: *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc)); +#endif + badcert: gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); return FALSE; @@ -1913,12 +1928,10 @@ int rc; uschar * yield; exim_gnutls_state_st * state = gnutls_session_get_ptr(session); -cert_list = gnutls_certificate_get_peers(session, &cert_list_size); -if (cert_list) +if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size))) while (cert_list_size--) { - rc = import_cert(&cert_list[cert_list_size], &crt); - if (rc != GNUTLS_E_SUCCESS) + 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)); @@ -1975,7 +1988,7 @@ int rc; exim_gnutls_state_st * state = NULL; /* Check for previous activation */ -if (tls_in.active >= 0) +if (tls_in.active.sock >= 0) { tls_error(US"STARTTLS received after TLS started", "", NULL, errstr); smtp_printf("554 Already in TLS\r\n", FALSE); @@ -1989,7 +2002,7 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n"); if ((rc = tls_init(NULL, tls_certificate, tls_privatekey, NULL, tls_verify_certificates, tls_crl, - require_ciphers, &state, errstr)) != OK) return rc; + require_ciphers, &state, &tls_in, errstr)) != OK) return rc; /* If this is a host for which certificate verification is mandatory or optional, set up appropriately. */ @@ -2236,25 +2249,27 @@ Arguments: Which implies cert must be requested and supplied, dane verify must pass, and cert verify irrelevant (incl. hostnames), and (caller handled) require_tls + tlsp record details of channel configuration errstr error string pointer -Returns: OK/DEFER/FAIL (because using common functions), - but for a client, DEFER and FAIL have the same meaning +Returns: Pointer to TLS session context, or NULL on error */ -int +void * tls_client_start(int fd, host_item *host, address_item *addr ARG_UNUSED, transport_instance * tb, -#ifdef EXPERIMENTAL_DANE +#ifdef SUPPORT_DANE dns_answer * tlsa_dnsa, #endif - uschar ** errstr) + tls_support * tlsp, uschar ** errstr) { smtp_transport_options_block *ob = (smtp_transport_options_block *)tb->options_block; int rc; exim_gnutls_state_st * state = NULL; +uschar *cipher_list = NULL; + #ifndef DISABLE_OCSP BOOL require_ocsp = verify_check_given_host(&ob->hosts_require_ocsp, host) == OK; @@ -2264,10 +2279,25 @@ BOOL request_ocsp = require_ocsp ? TRUE DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd); -if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey, +#ifdef SUPPORT_DANE +if (tlsa_dnsa && ob->dane_require_tls_ciphers) + { + /* not using expand_check_tlsvar because not yet in state */ + if (!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers", + &cipher_list, errstr)) + return NULL; + cipher_list = cipher_list && *cipher_list + ? ob->dane_require_tls_ciphers : ob->tls_require_ciphers; + } +#endif + +if (!cipher_list) + cipher_list = ob->tls_require_ciphers; + +if (tls_init(host, ob->tls_certificate, ob->tls_privatekey, ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl, - ob->tls_require_ciphers, &state, errstr)) != OK) - return rc; + cipher_list, &state, tlsp, errstr) != OK) + return NULL; { int dh_min_bits = ob->tls_dh_min_bits; @@ -2336,9 +2366,11 @@ if (request_ocsp) DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n"); if ((rc = gnutls_ocsp_status_request_enable_client(state->session, NULL, 0, NULL)) != OK) - return tls_error(US"cert-status-req", - gnutls_strerror(rc), state->host, errstr); - tls_out.ocsp = OCSP_NOT_RESP; + { + tls_error(US"cert-status-req", gnutls_strerror(rc), state->host, errstr); + return NULL; + } + tlsp->ocsp = OCSP_NOT_RESP; } #endif @@ -2361,27 +2393,31 @@ DEBUG(D_tls) debug_printf("about to gnutls_handshake\n"); sigalrm_seen = FALSE; alarm(ob->command_timeout); do - { rc = gnutls_handshake(state->session); - } while ((rc == GNUTLS_E_AGAIN) || - (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen)); +while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen); alarm(0); if (rc != GNUTLS_E_SUCCESS) + { if (sigalrm_seen) { gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED); - return tls_error(US"gnutls_handshake", "timed out", state->host, errstr); + tls_error(US"gnutls_handshake", "timed out", state->host, errstr); } else - return tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host, errstr); + tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host, errstr); + return NULL; + } DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n"); /* Verify late */ if (!verify_certificate(state, errstr)) - return tls_error(US"certificate verification failed", *errstr, state->host, errstr); + { + tls_error(US"certificate verification failed", *errstr, state->host, errstr); + return NULL; + } #ifndef DISABLE_OCSP if (require_ocsp) @@ -2406,24 +2442,25 @@ if (require_ocsp) if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0) { - tls_out.ocsp = OCSP_FAILED; - return tls_error(US"certificate status check failed", NULL, state->host, errstr); + tlsp->ocsp = OCSP_FAILED; + tls_error(US"certificate status check failed", NULL, state->host, errstr); + return NULL; } DEBUG(D_tls) debug_printf("Passed OCSP checking\n"); - tls_out.ocsp = OCSP_VFIED; + tlsp->ocsp = OCSP_VFIED; } #endif /* Figure out peer DN, and if authenticated, etc. */ -if ((rc = peer_status(state, errstr)) != OK) - return rc; +if (peer_status(state, errstr) != OK) + return NULL; /* Sets various Exim expansion variables; may need to adjust for ACL callouts */ extract_exim_vars_from_tls_state(state); -return OK; +return state; } @@ -2437,35 +2474,39 @@ return OK; daemon, to shut down the TLS library, without actually doing a shutdown (which would tamper with the TLS session in the parent process). -Arguments: TRUE if gnutls_bye is to be called +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 + Returns: nothing */ void -tls_close(BOOL is_server, BOOL shutdown) +tls_close(void * ct_ctx, int shutdown) { -exim_gnutls_state_st *state = is_server ? &state_server : &state_client; +exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; -if (!state->tlsp || state->tlsp->active < 0) return; /* TLS was not active */ +if (!state->tlsp || state->tlsp->active.sock < 0) return; /* TLS was not active */ if (shutdown) { - DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n"); - gnutls_bye(state->session, GNUTLS_SHUT_WR); + DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n", + shutdown > 1 ? " (with response-wait)" : ""); + + alarm(2); + gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR); + alarm(0); } gnutls_deinit(state->session); gnutls_certificate_free_credentials(state->x509_cred); -state->tlsp->active = -1; +state->tlsp->active.sock = -1; +state->tlsp->active.tls_ctx = NULL; +if (state->xfer_buffer) store_free(state->xfer_buffer); memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); - -if ((state_server.session == NULL) && (state_client.session == NULL)) - { - gnutls_global_deinit(); - exim_gnutls_base_init_done = FALSE; - } } @@ -2480,20 +2521,29 @@ ssize_t inbytes; DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n", state->session, state->xfer_buffer, ssl_xfer_buffer_size); +sigalrm_seen = FALSE; if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); inbytes = gnutls_record_recv(state->session, state->xfer_buffer, MIN(ssl_xfer_buffer_size, lim)); -alarm(0); +if (smtp_receive_timeout > 0) alarm(0); + +if (had_command_timeout) /* set by signal handler */ + smtp_command_timeout_exit(); /* does not return */ +if (had_command_sigterm) + smtp_command_sigterm_exit(); +if (had_data_timeout) + smtp_data_timeout_exit(); +if (had_data_sigint) + smtp_data_sigint_exit(); -/* Timeouts do not get this far; see command_timeout_handler(). - A zero-byte return appears to mean that the TLS session has been - closed down, not that the socket itself has been closed down. Revert to - non-TLS handling. */ +/* Timeouts do not get this far. A zero-byte return appears to mean that the +TLS session has been closed down, not that the socket itself has been closed +down. Revert to non-TLS handling. */ if (sigalrm_seen) { DEBUG(D_tls) debug_printf("Got tls read timeout\n"); - state->xfer_error = 1; + state->xfer_error = TRUE; return FALSE; } @@ -2513,7 +2563,8 @@ else if (inbytes == 0) gnutls_certificate_free_credentials(state->x509_cred); state->session = NULL; - state->tlsp->active = -1; + state->tlsp->active.sock = -1; + state->tlsp->active.tls_ctx = NULL; state->tlsp->bits = 0; state->tlsp->certificate_verified = FALSE; tls_channelbinding_b64 = NULL; @@ -2529,7 +2580,7 @@ else if (inbytes == 0) else if (inbytes < 0) { record_io_error(state, (int) inbytes, US"recv", NULL); - state->xfer_error = 1; + state->xfer_error = TRUE; return FALSE; } #ifndef DISABLE_DKIM @@ -2622,6 +2673,7 @@ return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm then the caller must feed DKIM. Arguments: + ct_ctx client context pointer, or NULL for the one global server context buff buffer of data len size of buffer @@ -2630,9 +2682,9 @@ Returns: the number of bytes read */ int -tls_read(BOOL is_server, uschar *buff, size_t len) +tls_read(void * ct_ctx, uschar *buff, size_t len) { -exim_gnutls_state_st *state = is_server ? &state_server : &state_client; +exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; ssize_t inbytes; if (len > INT_MAX) @@ -2668,7 +2720,7 @@ return -1; /* Arguments: - is_server channel specifier + ct_ctx client context pointer, or NULL for the one global server context buff buffer of data len number of bytes more more data expected soon @@ -2678,11 +2730,11 @@ Returns: the number of bytes after a successful write, */ int -tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more) +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 = is_server ? &state_server : &state_client; +exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; #ifdef SUPPORT_CORK static BOOL corked = FALSE;