(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) },
(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,
#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) },
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()) */
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,
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
};
static uschar *smtp_command; /* Points to last cmd for error messages */
static uschar *mail_command; /* Points to MAIL cmd for error messages */
static BOOL update_waiting; /* TRUE to update the "wait" database */
+static BOOL pipelining_active; /* current transaction is in pipe mode */
/*************************************************
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
}
check_response(host_item *host, int *errno_value, int more_errno,
uschar *buffer, int *yield, uschar **message, BOOL *pass_message)
{
-uschar *pl = US"";
-
-if (smtp_use_pipelining &&
- (Ustrcmp(smtp_command, "MAIL") == 0 ||
- Ustrcmp(smtp_command, "RCPT") == 0 ||
- Ustrcmp(smtp_command, "DATA") == 0))
- pl = US"pipelined ";
+uschar * pl = pipelining_active ? US"pipelined " : US"";
*yield = '4'; /* Default setting is to give a temporary error */
count--;
if (!smtp_read_response(inblock, buffer, buffsize, '2', timeout))
{
+ DEBUG(D_transport) debug_printf("bad response for MAIL\n");
Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */
if (errno == 0 && buffer[0] != 0)
{
#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];
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
static BOOL
smtp_are_same_identities(uschar * message_id, smtp_compare_t * s_compare)
{
-
uschar * message_local_identity,
* current_local_identity,
* new_sender_address;
+uschar
+ehlo_response(uschar * buf, size_t bsize, uschar checks)
+{
+#ifdef SUPPORT_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_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;
+#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;
+#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_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;
+
+return checks;
+}
+
+
+
+/* Callback for emitting a BDAT data chunk header.
+
+If given a nonzero size, first flush any buffered SMTP commands
+then emit the command.
+
+Reap previous SMTP command responses if requested.
+Reap one SMTP command response if requested.
+
+Returns: OK or ERROR
+*/
+
+static int
+smtp_chunk_cmd_callback(int fd, transport_ctx * tctx,
+ unsigned chunk_size, unsigned flags)
+{
+smtp_transport_options_block * ob =
+ (smtp_transport_options_block *)(tctx->tblock->options_block);
+int cmd_count = 0;
+int prev_cmd_count;
+uschar * buffer = tctx->buffer;
+
+
+/* Write SMTP chunk header command */
+
+if (chunk_size > 0)
+ if((cmd_count = smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n",
+ chunk_size,
+ flags & tc_chunk_last ? " LAST" : "")
+ ) < 0) return ERROR;
+
+prev_cmd_count = cmd_count += tctx->cmd_count;
+
+/* Reap responses for any previous, but not one we just emitted */
+
+if (chunk_size > 0)
+ prev_cmd_count--;
+if (tctx->pending_BDAT)
+ prev_cmd_count--;
+
+if (flags & tc_reap_prev && prev_cmd_count > 0)
+ {
+
+ switch(sync_responses(tctx->first_addr, tctx->tblock->rcpt_include_affixes,
+ tctx->sync_addr, tctx->host, prev_cmd_count,
+ ob->address_retry_include_sender,
+ tctx->pending_MAIL, 0,
+ tctx->inblock,
+ ob->command_timeout,
+ buffer, 4096))
+/*XXX buffer size! */
+ {
+ case 1: /* 2xx (only) => OK */
+ case 3: tctx->good_RCPT = TRUE; /* 2xx & 5xx => OK & progress made */
+ case 2: *tctx->completed_address = TRUE; /* 5xx (only) => progress made */
+ case 0: break; /* No 2xx or 5xx, but no probs */
+
+ case -1: /* Timeout on RCPT */
+ default: return ERROR; /* I/O error, or any MAIL/DATA error */
+ }
+ cmd_count = 1;
+ if (!tctx->pending_BDAT)
+ pipelining_active = FALSE;
+ }
+
+/* Reap response for the cmd we just emitted, or an outstanding BDAT */
+
+if (flags & tc_reap_one || tctx->pending_BDAT)
+ {
+/*XXX buffer size! */
+ if (!smtp_read_response(tctx->inblock, buffer, 4096, '2',
+ ob->command_timeout))
+ {
+ if (errno == 0 && buffer[0] == '4')
+ {
+ errno = ERRNO_DATA4XX; /*XXX does this actually get used? */
+ tctx->first_addr->more_errno |=
+ ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ }
+ return ERROR;
+ }
+ cmd_count--;
+ tctx->pending_BDAT = FALSE;
+ pipelining_active = FALSE;
+ }
+else if (chunk_size > 0)
+ tctx->pending_BDAT = TRUE;
+
+
+tctx->cmd_count = cmd_count;
+return OK;
+}
+
+
+
/*************************************************
* Deliver address list to given host *
*************************************************/
BOOL esmtp = TRUE;
BOOL pending_MAIL;
BOOL pass_message = FALSE;
+uschar peer_offered = 0; /*XXX should this be handed on cf. tls_offered, smtp_use_dsn ? */
#ifndef DISABLE_PRDR
-BOOL prdr_offered = FALSE;
BOOL prdr_active;
#endif
#ifdef SUPPORT_I18N
BOOL utf8_needed = FALSE;
-BOOL utf8_offered = FALSE;
#endif
BOOL dsn_all_lasthop = TRUE;
#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
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)
if (!good_response) goto RESPONSE_FAILED;
}
- /* Set IGNOREQUOTA if the response to LHLO specifies support and the
- lmtp_ignore_quota option was set. */
-
- igquotstr = (lmtp && ob->lmtp_ignore_quota &&
- pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+ if (esmtp || lmtp)
+ peer_offered = ehlo_response(buffer, Ustrlen(buffer),
+ PEER_OFFERED_TLS /* others checked later */
+ );
/* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
#ifdef SUPPORT_TLS
- tls_offered = esmtp &&
- pcre_exec(regex_STARTTLS, NULL, CS buffer, Ustrlen(buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0;
-#endif
-
-#ifndef DISABLE_PRDR
- prdr_offered = esmtp
- && pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0
- && verify_check_given_host(&ob->hosts_try_prdr, host) == OK;
-
- if (prdr_offered)
- {DEBUG(D_transport) debug_printf("PRDR usable\n");}
-#endif
-
-#ifdef SUPPORT_I18N
- if (addrlist->prop.utf8_msg)
- {
- utf8_needed = !addrlist->prop.utf8_downcvt
- && !addrlist->prop.utf8_downcvt_maybe;
- DEBUG(D_transport) if (!utf8_needed) debug_printf("utf8: %s downconvert\n",
- addrlist->prop.utf8_downcvt ? "mandatory" : "optional");
-
- utf8_offered = esmtp
- && pcre_exec(regex_UTF8, NULL, CS buffer, Ustrlen(buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0;
- }
+ tls_offered = !!(peer_offered & PEER_OFFERED_TLS);
#endif
}
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;
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
/* 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*/
#endif
)
{
+ if (esmtp || lmtp)
+ 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)
+ /*XXX if we hand peercaps on to continued-conn processes,
+ must not depend on this addr */
+#endif
+ | PEER_OFFERED_DSN
+ | PEER_OFFERED_PIPE
+ | (ob->size_addition >= 0 ? PEER_OFFERED_SIZE : 0)
+ );
+
/* Set for IGNOREQUOTA if the response to LHLO specifies support and the
lmtp_ignore_quota option was set. */
- igquotstr = (lmtp && ob->lmtp_ignore_quota &&
- pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+ igquotstr = peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US"";
/* If the response to EHLO specified support for the SIZE parameter, note
this, provided size_addition is non-negative. */
- smtp_use_size = esmtp && ob->size_addition >= 0 &&
- pcre_exec(regex_SIZE, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0;
+ smtp_use_size = !!(peer_offered & PEER_OFFERED_SIZE);
/* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched
the current host, esmtp will be false, so PIPELINING can never be used. If
the current host matches hosts_avoid_pipelining, don't do it. */
- smtp_use_pipelining = esmtp
- && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK
- && pcre_exec(regex_PIPELINING, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0;
+ smtp_use_pipelining = peer_offered & PEER_OFFERED_PIPE
+ && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK;
DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
- smtp_use_pipelining? "" : "not ");
+ 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
- prdr_offered = esmtp
- && pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0
- && verify_check_given_host(&ob->hosts_try_prdr, host) == OK;
+ if ( peer_offered & PEER_OFFERED_PRDR
+ && verify_check_given_host(&ob->hosts_try_prdr, host) != OK)
+ peer_offered &= ~PEER_OFFERED_PRDR;
- if (prdr_offered)
+ if (peer_offered & PEER_OFFERED_PRDR)
{DEBUG(D_transport) debug_printf("PRDR usable\n");}
#endif
-#ifdef SUPPORT_I18N
- if (addrlist->prop.utf8_msg)
- utf8_offered = esmtp
- && pcre_exec(regex_UTF8, NULL, CS buffer, Ustrlen(buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0;
-#endif
-
/* Note if the server supports DSN */
- smtp_use_dsn = esmtp
- && pcre_exec(regex_DSN, NULL, CS buffer, Ustrlen(CS buffer), 0,
- PCRE_EOPT, NULL, 0) >= 0;
- DEBUG(D_transport) debug_printf("use_dsn=%d\n", smtp_use_dsn);
+ smtp_use_dsn = !!(peer_offered & PEER_OFFERED_DSN);
+ DEBUG(D_transport) debug_printf("%susing DSN\n", smtp_use_dsn ? "" : "not ");
/* Note if the response to EHLO specifies support for the AUTH extension.
If it has, check that this host is one we want to authenticate to, and do
case FAIL: goto RESPONSE_FAILED;
}
}
+pipelining_active = smtp_use_pipelining;
/* The setting up of the SMTP call is now complete. Any subsequent errors are
message-specific. */
setting_up = FALSE;
#ifdef SUPPORT_I18N
+if (addrlist->prop.utf8_msg)
+ {
+ utf8_needed = !addrlist->prop.utf8_downcvt
+ && !addrlist->prop.utf8_downcvt_maybe;
+ DEBUG(D_transport) if (!utf8_needed) debug_printf("utf8: %s downconvert\n",
+ addrlist->prop.utf8_downcvt ? "mandatory" : "optional");
+ }
+
/* If this is an international message we need the host to speak SMTPUTF8 */
-if (utf8_needed && !utf8_offered)
+if (utf8_needed && !(peer_offered & PEER_OFFERED_UTF8))
{
errno = ERRNO_UTF8_FWD;
goto RESPONSE_FAILED;
#ifndef DISABLE_PRDR
prdr_active = FALSE;
-if (prdr_offered)
- {
+if (peer_offered & PEER_OFFERED_PRDR)
for (addr = first_addr; addr; addr = addr->next)
if (addr->transport_return == PENDING_DEFER)
{
}
break;
}
- }
#endif
#ifdef SUPPORT_I18N
-if (addrlist->prop.utf8_msg && !addrlist->prop.utf8_downcvt && utf8_offered)
+if ( addrlist->prop.utf8_msg
+ && !addrlist->prop.utf8_downcvt
+ && peer_offered & PEER_OFFERED_UTF8
+ )
sprintf(CS p, " SMTPUTF8"), p += 9;
#endif
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++;
for the to-addresses (done below), and also (ugly) for re-doing when building
the delivery log line. */
- if (addrlist->prop.utf8_msg && (addrlist->prop.utf8_downcvt || !utf8_offered))
+ if ( addrlist->prop.utf8_msg
+ && (addrlist->prop.utf8_downcvt || !(peer_offered & PEER_OFFERED_UTF8))
+ )
{
if (s = string_address_utf8_to_alabel(return_path, &errstr), errstr)
{
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;
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)
{
}
}
- if (addr->dsn_orcpt != NULL)
+ if (addr->dsn_orcpt)
{
string_format(p, sizeof(buffer) - (p-buffer), " ORCPT=%s",
addr->dsn_orcpt);
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 */
case -1: goto END_OFF; /* Timeout on RCPT */
default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
}
+ pipelining_active = FALSE;
}
-/* 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
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.pending_BDAT = FALSE;
+ tctx.good_RCPT = ok;
+ tctx.completed_address = &completed_address;
+ tctx.cmd_count = 0;
+ tctx.buffer = buffer;
+ }
+ else
+ tctx.options |= topt_end_dot;
+
+ /* Save the first address of the next batch. */
+ first_addr = addr;
+
+ /* Responses from CHUNKING commands go in buffer. Otherwise,
+ there has not been a response. */
+
+ buffer[0] = 0;
+
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
transport_write_timeout = 0; /* for subsequent transports */
/* Failure can either be some kind of I/O disaster (including timeout),
- or the failure of a transport filter or the expansion of added headers. */
+ or the failure of a transport filter or the expansion of added headers.
+ Or, when CHUNKING, it can be a protocol-detected failure. */
if (!ok)
- {
- buffer[0] = 0; /* There hasn't been a response */
goto RESPONSE_FAILED;
- }
/* We used to send the terminating "." explicitly here, but because of
buffering effects at both ends of TCP/IP connections, you don't gain
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
{
ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '3',
ob->final_timeout);
- if (!ok && errno == 0)
- switch(buffer[0])
- {
- case '2': prdr_active = FALSE;
- ok = TRUE;
- break;
- case '4': errno = ERRNO_DATA4XX;
- addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
- break;
- }
+ if (!ok && errno == 0) switch(buffer[0])
+ {
+ case '2': prdr_active = FALSE;
+ ok = TRUE;
+ break;
+ case '4': errno = ERRNO_DATA4XX;
+ addrlist->more_errno |=
+ ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ break;
+ }
}
else
#endif
int delivery_time = (int)(time(NULL) - start_delivery_time);
int len;
uschar *conf = NULL;
+
send_rset = FALSE;
+ pipelining_active = FALSE;
/* Set up confirmation if needed - applies only to SMTP */
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
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
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)
debug_printf("hosts_max_try limit caused some hosts to be skipped\n");
setflag(addr, af_retry_skipped);
}
- }
if (queue_smtp) /* no deliveries attempted */
{
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 char * s;
if (hosts_retry == hosts_total)
- addr->message = US"retry time not reached for any host";
+ s = "retry time not reached for any host%s";
else if (hosts_fail == hosts_total)
- addr->message = US"all host address lookups failed permanently";
+ s = "all host address lookups%s failed permanently";
else if (hosts_defer == hosts_total)
- addr->message = US"all host address lookups failed temporarily";
+ s = "all host address lookups%s failed temporarily";
else if (hosts_serial == hosts_total)
- addr->message = US"connection limit reached for all hosts";
+ s = "connection limit reached for all hosts%s";
else if (hosts_fail+hosts_defer == hosts_total)
- addr->message = US"all host address lookups failed";
- else addr->message = US"some host address lookups failed and retry time "
- "not reached for other hosts or connection limit reached";
+ s = "all host address lookups%s failed";
+ else
+ s = "some host address lookups failed and retry time "
+ "not reached for other hosts or connection limit reached%s";
+
+ addr->message = string_sprintf(s,
+ addr->domain ? string_sprintf(" for '%s'", addr->domain) : US"");
}
}
}