Callback into smtp transport for BDAT commands
authorJeremy Harris <jgh146exb@wizmail.org>
Thu, 28 Jul 2016 21:41:17 +0000 (22:41 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 2 Aug 2016 15:46:31 +0000 (16:46 +0100)
12 files changed:
src/src/deliver.c
src/src/functions.h
src/src/smtp_in.c
src/src/smtp_out.c
src/src/structs.h
src/src/transport.c
src/src/transports/autoreply.c
src/src/transports/lmtp.c
src/src/transports/pipe.c
src/src/transports/smtp.c
src/src/verify.c
test/confs/0611

index 81f9a9a..419dee9 100644 (file)
@@ -6907,7 +6907,7 @@ if (addr_senddsn)
     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);
@@ -6987,7 +6987,6 @@ if (addr_senddsn)
 
     /* 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);
@@ -7445,16 +7444,14 @@ wording. */
       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);
 
@@ -7771,9 +7768,7 @@ else if (addr_defer != (address_item *)(+1))
         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")))
index 27e9ff8..260b365 100644 (file)
@@ -468,9 +468,8 @@ extern BOOL    transport_set_up_command(const uschar ***, uschar *,
 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 *);
index d4b3e56..3144b39 100644 (file)
@@ -73,7 +73,6 @@ enum {
   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 */
 
@@ -83,6 +82,15 @@ enum {
 
   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,
index 6b48431..76181b5 100644 (file)
@@ -357,6 +357,7 @@ Arguments:
   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
@@ -371,48 +372,51 @@ int count;
 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)
   {
index 61f8a41..2a6ca68 100644 (file)
@@ -223,14 +223,31 @@ typedef struct transport_info {
 } 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;
 
 
index 148c195..8c81e8a 100644 (file)
@@ -359,9 +359,7 @@ Arguments:
   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.
 
@@ -369,7 +367,7 @@ Returns:     TRUE on success, FALSE on failure (with errno preserved)
 */
 
 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;
@@ -412,19 +410,23 @@ possible. */
 
 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;
     }
@@ -435,7 +437,7 @@ for that */
 
     /* 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++;
 
@@ -552,14 +554,14 @@ Arguments:
   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;
@@ -581,7 +583,7 @@ for (pp = p;; pp = pp->parent)
   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;
   }
@@ -598,9 +600,9 @@ ppp->next = *pplist;
 *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));
 }
 
 
@@ -616,19 +618,23 @@ Arguments:
                           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
@@ -683,7 +689,7 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old)
       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 */
        }
@@ -691,7 +697,7 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old)
 
     /* 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 */
@@ -726,7 +732,7 @@ if (addr)
       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);
        }
@@ -751,8 +757,8 @@ if (tblock && (list = CUS tblock->add_headers))
       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)
          {
@@ -768,7 +774,7 @@ if (tblock && (list = CUS tblock->add_headers))
 
 /* Separate headers from body with a blank line */
 
-return sendfn(fd, US"\n", 1, use_crlf);
+return sendfn(fd, tctx, US"\n", 1);
 }
 
 
@@ -837,7 +843,6 @@ static BOOL
 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;
 
@@ -877,7 +882,7 @@ if (!(tctx->options & topt_no_headers))
     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 */
@@ -890,19 +895,19 @@ if (!(tctx->options & topt_no_headers))
     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);
     }
 
@@ -912,7 +917,7 @@ if (!(tctx->options & topt_no_headers))
     {
     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";
@@ -921,7 +926,7 @@ if (!(tctx->options & topt_no_headers))
   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;
   }
 
@@ -947,22 +952,13 @@ if (tctx->options & topt_use_bdat)
       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
@@ -979,7 +975,7 @@ if (!(tctx->options & topt_no_body))
     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. */
@@ -993,7 +989,7 @@ nl_check_length = nl_escape_length = 0;
 
 /* 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. */
@@ -1224,7 +1220,7 @@ BOOL last_filter_was_NL = TRUE;
 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;
 
@@ -1359,7 +1355,7 @@ for (;;)
 
   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');
     }
 
@@ -1441,8 +1437,8 @@ if (yield)
   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;
 
index 36a68b9..f07cd83 100644 (file)
@@ -694,8 +694,7 @@ if (return_message)
   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) |
index 9624ece..0cc9810 100644 (file)
@@ -612,8 +612,7 @@ if (send_data)
   transport_ctx tctx = {
     tblock,
     addrlist,
-    US".",
-    US"..",
+    US".", US"..",
     ob->options
   };
 
index e3d0197..d3841e0 100644 (file)
@@ -554,7 +554,7 @@ const uschar **argv;
 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,
index 33de45c..58a5943 100644 (file)
@@ -1361,6 +1361,63 @@ 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       *
 *************************************************/
@@ -2107,14 +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"); 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++;
@@ -2320,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 */
@@ -2343,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
@@ -2354,8 +2408,13 @@ 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)
+if (!(peer_offered & PEER_OFFERED_CHUNKING) && !ok)
+  {
+  /* Save the first address of the next batch. */
+  first_addr = addr;
+
   ok = TRUE;
+  }
 else
   {
   transport_ctx tctx = {
@@ -2367,24 +2426,41 @@ else
     | (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
@@ -2417,6 +2493,27 @@ 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
index f5f478e..4c4dfc5 100644 (file)
@@ -1559,9 +1559,9 @@ return cutthrough_response('3', NULL) == '3';
 }
 
 
-/* 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')))
@@ -1580,6 +1580,8 @@ return TRUE;
 BOOL
 cutthrough_headers_send(void)
 {
+transport_ctx tctx;
+
 if(cutthrough.fd < 0)
   return FALSE;
 
@@ -1588,9 +1590,13 @@ if(cutthrough.fd < 0)
 */
 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");
index e8b4c51..4f6f490 100644 (file)
@@ -11,6 +11,7 @@ spool_directory = DIR/spool
 gecos_pattern = ""
 gecos_name = CALLER_NAME
 tls_advertise_hosts =
+chunking_advertise_hosts =
 
 # ----- Main settings -----