X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/cb45303cf2a8d9922702f13db42b3285c48f6aa7..582f61444fa806694c2caae7c156830dbf8531d7:/src/src/transports/smtp.c diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 4d6135be7..194671272 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -2,9 +2,10 @@ * 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 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ #include "../exim.h" #include "smtp.h" @@ -64,6 +65,9 @@ optionlist smtp_transport_options[] = { { "final_timeout", opt_time, LOFF(final_timeout) }, { "gethostbyname", opt_bool, LOFF(gethostbyname) }, { "helo_data", opt_stringptr, LOFF(helo_data) }, +#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME) + { "host_name_extract", opt_stringptr, LOFF(host_name_extract) }, +# endif { "hosts", opt_stringptr, LOFF(hosts) }, { "hosts_avoid_esmtp", opt_stringptr, LOFF(hosts_avoid_esmtp) }, { "hosts_avoid_pipelining", opt_stringptr, LOFF(hosts_avoid_pipelining) }, @@ -110,7 +114,7 @@ optionlist smtp_transport_options[] = { { "interface", opt_stringptr, LOFF(interface) }, { "keepalive", opt_bool, LOFF(keepalive) }, { "lmtp_ignore_quota", opt_bool, LOFF(lmtp_ignore_quota) }, - { "max_rcpt", opt_int | opt_public, + { "max_rcpt", opt_stringptr | opt_public, OPT_OFF(transport_instance, max_addresses) }, { "message_linelength_limit", opt_int, LOFF(message_linelength_limit) }, { "multi_domain", opt_expand_bool | opt_public, @@ -231,6 +235,17 @@ static BOOL pipelining_active; /* current transaction is in pipe mode */ static unsigned ehlo_response(uschar * buf, unsigned checks); +/* sync_responses() return codes */ + +#define RESP_BIT_HAD_5XX BIT(1) +#define RESP_BIT_HAD_2XX BIT(0) +#define RESP_HAD_2_AND_5 (RESP_BIT_HAD_2XX | RESP_BIT_HAD_5XX) +#define RESP_NOERROR 0 +#define RESP_RCPT_TIMEO -1 +#define RESP_RCPT_ERROR -2 +#define RESP_MAIL_OR_DATA_ERROR -3 +#define RESP_EPIPE_EHLO_ERR -4 +#define RESP_EHLO_ERR_TLS -5 /******************************************************************************/ @@ -269,7 +284,7 @@ struct list for (struct list * l = list; l < list + nelem(list); l++) if (!*l->re) - *l->re = regex_must_compile(l->string, FALSE, TRUE); + *l->re = regex_must_compile(l->string, MCS_NOFLAGS, TRUE); } @@ -345,7 +360,7 @@ Returns: nothing void smtp_transport_init(transport_instance *tblock) { -smtp_transport_options_block *ob = SOB tblock->options_block; +smtp_transport_options_block * ob = SOB tblock->options_block; int old_pool = store_pool; /* Retry_use_local_part defaults FALSE if unset */ @@ -619,8 +634,8 @@ if (suffix) else message = string_fmt_append(message, " %s", exim_errstr(basic_errno)); -log_write(0, LOG_MAIN, "%s", string_from_gstring(message)); -deliver_msglog("%s %s\n", tod_stamp(tod_log), message->s); +log_write(0, LOG_MAIN, "%Y", message); +deliver_msglog("%s %.*s\n", tod_stamp(tod_log), message->ptr, message->s); } static void @@ -652,8 +667,7 @@ static void deferred_event_raise(address_item * addr, host_item * host, uschar * evstr) { uschar * action = addr->transport->event_action; -const uschar * save_domain; -uschar * save_local; +const uschar * save_domain, * save_local; if (!action) return; @@ -758,6 +772,29 @@ return TRUE; } +#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME) + +/* Grab a string differentiating server behind a loadbalancer, for TLS +resumption when such servers do not share a session-cache */ + +static void +ehlo_response_lbserver(smtp_context * sx, const uschar * name_extract) +{ +const uschar * s; +uschar * save_item = iterate_item; + +if (sx->conn_args.have_lbserver) + return; +iterate_item = sx->buffer; +s = expand_cstring(name_extract); +iterate_item = save_item; +sx->conn_args.host_lbserver = s && !*s ? NULL : s; +sx->conn_args.have_lbserver = TRUE; +} +#endif + + + /******************************************************************************/ #ifdef EXPERIMENTAL_ESMTP_LIMITS @@ -827,10 +864,12 @@ ehlo_limits_apply(sx, sx->peer_limit_mail, sx->peer_limit_rcpt, static void ehlo_cache_limits_apply(smtp_context * sx) { +# ifndef DISABLE_PIPE_CONNECT ehlo_limits_apply(sx, sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt, sx->ehlo_resp.limit_rcptdom); +# endif } -#endif +#endif /*EXPERIMENTAL_ESMTP_LIMITS*/ /******************************************************************************/ @@ -861,11 +900,11 @@ write_ehlo_cache_entry(smtp_context * sx) { open_db dbblock, * dbm_file; -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS sx->ehlo_resp.limit_mail = sx->peer_limit_mail; sx->ehlo_resp.limit_rcpt = sx->peer_limit_rcpt; sx->ehlo_resp.limit_rcptdom = sx->peer_limit_rcptdom; -#endif +# endif if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) { @@ -873,7 +912,7 @@ if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) dbdata_ehlo_resp er = { .data = sx->ehlo_resp }; HDEBUG(D_transport) -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS if (sx->ehlo_resp.limit_mail || sx->ehlo_resp.limit_rcpt || sx->ehlo_resp.limit_rcptdom) debug_printf("writing clr %04x/%04x cry %04x/%04x lim %05d/%05d/%05d\n", sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths, @@ -881,7 +920,7 @@ if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt, sx->ehlo_resp.limit_rcptdom); else -#endif +# endif debug_printf("writing clr %04x/%04x cry %04x/%04x\n", sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths, sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths); @@ -900,6 +939,16 @@ if ( sx->early_pipe_active && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) { uschar * ehlo_resp_key = ehlo_cache_key(sx); + HDEBUG(D_transport) + { + dbdata_ehlo_resp * er; + + if (!(er = dbfn_read_enforce_length(dbm_file, ehlo_resp_key, sizeof(dbdata_ehlo_resp)))) + debug_printf("no ehlo-resp record!\n"); + else + debug_printf("ehlo-resp record is %d seconds old\n", time(NULL) - er->time_stamp); + } + dbfn_delete(dbm_file, ehlo_resp_key); dbfn_close(dbm_file); } @@ -930,7 +979,7 @@ else else { DEBUG(D_transport) -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS if (er->data.limit_mail || er->data.limit_rcpt || er->data.limit_rcptdom) debug_printf("EHLO response bits from cache:" " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x lim %05d/%05d/%05d\n", @@ -938,16 +987,16 @@ else er->data.crypted_features, er->data.crypted_auths, er->data.limit_mail, er->data.limit_rcpt, er->data.limit_rcptdom); else -#endif +# endif debug_printf("EHLO response bits from cache:" " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x\n", er->data.cleartext_features, er->data.cleartext_auths, er->data.crypted_features, er->data.crypted_auths); sx->ehlo_resp = er->data; -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS ehlo_cache_limits_apply(sx); -#endif +# endif dbfn_close(dbm_file); return TRUE; } @@ -959,7 +1008,7 @@ return FALSE; /* Return an auths bitmap for the set of AUTH methods offered by the server -which match our authenticators. */ +which match our client-side authenticators. */ static unsigned short study_ehlo_auths(smtp_context * sx) @@ -970,7 +1019,7 @@ uschar authnum; unsigned short authbits = 0; if (!sx->esmtp) return 0; -if (!regex_AUTH) regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE); +if (!regex_AUTH) regex_AUTH = regex_must_compile(AUTHS_REGEX, MCS_NOFLAGS, TRUE); if (!regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)) return 0; expand_nmax = -1; /* reset */ names = string_copyn(expand_nstring[1], expand_nlength[1]); @@ -985,7 +1034,7 @@ for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client) } DEBUG(D_transport) - debug_printf("server offers %s AUTH, methods '%s', bitmap 0x%04x\n", + debug_printf("server offers %s AUTH, methods '%s', usable-bitmap 0x%04x\n", tls_out.active.sock >= 0 ? "crypted" : "plaintext", names, authbits); if (tls_out.active.sock >= 0) @@ -1026,6 +1075,8 @@ sx->pending_EHLO = FALSE; if (pending_BANNER) { + const uschar * s; + DEBUG(D_transport) debug_printf("%s expect banner\n", __FUNCTION__); (*countp)--; if (!smtp_reap_banner(sx)) @@ -1034,6 +1085,13 @@ if (pending_BANNER) if (tls_out.active.sock >= 0) rc = DEFER; goto fail; } + /*XXX EXPERIMENTAL_ESMTP_LIMITS ? */ + +# ifndef DISABLE_TLS_RESUME + s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract; + if (!s) s = HNE_DEFAULT; + ehlo_response_lbserver(sx, s); +# endif } if (pending_EHLO) @@ -1062,10 +1120,10 @@ if (pending_EHLO) | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE | OPTION_UTF8 | OPTION_EARLY_PIPE ); -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS if (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS)) ehlo_response_limits_read(sx); -#endif +# endif if ( peer_offered != sx->peer_offered || (authbits = study_ehlo_auths(sx)) != *ap) { @@ -1082,11 +1140,14 @@ if (pending_EHLO) write_ehlo_cache_entry(sx); } else + { invalidate_ehlo_cache_entry(sx); + sx->early_pipe_active = FALSE; /* cancel further early-pipe on this conn */ + } return OK; /* just carry on */ } -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS /* If we are handling LIMITS, compare the actual EHLO LIMITS values with the cached values and invalidate cache if different. OK to carry on with connect since values are advisory. */ @@ -1110,7 +1171,7 @@ if (pending_EHLO) invalidate_ehlo_cache_entry(sx); } } -#endif +# endif } return OK; @@ -1175,7 +1236,7 @@ int yield = 0; #ifndef DISABLE_PIPE_CONNECT int rc; if ((rc = smtp_reap_early_pipe(sx, &count)) != OK) - return rc == FAIL ? -4 : -5; + return rc == FAIL ? RESP_EPIPE_EHLO_ERR : RESP_EHLO_ERR_TLS; #endif /* Handle the response for a MAIL command. On error, reinstate the original @@ -1193,7 +1254,7 @@ 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; + return RESP_EHLO_ERR_TLS; if (errno == 0 && sx->buffer[0] != 0) { int save_errno = 0; @@ -1213,7 +1274,7 @@ if (sx->pending_MAIL) addr->host_used = sx->conn_args.host; addr = addr->next; } - return -3; + return RESP_MAIL_OR_DATA_ERROR; } } @@ -1227,7 +1288,7 @@ while (count-- > 0) { while (addr->transport_return != PENDING_DEFER) if (!(addr = addr->next)) - return -2; + return RESP_RCPT_ERROR; /* The address was accepted */ addr->host_used = sx->conn_args.host; @@ -1236,7 +1297,7 @@ while (count-- > 0) if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', ob->command_timeout)) { - yield |= 1; + yield |= RESP_BIT_HAD_2XX; addr->transport_return = PENDING_OK; /* If af_dr_retry_exists is set, there was a routing delay on this address; @@ -1255,7 +1316,7 @@ while (count-- > 0) /* Error on first TLS read */ else if (errno == ERRNO_TLSFAILURE) - return -5; + return RESP_EHLO_ERR_TLS; /* Timeout while reading the response */ @@ -1266,7 +1327,7 @@ while (count-- > 0) set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE, &sx->delivery_start); retry_add_item(addr, addr->address_retry_key, 0); update_waiting = FALSE; - return -1; + return RESP_RCPT_TIMEO; } /* Handle other errors in obtaining an SMTP response by returning -1. This @@ -1284,7 +1345,7 @@ while (count-- > 0) g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "RCPT TO:<%s>", transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes)); string_from_gstring(g); - return -2; + return RESP_RCPT_ERROR; } /* Handle SMTP permanent and temporary response codes. */ @@ -1304,7 +1365,7 @@ while (count-- > 0) if (sx->buffer[0] == '5') { addr->transport_return = FAIL; - yield |= 2; + yield |= RESP_BIT_HAD_5XX; } /* The response was 4xx */ @@ -1324,7 +1385,7 @@ while (count-- > 0) /* If a 452 and we've had at least one 2xx or 5xx, set next_addr to the start point for another MAIL command. */ - if (addr->more_errno >> 8 == 52 && yield & 3) + if (addr->more_errno >> 8 == 52 && yield > 0) { if (!sx->RCPT_452) /* initialised at MAIL-ack above */ { @@ -1374,7 +1435,7 @@ while (count-- > 0) } } if (count && !(addr = addr->next)) - return -2; + return RESP_RCPT_ERROR; } /* Loop for next RCPT response */ /* Update where to start at for the next block of responses, unless we @@ -1396,16 +1457,16 @@ if (pending_DATA != 0) BOOL pass_message; if (errno == ERRNO_TLSFAILURE) /* Error on first TLS read */ - return -5; + return RESP_EHLO_ERR_TLS; - if (pending_DATA > 0 || (yield & 1) != 0) + if (pending_DATA > 0 || yield & RESP_BIT_HAD_2XX) { if (errno == 0 && sx->buffer[0] == '4') { errno = ERRNO_DATA4XX; sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8; } - return -3; + return RESP_MAIL_OR_DATA_ERROR; } (void)check_response(sx->conn_args.host, &errno, 0, sx->buffer, &code, &msg, &pass_message); DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining " @@ -1444,7 +1505,7 @@ rc = (au->info->clientcode)(au, sx, ob->command_timeout, sx->buffer, sizeof(sx->buffer)); sx->outblock.authenticating = FALSE; driver_srcfile = authenticator_name = NULL; driver_srcline = 0; -DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", au->name, rc); +DEBUG(D_transport) debug_printf("%s authenticator yielded %s\n", au->name, rc_names[rc]); /* A temporary authentication failure must hold up delivery to this host. After a permanent authentication failure, we carry on @@ -1468,10 +1529,25 @@ switch(rc) /* Failure after reading a response */ case FAIL: + { + uschar * logmsg = NULL; + if (errno != 0 || sx->buffer[0] != '5') return FAIL; - log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s", - au->name, host->name, host->address, sx->buffer); +#ifndef DISABLE_EVENT + { + uschar * save_name = sender_host_authenticated; + sender_host_authenticated = au->name; + if ((logmsg = event_raise(sx->conn_args.tblock->event_action, US"auth:fail", + sx->buffer, NULL))) + log_write(0, LOG_MAIN, "%s", logmsg); + sender_host_authenticated = save_name; + } +#endif + if (!logmsg) + log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s", + au->name, host->name, host->address, sx->buffer); break; + } /* Failure by some other means. In effect, the authenticator decided it wasn't prepared to handle this case. Typically this @@ -1531,7 +1607,7 @@ f.smtp_authenticated = FALSE; client_authenticator = client_authenticated_id = client_authenticated_sender = NULL; if (!regex_AUTH) - regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE); + regex_AUTH = regex_must_compile(AUTHS_REGEX, MCS_NOFLAGS, TRUE); /* Is the server offering AUTH? */ @@ -1730,7 +1806,7 @@ return FALSE; typedef struct smtp_compare_s { - uschar * current_sender_address; + const uschar * current_sender_address; struct transport_instance * tblock; } smtp_compare_t; @@ -1740,7 +1816,7 @@ sender_address, helo_data and tls_certificate if enabled. */ static uschar * -smtp_local_identity(uschar * sender, struct transport_instance * tblock) +smtp_local_identity(const uschar * sender, struct transport_instance * tblock) { address_item * addr1; uschar * if1 = US""; @@ -1748,7 +1824,7 @@ uschar * helo1 = US""; #ifndef DISABLE_TLS uschar * tlsc1 = US""; #endif -uschar * save_sender_address = sender_address; +const uschar * save_sender_address = sender_address; uschar * local_identity = NULL; smtp_transport_options_block * ob = SOB tblock->options_block; @@ -1817,57 +1893,57 @@ pcre2_match_data * md = pcre2_match_data_create(1, pcre_gen_ctx); #ifndef DISABLE_TLS if ( checks & OPTION_TLS && pcre2_match(regex_STARTTLS, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) #endif checks &= ~OPTION_TLS; if ( checks & OPTION_IGNQ && pcre2_match(regex_IGNOREQUOTA, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) checks &= ~OPTION_IGNQ; if ( checks & OPTION_CHUNKING && pcre2_match(regex_CHUNKING, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) checks &= ~OPTION_CHUNKING; #ifndef DISABLE_PRDR if ( checks & OPTION_PRDR && pcre2_match(regex_PRDR, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) #endif checks &= ~OPTION_PRDR; #ifdef SUPPORT_I18N if ( checks & OPTION_UTF8 && pcre2_match(regex_UTF8, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) #endif checks &= ~OPTION_UTF8; if ( checks & OPTION_DSN && pcre2_match(regex_DSN, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) checks &= ~OPTION_DSN; if ( checks & OPTION_PIPE && pcre2_match(regex_PIPELINING, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) checks &= ~OPTION_PIPE; if ( checks & OPTION_SIZE && pcre2_match(regex_SIZE, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) checks &= ~OPTION_SIZE; #ifndef DISABLE_PIPE_CONNECT if ( checks & OPTION_EARLY_PIPE && pcre2_match(regex_EARLY_PIPE, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) #endif checks &= ~OPTION_EARLY_PIPE; -pcre2_match_data_free(md); +/* pcre2_match_data_free(md); gen ctx needs no free */ /* debug_printf("%s: found 0x%04x\n", __FUNCTION__, checks); */ return checks; } @@ -1943,18 +2019,18 @@ if (flags & tc_reap_prev && prev_cmd_count > 0) switch(sync_responses(sx, prev_cmd_count, 0)) { - case 1: /* 2xx (only) => OK */ - case 3: sx->good_RCPT = TRUE; /* 2xx & 5xx => OK & progress made */ - case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */ - case 0: break; /* No 2xx or 5xx, but no probs */ + case RESP_BIT_HAD_2XX: /* OK */ + case RESP_HAD_2_AND_5: sx->good_RCPT = TRUE; /* OK & progress made */ + case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE; /* progress made */ + case RESP_NOERROR: break; /* No 2xx or 5xx, but no probs */ - case -5: errno = ERRNO_TLSFAILURE; - return DEFER; + case RESP_EHLO_ERR_TLS:errno = ERRNO_TLSFAILURE; + return DEFER; #ifndef DISABLE_PIPE_CONNECT - case -4: /* non-2xx for pipelined banner or EHLO */ + case RESP_EPIPE_EHLO_ERR: /* non-2xx for pipelined banner or EHLO */ #endif - case -1: /* Timeout on RCPT */ - default: return ERROR; /* I/O error, or any MAIL/DATA error */ + case RESP_RCPT_TIMEO: /* Timeout on RCPT */ + default: return ERROR;/* I/O error, or any MAIL/DATA error */ } cmd_count = 1; if (!sx->pending_BDAT) @@ -1992,6 +2068,38 @@ return OK; +#ifdef SUPPORT_DANE +static int +check_force_dane_conn(smtp_context * sx, smtp_transport_options_block * ob) +{ +int rc; +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, &sx->conn_args.tlsa_dnsa, sx->dane_required)) + { + case OK: sx->conn_args.dane = TRUE; + ob->tls_tempfail_tryclear = FALSE; /* force TLS */ + ob->tls_sni = sx->conn_args.host->name; /* force SNI */ + break; + case FAIL_FORCED: break; + default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, + string_sprintf("DANE error: tlsa lookup %s", + rc_to_string(rc)), + rc, FALSE, &sx->delivery_start); +# ifndef DISABLE_EVENT + (void) event_raise(sx->conn_args.tblock->event_action, + US"dane:fail", sx->dane_required + ? US"dane-required" : US"dnssec-invalid", + NULL); +# endif + return rc; + } +return OK; +} +#endif + + /************************************************* * Make connection for given message * *************************************************/ @@ -2041,8 +2149,9 @@ sx->dane_required = verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK; #endif -if ((sx->max_mail = sx->conn_args.tblock->connection_max_messages) == 0) sx->max_mail = 999999; -if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999; +if ((sx->max_mail = sx->conn_args.tblock->connection_max_messages) == 0) + sx->max_mail = UNLIMITED_ADDRS; +sx->max_rcpt = expand_max_rcpt(sx->conn_args.tblock->max_addresses); sx->igquotstr = US""; if (!sx->helo_data) sx->helo_data = ob->helo_data; @@ -2101,33 +2210,14 @@ if (continue_hostname && continue_proxy_cipher) const uschar * sni = US""; # ifdef SUPPORT_DANE - /* Check if the message will be DANE-verified; if so force its SNI */ + /* Check if the message will be DANE-verified; if so force TLS and its SNI */ tls_out.dane_verified = FALSE; smtp_port_for_connect(sx->conn_args.host, sx->port); if ( sx->conn_args.host->dnssec == DS_YES - && ( 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, &sx->conn_args.tlsa_dnsa, sx->dane_required)) - { - case OK: sx->conn_args.dane = TRUE; - ob->tls_tempfail_tryclear = FALSE; /* force TLS */ - ob->tls_sni = sx->conn_args.host->name; /* force SNI */ - break; - case FAIL_FORCED: break; - default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, - string_sprintf("DANE error: tlsa lookup %s", - rc_to_string(rc)), - rc, FALSE, &sx->delivery_start); -# ifndef DISABLE_EVENT - (void) event_raise(sx->conn_args.tblock->event_action, - US"dane:fail", sx->dane_required - ? US"dane-required" : US"dnssec-invalid", - NULL); -# endif - return rc; - } + && (rc = check_force_dane_conn(sx, ob)) != OK + ) + return rc; # endif /* If the SNI or the DANE status required for the new message differs from the @@ -2194,28 +2284,8 @@ if (!continue_hostname) if (sx->conn_args.host->dnssec == DS_YES) { int rc; - 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, &sx->conn_args.tlsa_dnsa, sx->dane_required)) - { - case OK: sx->conn_args.dane = TRUE; - ob->tls_tempfail_tryclear = FALSE; /* force TLS */ - ob->tls_sni = sx->conn_args.host->name; /* force SNI */ - break; - case FAIL_FORCED: break; - default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, - string_sprintf("DANE error: tlsa lookup %s", - rc_to_string(rc)), - rc, FALSE, &sx->delivery_start); -# ifndef DISABLE_EVENT - (void) event_raise(sx->conn_args.tblock->event_action, - US"dane:fail", sx->dane_required - ? US"dane-required" : US"dnssec-invalid", - NULL); -# endif - return rc; - } + if ((rc = check_force_dane_conn(sx, ob)) != OK) + return rc; } else if (sx->dane_required) { @@ -2267,7 +2337,7 @@ if (!continue_hostname) } } else DEBUG(D_transport) - debug_printf("helo needs $sending_ip_address\n"); + debug_printf("helo needs $sending_ip_address; avoid early-pipelining\n"); PIPE_CONNECT_RETRY: if (sx->early_pipe_active) @@ -2419,10 +2489,21 @@ goto SEND_QUIT; #ifndef DISABLE_TLS if (sx->smtps) { + const uschar * s; + smtp_peer_options |= OPTION_TLS; suppress_tls = FALSE; ob->tls_tempfail_tryclear = FALSE; smtp_command = US"SSL-on-connect"; + +# ifndef DISABLE_TLS_RESUME + /* Having no EHLO response yet, cannot peek there for a servername to detect + an LB. Call this anyway, so that a dummy host_name_extract option value can + force resumption attempts. */ + + if (!(s = ob->host_name_extract)) s = US"never-LB"; + ehlo_response_lbserver(sx, s); +# endif goto TLS_NEGOTIATE; } #endif @@ -2453,7 +2534,7 @@ goto SEND_QUIT; DEBUG(D_transport) debug_printf("may need to auth, so pipeline no further\n"); if (smtp_write_command(sx, SCMD_FLUSH, NULL) < 0) goto SEND_FAILED; - if (sync_responses(sx, 2, 0) != 0) + if (sync_responses(sx, 2, 0) != RESP_NOERROR) { HDEBUG(D_transport) debug_printf("failed reaping pipelined cmd responses\n"); @@ -2510,6 +2591,8 @@ goto SEND_QUIT; if (!sx->early_pipe_active) #endif { + const uschar * s; + sx->peer_offered = ehlo_response(sx->buffer, OPTION_TLS /* others checked later */ #ifndef DISABLE_PIPE_CONNECT @@ -2539,11 +2622,15 @@ goto SEND_QUIT; if ( (sx->peer_offered & (OPTION_PIPE | OPTION_EARLY_PIPE)) == (OPTION_PIPE | OPTION_EARLY_PIPE)) { - DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n"); + DEBUG(D_transport) debug_printf("PIPECONNECT usable in future for this IP\n"); sx->ehlo_resp.cleartext_auths = study_ehlo_auths(sx); write_ehlo_cache_entry(sx); } } +#endif +#ifndef DISABLE_TLS_RESUME + if (!(s = ob->host_name_extract)) s = HNE_DEFAULT; + ehlo_response_lbserver(sx, s); #endif } @@ -2636,14 +2723,17 @@ if ( smtp_peer_options & OPTION_TLS the response for the STARTTLS we just sent alone. On fail, assume wrong cached capability and retry with the pipelining disabled. */ - if (sx->early_pipe_active && sync_responses(sx, 2, 0) != 0) + if (sx->early_pipe_active) { - HDEBUG(D_transport) - debug_printf("failed reaping pipelined cmd responses\n"); - close(sx->cctx.sock); - sx->cctx.sock = -1; - sx->early_pipe_active = FALSE; - goto PIPE_CONNECT_RETRY; + if (sync_responses(sx, 2, 0) != RESP_NOERROR) + { + HDEBUG(D_transport) + debug_printf("failed reaping pipelined cmd responses\n"); + close(sx->cctx.sock); + sx->cctx.sock = -1; + sx->early_pipe_active = FALSE; + goto PIPE_CONNECT_RETRY; + } } #endif @@ -2672,6 +2762,7 @@ if ( smtp_peer_options & OPTION_TLS else TLS_NEGOTIATE: { + sx->conn_args.sending_ip_address = sending_ip_address; 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 @@ -2773,8 +2864,9 @@ if (tls_out.active.sock >= 0) #ifdef EXPERIMMENTAL_ESMTP_LIMITS /* As we are about to send another EHLO, forget any LIMITS received so far. */ sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom = 0; - if ((sx->max_mail = sx->conn_args.tblock->connection_max_message) == 0) sx->max_mail = 999999; - if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999; + if ((sx->max_mail = sx->conn_args.tblock->connection_max_message) == 0) + sx->max_mail = UNLIMITED_ADDRS; + sx->max_rcpt = expand_max_rcpt(sx->conn_args.tblock->max_addresses); sx->single_rcpt_domain = FALSE; #endif @@ -3358,7 +3450,7 @@ buffer. */ sx->pending_MAIL = TRUE; /* The block starts with MAIL */ { - uschar * s = sx->from_addr; + const uschar * s = sx->from_addr; #ifdef SUPPORT_I18N uschar * errstr = NULL; @@ -3429,7 +3521,7 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100; { int cmds_sent; BOOL no_flush; - uschar * rcpt_addr; + const uschar * rcpt_addr; #ifdef EXPERIMENTAL_ESMTP_LIMITS if ( sx->single_rcpt_domain /* restriction on domains */ @@ -3487,14 +3579,14 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100; { switch(sync_responses(sx, cmds_sent, 0)) { - case 3: sx->ok = TRUE; /* 2xx & 5xx => OK & progress made */ - case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */ + case RESP_HAD_2_AND_5: sx->ok = TRUE; /* OK & progress made */ + case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE; /* progress made */ break; - case 1: sx->ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ + case RESP_BIT_HAD_2XX: sx->ok = TRUE; /* OK, but if LMTP, */ if (!sx->lmtp) /* can't tell about progress yet */ sx->completed_addr = TRUE; - case 0: /* No 2xx or 5xx, but no probs */ + case RESP_NOERROR: /* No 2xx or 5xx, but no probs */ /* If any RCPT got a 452 response then next_addr has been updated for restarting with a new MAIL on the same connection. Send no more RCPTs for this MAIL. */ @@ -3508,13 +3600,13 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100; } break; - case -1: return -3; /* Timeout on RCPT */ - case -2: return -2; /* non-MAIL read i/o error */ - default: return -1; /* any MAIL error */ + case RESP_RCPT_TIMEO: return -3; /* Timeout on RCPT */ + case RESP_RCPT_ERROR: return -2; /* non-MAIL read i/o error */ + default: return -1; /* any MAIL error */ #ifndef DISABLE_PIPE_CONNECT - case -4: return -1; /* non-2xx for pipelined banner or EHLO */ - case -5: return -1; /* TLS first-read error */ + case RESP_EPIPE_EHLO_ERR: return -1; /* non-2xx for pipelined banner or EHLO */ + case RESP_EHLO_ERR_TLS: return -1; /* TLS first-read error */ #endif } } @@ -3546,13 +3638,14 @@ Arguments: bufsiz size of buffer pfd pipe filedescriptor array; [0] is comms to proxied process timeout per-read timeout, seconds + host hostname of remote Does not return. */ void smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd, - int timeout) + int timeout, const uschar * host) { struct pollfd p[2] = {{.fd = tls_out.active.sock, .events = POLLIN}, {.fd = pfd[0], .events = POLLIN}}; @@ -3563,7 +3656,7 @@ close(pfd[1]); if ((rc = exim_fork(US"tls-proxy"))) _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS); -set_process_info("proxying TLS connection for continued transport"); +set_process_info("proxying TLS connection for continued transport to %s\n", host); do { @@ -3771,7 +3864,7 @@ if (tblock->filter_command) yield ERROR. */ if (!transport_set_up_command(&transport_filter_argv, - tblock->filter_command, TRUE, DEFER, addrlist, FALSE, + tblock->filter_command, TSUC_EXPAND_ARGS, DEFER, addrlist, string_sprintf("%.50s transport filter", tblock->name), NULL)) { set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER, @@ -3874,17 +3967,19 @@ if ( !(smtp_peer_options & OPTION_CHUNKING) int count = smtp_write_command(sx, SCMD_FLUSH, "DATA\r\n"); if (count < 0) goto SEND_FAILED; + switch(sync_responses(sx, count, sx->ok ? +1 : -1)) { - case 3: sx->ok = TRUE; /* 2xx & 5xx => OK & progress made */ - case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */ - break; + case RESP_HAD_2_AND_5: sx->ok = TRUE; /* OK & progress made */ + case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE; /* progress made */ + break; - 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 RESP_BIT_HAD_2XX: sx->ok = TRUE; /* OK, but if LMTP, */ + if (!sx->lmtp) /* can't tell about progress yet */ + sx->completed_addr = TRUE; + case RESP_NOERROR: break; /* No 2xx or 5xx, but no probs */ - case -1: goto END_OFF; /* Timeout on RCPT */ + case RESP_RCPT_TIMEO: goto END_OFF; #ifndef DISABLE_PIPE_CONNECT case -5: /* TLS first-read error */ @@ -3962,7 +4057,7 @@ else if (smtp_peer_options & OPTION_CHUNKING) debug_printf(" will write message using CHUNKING\n"); else - debug_printf(" SMTP>> writing message and terminating \".\"\n"); + debug_printf(" SMTP>> (writing message)\n"); transport_count = 0; #ifndef DISABLE_DKIM @@ -4114,22 +4209,23 @@ else /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */ switch(sync_responses(sx, sx->cmd_count-1, 0)) { - case 3: sx->ok = TRUE; /* 2xx & 5xx => OK & progress made */ - case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */ - break; + case RESP_HAD_2_AND_5: sx->ok = TRUE; /* OK & progress made */ + case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE; /* progress made */ + break; - 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 RESP_BIT_HAD_2XX: sx->ok = TRUE; /* OK, but if LMTP, */ + if (!sx->lmtp) /* can't tell about progress yet */ + sx->completed_addr = TRUE; + case RESP_NOERROR: break; /* No 2xx or 5xx, but no probs */ - case -1: goto END_OFF; /* Timeout on RCPT */ + case RESP_RCPT_TIMEO: goto END_OFF; /* Timeout on RCPT */ #ifndef DISABLE_PIPE_CONNECT - case -5: /* TLS first-read error */ - case -4: HDEBUG(D_transport) + case RESP_EHLO_ERR_TLS: /* TLS first-read error */ + case RESP_EPIPE_EHLO_ERR: 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 */ } } @@ -4649,7 +4745,10 @@ if (sx->completed_addr && sx->ok && sx->send_quit) open, we must shut down TLS. Not all MTAs allow for the continuation of the SMTP session when TLS is shut down. We test for this by sending a new EHLO. If we don't get a good response, we don't attempt to pass - the socket on. */ + the socket on. + NB: TLS close is *required* per RFC 9266 when tls-exporter info has + been used, which we do under TLSv1.3 for the gsasl SCRAM*PLUS methods. + But we were always doing it anyway. */ tls_close(sx->cctx.tls_ctx, sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY); @@ -4714,7 +4813,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit) { /* does not return */ smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd, - ob->command_timeout); + ob->command_timeout, host->name); } if (pid > 0) /* parent */ @@ -4921,7 +5020,7 @@ smtp_transport_closedown(transport_instance *tblock) { smtp_transport_options_block * ob = SOB tblock->options_block; client_conn_ctx cctx; -smtp_context sx; +smtp_context sx = {0}; uschar buffer[256]; uschar inbuffer[4096]; uschar outbuffer[16]; @@ -5266,6 +5365,17 @@ retry_non_continued: uschar *retry_message_key = NULL; uschar *serialize_key = NULL; + /* Deal slightly better with a possible Linux kernel bug that results + in intermittent TFO-conn fails deep into the TCP flow. Bug 2907 tracks. + Hack: Clear TFO option for any further hosts on this tpt run. */ + + if (total_hosts_tried > 0) + { + DEBUG(D_transport|D_acl|D_v) + debug_printf("Clearing TFO as not first host for message\n"); + ob->hosts_try_fastopen = US""; + } + /* Default next host is next host. :-) But this can vary if the hosts_max_try limit is hit (see below). It may also be reset if a host address is looked up here (in case the host was multihomed). */