recive smtp command
[exim.git] / src / src / smtp_in.c
index 98968f95dd349d34826e7d9639e7eec2f2385665..b00537eb58af560023a28135dfb9e34a170285d6 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
@@ -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 */
@@ -1520,24 +1522,22 @@ sender_verified_list = NULL;        /* No senders verified */
 memset(sender_address_cache, 0, sizeof(sender_address_cache));
 memset(sender_domain_cache, 0, sizeof(sender_domain_cache));
 
-#ifndef DISABLE_PRDR
-prdr_requested = FALSE;
-#endif
-
-/* Reset the DSN flags */
-dsn_ret = 0;
-dsn_envid = NULL;
-
 authenticated_sender = NULL;
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
+chunking_state = CHUNKING_NOT_OFFERED;
 #ifndef DISABLE_DKIM
 dkim_signers = NULL;
 dkim_disable_verify = FALSE;
 dkim_collect_input = FALSE;
 #endif
+dsn_ret = 0;
+dsn_envid = NULL;
+#ifndef DISABLE_PRDR
+prdr_requested = FALSE;
+#endif
 #ifdef EXPERIMENTAL_SPF
 spf_header_comment = NULL;
 spf_received = NULL;
@@ -1729,16 +1729,15 @@ while (done <= 0)
     /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
     recipient address */
 
-    recipient = ((rewrite_existflags & rewrite_smtp) != 0)?
-      rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
-        global_rewrite_rules) : smtp_cmd_data;
+    recipient = rewrite_existflags & rewrite_smtp
+      ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+                   global_rewrite_rules)
+      : smtp_cmd_data;
 
-    /* rfc821_domains = TRUE; << no longer needed */
     recipient = parse_extract_address(recipient, &errmess, &start, &end,
       &recipient_domain, FALSE);
-    /* rfc821_domains = FALSE; << no longer needed */
 
-    if (recipient == NULL)
+    if (!recipient)
       /* The function moan_smtp_batch() does not return. */
       moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
 
@@ -2394,20 +2393,20 @@ do       /* At least once, in case we have an empty string */
   {
   int len;
   uschar *linebreak = Ustrchr(p, '\n');
-  ss = string_cat(ss, &size, &ptr, code, 3);
+  ss = string_catn(ss, &size, &ptr, code, 3);
   if (linebreak == NULL)
     {
     len = Ustrlen(p);
-    ss = string_cat(ss, &size, &ptr, US" ", 1);
+    ss = string_catn(ss, &size, &ptr, US" ", 1);
     }
   else
     {
     len = linebreak - p;
-    ss = string_cat(ss, &size, &ptr, US"-", 1);
+    ss = string_catn(ss, &size, &ptr, US"-", 1);
     }
-  ss = string_cat(ss, &size, &ptr, esc, esclen);
-  ss = string_cat(ss, &size, &ptr, p, len);
-  ss = string_cat(ss, &size, &ptr, US"\r\n", 2);
+  ss = string_catn(ss, &size, &ptr, esc, esclen);
+  ss = string_catn(ss, &size, &ptr, p, len);
+  ss = string_catn(ss, &size, &ptr, US"\r\n", 2);
   p += len;
   if (linebreak != NULL) p++;
   }
@@ -2837,14 +2836,18 @@ is closing if required and return 2.  */
 if (log_reject_target != 0)
   {
 #ifdef SUPPORT_TLS
-  uschar * s = s_tlslog(NULL, NULL, NULL);
-  if (!s) s = US"";
+  uschar * tls = s_tlslog(NULL, NULL, NULL);
+  if (!tls) tls = US"";
 #else
-  uschar * s = US"";
+  uschar * tls = US"";
 #endif
-  log_write(0, log_reject_target, "%s%s %s%srejected %s%s",
-    host_and_ident(TRUE), s,
-    sender_info, (rc == FAIL)? US"" : US"temporarily ", what, log_msg);
+  log_write(0, log_reject_target, "%s%s%s %s%srejected %s%s",
+    LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
+    host_and_ident(TRUE),
+    tls,
+    sender_info,
+    rc == FAIL ? US"" : US"temporarily ",
+    what, log_msg);
   }
 
 if (!drop) return 0;
@@ -3211,6 +3214,31 @@ return rc;
 
 
 
+
+
+static int
+qualify_recipient(uschar ** recipient, uschar * smtp_cmd_data, uschar * tag)
+{
+int rd;
+if (allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
+  {
+  DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+    *recipient);
+  rd = Ustrlen(recipient) + 1;
+  *recipient = rewrite_address_qualify(*recipient, TRUE);
+  return rd;
+  }
+smtp_printf("501 %s: recipient address must contain a domain\r\n",
+  smtp_cmd_data);
+log_write(L_smtp_syntax_error,
+  LOG_MAIN|LOG_REJECT, "unqualified %s rejected: <%s> %s%s",
+  tag, *recipient, host_and_ident(TRUE), host_lookup_msg);
+return 0;
+}
+
+
+
+
 /*************************************************
 *       Initialize for SMTP incoming message     *
 *************************************************/
