recive smtp command
authorJeremy Harris <jgh146exb@wizmail.org>
Mon, 11 Jul 2016 22:36:45 +0000 (23:36 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 24 Jul 2016 16:46:23 +0000 (17:46 +0100)
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

index 5ff0f844b8251f828b130b1e7645b5c7f39ff8b2..2e3fe4074445549d0bbc5779bf8b362fff00fa95 100644 (file)
@@ -492,7 +492,11 @@ int     check_log_space        = 0;
 BOOL    check_rfc2047_length   = TRUE;
 int     check_spool_inodes     = 0;
 int     check_spool_space      = 0;
+
 uschar *chunking_advertise_hosts = US"*";
+unsigned chunking_datasize     = 0;
+chunking_state_t chunking_state= CHUNKING_NOT_OFFERED;
+
 uschar *client_authenticator   = NULL;
 uschar *client_authenticated_id = NULL;
 uschar *client_authenticated_sender = NULL;
index e5bdec4a274765267c5463cadbce4883d49db890..184a144f27aabf744ad40c2057a5cd3e852e5ce5 100644 (file)
@@ -268,6 +268,8 @@ extern BOOL    check_rfc2047_length;   /* Check RFC 2047 encoded string length *
 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 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 */
 extern uschar *client_authenticated_sender; /* AUTH option to SMTP MAIL FROM (not yet used) */
index 53abeb5c235447cdf313c4786b7df77686d0566d..dbc49f01e2cbf95ed1830f0e7060153a26d0daec 100644 (file)
@@ -787,7 +787,8 @@ most recent SMTP commands. Must be kept in step with the list of names in
 smtp_in.c that is used for creating the smtp_no_mail logging action. SCH_NONE
 is "empty". */
 
-enum { SCH_NONE, SCH_AUTH, SCH_DATA, SCH_EHLO, SCH_ETRN, SCH_EXPN, SCH_HELO,
+enum { SCH_NONE, SCH_AUTH, SCH_DATA, SCH_BDAT,
+       SCH_EHLO, SCH_ETRN, SCH_EXPN, SCH_HELO,
        SCH_HELP, SCH_MAIL, SCH_NOOP, SCH_QUIT, SCH_RCPT, SCH_RSET, SCH_STARTTLS,
        SCH_VRFY };
 
index 52e041c9019619d3394745a2607837180ff0f5bf..f6bdf47420532bc3e5e3b9e34ea97b87561dece9 100644 (file)
@@ -770,6 +770,9 @@ 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)
@@ -1613,6 +1616,7 @@ 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
@@ -2831,6 +2835,7 @@ if (filter_test != FTEST_NONE)
   return message_ended == END_DOT;
   }
 
+/*XXX CHUNKING: need to cancel cutthrough under BDAT, for now */
 /* 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.
@@ -2928,6 +2933,7 @@ 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);
     receive_linecount++;                /* The terminating "." line */
     }
index bc53166e55ccd0dd6c0fff8f901ddad35f970d77..b00537eb58af560023a28135dfb9e34a170285d6 100644 (file)
@@ -73,6 +73,7 @@ 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 */
 
@@ -182,6 +183,7 @@ static smtp_cmd_list cmd_list[] = {
   { "mail from:", sizeof("mail from:")-1, MAIL_CMD, TRUE,  TRUE  },
   { "rcpt to:",   sizeof("rcpt to:")-1,   RCPT_CMD, TRUE,  TRUE  },
   { "data",       sizeof("data")-1,       DATA_CMD, FALSE, TRUE  },
+  { "bdat",       sizeof("bdat")-1,       BDAT_CMD, TRUE,  TRUE  },
   { "quit",       sizeof("quit")-1,       QUIT_CMD, FALSE, TRUE  },
   { "noop",       sizeof("noop")-1,       NOOP_CMD, TRUE,  FALSE },
   { "etrn",       sizeof("etrn")-1,       ETRN_CMD, TRUE,  FALSE },
@@ -205,9 +207,9 @@ It must be kept in step with the SCH_xxx enumerations. */
 
 static uschar *smtp_names[] =
   {
-  US"NONE", US"AUTH", US"DATA", US"EHLO", US"ETRN", US"EXPN", US"HELO",
-  US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET", US"STARTTLS",
-  US"VRFY" };
+  US"NONE", US"AUTH", US"DATA", US"BDAT", US"EHLO", US"ETRN", US"EXPN",
+  US"HELO", US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET",
+  US"STARTTLS", US"VRFY" };
 
 static uschar *protocols_local[] = {
   US"local-smtp",        /* HELO */
@@ -1525,6 +1527,7 @@ 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;
@@ -3766,17 +3769,20 @@ while (done <= 0)
        if (!first) s = string_catn(s, &size, &ptr, US"\r\n", 2);
        }
 
