#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()) */
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)
}
+
+/* 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 *
*************************************************/
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)
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)
if (smtp_use_dsn && !dsn_all_lasthop)
{
if (dsn_ret == dsn_ret_hdrs)
- {
- Ustrcpy(p, " RET=HDRS"); p += 9;
- }
+ { Ustrcpy(p, " RET=HDRS"); p += 9; }
else if (dsn_ret == dsn_ret_full)
- {
- Ustrcpy(p, " RET=FULL"); p += 9;
- }
- 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++;
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 */
}
}
-/* 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)
+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),
- tblock->add_headers, tblock->remove_headers,
- US".", US"..", /* Escaping strings */
- tblock->rewrite_rules, tblock->rewrite_existflags,
- &ob->dkim
- );
+ 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
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