receive flow processing
authorJeremy Harris <jgh146exb@wizmail.org>
Wed, 13 Jul 2016 20:28:18 +0000 (21:28 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 2 Aug 2016 15:46:16 +0000 (16:46 +0100)
14 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/src/functions.h
src/src/globals.c
src/src/globals.h
src/src/macros.h
src/src/receive.c
src/src/smtp_in.c
src/src/structs.h
test/confs/0900 [new file with mode: 0644]
test/log/0900 [new file with mode: 0644]
test/scripts/0000-Basic/0900 [new file with mode: 0644]
test/stdout/0900 [new file with mode: 0644]
test/stdout/2122

index ca64f9f..5c2c2ba 100644 (file)
@@ -13801,6 +13801,7 @@ See also the &'Policy controls'& section above.
 .table2
 .row &%accept_8bitmime%&             "advertise 8BITMIME"
 .row &%auth_advertise_hosts%&        "advertise AUTH to these hosts"
+.row &%chunking_advertise_hosts%&    "advertise CHUNKING to these hosts"
 .row &%dsn_advertise_hosts%&         "advertise DSN extensions to these hosts"
 .row &%ignore_fromline_hosts%&       "allow &""From ""& from these hosts"
 .row &%ignore_fromline_local%&       "allow &""From ""& from local SMTP"
@@ -14358,6 +14359,13 @@ For non-SMTP input and for batched SMTP input, the test is done at start-up; on
 failure a message is written to stderr and Exim exits with a non-zero code, as
 it obviously cannot send an error message of any kind.
 
+.option chunking_advertise_hosts main "host list&!!" *
+.cindex CHUNKING advertisement
+.cindex "RFC 3030" "CHUNKING"
+The CHUNKING extension (RFC3030) will be advertised in the EHLO message to
+these hosts.
+Hosts may use the BDAT command as an alternate to DATA.
+
 .option daemon_smtp_ports main string &`smtp`&
 .cindex "port" "for daemon"
 .cindex "TCP/IP" "setting listening ports"
@@ -27741,6 +27749,15 @@ received, before the final response to the DATA command is sent. This is
 the ACL specified by &%acl_smtp_data%&, which is the second ACL that is
 associated with the DATA command.
 
+.cindex CHUNKING "BDAT command"
+.cindex BDAT "SMTP command"
+.cindex "RFC 3030" CHUNKING
+If CHUNKING was advertised and a BDAT command sequence is received,
+the &%acl_smtp_predata%& ACL is not run.
+.XXX why not?  It should be possible, for the first BDAT.
+The &%acl_smtp_data%& is run after the last BDAT command and all of
+the data specified is received.
+
 For both of these ACLs, it is not possible to reject individual recipients. An
 error response rejects the entire message. Unfortunately, it is known that some
 MTAs do not treat hard (5&'xx'&) responses to the DATA command (either
@@ -35620,10 +35637,12 @@ the following table:
 &`F   `&        sender address (on delivery lines)
 &`H   `&        host name and IP address
 &`I   `&        local interface used
+&`K   `&        CHUNKING extension used
 &`id  `&        message id for incoming message
 &`P   `&        on &`<=`& lines: protocol used
 &`    `&        on &`=>`& and &`**`& lines: return path
-&`PRX `&        on &'<='& and&`=>`& lines: proxy address
+&`PRDR`&        PRDR extension used
+&`PRX `&        on &'<='& and &`=>`& lines: proxy address
 &`Q   `&        alternate queue name
 &`QT  `&        on &`=>`& lines: time spent on queue so far
 &`    `&        on &"Completed"& lines: time spent on queue
index 2a776b7..c37f7cd 100644 (file)
@@ -26,6 +26,10 @@ Version 4.88
     the queue to be used for a message.  A $queue_name variable gives
     visibility.
 
+ 6. The CHUNKING ESMTP extension from RFC 3030.  May give some slight
+    performance increase and network load decrease.  Main config option
+    chucking_advertise_hosts for control.
+
 
 Version 4.87
 ------------
index b5e3098..0b77aa1 100644 (file)
@@ -100,6 +100,7 @@ extern int     auth_xtextdecode(uschar *, uschar **);
 
 extern uschar *b64encode(uschar *, int);
 extern int     b64decode(uschar *, uschar **);
+extern int     bdat_getc(void);
 extern void    bits_clear(unsigned int *, size_t, int *);
 extern void    bits_set(unsigned int *, size_t, int *);
 
index 2e3fe40..c86b947 100644 (file)
@@ -185,6 +185,8 @@ incoming TCP/IP. The defaults use stdin. We never need these for any
 stand-alone tests. */
 
 #ifndef STAND_ALONE
+int (*lwr_receive_getc)(void)  = stdin_getc;
+int (*lwr_receive_ungetc)(int) = stdin_ungetc;
 int (*receive_getc)(void)      = stdin_getc;
 int (*receive_ungetc)(int)     = stdin_ungetc;
 int (*receive_feof)(void)      = stdin_feof;
@@ -495,6 +497,8 @@ int     check_spool_space      = 0;
 
 uschar *chunking_advertise_hosts = US"*";
 unsigned chunking_datasize     = 0;
+unsigned chunking_data_left    = 0;
+BOOL    chunking_offered       = FALSE;
 chunking_state_t chunking_state= CHUNKING_NOT_OFFERED;
 
 uschar *client_authenticator   = NULL;
index 184a144..c5767d7 100644 (file)
@@ -138,6 +138,8 @@ extern uschar  *dsn_advertise_hosts;   /* host for which TLS is advertised */
 /* Input-reading functions for messages, so we can use special ones for
 incoming TCP/IP. */
 
+extern int (*lwr_receive_getc)(void);
+extern int (*lwr_receive_ungetc)(int);
 extern int (*receive_getc)(void);
 extern int (*receive_ungetc)(int);
 extern int (*receive_feof)(void);
@@ -269,6 +271,8 @@ extern int     check_spool_inodes;     /* Minimum for message acceptance */
 extern int     check_spool_space;      /* Minimum for message acceptance */
 extern uschar *chunking_advertise_hosts;    /* RFC 3030 CHUNKING */
 extern unsigned chunking_datasize;
+extern unsigned chunking_data_left;
+extern BOOL    chunking_offered;
 extern chunking_state_t chunking_state;
 extern uschar *client_authenticator;        /* Authenticator name used for smtp delivery */
 extern uschar *client_authenticated_id;     /* "login" name used for SMTP AUTH */
index dbc49f0..f567c7e 100644 (file)
@@ -320,6 +320,13 @@ for having to swallow the rest of an SMTP message is whether the value is
 #define END_NOTENDED   3    /* Message reading not yet ended */
 #define END_SIZE       4    /* Reading ended because message too big */
 #define END_WERROR     5    /* Write error while reading the message */
+#define END_PROTOCOL   6    /* Protocol error in CHUNKING sequence */
+
+/* result codes for bdat_getc() (which can also return EOF) */
+
+#define EOD (-2)
+#define ERR (-3)
+
 
 /* Bit masks for debug and log selectors */
 
index f6bdf47..9ff339d 100644 (file)
@@ -770,16 +770,13 @@ Arguments:
 
 Returns:    One of the END_xxx values indicating why it stopped reading
 */
-/*XXX CHUNKING: maybe a variant routing specialised for BDAT, assuming
-string RFC compliance ie. CRLF always?  We still have to strip the CR
-but we are not dealing with variant lunacy or looking for the end-dot */
 
 static int
 read_message_data_smtp(FILE *fout)
 {
 int ch_state = 0;
 int ch;
-register int linelength = 0;
+int linelength = 0;
 
 while ((ch = (receive_getc)()) != EOF)
   {
@@ -869,7 +866,7 @@ while ((ch = (receive_getc)()) != EOF)
 
   message_size++;
   linelength++;
-  if (fout != NULL)
+  if (fout)
     {
     if (fputc(ch, fout) == EOF) return END_WERROR;
     if (message_size > thismessage_size_limit) return END_SIZE;
@@ -878,7 +875,7 @@ while ((ch = (receive_getc)()) != EOF)
     (void) cutthrough_put_nl();
   else
     {
-    uschar c= ch;
+    uschar c = ch;
     (void) cutthrough_puts(&c, 1);
     }
   }
@@ -892,6 +889,63 @@ return END_EOF;
 
 
 
+/* Variant of the above read_message_data_smtp() specialised for RFC 3030
+CHUNKING.  We assume that the incoming has proper CRLF, so only have to scan
+for and strip CR.  On the downside there are more protocol reasons to stop.
+
+Arguments:
+  fout      a FILE to which to write the message; NULL if skipping
+
+Returns:    One of the END_xxx values indicating why it stopped reading
+*/
+
+static int
+read_message_bdat_smtp(FILE *fout)
+{
+int ch;
+int linelength = 0;
+
+for (;;) switch (ch = bdat_getc())
+  {
+  case EOF: return END_EOF;
+  case EOD: return END_DOT;
+  case ERR: return END_PROTOCOL;
+
+  case '\r':
+    body_linecount++;
+    if (linelength > max_received_linelength)
+      max_received_linelength = linelength;
+    linelength = -1;
+    break;
+
+  case 0:
+    body_zerocount++;
+    /*FALLTHROUGH*/
+  default:
+    message_size++;
+    linelength++;
+    if (fout)
+      {
+      if (fputc(ch, fout) == EOF) return END_WERROR;
+      if (message_size > thismessage_size_limit) return END_SIZE;
+      }
+#ifdef notyet
+    if(ch == '\n')
+      (void) cutthrough_put_nl();
+    else
+      {
+      uschar c = ch;
+      (void) cutthrough_puts(&c, 1);
+      }
+#endif
+    break;
+  }
+/*NOTREACHED*/
+}
+
+
+
+
 /*************************************************
 *             Swallow SMTP message               *
 *************************************************/
@@ -908,6 +962,7 @@ Returns:     nothing
 void
 receive_swallow_smtp(void)
 {
+/*XXX CHUNKING: not enough.  read chunks until RSET? */
 if (message_ended >= END_NOTENDED)
   message_ended = read_message_data_smtp(NULL);
 }
@@ -1616,7 +1671,6 @@ next->text. */
 
 for (;;)
   {
-/*XXX CHUNKING: account for BDAT size & last, and do more chunks as needed */
   int ch = (receive_getc)();
 
   /* If we hit EOF on a SMTP connection, it's an error, since incoming
@@ -2835,7 +2889,14 @@ if (filter_test != FTEST_NONE)
   return message_ended == END_DOT;
   }
 
-/*XXX CHUNKING: need to cancel cutthrough under BDAT, for now */
+/*XXX CHUNKING: need to cancel cutthrough under BDAT, for now.  In future,
+think more if it could be handled.  Cannot do onward CHUNKING unless
+inbound is, but inbound chunking ought to be ok with outbound plain.
+Could we do onward CHUNKING given inbound CHUNKING?
+*/
+if (chunking_state > CHUNKING_OFFERED)
+  cancel_cutthrough_connection("chunking active");
+
 /* Cutthrough delivery:
 We have to create the Received header now rather than at the end of reception,
 so the timestamp behaviour is a change to the normal case.
@@ -2933,8 +2994,9 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
   {
   if (smtp_input)
     {
-/*XXX CHUNKING: main data read, for message body */
-    message_ended = read_message_data_smtp(data_file);
+    message_ended = chunking_state > CHUNKING_OFFERED
+      ? read_message_bdat_smtp(data_file)
+      : read_message_data_smtp(data_file);
     receive_linecount++;                /* The terminating "." line */
     }
   else message_ended = read_message_data(data_file);
@@ -2942,51 +3004,64 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
   receive_linecount += body_linecount;  /* For BSMTP errors mainly */
   message_linecount += body_linecount;
 
-  /* Handle premature termination of SMTP */
-
-  if (smtp_input && message_ended == END_EOF)
+  switch (message_ended)
     {
-    Uunlink(spool_name);                     /* Lose data file when closed */
-    cancel_cutthrough_connection("sender closed connection");
-    message_id[0] = 0;                       /* Indicate no message accepted */
-    smtp_reply = handle_lost_connection(US"");
-    smtp_yield = FALSE;
-    goto TIDYUP;                             /* Skip to end of function */
-    }
+    /* Handle premature termination of SMTP */
 
-  /* Handle message that is too big. Don't use host_or_ident() in the log
-  message; we want to see the ident value even for non-remote messages. */
+    case END_EOF:
+      if (smtp_input)
+       {
+       Uunlink(spool_name);                 /* Lose data file when closed */
+       cancel_cutthrough_connection("sender closed connection");
+       message_id[0] = 0;                   /* Indicate no message accepted */
+       smtp_reply = handle_lost_connection(US"");
+       smtp_yield = FALSE;
+       goto TIDYUP;                         /* Skip to end of function */
+       }
+      break;
 
-  if (message_ended == END_SIZE)
-    {
-    Uunlink(spool_name);                /* Lose the data file when closed */
-    cancel_cutthrough_connection("mail too big");
-    if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
+    /* Handle message that is too big. Don't use host_or_ident() in the log
+    message; we want to see the ident value even for non-remote messages. */
 
-    log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
-      "message too big: read=%d max=%d",
-      sender_address,
-      (sender_fullhost == NULL)? "" : " H=",
-      (sender_fullhost == NULL)? US"" : sender_fullhost,
-      (sender_ident == NULL)? "" : " U=",
-      (sender_ident == NULL)? US"" : sender_ident,
-      message_size,
-      thismessage_size_limit);
+    case END_SIZE:
+      Uunlink(spool_name);                /* Lose the data file when closed */
+      cancel_cutthrough_connection("mail too big");
+      if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
 
-    if (smtp_input)
-      {
-      smtp_reply = US"552 Message size exceeds maximum permitted";
-      message_id[0] = 0;               /* Indicate no message accepted */
-      goto TIDYUP;                     /* Skip to end of function */
-      }
-    else
-      {
-      fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
-      give_local_error(ERRMESS_TOOBIG,
-        string_sprintf("message too big (max=%d)", thismessage_size_limit),
-        US"message rejected: ", error_rc, data_file, header_list);
-      /* Does not return */
-      }
+      log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+       "message too big: read=%d max=%d",
+       sender_address,
+       (sender_fullhost == NULL)? "" : " H=",
+       (sender_fullhost == NULL)? US"" : sender_fullhost,
+       (sender_ident == NULL)? "" : " U=",
+       (sender_ident == NULL)? US"" : sender_ident,
+       message_size,
+       thismessage_size_limit);
+
+      if (smtp_input)
+       {
+       smtp_reply = US"552 Message size exceeds maximum permitted";
+       message_id[0] = 0;               /* Indicate no message accepted */
+       goto TIDYUP;                     /* Skip to end of function */
+       }
+      else
+       {
+       fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+       give_local_error(ERRMESS_TOOBIG,
+         string_sprintf("message too big (max=%d)", thismessage_size_limit),
+         US"message rejected: ", error_rc, data_file, header_list);
+       /* Does not return */
+       }
+      break;
+
+    /* Handle bad BDAT protocol sequence */
+
+    case END_PROTOCOL:
+      Uunlink(spool_name);             /* Lose the data file when closed */
+      cancel_cutthrough_connection("sender protocol error");
+      smtp_reply = US"";               /* Response already sent */
+      message_id[0] = 0;               /* Indicate no message accepted */
+      goto TIDYUP;                     /* Skip to end of function */
     }
   }
 
@@ -3169,9 +3244,8 @@ user_msg = NULL;
 enable_dollar_recipients = TRUE;
 
 if (recipients_count == 0)
-  {
-  blackholed_by = recipients_discarded? US"MAIL ACL" : US"RCPT ACL";
-  }
+  blackholed_by = recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
+
 else
   {
   /* Handle interactive SMTP messages */
@@ -3187,18 +3261,15 @@ else
       dkim_exim_verify_finish();
 
       /* Check if we must run the DKIM ACL */
-      if ((acl_smtp_dkim != NULL) &&
-          (dkim_verify_signers != NULL) &&
-          (dkim_verify_signers[0] != '\0'))
+      if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
         {
         uschar *dkim_verify_signers_expanded =
           expand_string(dkim_verify_signers);
-        if (dkim_verify_signers_expanded == NULL)
-          {
+        if (!dkim_verify_signers_expanded)
           log_write(0, LOG_MAIN|LOG_PANIC,
             "expansion of dkim_verify_signers option failed: %s",
             expand_string_message);
-          }
+
         else
           {
           int sep = 0;
@@ -3207,28 +3278,23 @@ else
           uschar *seen_items = NULL;
           int     seen_items_size = 0;
           int     seen_items_offset = 0;
-          uschar itembuf[256];
           /* Default to OK when no items are present */
           rc = OK;
-          while ((item = string_nextinlist(&ptr, &sep,
-                                           itembuf,
-                                           sizeof(itembuf))))
+          while ((item = string_nextinlist(&ptr, &sep, NULL, 0)))
             {
             /* Prevent running ACL for an empty item */
-            if (!item || (item[0] == '\0')) continue;
+            if (!item || !*item) continue;
 
             /* Only run ACL once for each domain or identity,
            no matter how often it appears in the expanded list. */
             if (seen_items)
               {
               uschar *seen_item = NULL;
-              uschar seen_item_buf[256];
               const uschar *seen_items_list = seen_items;
               BOOL seen_this_item = FALSE;
 
               while ((seen_item = string_nextinlist(&seen_items_list, &sep,
-                                                    seen_item_buf,
-                                                    sizeof(seen_item_buf))))
+                                                    NULL, 0)))
                if (Ustrcmp(seen_item,item) == 0)
                  {
                  seen_this_item = TRUE;
@@ -3787,7 +3853,7 @@ if (sender_host_authenticated)
 
 #ifndef DISABLE_PRDR
 if (prdr_requested)
-  s = string_append(s, &size, &sptr, 1, US" PRDR");
+  s = string_catn(s, &size, &sptr, US" PRDR", 5);
 #endif
 
 #ifdef SUPPORT_PROXY
@@ -3795,6 +3861,9 @@ if (proxy_session && LOGGING(proxy))
   s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_local_address);
 #endif
 
+if (chunking_state > CHUNKING_OFFERED)
+  s = string_catn(s, &size, &sptr, US" K", 2);
+
 sprintf(CS big_buffer, "%d", msg_size);
 s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
 
@@ -4093,8 +4162,15 @@ if (smtp_input)
 
       /* Default OK response */
 
+      else if (chunking_state > CHUNKING_OFFERED)
+       {
+        smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n",
+           chunking_datasize, message_size+message_linecount, message_id);
+       chunking_state = CHUNKING_OFFERED;
+       }
       else
         smtp_printf("250 OK id=%s\r\n", message_id);
+
       if (host_checking)
         fprintf(stdout,
           "\n**** SMTP testing: that is not a real message id!\n\n");
index b00537e..d4b3e56 100644 (file)
@@ -291,6 +291,13 @@ static int     smtp_had_eof;
 static int     smtp_had_error;
 
 
+/* forward declarations */
+int bdat_ungetc(int ch);
+static int smtp_read_command(BOOL check_sync);
+static int synprot_error(int type, int code, uschar *data, uschar *errmess);
+static void smtp_quit_handler(uschar **, uschar **);
+static void smtp_rset_handler(void);
+
 /*************************************************
 *          SMTP version of getc()                *
 *************************************************/
@@ -339,6 +346,118 @@ return *smtp_inptr++;
 
 
 
+/* Get a byte from the smtp input, in CHUNKING mode.  Handle ack of the
+previous BDAT chunk and getting new ones when we run out.  Uses the
+underlying smtp_getc or tls_getc both for that and for getting the
+(buffered) data byte.  EOD signals (an expected) no further data.
+ERR signals a protocol error, and EOF a closed input stream.
+
+Called from read_bdat_smtp() in receive.c for the message body, but also
+by the headers read loop in receive_msg(); manipulates chunking_state
+to handle the BDAT command/response.
+Placed here due to the correlation with the above smtp_getc(), which it wraps,
+and also by the need to do smtp command/response handling.
+
+Arguments:  none
+Returns:    the next character or ERR, EOD or EOF
+*/
+
+int
+bdat_getc(void)
+{
+uschar * user_msg = NULL;
+uschar * log_msg;
+
+for(;;)
+  {
+  if (chunking_data_left-- > 0)
+    return lwr_receive_getc();
+
+  receive_getc = lwr_receive_getc;
+  receive_ungetc = lwr_receive_ungetc;
+
+  /* If not the last, ack the received chunk.  The last response is delayed
+  until after the data ACL decides on it */
+  /*XXX find that "last response" and append the chunk size */
+
+  if (chunking_state == CHUNKING_LAST)
+    return EOD;
+
+  chunking_state = CHUNKING_OFFERED;
+  smtp_printf("250 %u byte chunk received\r\n", chunking_datasize);
+
+  /* Expect another BDAT cmd from input. RFC 3030 says nothing about
+  QUIT, RSET or NOOP but handling them seems obvious */
+
+next_cmd:
+  switch(smtp_read_command(TRUE))
+    {
+    default:
+      (void) synprot_error(L_smtp_protocol_error, 503, NULL,
+       US"only BDAT permissible after non-LAST BDAT");
+
+  repeat_until_rset:
+      switch(smtp_read_command(TRUE))
+       {
+       case QUIT_CMD:  smtp_quit_handler(&user_msg, &log_msg); /*FALLTHROUGH */
+       case EOF_CMD:   return EOF;
+       case RSET_CMD:  smtp_rset_handler(); return ERR;
+       default:        if (synprot_error(L_smtp_protocol_error, 503, NULL,
+                                         US"only RSET accepted now") > 0)
+                         return EOF;
+                       goto repeat_until_rset;
+       }
+
+    case QUIT_CMD:
+      smtp_quit_handler(&user_msg, &log_msg);
+      /*FALLTHROUGH*/
+    case EOF_CMD:
+      return EOF;
+
+    case RSET_CMD:
+      smtp_rset_handler();
+      return ERR;
+
+    case NOOP_CMD:
+      HAD(SCH_NOOP);
+      smtp_printf("250 OK\r\n");
+      goto next_cmd;
+
+    case BDAT_CMD:
+      {
+      int n;
+
+      if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1)
+       {
+       (void) synprot_error(L_smtp_protocol_error, 501, NULL,
+         US"missing size for BDAT command");
+       return ERR;
+       }
+      chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0
+       ? CHUNKING_LAST : CHUNKING_ACTIVE;
+      chunking_data_left = chunking_datasize;
+
+      if (chunking_datasize == 0)
+       if (chunking_state == CHUNKING_LAST)
+         return EOD;
+       else
+         {
+         (void) synprot_error(L_smtp_protocol_error, 504, NULL,
+           US"zero size for BDAT command");
+         goto repeat_until_rset;
+         }
+
+      receive_getc = bdat_getc;
+      receive_ungetc = bdat_ungetc;
+      break;   /* to top of main loop */
+      }
+    }
+  }
+}
+
+
+
+
 /*************************************************
 *          SMTP version of ungetc()              *
 *************************************************/
@@ -355,11 +474,18 @@ Returns:       the character
 int
 smtp_ungetc(int ch)
 {
-*(--smtp_inptr) = ch;
+*--smtp_inptr = ch;
 return ch;
 }
 
 
+int
+bdat_ungetc(int ch)
+{
+chunking_data_left++;
+return lwr_receive_ungetc(ch);
+}
+
 
 
 /*************************************************
@@ -1527,7 +1653,6 @@ authenticated_sender = NULL;
 bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
-chunking_state = CHUNKING_NOT_OFFERED;
 #ifndef DISABLE_DKIM
 dkim_signers = NULL;
 dkim_disable_verify = FALSE;
@@ -3239,6 +3364,43 @@ return 0;
 
 
 
+static void
+smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp)
+{
+HAD(SCH_QUIT);
+incomplete_transaction_log(US"QUIT");
+if (acl_smtp_quit != NULL)
+  {
+  int rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp);
+  if (rc == ERROR)
+    log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
+      *log_msgp);
+  }
+if (*user_msgp)
+  smtp_respond(US"221", 3, TRUE, *user_msgp);
+else
+  smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+
+#ifdef SUPPORT_TLS
+tls_close(TRUE, TRUE);
+#endif
+
+log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
+  smtp_get_connection_info());
+}
+
+
+static void
+smtp_rset_handler(void)
+{
+HAD(SCH_RSET);
+incomplete_transaction_log(US"RSET");
+smtp_printf("250 Reset OK\r\n");
+cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
+}
+
+
+
 /*************************************************
 *       Initialize for SMTP incoming message     *
 *************************************************/
@@ -3285,6 +3447,8 @@ for the host). Note: we do NOT reset AUTH at this point. */
 smtp_reset(reset_point);
 message_ended = END_NOTSTARTED;
 
