X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/3cf01803d0ca676cb71b133dfc1535406f1cbe26..6d5c916cc5720591335fea53242dd6b97ea56fe3:/src/src/transports/smtp.c diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 975fc16aa..58a59433d 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -44,17 +44,17 @@ optionlist smtp_transport_options[] = { (void *)offsetof(smtp_transport_options_block, delay_after_cutoff) }, #ifndef DISABLE_DKIM { "dkim_canon", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, dkim_canon) }, + (void *)offsetof(smtp_transport_options_block, dkim.dkim_canon) }, { "dkim_domain", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, dkim_domain) }, + (void *)offsetof(smtp_transport_options_block, dkim.dkim_domain) }, { "dkim_private_key", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, dkim_private_key) }, + (void *)offsetof(smtp_transport_options_block, dkim.dkim_private_key) }, { "dkim_selector", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, dkim_selector) }, + (void *)offsetof(smtp_transport_options_block, dkim.dkim_selector) }, { "dkim_sign_headers", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, dkim_sign_headers) }, + (void *)offsetof(smtp_transport_options_block, dkim.dkim_sign_headers) }, { "dkim_strict", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, dkim_strict) }, + (void *)offsetof(smtp_transport_options_block, dkim.dkim_strict) }, #endif { "dns_qualify_single", opt_bool, (void *)offsetof(smtp_transport_options_block, dns_qualify_single) }, @@ -72,17 +72,6 @@ optionlist smtp_transport_options[] = { (void *)offsetof(smtp_transport_options_block, final_timeout) }, { "gethostbyname", opt_bool, (void *)offsetof(smtp_transport_options_block, gethostbyname) }, -#ifdef SUPPORT_TLS - /* These are no longer honoured, as of Exim 4.80; for now, we silently - ignore; 4.83 will warn, and a later-still release will remove - these options, so that using them becomes an error. */ - { "gnutls_require_kx", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, gnutls_require_kx) }, - { "gnutls_require_mac", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, gnutls_require_mac) }, - { "gnutls_require_protocols", opt_stringptr, - (void *)offsetof(smtp_transport_options_block, gnutls_require_proto) }, -#endif { "helo_data", opt_stringptr, (void *)offsetof(smtp_transport_options_block, helo_data) }, { "hosts", opt_stringptr, @@ -127,6 +116,8 @@ optionlist smtp_transport_options[] = { #endif { "hosts_try_auth", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_try_auth) }, + { "hosts_try_chunking", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, hosts_try_chunking) }, #if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) { "hosts_try_dane", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_try_dane) }, @@ -211,12 +202,13 @@ smtp_transport_options_block smtp_transport_option_defaults = { NULL, /* serialize_hosts */ NULL, /* hosts_try_auth */ NULL, /* hosts_require_auth */ + US"*", /* hosts_try_chunking */ #ifdef EXPERIMENTAL_DANE NULL, /* hosts_try_dane */ NULL, /* hosts_require_dane */ #endif #ifndef DISABLE_PRDR - US"*", /* hosts_try_prdr */ + US"*", /* hosts_try_prdr */ #endif #ifndef DISABLE_OCSP US"*", /* hosts_request_ocsp (except under DANE; tls_client_start()) */ @@ -257,9 +249,6 @@ smtp_transport_options_block smtp_transport_option_defaults = { NULL, /* tls_crl */ NULL, /* tls_privatekey */ NULL, /* tls_require_ciphers */ - NULL, /* gnutls_require_kx */ - NULL, /* gnutls_require_mac */ - NULL, /* gnutls_require_proto */ NULL, /* tls_sni */ US"system", /* tls_verify_certificates */ EXIM_CLIENT_DH_DEFAULT_MIN_BITS, @@ -270,12 +259,12 @@ smtp_transport_options_block smtp_transport_option_defaults = { US"*" /* tls_verify_cert_hostnames */ #endif #ifndef DISABLE_DKIM - ,NULL, /* dkim_canon */ - NULL, /* dkim_domain */ - NULL, /* dkim_private_key */ - NULL, /* dkim_selector */ - NULL, /* dkim_sign_headers */ - NULL /* dkim_strict */ + , {NULL, /* dkim_canon */ + NULL, /* dkim_domain */ + NULL, /* dkim_private_key */ + NULL, /* dkim_selector */ + NULL, /* dkim_sign_headers */ + NULL} /* dkim_strict */ #endif }; @@ -411,15 +400,6 @@ if (ob->hosts_override && ob->hosts != NULL) tblock->overrides_hosts = TRUE; for them, but do not do any lookups at this time. */ host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE); - -#ifdef SUPPORT_TLS -if ( ob->gnutls_require_kx - || ob->gnutls_require_mac - || ob->gnutls_require_proto) - log_write(0, LOG_MAIN, "WARNING: smtp transport options" - " gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols" - " are obsolete\n"); -#endif } @@ -1215,9 +1195,15 @@ return FALSE; #ifdef EXPERIMENTAL_DANE +/* Lookup TLSA record for host/port. +Return: OK success with dnssec; DANE mode + DEFER Do not use this host now, may retry later + FAIL_FORCED No TLSA record; DANE not usable + FAIL Do not use this connection +*/ + int -tlsa_lookup(const host_item * host, dns_answer * dnsa, - BOOL dane_required, BOOL * dane) +tlsa_lookup(const host_item * host, dns_answer * dnsa, BOOL dane_required) { /* move this out to host.c given the similarity to dns_lookup() ? */ uschar buffer[300]; @@ -1228,25 +1214,24 @@ const uschar * fullname = buffer; switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname)) { - case DNS_AGAIN: - return DEFER; /* just defer this TLS'd conn */ - - default: - case DNS_FAIL: - if (dane_required) - return FAIL; - break; - case DNS_SUCCEED: if (!dns_is_secure(dnsa)) { log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC"); return DEFER; } - *dane = TRUE; - break; + return OK; + + case DNS_AGAIN: + return DEFER; /* just defer this TLS'd conn */ + + case DNS_NOMATCH: + return dane_required ? FAIL : FAIL_FORCED; + + default: + case DNS_FAIL: + return dane_required ? FAIL : DEFER; } -return OK; } #endif @@ -1311,7 +1296,6 @@ we will veto this new message. */ static BOOL smtp_are_same_identities(uschar * message_id, smtp_compare_t * s_compare) { - uschar * message_local_identity, * current_local_identity, * new_sender_address; @@ -1334,45 +1318,106 @@ uschar ehlo_response(uschar * buf, size_t bsize, uschar checks) { #ifdef SUPPORT_TLS -if (checks & PEER_OFFERED_TLS) - if (pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) - checks &= ~PEER_OFFERED_TLS; +if ( checks & PEER_OFFERED_TLS + && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) + checks &= ~PEER_OFFERED_TLS; #endif - if ( checks & PEER_OFFERED_IGNQ - && pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0, - PCRE_EOPT, NULL, 0) < 0) - checks &= ~PEER_OFFERED_IGNQ; +if ( checks & PEER_OFFERED_IGNQ + && pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0, + PCRE_EOPT, NULL, 0) < 0) + checks &= ~PEER_OFFERED_IGNQ; + +if ( checks & PEER_OFFERED_CHUNKING + && pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) + checks &= ~PEER_OFFERED_CHUNKING; #ifndef DISABLE_PRDR - if ( checks & PEER_OFFERED_PRDR - && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) - checks &= ~PEER_OFFERED_PRDR; +if ( checks & PEER_OFFERED_PRDR + && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) + checks &= ~PEER_OFFERED_PRDR; #endif #ifdef SUPPORT_I18N - if ( checks & PEER_OFFERED_UTF8 - && pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) - checks &= ~PEER_OFFERED_UTF8; +if ( checks & PEER_OFFERED_UTF8 + && pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) + checks &= ~PEER_OFFERED_UTF8; #endif - if ( checks & PEER_OFFERED_DSN - && pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) - checks &= ~PEER_OFFERED_DSN; +if ( checks & PEER_OFFERED_DSN + && pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) + checks &= ~PEER_OFFERED_DSN; - if ( checks & PEER_OFFERED_PIPE - && pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0, - PCRE_EOPT, NULL, 0) < 0) - checks &= ~PEER_OFFERED_PIPE; +if ( checks & PEER_OFFERED_PIPE + && pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0, + PCRE_EOPT, NULL, 0) < 0) + checks &= ~PEER_OFFERED_PIPE; - if ( checks & PEER_OFFERED_SIZE - && pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) - checks &= ~PEER_OFFERED_SIZE; +if ( checks & PEER_OFFERED_SIZE + && pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) + checks &= ~PEER_OFFERED_SIZE; return checks; } + +/* Callback for emitting a BDAT data chunk header. +Flush any buffered SMTP commands first. +Reap SMTP command responses if not the BDAT LAST. + +A nonlast request that is size zero is special-cased to only flush the +command buffer and reap all outstanding responses. + +Returns: OK or ERROR +*/ + +static int +smtp_chunk_cmd_callback(int fd, transport_ctx * tctx, + unsigned chunk_size, BOOL chunk_last) +{ +smtp_transport_options_block * ob = + (smtp_transport_options_block *)(tctx->tblock->options_block); +uschar buffer[128]; + +if ( (tctx->cmd_count = chunk_size == 0 && !chunk_last + + /* Handle flush request */ + ? smtp_write_command(tctx->outblock, FALSE, NULL) + + /* Write SMTP chunk header command */ + : smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n", + chunk_size, chunk_last ? " LAST" : "") + ) + < 0) + return ERROR; + +if (chunk_last) + return OK; + +/* Reap responses for this and any previous, and error out on failure */ +debug_printf("(look for %d responses)\n", tctx->cmd_count); + +switch(sync_responses(tctx->first_addr, tctx->tblock->rcpt_include_affixes, + tctx->sync_addr, tctx->host, tctx->cmd_count, + ob->address_retry_include_sender, + tctx->pending_MAIL, 0, + tctx->inblock, + ob->command_timeout, + buffer, sizeof(buffer))) + { + case 1: /* 2xx (only) => OK */ + case 3: /* 2xx & 5xx => OK & progress made */ + case 2: *tctx->completed_address = TRUE; /* 5xx (only) => progress made */ + case 0: return OK; /* No 2xx or 5xx, but no probs */ + + case -1: /* Timeout on RCPT */ + default: return ERROR; /* I/O error, or any MAIL/DATA error */ + } +} + + + /************************************************* * Deliver address list to given host * *************************************************/ @@ -1545,26 +1590,26 @@ if (continue_hostname == NULL) if (host->dnssec == DS_YES) { - if( ( dane_required - || verify_check_given_host(&ob->hosts_try_dane, host) == OK - ) - && (rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK - && dane_required /* do not error on only dane-requested */ + if( dane_required + || verify_check_given_host(&ob->hosts_try_dane, host) == OK ) - { - set_errno_nohost(addrlist, ERRNO_DNSDEFER, - string_sprintf("DANE error: tlsa lookup %s", - rc == DEFER ? "DEFER" : "FAIL"), - rc, FALSE); - return rc; - } + switch (rc = tlsa_lookup(host, &tlsa_dnsa, dane_required)) + { + case OK: dane = TRUE; break; + case FAIL_FORCED: break; + default: set_errno_nohost(addrlist, ERRNO_DNSDEFER, + string_sprintf("DANE error: tlsa lookup %s", + rc == DEFER ? "DEFER" : "FAIL"), + rc, FALSE); + return rc; + } } else if (dane_required) { set_errno_nohost(addrlist, ERRNO_DNSDEFER, string_sprintf("DANE error: %s lookup not DNSSEC", host->name), FAIL, FALSE); - return FAIL; + return FAIL; } if (dane) @@ -1728,13 +1773,7 @@ goto SEND_QUIT; if (esmtp || lmtp) peer_offered = ehlo_response(buffer, Ustrlen(buffer), - PEER_OFFERED_TLS - | 0 /* IGNQ checked later */ - | 0 /* PRDR checked later */ - | 0 /* UTF8 checked later */ - | 0 /* DSN checked later */ - | 0 /* PIPE checked later */ - | 0 /* SIZE checked later */ + PEER_OFFERED_TLS /* others checked later */ ); /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */ @@ -1790,8 +1829,10 @@ if ( tls_offered if (!smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2', ob->command_timeout)) { - if (errno != 0 || buffer2[0] == 0 || - (buffer2[0] == '4' && !ob->tls_tempfail_tryclear)) + if ( errno != 0 + || buffer2[0] == 0 + || (buffer2[0] == '4' && !ob->tls_tempfail_tryclear) + ) { Ustrncpy(buffer, buffer2, sizeof(buffer)); goto RESPONSE_FAILED; @@ -1816,13 +1857,11 @@ if ( tls_offered if (rc != OK) { # ifdef EXPERIMENTAL_DANE - if (rc == DEFER && dane && !dane_required) + if (rc == DEFER && dane) { - log_write(0, LOG_MAIN, "DANE attempt failed;" - " trying CA-root TLS to %s [%s] (not in hosts_require_dane)", + log_write(0, LOG_MAIN, + "DANE attempt failed; no TLS connection to %s [%s]", host->name, host->address); - dane = FALSE; - goto TLS_NEGOTIATE; } # endif @@ -1908,17 +1947,17 @@ if (tls_out.active >= 0) /* If the host is required to use a secure channel, ensure that we have one. */ -else if ( +else if ( smtps # ifdef EXPERIMENTAL_DANE - dane || + || dane # endif - verify_check_given_host(&ob->hosts_require_tls, host) == OK + || verify_check_given_host(&ob->hosts_require_tls, host) == OK ) { save_errno = ERRNO_TLSREQUIRED; message = string_sprintf("a TLS session is required, but %s", - tls_offered? "an attempt to start TLS failed" : - "the server did not offer TLS support"); + tls_offered ? "an attempt to start TLS failed" + : "the server did not offer TLS support"); goto TLS_FAILED; } #endif /*SUPPORT_TLS*/ @@ -1938,6 +1977,7 @@ if (continue_hostname == NULL peer_offered = ehlo_response(buffer, Ustrlen(buffer), 0 /* no TLS */ | (lmtp && ob->lmtp_ignore_quota ? PEER_OFFERED_IGNQ : 0) + | PEER_OFFERED_CHUNKING | PEER_OFFERED_PRDR #ifdef SUPPORT_I18N | (addrlist->prop.utf8_msg ? PEER_OFFERED_UTF8 : 0) @@ -1969,6 +2009,13 @@ if (continue_hostname == NULL DEBUG(D_transport) debug_printf("%susing PIPELINING\n", smtp_use_pipelining ? "" : "not "); + if ( peer_offered & PEER_OFFERED_CHUNKING + && verify_check_given_host(&ob->hosts_try_chunking, host) != OK) + peer_offered &= ~PEER_OFFERED_CHUNKING; + + if (peer_offered & PEER_OFFERED_CHUNKING) + {DEBUG(D_transport) debug_printf("CHUNKING usable\n");} + #ifndef DISABLE_PRDR if ( peer_offered & PEER_OFFERED_PRDR && verify_check_given_host(&ob->hosts_try_prdr, host) != OK) @@ -2117,16 +2164,11 @@ for (dsn_all_lasthop = TRUE, addr = first_addr; if (smtp_use_dsn && !dsn_all_lasthop) { if (dsn_ret == dsn_ret_hdrs) - { - Ustrcpy(p, " RET=HDRS"); - while (*p) p++; - } + { Ustrcpy(p, " RET=HDRS"); p += 9; } else if (dsn_ret == dsn_ret_full) - { - Ustrcpy(p, " RET=FULL"); - while (*p) p++; - } - if (dsn_envid != NULL) + { Ustrcpy(p, " RET=FULL"); p += 9; } + + if (dsn_envid) { string_format(p, sizeof(buffer) - (p-buffer), " ENVID=%s", dsn_envid); while (*p) p++; @@ -2217,7 +2259,7 @@ the PENDING_DEFER status, because only one attempt is ever made, and we know that max_rcpt will be large, so all addresses will be done at once. */ for (addr = first_addr; - address_count < max_rcpt && addr != NULL; + addr && address_count < max_rcpt; addr = addr->next) { int count; @@ -2229,14 +2271,14 @@ for (addr = first_addr; if (addr->transport_return != PENDING_DEFER) continue; address_count++; - no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next != NULL); + no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next); /* Add any DSN flags to the rcpt command and add to the sent string */ p = buffer; *p = 0; - if (smtp_use_dsn && (addr->dsn_flags & rf_dsnlasthop) != 1) + if (smtp_use_dsn && !(addr->dsn_flags & rf_dsnlasthop)) { if ((addr->dsn_flags & rf_dsnflags) != 0) { @@ -2254,7 +2296,7 @@ for (addr = first_addr; } } - if (addr->dsn_orcpt != NULL) + if (addr->dsn_orcpt) { string_format(p, sizeof(buffer) - (p-buffer), " ORCPT=%s", addr->dsn_orcpt); @@ -2332,15 +2374,19 @@ if (mua_wrapper) send DATA, but if it is FALSE (in the normal, non-wrapper case), we may still have a good recipient buffered up if we are pipelining. We don't want to waste time sending DATA needlessly, so we only send it if either ok is TRUE or if we -are pipelining. The responses are all handled by sync_responses(). */ +are pipelining. The responses are all handled by sync_responses(). +If using CHUNKING, do not send a BDAT until we know how big a chunk we want +to send is. */ -if (ok || (smtp_use_pipelining && !mua_wrapper)) +if ( !(peer_offered & PEER_OFFERED_CHUNKING) + && (ok || (smtp_use_pipelining && !mua_wrapper))) { int count = smtp_write_command(&outblock, FALSE, "DATA\r\n"); + if (count < 0) goto SEND_FAILED; switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr, host, count, ob->address_retry_include_sender, pending_MAIL, - ok? +1 : -1, &inblock, ob->command_timeout, buffer, sizeof(buffer))) + ok ? +1 : -1, &inblock, ob->command_timeout, buffer, sizeof(buffer))) { case 3: ok = TRUE; /* 2xx & 5xx => OK & progress made */ case 2: completed_address = TRUE; /* 5xx (only) => progress made */ @@ -2355,10 +2401,6 @@ if (ok || (smtp_use_pipelining && !mua_wrapper)) } } -/* Save the first address of the next batch. */ - -first_addr = addr; - /* If there were no good recipients (but otherwise there have been no problems), just set ok TRUE, since we have handled address-specific errors already. Otherwise, it's OK to send the message. Use the check/escape mechanism @@ -2366,42 +2408,65 @@ for handling the SMTP dot-handling protocol, flagging to apply to headers as well as body. Set the appropriate timeout value to be used for each chunk. (Haven't been able to make it work using select() for writing yet.) */ -if (!ok) ok = TRUE; else +if (!(peer_offered & PEER_OFFERED_CHUNKING) && !ok) + { + /* Save the first address of the next batch. */ + first_addr = addr; + + ok = TRUE; + } +else { + transport_ctx tctx = { + tblock, + addrlist, + US".", US"..", /* Escaping strings */ + topt_use_crlf | topt_escape_headers + | (tblock->body_only ? topt_no_headers : 0) + | (tblock->headers_only ? topt_no_body : 0) + | (tblock->return_path_add ? topt_add_return_path : 0) + | (tblock->delivery_date_add ? topt_add_delivery_date : 0) + | (tblock->envelope_to_add ? topt_add_envelope_to : 0), + }; + + /* If using CHUNKING we need a callback from the generic transport + support to us, for the sending of BDAT smtp commands and the reaping + of responses. The callback needs a whole bunch of state so set up + a transport-context structure to be passed around. */ + + if (peer_offered & PEER_OFFERED_CHUNKING) + { + tctx.check_string = tctx.escape_string = NULL; + tctx.options |= topt_use_bdat; + tctx.chunk_cb = smtp_chunk_cmd_callback; + tctx.inblock = &inblock; + tctx.outblock = &outblock; + tctx.host = host; + tctx.first_addr = first_addr; + tctx.sync_addr = &sync_addr; + tctx.pending_MAIL = pending_MAIL; + tctx.completed_address = &completed_address; + } + else + tctx.options |= topt_end_dot; + + /* Save the first address of the next batch. */ + first_addr = addr; + sigalrm_seen = FALSE; transport_write_timeout = ob->data_timeout; smtp_command = US"sending data block"; /* For error messages */ DEBUG(D_transport|D_v) - debug_printf(" SMTP>> writing message and terminating \".\"\n"); + if (peer_offered & PEER_OFFERED_CHUNKING) + debug_printf(" will write message using CHUNKING\n"); + else + debug_printf(" SMTP>> writing message and terminating \".\"\n"); transport_count = 0; #ifndef DISABLE_DKIM - ok = dkim_transport_write_message(addrlist, inblock.sock, - topt_use_crlf | topt_end_dot | topt_escape_headers | - (tblock->body_only? topt_no_headers : 0) | - (tblock->headers_only? topt_no_body : 0) | - (tblock->return_path_add? topt_add_return_path : 0) | - (tblock->delivery_date_add? topt_add_delivery_date : 0) | - (tblock->envelope_to_add? topt_add_envelope_to : 0), - 0, /* No size limit */ - tblock->add_headers, tblock->remove_headers, - US".", US"..", /* Escaping strings */ - tblock->rewrite_rules, tblock->rewrite_existflags, - ob->dkim_private_key, ob->dkim_domain, ob->dkim_selector, - ob->dkim_canon, ob->dkim_strict, ob->dkim_sign_headers - ); + ok = dkim_transport_write_message(inblock.sock, &tctx, &ob->dkim); #else - ok = transport_write_message(addrlist, inblock.sock, - topt_use_crlf | topt_end_dot | topt_escape_headers | - (tblock->body_only? topt_no_headers : 0) | - (tblock->headers_only? topt_no_body : 0) | - (tblock->return_path_add? topt_add_return_path : 0) | - (tblock->delivery_date_add? topt_add_delivery_date : 0) | - (tblock->envelope_to_add? topt_add_envelope_to : 0), - 0, /* No size limit */ - tblock->add_headers, tblock->remove_headers, - US".", US"..", /* Escaping strings */ - tblock->rewrite_rules, tblock->rewrite_existflags); + ok = transport_write_message(inblock.sock, &tctx, 0); #endif /* transport_write_message() uses write() because it is called from other @@ -2428,6 +2493,27 @@ if (!ok) ok = TRUE; else smtp_command = US"end of data"; + if (peer_offered & PEER_OFFERED_CHUNKING && tctx.cmd_count > 1) + { + /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */ + switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr, + host, tctx.cmd_count-1, ob->address_retry_include_sender, + pending_MAIL, 0, + &inblock, ob->command_timeout, buffer, sizeof(buffer))) + { + case 3: ok = TRUE; /* 2xx & 5xx => OK & progress made */ + case 2: completed_address = TRUE; /* 5xx (only) => progress made */ + break; + + case 1: ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ + if (!lmtp) completed_address = TRUE; /* can't tell about progress yet */ + case 0: break; /* No 2xx or 5xx, but no probs */ + + case -1: goto END_OFF; /* Timeout on RCPT */ + default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */ + } + } + #ifndef DISABLE_PRDR /* For PRDR we optionally get a partial-responses warning * followed by the individual responses, before going on with @@ -2958,6 +3044,7 @@ 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(" SMTP(close)>>\n"); (void)close(inblock.sock); #ifndef DISABLE_EVENT @@ -3916,7 +4003,7 @@ If queue_smtp is set, or this transport was called to send a subsequent message down an existing TCP/IP connection, and something caused the host not to be found, we end up here, but can detect these cases and handle them specially. */ -for (addr = addrlist; addr != NULL; addr = addr->next) +for (addr = addrlist; addr; addr = addr->next) { /* If host is not NULL, it means that we stopped processing the host list because of hosts_max_try or hosts_max_try_hardlimit. In the former case, this @@ -3925,8 +4012,7 @@ for (addr = addrlist; addr != NULL; addr = addr->next) However, if we have hit hosts_max_try_hardlimit, we want to behave as if all hosts were tried. */ - if (host != NULL) - { + if (host) if (total_hosts_tried >= ob->hosts_max_try_hardlimit) { DEBUG(D_transport) @@ -3939,7 +4025,6 @@ for (addr = addrlist; addr != NULL; addr = addr->next) debug_printf("hosts_max_try limit caused some hosts to be skipped\n"); setflag(addr, af_retry_skipped); } - } if (queue_smtp) /* no deliveries attempted */ { @@ -3948,45 +4033,45 @@ for (addr = addrlist; addr != NULL; addr = addr->next) addr->message = US"SMTP delivery explicitly queued"; } - else if (addr->transport_return == DEFER && - (addr->basic_errno == ERRNO_UNKNOWNERROR || addr->basic_errno == 0) && - addr->message == NULL) + else if ( addr->transport_return == DEFER + && (addr->basic_errno == ERRNO_UNKNOWNERROR || addr->basic_errno == 0) + && !addr->message + ) { addr->basic_errno = ERRNO_HRETRY; - if (continue_hostname != NULL) - { + if (continue_hostname) addr->message = US"no host found for existing SMTP connection"; - } else if (expired) { setflag(addr, af_pass_message); /* This is not a security risk */ - addr->message = ob->delay_after_cutoff - ? US"retry time not reached for any host after a long failure period" - : US"all hosts have been failing for a long time and were last tried " - "after this message arrived"; + addr->message = string_sprintf( + "all hosts%s have been failing for a long time %s", + addr->domain ? string_sprintf(" for '%s'", addr->domain) : US"", + ob->delay_after_cutoff + ? US"(and retry time not reached)" + : US"and were last tried after this message arrived"); /* If we are already using fallback hosts, or there are no fallback hosts defined, convert the result to FAIL to cause a bounce. */ - if (addr->host_list == addr->fallback_hosts || - addr->fallback_hosts == NULL) + if (addr->host_list == addr->fallback_hosts || !addr->fallback_hosts) addr->transport_return = FAIL; } else { - const uschar * s; + const char * s; if (hosts_retry == hosts_total) - s = US"retry time not reached for any host%s"; + s = "retry time not reached for any host%s"; else if (hosts_fail == hosts_total) - s = US"all host address lookups%s failed permanently"; + s = "all host address lookups%s failed permanently"; else if (hosts_defer == hosts_total) - s = US"all host address lookups%s failed temporarily"; + s = "all host address lookups%s failed temporarily"; else if (hosts_serial == hosts_total) - s = US"connection limit reached for all hosts%s"; + s = "connection limit reached for all hosts%s"; else if (hosts_fail+hosts_defer == hosts_total) - s = US"all host address lookups%s failed"; + s = "all host address lookups%s failed"; else - s = US"some host address lookups failed and retry time " + s = "some host address lookups failed and retry time " "not reached for other hosts or connection limit reached%s"; addr->message = string_sprintf(s,