@@ -3592,10 +3620,9 @@ while (done <= 0)
 
       if (sender_host_address != NULL)
         {
-        s = string_cat(s, &size, &ptr, US" [", 2);
-        s = string_cat(s, &size, &ptr, sender_host_address,
-          Ustrlen(sender_host_address));
-        s = string_cat(s, &size, &ptr, US"]", 1);
+        s = string_catn(s, &size, &ptr, US" [", 2);
+        s = string_cat (s, &size, &ptr, sender_host_address);
+        s = string_catn(s, &size, &ptr, US"]", 1);
         }
       }
 
@@ -3619,7 +3646,7 @@ while (done <= 0)
       size = ptr + 1;
       }
 
-    s = string_cat(s, &size, &ptr, US"\r\n", 2);
+    s = string_catn(s, &size, &ptr, US"\r\n", 2);
 
     /* If we received EHLO, we must create a multiline response which includes
     the functions supported. */
@@ -3638,12 +3665,12 @@ while (done <= 0)
         {
         sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code,
           thismessage_size_limit);
-        s = string_cat(s, &size, &ptr, big_buffer, Ustrlen(big_buffer));
+        s = string_cat(s, &size, &ptr, big_buffer);
         }
       else
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-SIZE\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-SIZE\r\n", 7);
         }
 
       /* Exim does not do protocol conversion or data conversion. It is 8-bit
@@ -3655,15 +3682,15 @@ while (done <= 0)
 
       if (accept_8bitmime)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-8BITMIME\r\n", 11);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-8BITMIME\r\n", 11);
         }
 
       /* Advertise DSN support if configured to do so. */
       if (verify_check_host(&dsn_advertise_hosts) != FAIL)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-DSN\r\n", 6);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-DSN\r\n", 6);
         dsn_advertised = TRUE;
         }
 
@@ -3672,8 +3699,8 @@ while (done <= 0)
 
       if (acl_smtp_etrn != NULL)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-ETRN\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-ETRN\r\n", 7);
         }
 
       /* Advertise EXPN if there's an ACL checking whether a host is
@@ -3681,8 +3708,8 @@ while (done <= 0)
 
       if (acl_smtp_expn != NULL)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-EXPN\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-EXPN\r\n", 7);
         }
 
       /* Exim is quite happy with pipelining, so let the other end know that
@@ -3691,8 +3718,8 @@ while (done <= 0)
       if (pipelining_enable &&
           verify_check_host(&pipelining_advertise_hosts) == OK)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-PIPELINING\r\n", 13);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-PIPELINING\r\n", 13);
         sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
         pipelining_advertised = TRUE;
         }
@@ -3725,24 +3752,32 @@ while (done <= 0)
            int saveptr;
            if (first)
              {
-             s = string_cat(s, &size, &ptr, smtp_code, 3);
-             s = string_cat(s, &size, &ptr, US"-AUTH", 5);
+             s = string_catn(s, &size, &ptr, smtp_code, 3);
+             s = string_catn(s, &size, &ptr, US"-AUTH", 5);
              first = FALSE;
              auth_advertised = TRUE;
              }
            saveptr = ptr;
-           s = string_cat(s, &size, &ptr, US" ", 1);
-           s = string_cat(s, &size, &ptr, au->public_name,
-             Ustrlen(au->public_name));
+           s = string_catn(s, &size, &ptr, US" ", 1);
+           s = string_cat (s, &size, &ptr, au->public_name);
            while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]);
            au->advertised = TRUE;
            }
          else
            au->advertised = FALSE;
 
-       if (!first) s = string_cat(s, &size, &ptr, US"\r\n", 2);
+       if (!first) s = string_catn(s, &size, &ptr, US"\r\n", 2);
        }
 
+      /* 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
@@ -3752,8 +3787,8 @@ while (done <= 0)
       if (tls_in.active < 0 &&
           verify_check_host(&tls_advertise_hosts) != FAIL)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-STARTTLS\r\n", 11);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-STARTTLS\r\n", 11);
         tls_advertised = TRUE;
         }
 #endif
@@ -3762,8 +3797,8 @@ while (done <= 0)
       /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
       if (prdr_enable)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-PRDR\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-PRDR\r\n", 7);
        }
 #endif
 
@@ -3771,16 +3806,16 @@ while (done <= 0)
       if (  accept_8bitmime
          && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
        {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-SMTPUTF8\r\n", 11);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-SMTPUTF8\r\n", 11);
         smtputf8_advertised = TRUE;
        }
 #endif
 
       /* Finish off the multiline reply with one that is always available. */
 
