List separator specifiers in router/transport headers_add/remove. Bug 1581
[exim.git] / src / src / smtp_in.c
index 82a805a21a0f03703b6d838aa87c035b27f2a16c..4e4cdb825ec879b3d24fd6b4bd1d79b6ad64d843 100644 (file)
@@ -121,6 +121,7 @@ static BOOL auth_advertised;
 #ifdef SUPPORT_TLS
 static BOOL tls_advertised;
 #endif
+static BOOL dsn_advertised;
 static BOOL esmtp;
 static BOOL helo_required = FALSE;
 static BOOL helo_verify = FALSE;
@@ -214,9 +215,10 @@ static uschar *protocols[] = {
 /* Sanity check and validate optional args to MAIL FROM: envelope */
 enum {
   ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH,
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
   ENV_MAIL_OPT_PRDR,
 #endif
+  ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
   ENV_MAIL_OPT_NULL
   };
 typedef struct {
@@ -229,9 +231,11 @@ static env_mail_type_t env_mail_type_list[] = {
     { US"SIZE",   ENV_MAIL_OPT_SIZE,   TRUE  },
     { US"BODY",   ENV_MAIL_OPT_BODY,   TRUE  },
     { US"AUTH",   ENV_MAIL_OPT_AUTH,   TRUE  },
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
     { US"PRDR",   ENV_MAIL_OPT_PRDR,   FALSE },
 #endif
+    { US"RET",    ENV_MAIL_OPT_RET,    TRUE },
+    { US"ENVID",  ENV_MAIL_OPT_ENVID,  TRUE },
     { US"NULL",   ENV_MAIL_OPT_NULL,   FALSE }
   };
 
@@ -623,10 +627,9 @@ union {
   } v1;
   struct {
     uschar sig[12];
-    uschar ver;
-    uschar cmd;
-    uschar fam;
-    uschar len;
+    uint8_t ver_cmd;
+    uint8_t fam;
+    uint16_t len;
     union {
       struct { /* TCP/UDP over IPv4, len = 12 */
         uint32_t src_addr;
@@ -657,7 +660,7 @@ struct sockaddr_in6 tmpaddr6;
 
 int get_ok = 0;
 int size, ret, fd;
-const char v2sig[13] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x02";
+const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
 uschar *iptype;  /* To display debug info */
 struct timeval tv;
 socklen_t vslen = 0;
@@ -693,16 +696,32 @@ if (ret == -1)
   }
 
 if (ret >= 16 &&
-    memcmp(&hdr.v2, v2sig, 13) == 0)
+    memcmp(&hdr.v2, v2sig, 12) == 0)
   {
+  uint8_t ver, cmd;
+
+  /* May 2014: haproxy combined the version and command into one byte to
+     allow two full bytes for the length field in order to proxy SSL
+     connections.  SSL Proxy is not supported in this version of Exim, but
+     must still seperate values here. */
+  ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
+  cmd = (hdr.v2.ver_cmd & 0x0f);
+
+  if (ver != 0x02)
+    {
+    DEBUG(D_receive) debug_printf("Invalid Proxy Protocol version: %d\n", ver);
+    goto proxyfail;
+    }
   DEBUG(D_receive) debug_printf("Detected PROXYv2 header\n");
+  /* The v2 header will always be 16 bytes per the spec. */
   size = 16 + hdr.v2.len;
   if (ret < size)
     {
-    DEBUG(D_receive) debug_printf("Truncated or too large PROXYv2 header\n");
+    DEBUG(D_receive) debug_printf("Truncated or too large PROXYv2 header (%d/%d)\n",
+                                  ret, size);
     goto proxyfail;
     }
-  switch (hdr.v2.cmd)
+  switch (cmd)
     {
     case 0x01: /* PROXY command */
       switch (hdr.v2.fam)
@@ -772,8 +791,7 @@ if (ret >= 16 &&
       break;
     default:
       DEBUG(D_receive)
-        debug_printf("Unsupported PROXYv2 command: 0x%02x\n",
-                     hdr.v2.cmd);
+        debug_printf("Unsupported PROXYv2 command: 0x%x\n", cmd);
       goto proxyfail;
     }
   }
@@ -1293,7 +1311,8 @@ for (i = 0; i < smtp_ch_index; i++)
 if (s != NULL) s[ptr] = 0; else s = US"";
 log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s",
   host_and_ident(FALSE),
-  readconf_printtime(time(NULL) - smtp_connection_start), s);
+  readconf_printtime( (int) ((long)time(NULL) - (long)smtp_connection_start)),
+  s);
 }
 
 
@@ -1474,6 +1493,11 @@ sender_address_unrewritten = NULL;  /* Set only after verify rewrite */
 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));
