X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/2aaa3cdd22a987cbf7c8343d3e6103ee521296dc..b10c87b38c2345d15d30da5c18c823355ac506a9:/src/src/transports/smtp.c diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index c1cba598c..f76b4f730 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -183,6 +183,10 @@ optionlist smtp_transport_options[] = { (void *)offsetof(smtp_transport_options_block, tls_privatekey) }, { "tls_require_ciphers", opt_stringptr, (void *)offsetof(smtp_transport_options_block, tls_require_ciphers) }, +# ifdef EXPERIMENTAL_TLS_RESUME + { "tls_resumption_hosts", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, tls_resumption_hosts) }, +# endif { "tls_sni", opt_stringptr, (void *)offsetof(smtp_transport_options_block, tls_sni) }, { "tls_tempfail_tryclear", opt_bool, @@ -293,6 +297,9 @@ smtp_transport_options_block smtp_transport_option_defaults = { .tls_verify_certificates = US"system", .tls_dh_min_bits = EXIM_CLIENT_DH_DEFAULT_MIN_BITS, .tls_tempfail_tryclear = TRUE, +# ifdef EXPERIMENTAL_TLS_RESUME + .tls_resumption_hosts = NULL, +# endif .tls_verify_hosts = NULL, .tls_try_verify_hosts = US"*", .tls_verify_cert_hostnames = US"*", @@ -593,6 +600,11 @@ switch(*errno_value) pl, smtp_command, s); return FALSE; + case ERRNO_TLSFAILURE: /* Handle bad first read; can happen with + GnuTLS and TLS1.3 */ + *message = US"bad first read from TLS conn"; + return TRUE; + case ERRNO_FILTER_FAIL: /* Handle a failed filter process error; can't send QUIT as we mustn't end the DATA. */ *message = string_sprintf("transport filter process failed (%d)%s", @@ -820,7 +832,7 @@ write_ehlo_cache_entry(const smtp_context * sx) { open_db dbblock, * dbm_file; -if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE))) +if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) { uschar * ehlo_resp_key = ehlo_cache_key(sx); dbdata_ehlo_resp er = { .data = sx->ehlo_resp }; @@ -840,7 +852,7 @@ invalidate_ehlo_cache_entry(smtp_context * sx) open_db dbblock, * dbm_file; if ( sx->early_pipe_active - && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE))) + && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) { uschar * ehlo_resp_key = ehlo_cache_key(sx); dbfn_delete(dbm_file, ehlo_resp_key); @@ -854,7 +866,7 @@ read_ehlo_cache_entry(smtp_context * sx) open_db dbblock; open_db * dbm_file; -if (!(dbm_file = dbfn_open(US"misc", O_RDONLY, &dbblock, FALSE))) +if (!(dbm_file = dbfn_open(US"misc", O_RDONLY, &dbblock, FALSE, TRUE))) { DEBUG(D_transport) debug_printf("ehlo-cache: no misc DB\n"); } else { @@ -867,7 +879,7 @@ else { DEBUG(D_transport) debug_printf("ehlo-resp record too old\n"); dbfn_close(dbm_file); - if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE))) + if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) dbfn_delete(dbm_file, ehlo_resp_key); } else @@ -941,6 +953,7 @@ Arguments: Return: OK all well + DEFER error on first read of TLS'd conn FAIL SMTP error in response */ int @@ -948,6 +961,7 @@ smtp_reap_early_pipe(smtp_context * sx, int * countp) { BOOL pending_BANNER = sx->pending_BANNER; BOOL pending_EHLO = sx->pending_EHLO; +int rc = FAIL; sx->pending_BANNER = FALSE; /* clear early to avoid recursion */ sx->pending_EHLO = FALSE; @@ -959,6 +973,7 @@ if (pending_BANNER) if (!smtp_reap_banner(sx)) { DEBUG(D_transport) debug_printf("bad banner\n"); + if (tls_out.active.sock >= 0) rc = DEFER; goto fail; } } @@ -973,6 +988,7 @@ if (pending_EHLO) if (!smtp_reap_ehlo(sx)) { DEBUG(D_transport) debug_printf("bad response for EHLO\n"); + if (tls_out.active.sock >= 0) rc = DEFER; goto fail; } @@ -983,7 +999,7 @@ if (pending_EHLO) ? &sx->ehlo_resp.cleartext_auths : &sx->ehlo_resp.crypted_auths; peer_offered = ehlo_response(sx->buffer, - (tls_out.active.sock < 0 ? OPTION_TLS : OPTION_REQUIRETLS) + (tls_out.active.sock < 0 ? OPTION_TLS : 0) | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE | OPTION_UTF8 | OPTION_EARLY_PIPE ); @@ -1010,7 +1026,7 @@ return OK; fail: invalidate_ehlo_cache_entry(sx); (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp); - return FAIL; + return rc; } #endif @@ -1055,6 +1071,7 @@ Returns: 3 if at least one address had 2xx and one had 5xx -2 I/O or other non-response error for RCPT -3 DATA or MAIL failed - errno and buffer set -4 banner or EHLO failed (early-pipelining) + -5 banner or EHLO failed (early-pipelining, TLS) */ static int @@ -1065,8 +1082,9 @@ smtp_transport_options_block * ob = sx->conn_args.ob; int yield = 0; #ifdef EXPERIMENTAL_PIPE_CONNECT -if (smtp_reap_early_pipe(sx, &count) != OK) - return -4; +int rc; +if ((rc = smtp_reap_early_pipe(sx, &count)) != OK) + return rc == FAIL ? -4 : -5; #endif /* Handle the response for a MAIL command. On error, reinstate the original @@ -1082,6 +1100,8 @@ if (sx->pending_MAIL) { DEBUG(D_transport) debug_printf("bad response for MAIL\n"); Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */ + if (errno == ERRNO_TLSFAILURE) + return -5; if (errno == 0 && sx->buffer[0] != 0) { int save_errno = 0; @@ -1140,6 +1160,11 @@ while (count-- > 0) } } + /* Error on first TLS read */ + + else if (errno == ERRNO_TLSFAILURE) + return -5; + /* Timeout while reading the response */ else if (errno == ETIMEDOUT) @@ -1252,6 +1277,10 @@ if (pending_DATA != 0) int code; uschar *msg; BOOL pass_message; + + if (errno == ERRNO_TLSFAILURE) /* Error on first TLS read */ + return -5; + if (pending_DATA > 0 || (yield & 1) != 0) { if (errno == 0 && sx->buffer[0] == '4') @@ -1405,8 +1434,6 @@ if ( sx->esmtp if ( require_auth == OK || verify_check_given_host(CUSS &ob->hosts_try_auth, host) == OK) { - auth_instance * au; - DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n"); fail_reason = US"no common mechanisms were found"; @@ -1419,6 +1446,7 @@ if ( sx->esmtp client function. We are limited to supporting up to 16 authenticator public-names by the number of bits in a short. */ + auth_instance * au; uschar bitnum; int rc; @@ -1727,12 +1755,6 @@ size_t bsize = Ustrlen(buf); /* debug_printf("%s: check for 0x%04x\n", __FUNCTION__, checks); */ #ifdef SUPPORT_TLS -# ifdef EXPERIMENTAL_REQUIRETLS -if ( checks & OPTION_REQUIRETLS - && pcre_exec(regex_REQUIRETLS, NULL, CS buf,bsize, 0, PCRE_EOPT, NULL,0) < 0) -# endif - checks &= ~OPTION_REQUIRETLS; - if ( checks & OPTION_TLS && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) #endif @@ -1800,7 +1822,9 @@ Args: tc_chunk_last add LAST option to SMTP BDAT command tc_reap_prev reap response to previous SMTP commands -Returns: OK or ERROR +Returns: + OK or ERROR + DEFER TLS error on first read (EHLO-resp); errno set */ static int @@ -1857,10 +1881,12 @@ if (flags & tc_reap_prev && prev_cmd_count > 0) case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */ case 0: break; /* No 2xx or 5xx, but no probs */ - case -1: /* Timeout on RCPT */ + case -5: errno = ERRNO_TLSFAILURE; + return DEFER; #ifdef EXPERIMENTAL_PIPE_CONNECT case -4: /* non-2xx for pipelined banner or EHLO */ #endif + case -1: /* Timeout on RCPT */ default: return ERROR; /* I/O error, or any MAIL/DATA error */ } cmd_count = 1; @@ -1923,14 +1949,14 @@ Returns: OK - the connection was made and the delivery attempted; int smtp_setup_conn(smtp_context * sx, BOOL suppress_tls) { -#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE) -dns_answer tlsa_dnsa; -#endif smtp_transport_options_block * ob = sx->conn_args.tblock->options_block; BOOL pass_message = FALSE; uschar * message = NULL; int yield = OK; int rc; +#ifdef SUPPORT_TLS +uschar * tls_errstr; +#endif sx->conn_args.ob = ob; @@ -1947,7 +1973,7 @@ sx->utf8_needed = FALSE; #endif sx->dsn_all_lasthop = TRUE; #if defined(SUPPORT_TLS) && defined(SUPPORT_DANE) -sx->dane = FALSE; +sx->conn_args.dane = FALSE; sx->dane_required = verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK; #endif @@ -1997,6 +2023,9 @@ tls_out.peerdn = NULL; tls_out.sni = NULL; #endif tls_out.ocsp = OCSP_NOT_REQ; +#ifdef EXPERIMENTAL_TLS_RESUME +tls_out.resumption = 0; +#endif /* Flip the legacy TLS-related variables over to the outbound set in case they're used in the context of the transport. Don't bother resetting @@ -2038,15 +2067,15 @@ if (!continue_hostname) if( sx->dane_required || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK ) - switch (rc = tlsa_lookup(sx->conn_args.host, &tlsa_dnsa, sx->dane_required)) + switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required)) { - case OK: sx->dane = TRUE; + case OK: sx->conn_args.dane = TRUE; ob->tls_tempfail_tryclear = FALSE; break; case FAIL_FORCED: break; default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, string_sprintf("DANE error: tlsa lookup %s", - rc == DEFER ? "DEFER" : "FAIL"), + rc_to_string(rc)), rc, FALSE); # ifndef DISABLE_EVENT (void) event_raise(sx->conn_args.tblock->event_action, @@ -2471,28 +2500,22 @@ if ( smtp_peer_options & OPTION_TLS else TLS_NEGOTIATE: { - address_item * addr; - uschar * errstr; - sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->conn_args.host, - sx->addrlist, sx->conn_args.tblock, -# ifdef SUPPORT_DANE - sx->dane ? &tlsa_dnsa : NULL, -# endif - &tls_out, &errstr); - - if (!sx->cctx.tls_ctx) + if (!tls_client_start(&sx->cctx, &sx->conn_args, sx->addrlist, &tls_out, &tls_errstr)) { /* TLS negotiation failed; give an error. From outside, this function may be called again to try in clear on a new connection, if the options permit it for this host. */ - DEBUG(D_tls) debug_printf("TLS session fail: %s\n", errstr); +#ifdef USE_GNUTLS + GNUTLS_CONN_FAILED: +#endif + DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr); # ifdef SUPPORT_DANE - if (sx->dane) + if (sx->conn_args.dane) { log_write(0, LOG_MAIN, "DANE attempt failed; TLS connection to %s [%s]: %s", - sx->conn_args.host->name, sx->conn_args.host->address, errstr); + sx->conn_args.host->name, sx->conn_args.host->address, tls_errstr); # ifndef DISABLE_EVENT (void) event_raise(sx->conn_args.tblock->event_action, US"dane:fail", US"validation-failure"); /* could do with better detail */ @@ -2501,7 +2524,7 @@ if ( smtp_peer_options & OPTION_TLS # endif errno = ERRNO_TLSFAILURE; - message = string_sprintf("TLS session: %s", errstr); + message = string_sprintf("TLS session: %s", tls_errstr); sx->send_quit = FALSE; goto TLS_FAILED; } @@ -2599,7 +2622,22 @@ if (tls_out.active.sock >= 0) #endif { if (!smtp_reap_ehlo(sx)) +#ifdef USE_GNUTLS + { + /* The GnuTLS layer in Exim only spots a server-rejection of a client + cert late, under TLS1.3 - which means here; the first time we try to + receive crypted data. Treat it as if it was a connect-time failure. + See also the early-pipe equivalent... which will be hard; every call + to sync_responses will need to check the result. + It would be nicer to have GnuTLS check the cert during the handshake. + Can it do that, with all the flexibility we need? */ + + tls_errstr = US"error on first read"; + goto GNUTLS_CONN_FAILED; + } +#else goto RESPONSE_FAILED; +#endif smtp_peer_options = 0; } } @@ -2609,24 +2647,17 @@ have one. */ else if ( sx->smtps # ifdef SUPPORT_DANE - || sx->dane -# endif -# ifdef EXPERIMENTAL_REQUIRETLS - || tls_requiretls & REQUIRETLS_MSG + || sx->conn_args.dane # endif || verify_check_given_host(CUSS &ob->hosts_require_tls, sx->conn_args.host) == OK ) { - errno = -# ifdef EXPERIMENTAL_REQUIRETLS - tls_requiretls & REQUIRETLS_MSG ? ERRNO_REQUIRETLS : -# endif - ERRNO_TLSREQUIRED; + errno = ERRNO_TLSREQUIRED; message = string_sprintf("a TLS session is required, but %s", smtp_peer_options & OPTION_TLS ? "an attempt to start TLS failed" : "the server did not offer TLS support"); # if defined(SUPPORT_DANE) && !defined(DISABLE_EVENT) - if (sx->dane) + if (sx->conn_args.dane) (void) event_raise(sx->conn_args.tblock->event_action, US"dane:fail", smtp_peer_options & OPTION_TLS ? US"validation-failure" /* could do with better detail */ @@ -2658,7 +2689,7 @@ if (continue_hostname == NULL #ifdef EXPERIMENTAL_PIPE_CONNECT | (sx->lmtp && ob->lmtp_ignore_quota ? OPTION_IGNQ : 0) | OPTION_DSN | OPTION_PIPE | OPTION_SIZE - | OPTION_CHUNKING | OPTION_PRDR | OPTION_UTF8 | OPTION_REQUIRETLS + | OPTION_CHUNKING | OPTION_PRDR | OPTION_UTF8 | (tls_out.active.sock >= 0 ? OPTION_EARLY_PIPE : 0) /* not for lmtp */ #else @@ -2674,9 +2705,6 @@ if (continue_hostname == NULL | OPTION_DSN | OPTION_PIPE | (ob->size_addition >= 0 ? OPTION_SIZE : 0) -# if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS) - | (tls_requiretls & REQUIRETLS_MSG ? OPTION_REQUIRETLS : 0) -# endif #endif ); #ifdef EXPERIMENTAL_PIPE_CONNECT @@ -2727,16 +2755,6 @@ if (continue_hostname == NULL DEBUG(D_transport) debug_printf("%susing DSN\n", sx->peer_offered & OPTION_DSN ? "" : "not "); -#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS) - if (sx->peer_offered & OPTION_REQUIRETLS) - { - smtp_peer_options |= OPTION_REQUIRETLS; - DEBUG(D_transport) debug_printf( - tls_requiretls & REQUIRETLS_MSG - ? "using REQUIRETLS\n" : "REQUIRETLS offered\n"); - } -#endif - #ifdef EXPERIMENTAL_PIPE_CONNECT if ( sx->early_pipe_ok && !sx->early_pipe_active @@ -2822,22 +2840,6 @@ if (sx->utf8_needed && !(sx->peer_offered & OPTION_UTF8)) } #endif /*SUPPORT_I18N*/ -#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS) - /*XXX should tls_requiretls actually be per-addr? */ - -if ( tls_requiretls & REQUIRETLS_MSG - && !(sx->peer_offered & OPTION_REQUIRETLS) - ) - { - sx->setting_up = TRUE; - errno = ERRNO_REQUIRETLS; - message = US"REQUIRETLS support is required from the server" - " but it was not offered"; - DEBUG(D_transport) debug_printf("%s\n", message); - goto TLS_FAILED; - } -#endif - return OK; @@ -2874,13 +2876,7 @@ return OK; #ifdef SUPPORT_TLS TLS_FAILED: -# ifdef EXPERIMENTAL_REQUIRETLS - if (errno == ERRNO_REQUIRETLS) - code = '5', yield = FAIL; - /*XXX DSN will be labelled 500; prefer 530 5.7.4 */ - else -# endif - code = '4', yield = DEFER; + code = '4', yield = DEFER; goto FAILED; #endif @@ -3018,11 +3014,6 @@ if ( sx->peer_offered & OPTION_UTF8 Ustrcpy(p, " SMTPUTF8"), p += 9; #endif -#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS) -if (tls_requiretls & REQUIRETLS_MSG) - Ustrcpy(p, " REQUIRETLS") , p += 11; -#endif - /* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */ for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0; addr && address_count < sx->max_rcpt; @@ -3258,6 +3249,7 @@ for (addr = sx->first_addr, address_count = 0; #ifdef EXPERIMENTAL_PIPE_CONNECT case -4: return -1; /* non-2xx for pipelined banner or EHLO */ + case -5: return -1; /* TLS first-read error */ #endif } sx->pending_MAIL = FALSE; /* Dealt with MAIL */ @@ -3433,7 +3425,6 @@ smtp_deliver(address_item *addrlist, host_item *host, int host_af, int defport, uschar *interface, transport_instance *tblock, BOOL *message_defer, BOOL suppress_tls) { -address_item *addr; smtp_transport_options_block * ob = SOB tblock->options_block; int yield = OK; int save_errno; @@ -3584,11 +3575,12 @@ if ( !(sx.peer_offered & OPTION_CHUNKING) case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */ - case 0: break; /* No 2xx or 5xx, but no probs */ + case 0: break; /* No 2xx or 5xx, but no probs */ - case -1: goto END_OFF; /* Timeout on RCPT */ + case -1: goto END_OFF; /* Timeout on RCPT */ #ifdef EXPERIMENTAL_PIPE_CONNECT + case -5: /* TLS first-read error */ case -4: HDEBUG(D_transport) debug_printf("failed reaping pipelined cmd responses\n"); #endif @@ -3725,19 +3717,20 @@ else { case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */ case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */ - break; + break; - case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ + case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */ - case 0: break; /* No 2xx or 5xx, but no probs */ + case 0: break; /* No 2xx or 5xx, but no probs */ - case -1: goto END_OFF; /* Timeout on RCPT */ + case -1: goto END_OFF; /* Timeout on RCPT */ #ifdef EXPERIMENTAL_PIPE_CONNECT + case -5: /* TLS first-read error */ case -4: HDEBUG(D_transport) debug_printf("failed reaping pipelined cmd responses\n"); #endif - default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */ + default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */ } } @@ -4496,14 +4489,13 @@ int hosts_retry = 0; int hosts_serial = 0; int hosts_total = 0; int total_hosts_tried = 0; -address_item *addr; BOOL expired = TRUE; uschar *expanded_hosts = NULL; uschar *pistring; uschar *tid = string_sprintf("%s transport", tblock->name); smtp_transport_options_block *ob = SOB tblock->options_block; host_item *hostlist = addrlist->host_list; -host_item *host; +host_item *host = NULL; DEBUG(D_transport) { @@ -4534,12 +4526,6 @@ same one in order to be passed to a single transport - or if the transport has a host list with hosts_override set, use the host list supplied with the transport. It is an error for this not to exist. */ -#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS) -if (tls_requiretls & REQUIRETLS_MSG) - ob->tls_tempfail_tryclear = FALSE; /*XXX surely we should have a local for this - rather than modifying the transport? */ -#endif - if (!hostlist || (ob->hosts_override && ob->hosts)) { if (!ob->hosts) @@ -4712,7 +4698,6 @@ retry_non_continued: { int rc; int host_af; - uschar *rs; BOOL host_is_expired = FALSE; BOOL message_defer = FALSE; BOOL some_deferred = FALSE; @@ -4900,11 +4885,14 @@ retry_non_continued: treated separately. */ host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6; - if ((rs = ob->interface) && *rs) { - if (!smtp_get_interface(rs, host_af, addrlist, &interface, tid)) - return FALSE; - pistring = string_sprintf("%s/%s", pistring, interface); + uschar * s = ob->interface; + if (s && *s) + { + if (!smtp_get_interface(s, host_af, addrlist, &interface, tid)) + return FALSE; + pistring = string_sprintf("%s/%s", pistring, interface); + } } /* The first time round the outer loop, check the status of the host by @@ -5149,14 +5137,9 @@ retry_non_continued: /* Delivery attempt finished */ - rs = rc == OK ? US"OK" - : rc == DEFER ? US"DEFER" - : rc == ERROR ? US"ERROR" - : US"?"; - set_process_info("delivering %s: just tried %s [%s]%s for %s%s: result %s", message_id, host->name, host->address, pistring, addrlist->address, - addrlist->next ? " (& others)" : "", rs); + addrlist->next ? " (& others)" : "", rc_to_string(rc)); /* Release serialization if set up */