FILE *f = fdopen(fd, "wb");
/* header only as required by RFC. only failure DSN needs to honor RET=FULL */
uschar * bound;
- transport_ctx tctx;
+ transport_ctx tctx = {0};
DEBUG(D_deliver)
debug_printf("sending error message to: %s\n", sender_address);
/* Write the original email out */
- bzero(&tctx, sizeof(tctx));
tctx.options = topt_add_return_path | topt_no_body;
transport_write_message(fileno(f), &tctx, 0);
fflush(f);
transport_filter_argv = NULL; /* Just in case */
return_path = sender_address; /* In case not previously set */
{ /* Dummy transport for headers add */
- transport_ctx * tctx =
- store_get(sizeof(*tctx) + sizeof(transport_instance));
- transport_instance * tb = (transport_instance *)(tctx+1);
+ transport_ctx tctx = {0};
+ transport_instance tb = {0};
- bzero(tctx, sizeof(*tctx)+sizeof(*tb));
- tctx->tblock = tb;
- tctx->options = topt;
- tb->add_headers = dsnnotifyhdr;
+ tctx.tblock = &tb;
+ tctx.options = topt;
+ tb.add_headers = dsnnotifyhdr;
- transport_write_message(fileno(f), tctx, 0);
+ transport_write_message(fileno(f), &tctx, 0);
}
fflush(f);
FILE *wmf = NULL;
FILE *f = fdopen(fd, "wb");
uschar * bound;
- transport_ctx tctx;
-
- bzero(&tctx, sizeof(tctx));
+ transport_ctx tctx = {0};
if (warn_message_file)
if (!(wmf = Ufopen(warn_message_file, "rb")))
extern void transport_update_waiting(host_item *, uschar *);
extern BOOL transport_write_block(int, uschar *, int);
extern BOOL transport_write_string(int, const char *, ...);
-extern BOOL transport_headers_send(address_item *, int, transport_instance *,
- BOOL (*)(int, uschar *, int, unsigned),
- BOOL);
+extern BOOL transport_headers_send(int, transport_ctx *,
+ BOOL (*)(int, transport_ctx *, uschar *, int));
extern BOOL transport_write_message(int, transport_ctx *, int);
extern void tree_add_duplicate(uschar *, address_item *);
extern void tree_add_nonrecipient(uschar *);
ETRN_CMD, /* This by analogy with TURN from the RFC */
STARTTLS_CMD, /* Required by the STARTTLS RFC */
TLS_AUTH_CMD, /* auto-command at start of SSL */
- BDAT_CMD, /* Implied by RFC3030 "After all MAIL and..." */
/* This is a dummy to identify the non-sync commands when pipelining */
MAIL_CMD, RCPT_CMD, RSET_CMD,
+ /* RFC3030 section 2: "After all MAIL and RCPT responses are collected and
+ processed the message is sent using a series of BDAT commands"
+ implies that BDAT should be synchronized. However, we see Google, at least,
+ sending MAIL,RCPT,BDAT-LAST in a single packet, clearly not waiting for
+ processing of the RPCT response(s). We shall do the same, and not require
+ synch for BDAT. */
+
+ BDAT_CMD,
+
/* This is a dummy to identify the non-sync commands when not pipelining */
NON_SYNC_CMD_NON_PIPELINING,
noflush if TRUE, save the command in the output buffer, for pipelining
format a format, starting with one of
of HELO, MAIL FROM, RCPT TO, DATA, ".", or QUIT.
+ If NULL, flush pipeline buffer only.
... data for the format
Returns: 0 if command added to pipelining buffer, with nothing transmitted
int rc = 0;
va_list ap;
-va_start(ap, format);
-if (!string_vformat(big_buffer, big_buffer_size, CS format, ap))
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
- "SMTP");
-va_end(ap);
-count = Ustrlen(big_buffer);
-
-if (count > outblock->buffersize)
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
- "SMTP");
-
-if (count > outblock->buffersize - (outblock->ptr - outblock->buffer))
+if (format)
{
- rc = outblock->cmd_count; /* flush resets */
- if (!flush_buffer(outblock)) return -1;
- }
+ va_start(ap, format);
+ if (!string_vformat(big_buffer, big_buffer_size, CS format, ap))
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
+ "SMTP");
+ va_end(ap);
+ count = Ustrlen(big_buffer);
+
+ if (count > outblock->buffersize)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
+ "SMTP");
+
+ if (count > outblock->buffersize - (outblock->ptr - outblock->buffer))
+ {
+ rc = outblock->cmd_count; /* flush resets */
+ if (!flush_buffer(outblock)) return -1;
+ }
-Ustrncpy(CS outblock->ptr, big_buffer, count);
-outblock->ptr += count;
-outblock->cmd_count++;
-count -= 2;
-big_buffer[count] = 0; /* remove \r\n for error message */
+ Ustrncpy(CS outblock->ptr, big_buffer, count);
+ outblock->ptr += count;
+ outblock->cmd_count++;
+ count -= 2;
+ big_buffer[count] = 0; /* remove \r\n for error message */
-/* We want to hide the actual data sent in AUTH transactions from reflections
-and logs. While authenticating, a flag is set in the outblock to enable this.
-The AUTH command itself gets any data flattened. Other lines are flattened
-completely. */
+ /* We want to hide the actual data sent in AUTH transactions from reflections
+ and logs. While authenticating, a flag is set in the outblock to enable this.
+ The AUTH command itself gets any data flattened. Other lines are flattened
+ completely. */
-if (outblock->authenticating)
- {
- uschar *p = big_buffer;
- if (Ustrncmp(big_buffer, "AUTH ", 5) == 0)
+ if (outblock->authenticating)
{
- p += 5;
- while (isspace(*p)) p++;
- while (!isspace(*p)) p++;
- while (isspace(*p)) p++;
+ uschar *p = big_buffer;
+ if (Ustrncmp(big_buffer, "AUTH ", 5) == 0)
+ {
+ p += 5;
+ while (isspace(*p)) p++;
+ while (!isspace(*p)) p++;
+ while (isspace(*p)) p++;
+ }
+ while (*p != 0) *p++ = '*';
}
- while (*p != 0) *p++ = '*';
- }
-HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> %s\n", big_buffer);
+ HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> %s\n", big_buffer);
+ }
if (!noflush)
{
} transport_info;
+/* smtp transport datachunk callback */
+
+struct transport_context;
+typedef int (*tpt_chunk_cmd_cb)(int fd, struct transport_context * tctx,
+ unsigned len, BOOL last);
+
/* Structure for information about a delivery-in-progress */
typedef struct transport_context {
- transport_instance * tblock;
- struct address_item * addr;
- uschar * check_string;
- uschar * escape_string;
- int options; /* topt_* */
+ transport_instance * tblock; /* transport */
+ struct address_item * addr;
+ uschar * check_string; /* string replacement */
+ uschar * escape_string;
+ int options; /* output processing topt_* */
+
+ /* items below only used with option topt_use_bdat */
+ tpt_chunk_cmd_cb chunk_cb; /* per-datachunk callback */
+ struct smtp_inblock * inblock;
+ struct smtp_outblock * outblock;
+ host_item * host;
+ struct address_item * first_addr;
+ struct address_item **sync_addr;
+ BOOL pending_MAIL;
+ BOOL * completed_address;
+ int cmd_count;
} transport_ctx;
fd file descript to write to
chunk pointer to data to write
len length of data to write
- flags bitmap of topt_ flags for processing options
- use_crlf terminate lines with CRLF
- use_bdat prepend chunks with RFC3030 BDAT header
+ tctx transport context - processing to be done during output
In addition, the static nl_xxx variables must be set as required.
*/
static BOOL
-write_chunk(int fd, uschar *chunk, int len, unsigned flags)
+write_chunk(int fd, transport_ctx * tctx, uschar *chunk, int len)
{
uschar *start = chunk;
uschar *end = chunk + len;
for (ptr = start; ptr < end; ptr++)
{
- int ch;
+ int ch, len;
/* Flush the buffer if it has reached the threshold - we want to leave enough
room for the next uschar, plus a possible extra CR for an LF, plus the escape
string. */
-/*XXX CHUNKING: need to prefix write_block with a BDAT cmd. Also possibly
-reap a response from a previous BDAT first. NEED a callback into the tpt
-for that */
- if (chunk_ptr - deliver_out_buffer > mlen)
+ /*XXX CHUNKING: probably want to increase DELIVER_OUT_BUFFER_SIZE */
+ if ((len = chunk_ptr - deliver_out_buffer) > mlen)
{
- if (!transport_write_block(fd, deliver_out_buffer,
- chunk_ptr - deliver_out_buffer))
+ /* If CHUNKING, prefix with BDAT (size) NON-LAST. Also, reap responses
+ from previous SMTP commands. */
+
+ if (tctx && tctx->options & topt_use_bdat && tctx->chunk_cb)
+ if (tctx->chunk_cb(fd, tctx, (unsigned)len, FALSE) != OK)
+ return FALSE;
+
+ if (!transport_write_block(fd, deliver_out_buffer, len))
return FALSE;
chunk_ptr = deliver_out_buffer;
}
/* Insert CR before NL if required */
- if (flags & topt_use_crlf) *chunk_ptr++ = '\r';
+ if (tctx && tctx->options & topt_use_crlf) *chunk_ptr++ = '\r';
*chunk_ptr++ = '\n';
transport_newlines++;
pdlist address of anchor of the list of processed addresses
first TRUE if this is the first address; set it FALSE afterwards
fd the file descriptor to write to
- flags to be passed on to write_chunk()
+ tctx transport context - processing to be done during output
Returns: FALSE if writing failed
*/
static BOOL
write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist,
- BOOL *first, int fd, unsigned flags)
+ BOOL *first, int fd, transport_ctx * tctx)
{
address_item *pp;
struct aci *ppp;
address_item *dup;
for (dup = addr_duplicate; dup; dup = dup->next)
if (dup->dupof == pp) /* a dup of our address */
- if (!write_env_to(dup, pplist, pdlist, first, fd, flags))
+ if (!write_env_to(dup, pplist, pdlist, first, fd, tctx))
return FALSE;
if (!pp->parent) break;
}
*pplist = ppp;
ppp->ptr = pp;
-if (!*first && !write_chunk(fd, US",\n ", 3, flags)) return FALSE;
+if (!*first && !write_chunk(fd, tctx, US",\n ", 3)) return FALSE;
*first = FALSE;
-return write_chunk(fd, pp->address, Ustrlen(pp->address), flags);
+return write_chunk(fd, tctx, pp->address, Ustrlen(pp->address));
}
only the first address is used
fd file descriptor to write the message to
sendfn function for output (transport or verify)
- use_crlf turn NL into CR LF
+ wck_flags
+ use_crlf turn NL into CR LF
+ use_bdat callback before chunk flush
rewrite_rules chain of header rewriting rules
rewrite_existflags flags for the rewriting rules
+ chunk_cb transport callback function for data-chunk commands
Returns: TRUE on success; FALSE on failure.
*/
BOOL
-transport_headers_send(address_item *addr, int fd, transport_instance * tblock,
- BOOL (*sendfn)(int fd, uschar * s, int len, unsigned options),
- BOOL use_crlf)
+transport_headers_send(int fd, transport_ctx * tctx,
+ BOOL (*sendfn)(int fd, transport_ctx * tctx, uschar * s, int len))
{
header_line *h;
const uschar *list;
+transport_instance * tblock = tctx ? tctx->tblock : NULL;
+address_item * addr = tctx ? tctx->addr : NULL;
/* Then the message's headers. Don't write any that are flagged as "old";
that means they were rewritten, or are a record of envelope rewriting, or
if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
tblock->rewrite_existflags, FALSE)))
{
- if (!sendfn(fd, hh->text, hh->slen, use_crlf)) return FALSE;
+ if (!sendfn(fd, tctx, hh->text, hh->slen)) return FALSE;
store_reset(reset_point);
continue; /* With the next header line */
}
/* Either no rewriting rules, or it didn't get rewritten */
- if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE;
+ if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
}
/* Header removed */
hprev = h;
if (i == 1)
{
- if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE;
+ if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
DEBUG(D_transport)
debug_printf("added header line(s):\n%s---\n", h->text);
}
int len = Ustrlen(s);
if (len > 0)
{
- if (!sendfn(fd, s, len, use_crlf)) return FALSE;
- if (s[len-1] != '\n' && !sendfn(fd, US"\n", 1, use_crlf))
+ if (!sendfn(fd, tctx, s, len)) return FALSE;
+ if (s[len-1] != '\n' && !sendfn(fd, tctx, US"\n", 1))
return FALSE;
DEBUG(D_transport)
{
/* Separate headers from body with a blank line */
-return sendfn(fd, US"\n", 1, use_crlf);
+return sendfn(fd, tctx, US"\n", 1);
}
internal_transport_write_message(int fd, transport_ctx * tctx, int size_limit)
{
int len;
-unsigned wck_flags = (unsigned) tctx->options;
off_t fsize;
int size;
uschar buffer[ADDRESS_MAXLENGTH + 20];
int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
return_path);
- if (!write_chunk(fd, buffer, n, wck_flags)) return FALSE;
+ if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
}
/* Add envelope-to: if requested */
struct aci *dlist = NULL;
void *reset_point = store_get(0);
- if (!write_chunk(fd, US"Envelope-to: ", 13, wck_flags)) return FALSE;
+ if (!write_chunk(fd, tctx, US"Envelope-to: ", 13)) return FALSE;
/* Pick up from all the addresses. The plist and dlist variables are
anchors for lists of addresses already handled; they have to be defined at
this level becuase write_env_to() calls itself recursively. */
for (p = tctx->addr; p; p = p->next)
- if (!write_env_to(p, &plist, &dlist, &first, fd, wck_flags))
+ if (!write_env_to(p, &plist, &dlist, &first, fd, tctx))
return FALSE;
/* Add a final newline and reset the store used for tracking duplicates */
- if (!write_chunk(fd, US"\n", 1, wck_flags)) return FALSE;
+ if (!write_chunk(fd, tctx, US"\n", 1)) return FALSE;
store_reset(reset_point);
}
{
uschar buffer[100];
int n = sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full));
- if (!write_chunk(fd, buffer, n, wck_flags)) return FALSE;
+ if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
}
/* Then the message's headers. Don't write any that are flagged as "old";
match any entries therein. Then check addr->prop.remove_headers too, provided that
addr is not NULL. */
- if (!transport_headers_send(tctx->addr, fd, tctx->tblock, &write_chunk, wck_flags))
+ if (!transport_headers_send(fd, tctx, &write_chunk))
return FALSE;
}
size += body_linecount; /* account for CRLF-expansion */
}
- /*XXX need an smtp_outblock here; can't really use the smtp
- tpts one. so that had better have been flushed.
-
- WORRY: smtp cmd response sync, needs an inblock and a LOT
- of tpt info. NEED a callback into the tpt.
+ /*XXX CHUNKING:
+ Emit a LAST datachunk command. */
-#ifdef notdef
- smtp_write_command(&outblock, FALSE, "BDAT %d LAST\r\n", size);
- if (count < 0) return FALSE;
- if (count > 0)
- {
- }
-#endif
- */
+ if (tctx->chunk_cb(fd, tctx, size, TRUE) != OK)
+ return FALSE;
- wck_flags &= ~topt_use_bdat;
+ tctx->options &= ~topt_use_bdat;
}
/* If the body is required, ensure that the data for check strings (formerly
return FALSE;
while ( (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
&& (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
- if (!write_chunk(fd, deliver_in_buffer, len, wck_flags))
+ if (!write_chunk(fd, tctx, deliver_in_buffer, len))
return FALSE;
/* A read error on the body will have left len == -1 and errno set. */
/* If requested, add a terminating "." line (SMTP output). */
-if (tctx->options & topt_end_dot && !write_chunk(fd, US".\n", 2, wck_flags))
+if (tctx->options & topt_end_dot && !write_chunk(fd, tctx, US".\n", 2))
return FALSE;
/* Write out any remaining data in the buffer before returning. */
int rc, len, yield, fd_read, fd_write, save_errno;
int pfd[2] = {-1, -1};
pid_t filter_pid, write_pid;
-static transport_ctx dummy_tctx = { NULL, NULL, NULL, NULL, 0 };
+static transport_ctx dummy_tctx = {0};
if (!tctx) tctx = &dummy_tctx;
if (len > 0)
{
- if (!write_chunk(fd, deliver_in_buffer, len, wck_flags)) goto TIDY_UP;
+ if (!write_chunk(fd, tctx, deliver_in_buffer, len)) goto TIDY_UP;
last_filter_was_NL = (deliver_in_buffer[len-1] == '\n');
}
nl_check_length = nl_escape_length = 0;
if ( tctx->options & topt_end_dot
&& ( last_filter_was_NL
- ? !write_chunk(fd, US".\n", 2, wck_flags)
- : !write_chunk(fd, US"\n.\n", 3, wck_flags)
+ ? !write_chunk(fd, tctx, US".\n", 2)
+ : !write_chunk(fd, tctx, US"\n.\n", 3)
) )
yield = FALSE;
transport_ctx tctx = {
tblock,
addr,
- NULL,
- NULL,
+ NULL, NULL,
(tblock->body_only ? topt_no_headers : 0) |
(tblock->headers_only ? topt_no_body : 0) |
(tblock->return_path_add ? topt_add_return_path : 0) |
transport_ctx tctx = {
tblock,
addrlist,
- US".",
- US"..",
+ US".", US"..",
ob->options
};
uschar *envp[50];
const uschar *envlist = ob->environment;
uschar *cmd, *ss;
-uschar *eol = (ob->use_crlf)? US"\r\n" : US"\n";
+uschar *eol = ob->use_crlf ? US"\r\n" : US"\n";
transport_ctx tctx = {
tblock,
addr,
}
+
+/* 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 *
*************************************************/
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->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->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.options |= topt_use_bdat;
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 %s\n",
- peer_offered & PEER_OFFERED_CHUNKING
- ? "using CHUNKING" : "and terminating \".\"");
+ 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
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
}
-/* fd and options args only to match write_chunk() */
+/* fd and tctx args only to match write_chunk() */
static BOOL
-cutthrough_write_chunk(int fd, uschar * s, int len, unsigned options)
+cutthrough_write_chunk(int fd, transport_ctx * tctx, uschar * s, int len)
{
uschar * s2;
while(s && (s2 = Ustrchr(s, '\n')))
BOOL
cutthrough_headers_send(void)
{
+transport_ctx tctx;
+
if(cutthrough.fd < 0)
return FALSE;
*/
HDEBUG(D_acl) debug_printf("----------- start cutthrough headers send -----------\n");
-if (!transport_headers_send(&cutthrough.addr, cutthrough.fd,
- cutthrough.addr.transport,
- &cutthrough_write_chunk, topt_use_crlf))
+tctx.tblock = cutthrough.addr.transport;
+tctx.addr = &cutthrough.addr;
+tctx.check_string = US".";
+tctx.escape_string = US"..";
+tctx.options = topt_use_crlf;
+
+if (!transport_headers_send(cutthrough.fd, &tctx, &cutthrough_write_chunk))
return FALSE;
HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n");
gecos_pattern = ""
gecos_name = CALLER_NAME
tls_advertise_hosts =
+chunking_advertise_hosts =
# ----- Main settings -----