+
+/* Reset the DSN flags */
+dsn_ret = 0;
+dsn_envid = NULL;
+
 authenticated_sender = NULL;
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 bmi_run = 0;
@@ -1823,6 +1847,7 @@ tls_in.sni = NULL;
 tls_in.ocsp = OCSP_NOT_REQ;
 tls_advertised = FALSE;
 #endif
+dsn_advertised = FALSE;
 
 /* Reset ACL connection variables */
 
@@ -2104,6 +2129,19 @@ if (!sender_host_unknown)
   set_process_info("handling incoming connection from %s",
     host_and_ident(FALSE));
 
+  /* Expand smtp_receive_timeout, if needed */
+
+  if (smtp_receive_timeout_s)
+    {
+    uschar * exp;
+    if (  !(exp = expand_string(smtp_receive_timeout_s))
+       || !(*exp)
+       || (smtp_receive_timeout = readconf_readtime(exp, 0, FALSE)) < 0
+       )
+      log_write(0, LOG_MAIN|LOG_PANIC,
+       "bad value for smtp_receive_timeout: '%s'", exp ? exp : US"");
+    }
+
   /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
   smtps port for use with older style SSL MTAs. */
 
@@ -2637,7 +2675,7 @@ uschar *what =
 #endif
   (where == ACL_WHERE_PREDATA)? US"DATA" :
   (where == ACL_WHERE_DATA)? US"after DATA" :
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
   (where == ACL_WHERE_PRDR)? US"after DATA PRDR" :
 #endif
   (smtp_cmd_data == NULL)?
@@ -3112,6 +3150,8 @@ while (done <= 0)
   int ptr, size, rc;
   int c, i;
   auth_instance *au;
+  uschar *orcpt = NULL;
+  int flags;
 
   switch(smtp_read_command(TRUE))
     {
@@ -3456,6 +3496,7 @@ while (done <= 0)
     #ifdef SUPPORT_TLS
     tls_advertised = FALSE;
     #endif
+    dsn_advertised = FALSE;
 
     smtp_code = US"250 ";        /* Default response code plus space*/
     if (user_msg == NULL)
@@ -3539,6 +3580,14 @@ while (done <= 0)
         s = string_cat(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);
+        dsn_advertised = TRUE;
+        }
+
       /* Advertise ETRN if there's an ACL checking whether a host is
       permitted to issue it; a check is made when any host actually tries. */
 
@@ -3628,12 +3677,13 @@ while (done <= 0)
         }
       #endif
 
-      #ifdef EXPERIMENTAL_PRDR
+      #ifndef DISABLE_PRDR
       /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
-      if (prdr_enable) {
+      if (prdr_enable)
+        {
         s = string_cat(s, &size, &ptr, smtp_code, 3);
         s = string_cat(s, &size, &ptr, US"-PRDR\r\n", 7);
-      }
+       }
       #endif
 
       /* Finish off the multiline reply with one that is always available. */
@@ -3793,6 +3843,42 @@ while (done <= 0)
           arg_error = TRUE;
           break;
 
+        /* Handle the two DSN options, but only if configured to do so (which
+        will have caused "DSN" to be given in the EHLO response). The code itself
+        is included only if configured in at build time. */
+
+        case ENV_MAIL_OPT_RET:
+          if (dsn_advertised) {
+            /* Check if RET has already been set */
+            if (dsn_ret > 0) {
+              synprot_error(L_smtp_syntax_error, 501, NULL,
+                US"RET can be specified once only");
+              goto COMMAND_LOOP;
+            }
+            dsn_ret = (strcmpic(value, US"HDRS") == 0)? dsn_ret_hdrs :
+                    (strcmpic(value, US"FULL") == 0)? dsn_ret_full : 0;
+            DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret);
+            /* Check for invalid invalid value, and exit with error */
+            if (dsn_ret == 0) {
+              synprot_error(L_smtp_syntax_error, 501, NULL,
+                US"Value for RET is invalid");
+              goto COMMAND_LOOP;
+            }
+          }
+          break;
+        case ENV_MAIL_OPT_ENVID:
+          if (dsn_advertised) {
+            /* Check if the dsn envid has been already set */
+            if (dsn_envid != NULL) {
+              synprot_error(L_smtp_syntax_error, 501, NULL,
+                US"ENVID can be specified once only");
+              goto COMMAND_LOOP;
+            }
+            dsn_envid = string_copy(value);
+            DEBUG(D_receive) debug_printf("DSN_ENVID: %s\n", dsn_envid);
+          }
+          break;
+
         /* Handle the AUTH extension. If the value given is not "<>" and either
         the ACL says "yes" or there is no ACL but the sending host is
         authenticated, we set it up as the authenticated sender. However, if the
@@ -3862,9 +3948,9 @@ while (done <= 0)
             }
             break;
 
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
         case ENV_MAIL_OPT_PRDR:
-          if ( prdr_enable )
+          if (prdr_enable)
             prdr_requested = TRUE;
           break;
 #endif
@@ -3989,29 +4075,32 @@ while (done <= 0)
     when pipelining is not advertised, do another sync check in case the ACL
     delayed and the client started sending in the meantime. */
 
