X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/81df60f6229e66dc8306e55ea2103e577782d984..ce15be78166725f6f802231dc8e0c0e4ec615009:/src/src/transports/smtp.c diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index eb6b77416..7bb1249cc 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -84,6 +84,7 @@ optionlist smtp_transport_options[] = { #if !defined(DISABLE_TLS) && !defined(DISABLE_OCSP) { "hosts_request_ocsp", opt_stringptr, LOFF(hosts_request_ocsp) }, #endif + { "hosts_require_alpn", opt_stringptr, LOFF(hosts_require_alpn) }, { "hosts_require_auth", opt_stringptr, LOFF(hosts_require_auth) }, #ifndef DISABLE_TLS # ifdef SUPPORT_DANE @@ -123,6 +124,7 @@ optionlist smtp_transport_options[] = { { "socks_proxy", opt_stringptr, LOFF(socks_proxy) }, #endif #ifndef DISABLE_TLS + { "tls_alpn", opt_stringptr, LOFF(tls_alpn) }, { "tls_certificate", opt_stringptr, LOFF(tls_certificate) }, { "tls_crl", opt_stringptr, LOFF(tls_crl) }, { "tls_dh_min_bits", opt_int, LOFF(tls_dh_min_bits) }, @@ -192,9 +194,7 @@ smtp_transport_options_block smtp_transport_option_defaults = { .keepalive = TRUE, .retry_include_ip_address = TRUE, #ifndef DISABLE_TLS -# if defined(SUPPORT_SYSDEFAULT_CABUNDLE) || !defined(USE_GNUTLS) .tls_verify_certificates = US"system", -# endif .tls_dh_min_bits = EXIM_CLIENT_DH_DEFAULT_MIN_BITS, .tls_tempfail_tryclear = TRUE, .tls_try_verify_hosts = US"*", @@ -274,6 +274,11 @@ if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA = if (!regex_EARLY_PIPE) regex_EARLY_PIPE = regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE); #endif + +#ifdef EXPERIMENTAL_ESMTP_LIMITS +if (!regex_LIMITS) regex_LIMITS = + regex_must_compile(US"\\n250[\\s\\-]LIMITS\\s", FALSE, TRUE); +#endif } @@ -350,6 +355,7 @@ void smtp_transport_init(transport_instance *tblock) { smtp_transport_options_block *ob = SOB tblock->options_block; +int old_pool = store_pool; /* Retry_use_local_part defaults FALSE if unset */ @@ -380,12 +386,14 @@ if (ob->command_timeout <= 0 || ob->data_timeout <= 0 || /* If hosts_override is set and there are local hosts, set the global flag that stops verify from showing router hosts. */ -if (ob->hosts_override && ob->hosts != NULL) tblock->overrides_hosts = TRUE; +if (ob->hosts_override && ob->hosts) tblock->overrides_hosts = TRUE; /* If there are any fallback hosts listed, build a chain of host items for them, but do not do any lookups at this time. */ -host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE); +store_pool = POOL_PERM; +host_build_hostlist(&ob->fallback_hostlist, ob->fallback_hosts, FALSE); +store_pool = old_pool; } @@ -712,7 +720,17 @@ return count; static BOOL smtp_reap_banner(smtp_context * sx) { -BOOL good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), +BOOL good_response; +#if defined(__linux__) && defined(TCP_QUICKACK) + { /* Hack to get QUICKACK disabled; has to be right after 3whs, and has to on->off */ + int sock = sx->cctx.sock; + struct pollfd p = {.fd = sock, .events = POLLOUT}; + int rc = poll(&p, 1, 1000); + (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &on, sizeof(on)); + (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off)); + } +#endif +good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', (SOB sx->conn_args.ob)->command_timeout); #ifdef EXPERIMENTAL_DSN_INFO sx->smtp_greeting = string_copy(sx->buffer); @@ -746,6 +764,82 @@ return TRUE; } +/******************************************************************************/ + +#ifdef EXPERIMENTAL_ESMTP_LIMITS +/* If TLS, or TLS not offered, called with the EHLO response in the buffer. +Check it for a LIMITS keyword and parse values into the smtp context structure. + +We don't bother with peers that we won't talk TLS to, even though they can, +just ignore their LIMITS advice (if any) and treat them as if they do not. +This saves us dealing with a duplicate set of values. */ + +static void +ehlo_response_limits_read(smtp_context * sx) +{ +int ovec[3]; /* results vector for a main-match only */ + +/* matches up to just after the first space after the keyword */ + +if (pcre_exec(regex_LIMITS, NULL, CS sx->buffer, Ustrlen(sx->buffer), + 0, PCRE_EOPT, ovec, nelem(ovec)) >= 0) + for (const uschar * s = sx->buffer + ovec[1]; *s; ) + { + while (isspace(*s)) s++; + if (*s == '\n') break; + + if (strncmpic(s, US"MAILMAX=", 8) == 0) + { + sx->peer_limit_mail = atoi(CS (s += 8)); + while (isdigit(*s)) s++; + } + else if (strncmpic(s, US"RCPTMAX=", 8) == 0) + { + sx->peer_limit_rcpt = atoi(CS (s += 8)); + while (isdigit(*s)) s++; + } + else if (strncmpic(s, US"RCPTDOMAINMAX=", 14) == 0) + { + sx->peer_limit_rcptdom = atoi(CS (s += 14)); + while (isdigit(*s)) s++; + } + else + while (*s && !isspace(*s)) s++; + } +} + +/* Apply given values to the current connection */ +static void +ehlo_limits_apply(smtp_context * sx, + unsigned limit_mail, unsigned limit_rcpt, unsigned limit_rcptdom) +{ +if (limit_mail && limit_mail < sx->max_mail) sx->max_mail = limit_mail; +if (limit_rcpt && limit_rcpt < sx->max_rcpt) sx->max_rcpt = limit_rcpt; +if (limit_rcptdom) + { + DEBUG(D_transport) debug_printf("will treat as !multi_domain\n"); + sx->single_rcpt_domain = TRUE; + } +} + +/* Apply values from EHLO-resp to the current connection */ +static void +ehlo_response_limits_apply(smtp_context * sx) +{ +ehlo_limits_apply(sx, sx->peer_limit_mail, sx->peer_limit_rcpt, + sx->peer_limit_rcptdom); +} + +/* Apply values read from cache to the current connection */ +static void +ehlo_cache_limits_apply(smtp_context * sx) +{ +ehlo_limits_apply(sx, sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt, + sx->ehlo_resp.limit_rcptdom); +} +#endif + +/******************************************************************************/ #ifndef DISABLE_PIPE_CONNECT static uschar * @@ -759,19 +853,45 @@ return Ustrchr(host->address, ':') host->port == PORT_NONE ? sx->port : host->port); } +/* Cache EHLO-response info for use by early-pipe. +Called +- During a normal flow on EHLO response (either cleartext or under TLS), + when we are willing to do PIPE_CONNECT and it is offered +- During an early-pipe flow on receiving the actual EHLO response and noting + disparity versus the cached info used, when PIPE_CONNECT is still being offered + +We assume that suitable values have been set in the sx.ehlo_resp structure for +features and auths; we handle the copy of limits. */ + static void -write_ehlo_cache_entry(const smtp_context * sx) +write_ehlo_cache_entry(smtp_context * sx) { open_db dbblock, * dbm_file; +#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 + 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 }; - HDEBUG(D_transport) 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); + HDEBUG(D_transport) +#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, + sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths, + sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt, + sx->ehlo_resp.limit_rcptdom); + else +#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); dbfn_write(dbm_file, ehlo_resp_key, &er, (int)sizeof(er)); dbfn_close(dbm_file); @@ -816,12 +936,26 @@ else } else { + DEBUG(D_transport) +#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", + er->data.cleartext_features, er->data.cleartext_auths, + er->data.crypted_features, er->data.crypted_auths, + er->data.limit_mail, er->data.limit_rcpt, er->data.limit_rcptdom); + else +#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 + ehlo_cache_limits_apply(sx); +#endif dbfn_close(dbm_file); - DEBUG(D_transport) 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); return TRUE; } dbfn_close(dbm_file); @@ -923,8 +1057,9 @@ if (pending_EHLO) goto fail; } - /* Compare the actual EHLO response to the cached value we assumed; - on difference, dump or rewrite the cache and arrange for a retry. */ + /* Compare the actual EHLO response extensions and AUTH methods to the cached + value we assumed; on difference, dump or rewrite the cache and arrange for a + retry. */ ap = tls_out.active.sock < 0 ? &sx->ehlo_resp.cleartext_auths : &sx->ehlo_resp.crypted_auths; @@ -934,6 +1069,10 @@ if (pending_EHLO) | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE | OPTION_UTF8 | OPTION_EARLY_PIPE ); +#ifdef EXPERIMENTAL_ESMTP_LIMITS + if (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS)) + ehlo_response_limits_read(sx); +#endif if ( peer_offered != sx->peer_offered || (authbits = study_ehlo_auths(sx)) != *ap) { @@ -941,16 +1080,44 @@ if (pending_EHLO) debug_printf("EHLO %s extensions changed, 0x%04x/0x%04x -> 0x%04x/0x%04x\n", tls_out.active.sock < 0 ? "cleartext" : "crypted", sx->peer_offered, *ap, peer_offered, authbits); - *(tls_out.active.sock < 0 - ? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) = peer_offered; - *ap = authbits; if (peer_offered & OPTION_EARLY_PIPE) + { + *(tls_out.active.sock < 0 + ? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) = + peer_offered; + *ap = authbits; write_ehlo_cache_entry(sx); + } else invalidate_ehlo_cache_entry(sx); return OK; /* just carry on */ } +#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. */ + { + if ( (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS)) + && ( sx->peer_limit_mail != sx->ehlo_resp.limit_mail + || sx->peer_limit_rcpt != sx->ehlo_resp.limit_rcpt + || sx->peer_limit_rcptdom != sx->ehlo_resp.limit_rcptdom + ) ) + { + HDEBUG(D_transport) + { + debug_printf("EHLO LIMITS changed:"); + if (sx->peer_limit_mail != sx->ehlo_resp.limit_mail) + debug_printf(" MAILMAX %u -> %u\n", sx->ehlo_resp.limit_mail, sx->peer_limit_mail); + else if (sx->peer_limit_rcpt != sx->ehlo_resp.limit_rcpt) + debug_printf(" RCPTMAX %u -> %u\n", sx->ehlo_resp.limit_rcpt, sx->peer_limit_rcpt); + else + debug_printf(" RCPTDOMAINMAX %u -> %u\n", sx->ehlo_resp.limit_rcptdom, sx->peer_limit_rcptdom); + } + invalidate_ehlo_cache_entry(sx); + } + } +#endif } return OK; @@ -959,7 +1126,7 @@ fail: (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp); return rc; } -#endif +#endif /*!DISABLE_PIPE_CONNECT*/ /************************************************* @@ -1026,7 +1193,7 @@ if (sx->pending_MAIL) { DEBUG(D_transport) debug_printf("%s expect mail\n", __FUNCTION__); count--; - sx->pending_MAIL = FALSE; + sx->pending_MAIL = sx->RCPT_452 = FALSE; if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', ob->command_timeout)) { @@ -1072,7 +1239,7 @@ while (count-- > 0) /* The address was accepted */ addr->host_used = sx->conn_args.host; - DEBUG(D_transport) debug_printf("%s expect rcpt\n", __FUNCTION__); + DEBUG(D_transport) debug_printf("%s expect rcpt for %s\n", __FUNCTION__, addr->address); if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', ob->command_timeout)) { @@ -1166,7 +1333,7 @@ while (count-- > 0) if (addr->more_errno >> 8 == 52 && yield & 3) { - if (!sx->RCPT_452) + if (!sx->RCPT_452) /* initialised at MAIL-ack above */ { DEBUG(D_transport) debug_printf("%s: seen first 452 too-many-rcpts\n", __FUNCTION__); @@ -1213,6 +1380,8 @@ while (count-- > 0) } } } + if (count && !(addr = addr->next)) + return -2; } /* Loop for next RCPT response */ /* Update where to start at for the next block of responses, unless we @@ -1488,7 +1657,9 @@ if ( sx->esmtp if (require_auth == OK && !f.smtp_authenticated) { +#ifndef DISABLE_PIPE_CONNECT invalidate_ehlo_cache_entry(sx); +#endif set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL, string_sprintf("authentication required but %s", fail_reason), DEFER, FALSE, &sx->delivery_start); @@ -1623,8 +1794,9 @@ uschar * message_local_identity, current_local_identity = smtp_local_identity(s_compare->current_sender_address, s_compare->tblock); -if (!(new_sender_address = deliver_get_sender_address(message_id))) - return FALSE; +if (!(new_sender_address = spool_sender_from_msgid(message_id))) + return FALSE; + message_local_identity = smtp_local_identity(new_sender_address, s_compare->tblock); @@ -1812,15 +1984,13 @@ return OK; - - /************************************************* * Make connection for given message * *************************************************/ /* Arguments: - ctx connection context + sx connection context suppress_tls if TRUE, don't attempt a TLS connection - this is set for a second attempt after TLS initialization fails @@ -1869,7 +2039,8 @@ sx->dane_required = /* sx->pending_BANNER = sx->pending_EHLO = sx->pending_MAIL = FALSE; */ #endif -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 = 999999; +if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999; /* sx->peer_offered = 0; */ /* sx->avoid_option = 0; */ sx->igquotstr = US""; @@ -1950,7 +2121,7 @@ if (continue_hostname && continue_proxy_cipher) { case OK: sx->conn_args.dane = TRUE; ob->tls_tempfail_tryclear = FALSE; /* force TLS */ - ob->tls_sni = sx->first_addr->domain; /* force SNI */ + ob->tls_sni = sx->conn_args.host->name; /* force SNI */ break; case FAIL_FORCED: break; default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, @@ -2012,6 +2183,11 @@ if (!continue_hostname) if (sx->verify) HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port); + /* Arrange to report to calling process this is a new connection */ + + clearflag(sx->first_addr, af_cont_conn); + setflag(sx->first_addr, af_new_conn); + /* Get the actual port the connection will use, into sx->conn_args.host */ smtp_port_for_connect(sx->conn_args.host, sx->port); @@ -2032,7 +2208,7 @@ if (!continue_hostname) { case OK: sx->conn_args.dane = TRUE; ob->tls_tempfail_tryclear = FALSE; /* force TLS */ - ob->tls_sni = sx->first_addr->domain; /* force SNI */ + ob->tls_sni = sx->conn_args.host->name; /* force SNI */ break; case FAIL_FORCED: break; default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, @@ -2065,6 +2241,9 @@ if (!continue_hostname) sx->cctx.tls_ctx = NULL; sx->inblock.cctx = sx->outblock.cctx = &sx->cctx; +#ifdef EXPERIMENTAL_ESMTP_LIMITS + sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom = +#endif sx->avoid_option = sx->peer_offered = smtp_peer_options = 0; #ifndef DISABLE_PIPE_CONNECT @@ -2299,8 +2478,8 @@ goto SEND_QUIT; int n = sizeof(sx->buffer); uschar * rsp = sx->buffer; - if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2) - { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; } + if (sx->esmtp_sent && (n = Ustrlen(sx->buffer) + 1) < sizeof(sx->buffer)/2) + { rsp = sx->buffer + n; n = sizeof(sx->buffer) - n; } if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0) goto SEND_FAILED; @@ -2343,6 +2522,13 @@ goto SEND_QUIT; ) #endif ); +#ifdef EXPERIMENTAL_ESMTP_LIMITS + if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS)) + { + ehlo_response_limits_read(sx); + ehlo_response_limits_apply(sx); + } +#endif #ifndef DISABLE_PIPE_CONNECT if (sx->early_pipe_ok) { @@ -2397,6 +2583,13 @@ else sx->inblock.cctx = sx->outblock.cctx = &sx->cctx; smtp_command = big_buffer; sx->peer_offered = smtp_peer_options; +#ifdef EXPERIMENTAL_ESMTP_LIMITS + /* Limits passed by cmdline over exec. */ + ehlo_limits_apply(sx, + sx->peer_limit_mail = continue_limit_mail, + sx->peer_limit_rcpt = continue_limit_rcpt, + sx->peer_limit_rcptdom = continue_limit_rcptdom); +#endif sx->helo_data = NULL; /* ensure we re-expand ob->helo_data */ /* For a continued connection with TLS being proxied for us, or a @@ -2438,7 +2631,8 @@ if ( smtp_peer_options & OPTION_TLS #ifndef DISABLE_PIPE_CONNECT /* If doing early-pipelining reap the banner and EHLO-response but leave - the response for the STARTTLS we just sent alone. */ + 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) { @@ -2502,6 +2696,7 @@ if ( smtp_peer_options & OPTION_TLS sx->send_quit = FALSE; goto TLS_FAILED; } + sx->send_tlsclose = TRUE; /* TLS session is set up. Check the inblock fill level. If there is content then as we have not yet done a tls read it must have arrived before @@ -2573,6 +2768,13 @@ if (tls_out.active.sock >= 0) DEBUG(D_transport) debug_printf("Using cached crypted PIPE_CONNECT\n"); } #endif +#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; + sx->single_rcpt_domain = FALSE; +#endif /* For SMTPS we need to wait for the initial OK response. */ if (sx->smtps) @@ -2663,7 +2865,7 @@ so its response needs to be analyzed. If TLS is not active and this is a continued session down a previously-used socket, we haven't just done EHLO, so we skip this. */ -if (continue_hostname == NULL +if ( !continue_hostname #ifndef DISABLE_TLS || tls_out.active.sock >= 0 #endif @@ -2702,6 +2904,14 @@ if (continue_hostname == NULL if (tls_out.active.sock >= 0) sx->ehlo_resp.crypted_features = sx->peer_offered; #endif + +#ifdef EXPERIMENTAL_ESMTP_LIMITS + if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS)) + { + ehlo_response_limits_read(sx); + ehlo_response_limits_apply(sx); + } +#endif } /* Set for IGNOREQUOTA if the response to LHLO specifies support and the @@ -2869,7 +3079,7 @@ return OK; SEND_FAILED: code = '4'; - message = US string_sprintf("send() to %s [%s] failed: %s", + message = US string_sprintf("smtp send to %s [%s] failed: %s", sx->conn_args.host->name, sx->conn_args.host->address, strerror(errno)); sx->send_quit = FALSE; yield = DEFER; @@ -2935,7 +3145,11 @@ if (sx->send_quit) #ifndef DISABLE_TLS if (sx->cctx.tls_ctx) { - tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); + if (sx->send_tlsclose) + { + tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); + sx->send_tlsclose = FALSE; + } sx->cctx.tls_ctx = NULL; } #endif @@ -3031,7 +3245,7 @@ if ( sx->peer_offered & OPTION_UTF8 /* 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; + addr && address_count < sx->max_rcpt; /*XXX maybe also || sx->single_rcpt_domain ? */ addr = addr->next) if (addr->transport_return == PENDING_DEFER) { address_count++; @@ -3119,6 +3333,9 @@ int smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield) { address_item * addr; +#ifdef EXPERIMENTAL_ESMTP_LIMITS +address_item * restart_addr = NULL; +#endif int address_count, pipe_limit; int rc; @@ -3203,13 +3420,31 @@ that max_rcpt will be large, so all addresses will be done at once. For verify we flush the pipeline after any (the only) rcpt address. */ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100; - addr && address_count < sx->max_rcpt; + addr && address_count < sx->max_rcpt; addr = addr->next) if (addr->transport_return == PENDING_DEFER) { int cmds_sent; BOOL no_flush; uschar * rcpt_addr; +#ifdef EXPERIMENTAL_ESMTP_LIMITS + if ( sx->single_rcpt_domain /* restriction on domains */ + && address_count > 0 /* not first being sent */ + && Ustrcmp(addr->domain, sx->first_addr->domain) != 0 /* dom diff from first */ + ) + { + DEBUG(D_transport) debug_printf("skipping different domain %s\n", addr->domain); + + /* Ensure the smtp-response reaper does not think the address had a RCPT + command sent for it. Reset to PENDING_DEFER in smtp_deliver(), where we + goto SEND_MESSAGE. */ + + addr->transport_return = SKIP; + if (!restart_addr) restart_addr = addr; /* note restart point */ + continue; /* skip this one */ + } +#endif + addr->dsn_aware = sx->peer_offered & OPTION_DSN ? dsn_support_yes : dsn_support_no; @@ -3281,7 +3516,11 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100; } } /* Loop for next address */ +#ifdef EXPERIMENTAL_ESMTP_LIMITS +sx->next_addr = restart_addr ? restart_addr : addr; +#else sx->next_addr = addr; +#endif return 0; } @@ -3314,6 +3553,7 @@ smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd, fd_set rfds, efds; int max_fd = MAX(pfd[0], tls_out.active.sock) + 1; int rc, i; +BOOL send_tls_shutdown = TRUE; close(pfd[1]); if ((rc = exim_fork(US"tls-proxy"))) @@ -3347,51 +3587,60 @@ for (int fd_bits = 3; fd_bits; ) goto done; } + /* For errors where not readable, bomb out */ + if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds)) { DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n", FD_ISSET(pfd[0], &efds) ? "proxy" : "tls"); - goto done; + if (!(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds))) + goto done; + DEBUG(D_transport) debug_printf("- but also readable; no exit yet\n"); } } while (rc < 0 || !(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds))); /* handle inbound data */ if (FD_ISSET(tls_out.active.sock, &rfds)) - if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0) - { + if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0) /* Expect -1 for EOF; */ + { /* that reaps the TLS Close Notify record */ fd_bits &= ~1; FD_CLR(tls_out.active.sock, &rfds); shutdown(pfd[0], SHUT_WR); timeout = 5; } else - { for (int nbytes = 0; rc - nbytes > 0; nbytes += i) if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done; - } - else if (fd_bits & 1) - FD_SET(tls_out.active.sock, &rfds); - /* handle outbound data */ + /* Handle outbound data. We cannot combine payload and the TLS-close + due to the limitations of the (pipe) channel feeding us. Maybe use a unix-domain + socket? */ if (FD_ISSET(pfd[0], &rfds)) if ((rc = read(pfd[0], buf, bsize)) <= 0) { - fd_bits = 0; - tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT); - ct_ctx = NULL; + fd_bits &= ~2; + FD_CLR(pfd[0], &rfds); + +# ifdef EXIM_TCP_CORK /* Use _CORK to get TLS Close Notify in FIN segment */ + (void) setsockopt(tls_out.active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); +# endif + tls_shutdown_wr(ct_ctx); + send_tls_shutdown = FALSE; + shutdown(tls_out.active.sock, SHUT_WR); } else - { for (int nbytes = 0; rc - nbytes > 0; nbytes += i) if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0) goto done; - } - else if (fd_bits & 2) - FD_SET(pfd[0], &rfds); + + if (fd_bits & 1) FD_SET(tls_out.active.sock, &rfds); + if (fd_bits & 2) FD_SET(pfd[0], &rfds); } done: + if (send_tls_shutdown) tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT); + ct_ctx = NULL; testharness_pause_ms(100); /* let logging complete */ exim_exit(EXIT_SUCCESS); } @@ -3457,13 +3706,17 @@ int yield = OK; int save_errno; int rc; -BOOL pass_message = FALSE; uschar *message = NULL; uschar new_message_id[MESSAGE_ID_LENGTH + 1]; smtp_context * sx = store_get(sizeof(*sx), TRUE); /* tainted, for the data buffers */ +BOOL pass_message = FALSE; +#ifdef EXPERIMENTAL_ESMTP_LIMITS +BOOL mail_limit = FALSE; +#endif #ifdef SUPPORT_DANE BOOL dane_held; #endif +BOOL tcw_done = FALSE, tcw = FALSE; *message_defer = FALSE; @@ -3479,8 +3732,8 @@ sx->conn_args.tblock = tblock; gettimeofday(&sx->delivery_start, NULL); sx->sync_addr = sx->first_addr = addrlist; +REPEAT_CONN: #ifdef SUPPORT_DANE -DANE_DOMAINS: dane_held = FALSE; #endif @@ -3753,6 +4006,46 @@ else report_time_since(&t0, US"dkim_exim_sign_init (delta)"); # endif } +#endif + + /* See if we can pipeline QUIT. Reasons not to are + - pipelining not active + - not ok to send quit + - errors in amtp transation responses + - more addrs to send for this message or this host + - this message was being retried + - more messages for this host + If we can, we want the message-write to not flush (the tail end of) its data out. */ + + if ( sx->pipelining_used + && (sx->ok && sx->completed_addr || sx->peer_offered & OPTION_CHUNKING) + && sx->send_quit + && !(sx->first_addr || f.continue_more) + && f.deliver_firsttime + ) + { + smtp_compare_t t_compare = + {.tblock = tblock, .current_sender_address = sender_address}; + + tcw_done = TRUE; + tcw = +#ifndef DISABLE_TLS + ( tls_out.active.sock < 0 && !continue_proxy_cipher + || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK + ) + && +#endif + transport_check_waiting(tblock->name, host->name, + tblock->connection_max_messages, new_message_id, + (oicf)smtp_are_same_identities, (void*)&t_compare); + if (!tcw) + { + HDEBUG(D_transport) debug_printf("will pipeline QUIT\n"); + tctx.options |= topt_no_flush; + } + } + +#ifndef DISABLE_DKIM sx->ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message); #else sx->ok = transport_write_message(&tctx, 0); @@ -3781,6 +4074,48 @@ else smtp_command = US"end of data"; + /* If we can pipeline a QUIT with the data them send it now. If a new message + for this host appeared in the queue while data was being sent, we will not see + it and it will have to wait for a queue run. If there was one but another + thread took it, we might attempt to send it - but locking of spoolfiles will + detect that. Use _MORE to get QUIT in FIN segment. */ + + if (tcw_done && !tcw) + { + /*XXX jgh 2021/03/10 google et. al screwup. G, at least, sends TCP FIN in response to TLS + close-notify. Under TLS 1.3, violating RFC. + However, TLS 1.2 does not have half-close semantics. */ + + if ( sx->cctx.tls_ctx +#if 0 && !defined(DISABLE_TLS) + && Ustrcmp(tls_out.ver, "TLS1.3") != 0 +#endif + || !f.deliver_firsttime + ) + { /* Send QUIT now and not later */ + (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n"); + sx->send_quit = FALSE; + } + else + { /* add QUIT to the output buffer */ + (void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n"); + sx->send_quit = FALSE; /* avoid sending it later */ + +#ifndef DISABLE_TLS + if (sx->cctx.tls_ctx) /* need to send TLS Close Notify */ + { +# ifdef EXIM_TCP_CORK /* Use _CORK to get Close Notify in FIN segment */ + (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); +# endif + tls_shutdown_wr(sx->cctx.tls_ctx); + sx->send_tlsclose = FALSE; /* avoid later repeat */ + } +#endif + HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(shutdown)>>\n"); + shutdown(sx->cctx.sock, SHUT_WR); /* flush output buffer, with TCP FIN */ + } + } + if (sx->peer_offered & OPTION_CHUNKING && sx->cmd_count > 1) { /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */ @@ -3873,15 +4208,16 @@ else !sx->lmtp ) { - const uschar *s = string_printing(sx->buffer); + const uschar * s = string_printing(sx->buffer); /* deconst cast ok here as string_printing was checked to have alloc'n'copied */ - conf = (s == sx->buffer)? US string_copy(s) : US s; + conf = s == sx->buffer ? US string_copy(s) : US s; } /* Process all transported addresses - for LMTP or PRDR, read a status for - each one. */ + each one. We used to drop out at first_addr, until someone returned a 452 + followed by a 250... and we screwed up the accepted addresses. */ - for (address_item * addr = addrlist; addr != sx->first_addr; addr = addr->next) + for (address_item * addr = addrlist; addr; addr = addr->next) { if (addr->transport_return != PENDING_OK) continue; @@ -3970,7 +4306,7 @@ else else sprintf(CS sx->buffer, "%.500s\n", addr->unique); - DEBUG(D_deliver) debug_printf("S:journalling %s\n", sx->buffer); + DEBUG(D_deliver) debug_printf("S:journalling %s", sx->buffer); len = Ustrlen(CS sx->buffer); if (write(journal_fd, sx->buffer, len) != len) log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for " @@ -4054,7 +4390,8 @@ if (!sx->ok) { save_errno = errno; message = NULL; - sx->send_quit = check_response(host, &save_errno, addrlist->more_errno, + /* Clear send_quit flag if needed. Do not set. */ + sx->send_quit &= check_response(host, &save_errno, addrlist->more_errno, sx->buffer, &code, &message, &pass_message); goto FAILED; } @@ -4063,7 +4400,7 @@ if (!sx->ok) { save_errno = errno; code = '4'; - message = string_sprintf("send() to %s [%s] failed: %s", + message = string_sprintf("smtp send to %s [%s] failed: %s", host->name, host->address, message ? message : US strerror(save_errno)); sx->send_quit = FALSE; goto FAILED; @@ -4214,83 +4551,98 @@ DEBUG(D_transport) sx->send_rset, f.continue_more, yield, sx->first_addr ? "not " : ""); if (sx->completed_addr && sx->ok && sx->send_quit) - { - smtp_compare_t t_compare; - - t_compare.tblock = tblock; - t_compare.current_sender_address = sender_address; - - if ( sx->first_addr != NULL /* more addrs for this message */ - || f.continue_more /* more addrs for coninued-host */ - || ( -#ifndef DISABLE_TLS - ( tls_out.active.sock < 0 && !continue_proxy_cipher - || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK - ) - && +#ifdef EXPERIMENTAL_ESMTP_LIMITS + if (mail_limit = continue_sequence >= sx->max_mail) + { + DEBUG(D_transport) + debug_printf("reached limit %u for MAILs per conn\n", sx->max_mail); + } + else #endif - transport_check_waiting(tblock->name, host->name, - tblock->connection_max_messages, new_message_id, - (oicf)smtp_are_same_identities, (void*)&t_compare) - ) ) { - uschar *msg; - BOOL pass_message; - - if (sx->send_rset) - if (! (sx->ok = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0)) - { - msg = US string_sprintf("send() to %s [%s] failed: %s", host->name, - host->address, strerror(errno)); - sx->send_quit = FALSE; - } - else if (! (sx->ok = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), - '2', ob->command_timeout))) - { - int code; - sx->send_quit = check_response(host, &errno, 0, sx->buffer, &code, &msg, - &pass_message); - if (!sx->send_quit) - { - DEBUG(D_transport) debug_printf("H=%s [%s] %s\n", - host->name, host->address, msg); - } - } + smtp_compare_t t_compare = + {.tblock = tblock, .current_sender_address = sender_address}; - /* Either RSET was not needed, or it succeeded */ - - if (sx->ok) - { + if ( sx->first_addr /* more addrs for this message */ + || f.continue_more /* more addrs for continued-host */ + || tcw_done && tcw /* more messages for host */ + || ( #ifndef DISABLE_TLS - int pfd[2]; + ( tls_out.active.sock < 0 && !continue_proxy_cipher + || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK + ) + && #endif - int socket_fd = sx->cctx.sock; + transport_check_waiting(tblock->name, host->name, + sx->max_mail, new_message_id, + (oicf)smtp_are_same_identities, (void*)&t_compare) + ) ) + { + uschar *msg; + BOOL pass_message; + + if (sx->send_rset) + if (! (sx->ok = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0)) + { + msg = US string_sprintf("smtp send to %s [%s] failed: %s", host->name, + host->address, strerror(errno)); + sx->send_quit = FALSE; + } + else if (! (sx->ok = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), + '2', ob->command_timeout))) + { + int code; + sx->send_quit = check_response(host, &errno, 0, sx->buffer, &code, &msg, + &pass_message); + if (!sx->send_quit) + { + DEBUG(D_transport) debug_printf("H=%s [%s] %s\n", + host->name, host->address, msg); + } + } + /* Either RSET was not needed, or it succeeded */ - if (sx->first_addr != NULL) /* More addresses still to be sent */ - { /* for this message */ - continue_sequence++; /* Causes * in logging */ - pipelining_active = sx->pipelining_used; /* was cleared at DATA */ - goto SEND_MESSAGE; - } + if (sx->ok) + { +#ifndef DISABLE_TLS + int pfd[2]; +#endif + int socket_fd = sx->cctx.sock; + + if (sx->first_addr) /* More addresses still to be sent */ + { /* for this message */ +#ifdef EXPERIMENTAL_ESMTP_LIMITS + /* Any that we marked as skipped, reset to do now */ + for (address_item * a = sx->first_addr; a; a = a->next) + if (a->transport_return == SKIP) + a->transport_return = PENDING_DEFER; +#endif + continue_sequence++; /* for consistency */ + clearflag(sx->first_addr, af_new_conn); + setflag(sx->first_addr, af_cont_conn); /* Causes * in logging */ + pipelining_active = sx->pipelining_used; /* was cleared at DATA */ + goto SEND_MESSAGE; + } - /* Unless caller said it already has more messages listed for this host, - pass the connection on to a new Exim process (below, the call to - transport_pass_socket). If the caller has more ready, just return with - the connection still open. */ + /* Unless caller said it already has more messages listed for this host, + pass the connection on to a new Exim process (below, the call to + transport_pass_socket). If the caller has more ready, just return with + the connection still open. */ #ifndef DISABLE_TLS - if (tls_out.active.sock >= 0) - if ( f.continue_more - || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK) - { - /* Before passing the socket on, or returning to caller with it still - 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. */ + if (tls_out.active.sock >= 0) + if ( f.continue_more + || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK) + { + /* Before passing the socket on, or returning to caller with it still + 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. */ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT); + sx->send_tlsclose = FALSE; sx->cctx.tls_ctx = NULL; tls_out.active.sock = -1; smtp_peer_options = smtp_peer_options_wrap; @@ -4300,119 +4652,110 @@ if (sx->completed_addr && sx->ok && sx->send_quit) && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', ob->command_timeout); - if (sx->ok && f.continue_more) - goto TIDYUP; /* More addresses for another run */ - } - else - { - /* Set up a pipe for proxying TLS for the new transport process */ - - smtp_peer_options |= OPTION_TLS; - if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0)) - socket_fd = pfd[1]; + if (sx->ok && f.continue_more) + goto TIDYUP; /* More addresses for another run */ + } else - set_errno(sx->first_addr, errno, US"internal allocation problem", - DEFER, FALSE, host, + { + /* Set up a pipe for proxying TLS for the new transport process */ + + smtp_peer_options |= OPTION_TLS; + if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0)) + socket_fd = pfd[1]; + else + set_errno(sx->first_addr, errno, US"internal allocation problem", + DEFER, FALSE, host, # ifdef EXPERIMENTAL_DSN_INFO - sx->smtp_greeting, sx->helo_response, + sx->smtp_greeting, sx->helo_response, # endif - &sx->delivery_start); - } - else + &sx->delivery_start); + } + else #endif - if (f.continue_more) - goto TIDYUP; /* More addresses for another run */ + if (f.continue_more) + goto TIDYUP; /* More addresses for another run */ - /* If the socket is successfully passed, we mustn't send QUIT (or - indeed anything!) from here. */ - -/*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to -propagate it from the initial -*/ - if (sx->ok && transport_pass_socket(tblock->name, host->name, - host->address, new_message_id, socket_fd)) - { - sx->send_quit = FALSE; + /* If the socket is successfully passed, we mustn't send QUIT (or + indeed anything!) from here. */ - /* We have passed the client socket to a fresh transport process. - If TLS is still active, we need to proxy it for the transport we - just passed the baton to. Fork a child to to do it, and return to - get logging done asap. Which way to place the work makes assumptions - about post-fork prioritisation which may not hold on all platforms. */ -#ifndef DISABLE_TLS - if (tls_out.active.sock >= 0) + /*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to + propagate it from the initial + */ + if (sx->ok && transport_pass_socket(tblock->name, host->name, + host->address, new_message_id, socket_fd +#ifdef EXPERIMENTAL_ESMTP_LIMITS + , sx->peer_limit_mail, sx->peer_limit_rcpt, sx->peer_limit_rcptdom +#endif + )) { - int pid = exim_fork(US"tls-proxy-interproc"); - if (pid == 0) /* child; fork again to disconnect totally */ - { - /* does not return */ - smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd, - ob->command_timeout); - } + sx->send_quit = FALSE; - if (pid > 0) /* parent */ + /* We have passed the client socket to a fresh transport process. + If TLS is still active, we need to proxy it for the transport we + just passed the baton to. Fork a child to to do it, and return to + get logging done asap. Which way to place the work makes assumptions + about post-fork prioritisation which may not hold on all platforms. */ +#ifndef DISABLE_TLS + if (tls_out.active.sock >= 0) { - close(pfd[0]); - /* tidy the inter-proc to disconn the proxy proc */ - waitpid(pid, NULL, 0); - tls_close(sx->cctx.tls_ctx, TLS_NO_SHUTDOWN); - sx->cctx.tls_ctx = NULL; - (void)close(sx->cctx.sock); - sx->cctx.sock = -1; - continue_transport = NULL; - continue_hostname = NULL; - goto TIDYUP; + int pid = exim_fork(US"tls-proxy-interproc"); + if (pid == 0) /* child; fork again to disconnect totally */ + { + /* does not return */ + smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd, + ob->command_timeout); + } + + if (pid > 0) /* parent */ + { + close(pfd[0]); + /* tidy the inter-proc to disconn the proxy proc */ + waitpid(pid, NULL, 0); + tls_close(sx->cctx.tls_ctx, TLS_NO_SHUTDOWN); + sx->cctx.tls_ctx = NULL; + (void)close(sx->cctx.sock); + sx->cctx.sock = -1; + continue_transport = NULL; + continue_hostname = NULL; + goto TIDYUP; + } + log_write(0, LOG_PANIC_DIE, "fork failed"); } - log_write(0, LOG_PANIC_DIE, "fork failed"); - } #endif + } } - } - /* If RSET failed and there are addresses left, they get deferred. */ - else - set_errno(sx->first_addr, errno, msg, DEFER, FALSE, host, + /* If RSET failed and there are addresses left, they get deferred. */ + else + set_errno(sx->first_addr, errno, msg, DEFER, FALSE, host, #ifdef EXPERIMENTAL_DSN_INFO - sx->smtp_greeting, sx->helo_response, + sx->smtp_greeting, sx->helo_response, #endif - &sx->delivery_start); + &sx->delivery_start); + } } - } /* End off tidily with QUIT unless the connection has died or the socket has -been passed to another process. There has been discussion on the net about what -to do after sending QUIT. The wording of the RFC suggests that it is necessary -to wait for a response, but on the other hand, there isn't anything one can do -with an error response, other than log it. Exim used to do that. However, -further discussion suggested that it is positively advantageous not to wait for -the response, but to close the session immediately. This is supposed to move -the TCP/IP TIME_WAIT state from the server to the client, thereby removing some -load from the server. (Hosts that are both servers and clients may not see much -difference, of course.) Further discussion indicated that this was safe to do -on Unix systems which have decent implementations of TCP/IP that leave the -connection around for a while (TIME_WAIT) after the application has gone away. -This enables the response sent by the server to be properly ACKed rather than -timed out, as can happen on broken TCP/IP implementations on other OS. - -This change is being made on 31-Jul-98. After over a year of trouble-free -operation, the old commented-out code was removed on 17-Sep-99. */ +been passed to another process. */ SEND_QUIT: if (sx->send_quit) - { -#ifdef EXIM_TCP_CORK - (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); + { /* Use _MORE to get QUIT in FIN segment */ + (void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n"); +#ifndef DISABLE_TLS + if (sx->cctx.tls_ctx) + { +# ifdef EXIM_TCP_CORK /* Use _CORK to get TLS Close Notify in FIN segment */ + (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); +# endif + tls_shutdown_wr(sx->cctx.tls_ctx); + sx->send_tlsclose = FALSE; + } #endif - (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n"); } END_OFF: -#ifndef DISABLE_TLS -tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT); -sx->cctx.tls_ctx = NULL; -#endif - /* Close the socket, and return the appropriate value, first setting works because the NULL setting is passed back to the calling process, and remote_max_parallel is forced to 1 when delivering over an existing connection, @@ -4423,17 +4766,62 @@ writing RSET might have failed, or there may be other addresses whose hosts are specified in the transports, and therefore not visible at top level, in which case continue_more won't get set. */ -HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); if (sx->send_quit) { + /* This flushes data queued in the socket, being the QUIT and any TLS Close, + sending them along with the client FIN flag. Us (we hope) sending FIN first + means we (client) take the TIME_WAIT state, so the server (which likely has a + higher connection rate) does not have to. */ + + HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(shutdown)>>\n"); shutdown(sx->cctx.sock, SHUT_WR); + } + +if (sx->send_quit || tcw_done && !tcw) + { + /* Wait for (we hope) ack of our QUIT, and a server FIN. Discard any data + received, then discard the socket. Any packet received after then, or receive + data still in the socket, will get a RST - hence the pause/drain. */ + + /* Reap the response to QUIT, timing out after one second */ + (void) smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', 1); +#ifndef DISABLE_TLS + if (sx->cctx.tls_ctx) + { + int n; + + /* Reap the TLS Close Notify from the server, timing out after one second */ + sigalrm_seen = FALSE; + ALARM(1); + do + n = tls_read(sx->cctx.tls_ctx, sx->inbuffer, sizeof(sx->inbuffer)); + while (!sigalrm_seen && n > 0); + ALARM_CLR(0); + +# ifdef EXIM_TCP_CORK + (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); +# endif + tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT); + sx->cctx.tls_ctx = NULL; + } +#endif millisleep(20); - testharness_pause_ms(200); if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0) - for (int i = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && i > 0;) - i--; /* drain socket */ + for (int i = 16, n; /* drain socket */ + (n = read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer))) > 0 && i > 0; + i--) HDEBUG(D_transport|D_acl|D_v) + { + int m = MIN(n, 64); + debug_printf_indent(" SMTP(drain %d bytes)<< %.*s\n", n, m, sx->inbuffer); + for (m = 0; m < n; m++) + debug_printf("0x%02x\n", sx->inbuffer[m]); + } } +HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); (void)close(sx->cctx.sock); +sx->cctx.sock = -1; +continue_transport = NULL; +continue_hostname = NULL; #ifndef DISABLE_EVENT (void) event_raise(tblock->event_action, US"tcp:close", NULL); @@ -4453,15 +4841,30 @@ if (dane_held) to get the domain string for SNI */ sx->first_addr = a; + clearflag(a, af_cont_conn); + setflag(a, af_new_conn); /* clear * from logging */ DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain); } } - goto DANE_DOMAINS; + continue_sequence = 1; /* for consistency */ + goto REPEAT_CONN; + } +#endif + +#ifdef EXPERIMENTAL_ESMTP_LIMITS +if (mail_limit && sx->first_addr) + { + /* Reset the sequence count since we closed the connection. This is flagged + on the pipe back to the delivery process so that a non-continued-conn delivery + is logged. */ + + continue_sequence = 1; /* for consistency */ + clearflag(sx->first_addr, af_cont_conn); + setflag(sx->first_addr, af_new_conn); /* clear * from logging */ + goto REPEAT_CONN; } #endif -continue_transport = NULL; -continue_hostname = NULL; return yield; TIDYUP: @@ -4676,7 +5079,7 @@ if (!hostlist || (ob->hosts_override && ob->hosts)) { uschar *s = ob->hosts; - if (Ustrchr(s, '$') != NULL) + if (Ustrchr(s, '$')) { if (!(expanded_hosts = expand_string(s))) { @@ -4692,11 +5095,8 @@ if (!hostlist || (ob->hosts_override && ob->hosts)) else if (ob->hosts_randomize) s = expanded_hosts = string_copy(s); - if (is_tainted(s)) + if (is_tainted2(s, LOG_MAIN|LOG_PANIC, "Tainted host list '%s' from '%s' in transport %s", s, ob->hosts, tblock->name)) { - log_write(0, LOG_MAIN|LOG_PANIC, - "attempt to use tainted host list '%s' from '%s' in transport %s", - s, ob->hosts, tblock->name); /* Avoid leaking info to an attacker */ addrlist->message = US"internal configuration error"; addrlist->transport_return = PANIC; @@ -5024,7 +5424,7 @@ retry_non_continued: because connections to the same host from a different interface should be treated separately. */ - host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6; + host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET; { uschar * s = ob->interface; if (s && *s)