-      /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
-      if it has been included in the binary, and the host matches
-      tls_advertise_hosts. We must *not* advertise if we are already in a
-      secure connection. */
+      /* RFC 3030 CHUNKING */
 
       if (verify_check_host(&chunking_advertise_hosts) != FAIL)
         {
         s = string_catn(s, &size, &ptr, smtp_code, 3);
         s = string_catn(s, &size, &ptr, US"-CHUNKING\r\n", 11);
+       chunking_state = CHUNKING_OFFERED;
         }
 
+      /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
+      if it has been included in the binary, and the host matches
+      tls_advertise_hosts. We must *not* advertise if we are already in a
+      secure connection. */
+
 #ifdef SUPPORT_TLS
       if (tls_in.active < 0 &&
           verify_check_host(&tls_advertise_hosts) != FAIL)
@@ -4524,8 +4530,38 @@ while (done <= 0)
     (often indicating some kind of system error), it is helpful to include it
     with the DATA rejection (an idea suggested by Tony Finch). */
 
+    case BDAT_CMD:
+    HAD(SCH_BDAT);
+      {
+      int n;
+
+      if (chunking_state != CHUNKING_OFFERED)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"BDAT command used when CHUNKING not advertised");
+       break;
+       }
+
+      /* grab size, endmarker */
+
+      if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"missing size for BDAT command");
+       break;
+       }
+      chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0
+       ? CHUNKING_LAST : CHUNKING_ACTIVE;
+
+      DEBUG(D_any)
+        debug_printf("chunking state %d\n", (int)chunking_state);
+      goto DATA_BDAT;
+      }
+
     case DATA_CMD:
     HAD(SCH_DATA);
+
+    DATA_BDAT:         /* Common code for DATA and BDAT */
     if (!discarded && recipients_count <= 0)
       {
       if (rcpt_smtp_response_same && rcpt_smtp_response != NULL)
@@ -4540,10 +4576,13 @@ while (done <= 0)
         smtp_respond(code, 3, FALSE, rcpt_smtp_response);
         }
       if (pipelining_advertised && last_was_rcpt)
-        smtp_printf("503 Valid RCPT command must precede DATA\r\n");
+        smtp_printf("503 Valid RCPT command must precede %s\r\n",
+         smtp_names[smtp_connection_had[smtp_ch_index-1]]);
       else
         done = synprot_error(L_smtp_protocol_error, 503, NULL,
-          US"valid RCPT command must precede DATA");
+         smtp_connection_had[smtp_ch_index-1] == SCH_DATA
+         ? US"valid RCPT command must precede DATA"
+         : US"valid RCPT command must precede BDAT");
       break;
       }
 
@@ -4555,11 +4594,21 @@ while (done <= 0)
       break;
       }
 
+    /* No go-ahead output for BDAT */
+
+    if (smtp_connection_had[smtp_ch_index-1] == SCH_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
+    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;
@@ -4702,7 +4751,7 @@ while (done <= 0)
     if (receive_smtp_buffered())
       {
       DEBUG(D_any)
-        debug_printf("Non-empty input buffer after STARTTLS; naive attack?");
+        debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
       if (tls_in.active < 0)
         smtp_inend = smtp_inptr = smtp_inbuffer;
       /* and if TLS is already active, tls_server_start() should fail */
index 1dd363a5c29d39dd2c89c7bfeda066e8fb565331..2b449a6485e279fe22660589354ef210f8f838de 100644 (file)
@@ -51,6 +51,8 @@ typedef struct ugid_block {
   BOOL    initgroups;
 } ugid_block;
 
+typedef enum {CHUNKING_NOT_OFFERED, 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
 host addresses is done using this structure. */