+chunking_state = chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
+
 cmd_list[CMD_LIST_RSET].is_mail_cmd = TRUE;
 cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE;
 cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
@@ -3775,6 +3939,7 @@ while (done <= 0)
         {
         s = string_catn(s, &size, &ptr, smtp_code, 3);
         s = string_catn(s, &size, &ptr, US"-CHUNKING\r\n", 11);
+       chunking_offered = TRUE;
        chunking_state = CHUNKING_OFFERED;
         }
 
@@ -4546,12 +4711,18 @@ while (done <= 0)
 
       if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1)
        {
-       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+       done = synprot_error(L_smtp_protocol_error, 501, NULL,
          US"missing size for BDAT command");
        break;
        }
       chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0
        ? CHUNKING_LAST : CHUNKING_ACTIVE;
+      chunking_data_left = chunking_datasize;
+
+      lwr_receive_getc = receive_getc;
+      lwr_receive_ungetc = receive_ungetc;
+      receive_getc = bdat_getc;
+      receive_ungetc = bdat_ungetc;
 
       DEBUG(D_any)
         debug_printf("chunking state %d\n", (int)chunking_state);
@@ -4594,45 +4765,45 @@ while (done <= 0)
       break;
       }
 
-    /* No go-ahead output for BDAT */
-
-    if (smtp_connection_had[smtp_ch_index-1] == SCH_BDAT)
-      {
+    if (chunking_state > CHUNKING_OFFERED)
+      {                                /* No predata ACL or go-ahead output for BDAT */
       rc = OK;
-      break;
       }
-
-    /* If there is an ACL, re-check the synchronization afterwards, since the
-    ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
-    to get the DATA command sent. */
-
-    if (acl_smtp_predata == NULL && cutthrough.fd < 0)
-      rc = OK;
     else
       {
-      uschar * acl= acl_smtp_predata ? acl_smtp_predata : US"accept";
-      enable_dollar_recipients = TRUE;
-      rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
-        &log_msg);
-      enable_dollar_recipients = FALSE;
-      if (rc == OK && !check_sync()) goto SYNC_FAILURE;
-      }
+      /* If there is an ACL, re-check the synchronization afterwards, since the
+      ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
+      to get the DATA command sent. */
 