-      s = string_cat(s, &size, &ptr, smtp_code, 3);
-      s = string_cat(s, &size, &ptr, US" HELP\r\n", 7);
+      s = string_catn(s, &size, &ptr, smtp_code, 3);
+      s = string_catn(s, &size, &ptr, US" HELP\r\n", 7);
       }
 
     /* Terminate the string (for debug), write it, and note that HELO/EHLO
@@ -4099,13 +4134,11 @@ while (done <= 0)
                    global_rewrite_rules)
       : smtp_cmd_data;
 
-    /* rfc821_domains = TRUE; << no longer needed */
     raw_sender =
       parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
         TRUE);
-    /* rfc821_domains = FALSE; << no longer needed */
 
-    if (raw_sender == NULL)
+    if (!raw_sender)
       {
       done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
       break;
@@ -4350,16 +4383,13 @@ while (done <= 0)
     /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
     as a recipient address */
 
-    recipient = ((rewrite_existflags & rewrite_smtp) != 0)?
-      rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
-        global_rewrite_rules) : smtp_cmd_data;
-
-    /* rfc821_domains = TRUE; << no longer needed */
-    recipient = parse_extract_address(recipient, &errmess, &start, &end,
-      &recipient_domain, FALSE);
-    /* rfc821_domains = FALSE; << no longer needed */
+    recipient = rewrite_existflags & rewrite_smtp
+      ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+         global_rewrite_rules)
+      : smtp_cmd_data;
 
-    if (recipient == NULL)
+    if (!(recipient = parse_extract_address(recipient, &errmess, &start, &end,
+      &recipient_domain, FALSE)))
       {
       done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
       rcpt_fail_count++;
@@ -4378,27 +4408,12 @@ while (done <= 0)
     we must always qualify this address, regardless. */
 
     if (recipient_domain == 0)
-      {
-      if (allow_unqualified_recipient ||
-          strcmpic(recipient, US"postmaster") == 0)
-        {
-        DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
-          recipient);
-        recipient_domain = Ustrlen(recipient) + 1;
-        recipient = rewrite_address_qualify(recipient, TRUE);
-        }
-      else
+      if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
+                                 US"recipient")))
         {
         rcpt_fail_count++;
-        smtp_printf("501 %s: recipient address must contain a domain\r\n",
-          smtp_cmd_data);
-        log_write(L_smtp_syntax_error,
-          LOG_MAIN|LOG_REJECT, "unqualified recipient rejected: "
-          "<%s> %s%s", recipient, host_and_ident(TRUE),
-          host_lookup_msg);
         break;
         }
-      }
 
     /* Check maximum allowed */
 
@@ -4515,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)
@@ -4531,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;
       }
 
@@ -4546,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;
@@ -4584,18 +4642,26 @@ while (done <= 0)
 
       HAD(SCH_VRFY);
 
-      if(!(address = parse_extract_address(smtp_cmd_data, &errmess, &start, &end,
-           &recipient_domain, FALSE)))
+      if (!(address = parse_extract_address(smtp_cmd_data, &errmess,
+            &start, &end, &recipient_domain, FALSE)))
+       {
        smtp_printf("501 %s\r\n", errmess);
+       break;
+       }
+
+      if (recipient_domain == 0)
+       if (!(recipient_domain = qualify_recipient(&address, smtp_cmd_data,
+                                   US"verify")))
+         break;
 
-      else if ((rc = acl_check(ACL_WHERE_VRFY, address, acl_smtp_vrfy,
+      if ((rc = acl_check(ACL_WHERE_VRFY, address, acl_smtp_vrfy,
                    &user_msg, &log_msg)) != OK)
        done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg);
       else
        {
-       uschar *s = NULL;
+       uschar * s = NULL;
+       address_item * addr = deliver_make_addr(address, FALSE);
 
-       address_item *addr = deliver_make_addr(address, FALSE);
        switch(verify_address(addr, NULL, vopt_is_recipient | vopt_qualify, -1,
               -1, -1, NULL, NULL, NULL))
          {
@@ -4685,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 */
@@ -4949,8 +5015,10 @@ while (done <= 0)
         break;
         }
       etrn_command = US"exim -R";
-      argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE, 2, US"-R",
-        smtp_cmd_data);
+      argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE,
+        *queue_name ? 4 : 2,
+       US"-R", smtp_cmd_data,
+       US"-MCG", queue_name);
       }
 
     /* If we are host-testing, don't actually do anything. */