-    if (acl_smtp_mail == NULL) rc = OK; else
+    if (acl_smtp_mail)
       {
       rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
       if (rc == OK && !pipelining_advertised && !check_sync())
         goto SYNC_FAILURE;
       }
+    else
+      rc = OK;
 
     if (rc == OK || rc == DISCARD)
       {
-      if (user_msg == NULL) 
+      if (!user_msg)
         smtp_printf("%s%s%s", US"250 OK",
-                  #ifdef EXPERIMENTAL_PRDR
-                    prdr_requested == TRUE ? US", PRDR Requested" :
-                  #endif
+                  #ifndef DISABLE_PRDR
+                    prdr_requested ? US", PRDR Requested" : US"",
+                 #else
                     US"",
+                  #endif
                     US"\r\n");
       else 
         {
-      #ifdef EXPERIMENTAL_PRDR
-        if ( prdr_requested == TRUE )
+      #ifndef DISABLE_PRDR
+        if (prdr_requested)
            user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested");
       #endif
-        smtp_user_msg(US"250",user_msg);
+        smtp_user_msg(US"250", user_msg);
         }
       smtp_delay_rcpt = smtp_rlr_base;
       recipients_discarded = (rc == DISCARD);
@@ -4067,6 +4156,89 @@ while (done <= 0)
       break;
       }
 
+    /* Set the DSN flags orcpt and dsn_flags from the session*/
+    orcpt = NULL;
+    flags = 0;
+
+    if (esmtp) for(;;)
+      {
+      uschar *name, *value;
+
+      if (!extract_option(&name, &value))
+        break;
+
+      if (dsn_advertised && strcmpic(name, US"ORCPT") == 0)
+        {
+        /* Check whether orcpt has been already set */
+        if (orcpt)
+         {
+          synprot_error(L_smtp_syntax_error, 501, NULL,
+            US"ORCPT can be specified once only");
+          goto COMMAND_LOOP;
+          }
+        orcpt = string_copy(value);
+        DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt);
+        }
+
+      else if (dsn_advertised && strcmpic(name, US"NOTIFY") == 0)
+        {
+        /* Check if the notify flags have been already set */
+        if (flags > 0)
+         {
+          synprot_error(L_smtp_syntax_error, 501, NULL,
+              US"NOTIFY can be specified once only");
+          goto COMMAND_LOOP;
+          }
+        if (strcmpic(value, US"NEVER") == 0)
+         flags |= rf_notify_never;
+       else
+          {
+          uschar *p = value;
+          while (*p != 0)
+            {
+            uschar *pp = p;
+            while (*pp != 0 && *pp != ',') pp++;
+           if (*pp == ',') *pp++ = 0;
+            if (strcmpic(p, US"SUCCESS") == 0)
+             {
+             DEBUG(D_receive) debug_printf("DSN: Setting notify success\n");
+             flags |= rf_notify_success;
+              }
+            else if (strcmpic(p, US"FAILURE") == 0)
+             {
+             DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n");
+             flags |= rf_notify_failure;
+              }
+            else if (strcmpic(p, US"DELAY") == 0)
+             {
+             DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n");
+             flags |= rf_notify_delay;
+              }
+            else
+             {
+              /* Catch any strange values */
+              synprot_error(L_smtp_syntax_error, 501, NULL,
+                US"Invalid value for NOTIFY parameter");
+              goto COMMAND_LOOP;
+              }
+            p = pp;
+            }
+            DEBUG(D_receive) debug_printf("DSN Flags: %x\n", flags);
+          }
+        }
+
+      /* Unknown option. Stick back the terminator characters and break
+      the loop. An error for a malformed address will occur. */
+
+      else
+        {
+        DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value);
+        name[-1] = ' ';
+        value[-1] = '=';
+        break;
+        }
+      }
+
     /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
     as a recipient address */
 
@@ -4180,6 +4352,18 @@ while (done <= 0)
       if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
         else smtp_user_msg(US"250", user_msg);
       receive_add_recipient(recipient, -1);
+      /* Set the dsn flags in the recipients_list */
+      if (orcpt != NULL)
+        recipients_list[recipients_count-1].orcpt = orcpt;
+      else
+        recipients_list[recipients_count-1].orcpt = NULL;
+
+      if (flags != 0)
+        recipients_list[recipients_count-1].dsn_flags = flags;
+      else
+        recipients_list[recipients_count-1].dsn_flags = 0;
+      DEBUG(D_receive) debug_printf("DSN: orcpt: %s  flags: %d\n", recipients_list[recipients_count-1].orcpt, recipients_list[recipients_count-1].dsn_flags);
       }
 
     /* The recipient was discarded */
@@ -4264,7 +4448,7 @@ while (done <= 0)
     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;