-    if (rc == OK)
-      {
-      uschar * code;
-      code = US"354";
-      if (user_msg == NULL)
-        smtp_printf("%s Enter message, ending with \".\" on a line by itself\r\n", code);
-      else smtp_user_msg(code, user_msg);
-      done = 3;
-      message_ended = END_NOTENDED;   /* Indicate in middle of data */
+      if (acl_smtp_predata == NULL && cutthrough.fd < 0)
+       rc = OK;
+      else
+       {
+       uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
+       enable_dollar_recipients = TRUE;
+       rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
+         &log_msg);
+       enable_dollar_recipients = FALSE;
+       if (rc == OK && !check_sync())
+         goto SYNC_FAILURE;
+
+       if (rc != OK)
+         {     /* Either the ACL failed the address, or it was deferred. */
+         done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
+         break;
+         }
+       }
+
+      if (user_msg)
+       smtp_user_msg(US"354", user_msg);
+      else
+       smtp_printf(
+         "354 Enter message, ending with \".\" on a line by itself\r\n");
       }
 
-    /* Either the ACL failed the address, or it was deferred. */
+    done = 3;
+    message_ended = END_NOTENDED;   /* Indicate in middle of data */
 
-    else
-      done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
     break;
 
 
@@ -4868,37 +5039,15 @@ while (done <= 0)
     message. */
 
     case QUIT_CMD:
-    HAD(SCH_QUIT);
-    incomplete_transaction_log(US"QUIT");
-    if (acl_smtp_quit != NULL)
-      {
-      rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg, &log_msg);
-      if (rc == ERROR)
-        log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
-          log_msg);
-      }
-    if (user_msg == NULL)
-      smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
-    else
-      smtp_respond(US"221", 3, TRUE, user_msg);
-
-    #ifdef SUPPORT_TLS
-    tls_close(TRUE, TRUE);
-    #endif
-
+    smtp_quit_handler(&user_msg, &log_msg);
     done = 2;
-    log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
-      smtp_get_connection_info());
     break;
 
 
     case RSET_CMD:
-    HAD(SCH_RSET);
-    incomplete_transaction_log(US"RSET");
+    smtp_rset_handler();
     smtp_reset(reset_point);
     toomany = FALSE;
-    smtp_printf("250 Reset OK\r\n");
-    cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
     break;
 
 
@@ -4925,7 +5074,7 @@ while (done <= 0)
           verify_check_host(&tls_advertise_hosts) != FAIL)
         Ustrcat(buffer, " STARTTLS");
       #endif
-      Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA");
+      Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT");
       Ustrcat(buffer, " NOOP QUIT RSET HELP");
       if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
       if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
index 2b449a6..b49f4b5 100644 (file)
@@ -51,7 +51,10 @@ typedef struct ugid_block {
   BOOL    initgroups;
 } ugid_block;
 
-typedef enum {CHUNKING_NOT_OFFERED, CHUNKING_OFFERED, CHUNKING_ACTIVE, CHUNKING_LAST} chunking_state_t;
+typedef enum { CHUNKING_NOT_OFFERED = -1,
+               CHUNKING_OFFERED,
+               CHUNKING_ACTIVE,
+               CHUNKING_LAST} chunking_state_t;
 
 /* Structure for holding information about a host for use mainly by routers,
 but also used when checking lists of hosts and when transporting. Looking up
diff --git a/test/confs/0900 b/test/confs/0900
new file mode 100644 (file)
index 0000000..4a014ce
--- /dev/null
@@ -0,0 +1,71 @@
+# Exim test configuration 0900
+
+exim_path = EXIM_PATH
+keep_environment =
+host_lookup_order = bydns
+spool_directory = DIR/spool
+log_file_path = DIR/spool/log/SERVER%slog
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+chunking_advertise_hosts = *
+tls_advertise_hosts =
+
+
+# ----- Main settings -----
+
+domainlist local_domains = @
+
+acl_smtp_rcpt = check_recipient
+acl_smtp_data = check_data
+message_id_header_domain = ${if eq{0}{0}{some.domain}}
+message_id_header_text = ${if eq{0}{0}{a@b[c]}}
+trusted_users = CALLER
+queue_only
+smtp_receive_timeout = 2s
+
+
+# ----- ACL -----
+
+begin acl
+
+check_recipient:
+  accept hosts = :
+  accept domains = +local_domains
+  deny   message = relay not permitted
+
+check_data:
+  warn   message = X-acl-message-linecount: $message_linecount
+  accept
+
+
+# ----- Routers -----
+
+begin routers
+
+fail_remote_domains:
+  driver = redirect
+  domains = ! +local_domains
+  data = :fail: unrouteable mail domain "$domain"
+
+localuser:
+  driver = accept
+  check_local_user
+  transport = local_delivery
+  headers_add = X-local-user: uid=$local_user_uid gid=$local_user_gid
+
+
+# ----- Transports -----
+
+begin transports
+
+local_delivery:
+  driver = appendfile
+  delivery_date_add
+  envelope_to_add
+  file = DIR/test-mail/$local_part
+  headers_add = "X-body-linecount: $body_linecount\n\
+                 X-message-linecount: $message_linecount\n\
+                 X-received-count: $received_count"
+  return_path_add
+
+# End
diff --git a/test/log/0900 b/test/log/0900
new file mode 100644 (file)
index 0000000..37d70da
--- /dev/null
@@ -0,0 +1,9 @@
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= someone@some.domain H=(tester) [127.0.0.1] P=esmtp K S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= someone@some.domain H=(tester) [127.0.0.1] P=esmtp K S=sss
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= someone@some.domain H=(tester) [127.0.0.1] P=esmtp K S=sss
+1999-03-02 09:44:33 10HmbA-0005vi-00 SMTP data timeout (message abandoned) on connection from (tester) [127.0.0.1] F=<someone@some.domain>
+1999-03-02 09:44:33 SMTP connection from (tester) [127.0.0.1] lost while reading message data
+1999-03-02 09:44:33 SMTP connection from (tester) [127.0.0.1] lost while reading message data
diff --git a/test/scripts/0000-Basic/0900 b/test/scripts/0000-Basic/0900
new file mode 100644 (file)
index 0000000..cb8cf63
--- /dev/null
@@ -0,0 +1,170 @@
+# CHUNKING reception
+exim -DSERVER=server -bd -oX PORT_D
+****
+#
+client 127.0.0.1 PORT_D
+??? 220
+ehlo tester
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250-CHUNKING
+??? 250 HELP
+mail from:someone@some.domain
+??? 250
+rcpt to:CALLER@HOSTNAME
+??? 250
+bdat 88 last
+To: Susan@random.com
+From: Sam@random.com
+Subject: This is a bodyless test message
+
+??? 250-
+??? 250
+quit
+??? 221
+****
+#
+client 127.0.0.1 PORT_D
+??? 220
+ehlo tester
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250 
+mail from:someone@some.domain
+??? 250
+rcpt to:CALLER@HOSTNAME
+??? 250
+bdat 100
+To: Susan@random.com
+From: Sam@random.com
+Subject: This is a bodyfull test message
+
+1234567890
+??? 250
+noop
+??? 250
+bdat 0 last
+??? 250-
+??? 250
+mail from:someone@some.domain
+??? 250
+rcpt to:CALLER@HOSTNAME
+??? 250
+bdat 10
+To: Susan@bdat 78 last
+??? 250
+random.com
+From: Sam@random.com
+Subject: This is a bodyless test message
+
+??? 250-
+??? 250
+quit
+??? 221
+****
+#
+# not enough data in chunk
+#
+client 127.0.0.1 PORT_D
+??? 220
+ehlo tester
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250 
+mail from:someone@some.domain
+??? 250
+rcpt to:CALLER@HOSTNAME
+??? 250
+bdat 89 last
+To: Susan@random.com
+From: Sam@random.com
+Subject: This is a bodyless test message
+
+??? 421
+****
+#
+# protocol failure cases
+#
+client 127.0.0.1 PORT_D
+??? 220
+ehlo tester
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250 
+mail from:someone@some.domain
+??? 250
+rcpt to:CALLER@HOSTNAME
+??? 250
+bdat 88
+To: Susan@random.com
+From: Sam@random.com
+Subject: This is a bodyless test message
+
+??? 250
+bdat 0
+??? 504
+quit
+??? 221
+****
+#
+client 127.0.0.1 PORT_D
+??? 220
+ehlo tester
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250 
+mail from:someone@some.domain
+??? 250
+rcpt to:CALLER@HOSTNAME
+??? 250
+bdat 88
+To: Susan@random.com
+From: Sam@random.com
+Subject: This is a bodyless test message
+
+??? 250
+data
+??? 503
+RSET
+??? 250
+EHLO tester
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250 
+mail from:someone@some.domain
+??? 250
+rcpt to:CALLER@HOSTNAME
+??? 250
+bdat 88
+To: Susan@random.com
+From: Sam@random.com
+Subject: This is a bodyless test message
+
+??? 250
+data
+??? 503
+data
+??? 503
+quit
+??? 221
+****
+#
+killdaemon
+no_msglog_check
diff --git a/test/stdout/0900 b/test/stdout/0900
new file mode 100644 (file)
index 0000000..24c5916
--- /dev/null
@@ -0,0 +1,232 @@
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> ehlo tester
+??? 250-
+<<< 250-the.local.host.name Hello tester [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-8BITMIME
+??? 250-
+<<< 250-PIPELINING
+??? 250-CHUNKING
+<<< 250-CHUNKING
+??? 250 HELP
+<<< 250 HELP
+>>> mail from:someone@some.domain
+??? 250
+<<< 250 OK
+>>> rcpt to:CALLER@the.local.host.name
+??? 250
+<<< 250 Accepted
+>>> bdat 88 last
+>>> To: Susan@random.com
+>>> From: Sam@random.com
+>>> Subject: This is a bodyless test message
+>>> 
+??? 250-
+<<< 250- 88 byte chunk, total 88
+??? 250
+<<< 250 OK id=10HmaX-0005vi-00
+>>> quit
+??? 221
+<<< 221 the.local.host.name closing connection
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> ehlo tester
+??? 250-
+<<< 250-the.local.host.name Hello tester [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-8BITMIME
+??? 250-
+<<< 250-PIPELINING
+??? 250-
+<<< 250-CHUNKING
+??? 250 
+<<< 250 HELP
+>>> mail from:someone@some.domain
+??? 250
+<<< 250 OK
+>>> rcpt to:CALLER@the.local.host.name
+??? 250
+<<< 250 Accepted
+>>> bdat 100
+>>> To: Susan@random.com
+>>> From: Sam@random.com
+>>> Subject: This is a bodyfull test message
+>>> 
+>>> 1234567890
+??? 250
+<<< 250 100 byte chunk received
+>>> noop
+??? 250
+<<< 250 OK
+>>> bdat 0 last
+??? 250-
+<<< 250- 0 byte chunk, total 100
+??? 250
+<<< 250 OK id=10HmaY-0005vi-00
+>>> mail from:someone@some.domain
+??? 250
+<<< 250 OK
+>>> rcpt to:CALLER@the.local.host.name
+??? 250
+<<< 250 Accepted
+>>> bdat 10
+>>> To: Susan@bdat 78 last
+??? 250
+<<< 250 10 byte chunk received
+>>> random.com
+>>> From: Sam@random.com
+>>> Subject: This is a bodyless test message
+>>> 
+??? 250-
+<<< 250- 78 byte chunk, total 88
+??? 250
+<<< 250 OK id=10HmaZ-0005vi-00
+>>> quit
+??? 221
+<<< 221 the.local.host.name closing connection
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> ehlo tester
+??? 250-
+<<< 250-the.local.host.name Hello tester [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-8BITMIME
+??? 250-
+<<< 250-PIPELINING
+??? 250-
+<<< 250-CHUNKING
+??? 250 
+<<< 250 HELP
+>>> mail from:someone@some.domain
+??? 250
+<<< 250 OK
+>>> rcpt to:CALLER@the.local.host.name
+??? 250
+<<< 250 Accepted
+>>> bdat 89 last
+>>> To: Susan@random.com
+>>> From: Sam@random.com
+>>> Subject: This is a bodyless test message
+>>> 
+??? 421
+<<< 421 the.local.host.name SMTP incoming data timeout - closing connection.
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> ehlo tester
+??? 250-
+<<< 250-the.local.host.name Hello tester [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-8BITMIME
+??? 250-
+<<< 250-PIPELINING
+??? 250-
+<<< 250-CHUNKING
+??? 250 
+<<< 250 HELP
+>>> mail from:someone@some.domain
+??? 250
+<<< 250 OK
+>>> rcpt to:CALLER@the.local.host.name
+??? 250
+<<< 250 Accepted
+>>> bdat 88
+>>> To: Susan@random.com
+>>> From: Sam@random.com
+>>> Subject: This is a bodyless test message
+>>> 
+??? 250
+<<< 250 88 byte chunk received
+>>> bdat 0
+??? 504
+<<< 504 zero size for BDAT command
+>>> quit
+??? 221
+<<< 221 the.local.host.name closing connection
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> ehlo tester
+??? 250-
+<<< 250-the.local.host.name Hello tester [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-8BITMIME
+??? 250-
+<<< 250-PIPELINING
+??? 250-
+<<< 250-CHUNKING
+??? 250 
+<<< 250 HELP
+>>> mail from:someone@some.domain
+??? 250
+<<< 250 OK
+>>> rcpt to:CALLER@the.local.host.name
+??? 250
+<<< 250 Accepted
+>>> bdat 88
+>>> To: Susan@random.com
+>>> From: Sam@random.com
+>>> Subject: This is a bodyless test message
+>>> 
+??? 250
+<<< 250 88 byte chunk received
+>>> data
+??? 503
+<<< 503 only BDAT permissible after non-LAST BDAT
+>>> RSET
+??? 250
+<<< 250 Reset OK
+>>> EHLO tester
+??? 250-
+<<< 250-the.local.host.name Hello tester [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-8BITMIME
+??? 250-
+<<< 250-PIPELINING
+??? 250-
+<<< 250-CHUNKING
+??? 250 
+<<< 250 HELP
+>>> mail from:someone@some.domain
+??? 250
+<<< 250 OK
+>>> rcpt to:CALLER@the.local.host.name
+??? 250
+<<< 250 Accepted
+>>> bdat 88
+>>> To: Susan@random.com
+>>> From: Sam@random.com
+>>> Subject: This is a bodyless test message
+>>> 
+??? 250
+<<< 250 88 byte chunk received
+>>> data
+??? 503
+<<< 503 only BDAT permissible after non-LAST BDAT
+>>> data
+??? 503
+<<< 503 only RSET accepted now
+>>> quit
+??? 221
+<<< 221 the.local.host.name closing connection
+End of script
index 7643332..55feb8f 100644 (file)
@@ -37,7 +37,7 @@ Succeeded in starting TLS
 ??? 214-
 <<< 214-Commands supported:
 ??? 214
-<<< 214 AUTH HELO EHLO MAIL RCPT DATA NOOP QUIT RSET HELP
+<<< 214 AUTH HELO EHLO MAIL RCPT DATA BDAT NOOP QUIT RSET HELP
 >>> quit
 ??? 221
 <<< 221 myhost.test.ex closing connection