Increase limit on SMTP confirmation message copy size. Bug 1572
[users/jgh/exim.git] / src / src / smtp_in.c
index 1d62bab05e11b3c70019d09388844918f401d5b2..58ca02dbe3496cc9de40441f19e9e002333859d9 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2013 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
@@ -121,6 +121,7 @@ static BOOL auth_advertised;
 #ifdef SUPPORT_TLS
 static BOOL tls_advertised;
 #endif
 #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;
 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,
 /* 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_PRDR,
 #endif
+  ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
   ENV_MAIL_OPT_NULL
   };
 typedef struct {
   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  },
     { 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"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 }
   };
 
     { US"NULL",   ENV_MAIL_OPT_NULL,   FALSE }
   };
 
@@ -602,22 +606,6 @@ return proxy_session;
 }
 
 
 }
 
 
-/*************************************************
-*           Flush waiting input string           *
-*************************************************/
-static void
-flush_input()
-{
-int rc;
-
-rc = smtp_getc();
-while (rc != '\n')             /* End of input string */
-  {
-  rc = smtp_getc();
-  }
-}
-
-
 /*************************************************
 *         Setup host for proxy protocol          *
 *************************************************/
 /*************************************************
 *         Setup host for proxy protocol          *
 *************************************************/
@@ -639,10 +627,9 @@ union {
   } v1;
   struct {
     uschar sig[12];
   } 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;
     union {
       struct { /* TCP/UDP over IPv4, len = 12 */
         uint32_t src_addr;
@@ -664,12 +651,18 @@ union {
   } v2;
 } hdr;
 
   } v2;
 } hdr;
 
+/* Temp variables used in PPv2 address:port parsing */
+uint16_t tmpport;
+char tmpip[INET_ADDRSTRLEN];
+struct sockaddr_in tmpaddr;
+char tmpip6[INET6_ADDRSTRLEN];
+struct sockaddr_in6 tmpaddr6;
+
+int get_ok = 0;
 int size, ret, fd;
 int size, ret, fd;
-uschar *tmpip;
-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;
 uschar *iptype;  /* To display debug info */
 struct timeval tv;
-int get_ok = 0;
 socklen_t vslen = 0;
 struct timeval tvtmp;
 
 socklen_t vslen = 0;
 struct timeval tvtmp;
 
@@ -690,7 +683,9 @@ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,
 
 do
   {
 
 do
   {
-  ret = recv(fd, &hdr, sizeof(hdr), MSG_PEEK);
+  /* The inbound host was declared to be a Proxy Protocol host, so
+     don't do a PEEK into the data, actually slurp it up. */
+  ret = recv(fd, &hdr, sizeof(hdr), 0);
   }
   while (ret == -1 && errno == EINTR);
 
   }
   while (ret == -1 && errno == EINTR);
 
@@ -701,34 +696,93 @@ if (ret == -1)
   }
 
 if (ret >= 16 &&
   }
 
 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");
   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)
     {
   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;
     }
     goto proxyfail;
     }
-  switch (hdr.v2.cmd)
+  switch (cmd)
     {
     case 0x01: /* PROXY command */
       switch (hdr.v2.fam)
         {
     {
     case 0x01: /* PROXY command */
       switch (hdr.v2.fam)
         {
-        case 0x11:  /* TCPv4 */
-          tmpip = string_sprintf("%s", hdr.v2.addr.ip4.src_addr);
-          if (!string_is_ip_address(tmpip,NULL))
+        case 0x11:  /* TCPv4 address type */
+          iptype = US"IPv4";
+          tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.src_addr;
+          inet_ntop(AF_INET, &(tmpaddr.sin_addr), (char *)&tmpip, sizeof(tmpip));
+          if (!string_is_ip_address(US tmpip,NULL))
+            {
+            DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype);
             return ERRNO_PROXYFAIL;
             return ERRNO_PROXYFAIL;
-          sender_host_address = tmpip;
-          sender_host_port    = hdr.v2.addr.ip4.src_port;
+            }
+          proxy_host_address  = sender_host_address;
+          sender_host_address = string_copy(US tmpip);
+          tmpport             = ntohs(hdr.v2.addr.ip4.src_port);
+          proxy_host_port     = sender_host_port;
+          sender_host_port    = tmpport;
+          /* Save dest ip/port */
+          tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.dst_addr;
+          inet_ntop(AF_INET, &(tmpaddr.sin_addr), (char *)&tmpip, sizeof(tmpip));
+          if (!string_is_ip_address(US tmpip,NULL))
+            {
+            DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype);
+            return ERRNO_PROXYFAIL;
+            }
+          proxy_target_address = string_copy(US tmpip);
+          tmpport              = ntohs(hdr.v2.addr.ip4.dst_port);
+          proxy_target_port    = tmpport;
           goto done;
           goto done;
-        case 0x21:  /* TCPv6 */
-          tmpip = string_sprintf("%s", hdr.v2.addr.ip6.src_addr);
-          if (!string_is_ip_address(tmpip,NULL))
+        case 0x21:  /* TCPv6 address type */
+          iptype = US"IPv6";
+          memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.src_addr, 16);
+          inet_ntop(AF_INET6, &(tmpaddr6.sin6_addr), (char *)&tmpip6, sizeof(tmpip6));
+          if (!string_is_ip_address(US tmpip6,NULL))
+            {
+            DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype);
             return ERRNO_PROXYFAIL;
             return ERRNO_PROXYFAIL;
-          sender_host_address = tmpip;
-          sender_host_port    = hdr.v2.addr.ip6.src_port;
+            }
+          proxy_host_address  = sender_host_address;
+          sender_host_address = string_copy(US tmpip6);
+          tmpport             = ntohs(hdr.v2.addr.ip6.src_port);
+          proxy_host_port     = sender_host_port;
+          sender_host_port    = tmpport;
+          /* Save dest ip/port */
+          memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.dst_addr, 16);
+          inet_ntop(AF_INET6, &(tmpaddr6.sin6_addr), (char *)&tmpip6, sizeof(tmpip6));
+          if (!string_is_ip_address(US tmpip6,NULL))
+            {
+            DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype);
+            return ERRNO_PROXYFAIL;
+            }
+          proxy_target_address = string_copy(US tmpip6);
+          tmpport              = ntohs(hdr.v2.addr.ip6.dst_port);
+          proxy_target_port    = tmpport;
           goto done;
           goto done;
+        default:
+          DEBUG(D_receive)
+            debug_printf("Unsupported PROXYv2 connection type: 0x%02x\n",
+                         hdr.v2.fam);
+          goto proxyfail;
         }
       /* Unsupported protocol, keep local connection address */
       break;
         }
       /* Unsupported protocol, keep local connection address */
       break;
@@ -736,7 +790,8 @@ if (ret >= 16 &&
       /* Keep local connection address for LOCAL */
       break;
     default:
       /* Keep local connection address for LOCAL */
       break;
     default:
-      DEBUG(D_receive) debug_printf("Unsupported PROXYv2 command\n");
+      DEBUG(D_receive)
+        debug_printf("Unsupported PROXYv2 command: 0x%x\n", cmd);
       goto proxyfail;
     }
   }
       goto proxyfail;
     }
   }
@@ -816,7 +871,7 @@ else if (ret >= 8 &&
       debug_printf("Proxy dest arg is not an %s address\n", iptype);
     goto proxyfail;
     }
       debug_printf("Proxy dest arg is not an %s address\n", iptype);
     goto proxyfail;
     }
-  /* Should save dest ip somewhere? */
+  proxy_target_address = p;
   p = sp + 1;
   if ((sp = Ustrchr(p, ' ')) == NULL)
     {
   p = sp + 1;
   if ((sp = Ustrchr(p, ' ')) == NULL)
     {
@@ -846,29 +901,27 @@ else if (ret >= 8 &&
       debug_printf("Proxy dest port '%s' not an integer\n", p);
     goto proxyfail;
     }
       debug_printf("Proxy dest port '%s' not an integer\n", p);
     goto proxyfail;
     }
-  /* Should save dest port somewhere? */
+  proxy_target_port = tmp_port;
   /* Already checked for /r /n above. Good V1 header received. */
   goto done;
   }
 else
   {
   /* Wrong protocol */
   /* Already checked for /r /n above. Good V1 header received. */
   goto done;
   }
 else
   {
   /* Wrong protocol */
-  DEBUG(D_receive) debug_printf("Wrong proxy protocol specified\n");
+  DEBUG(D_receive) debug_printf("Invalid proxy protocol version negotiation\n");
   goto proxyfail;
   }
 
 proxyfail:
 restore_socket_timeout(fd, get_ok, tvtmp, vslen);
 /* Don't flush any potential buffer contents. Any input should cause a
   goto proxyfail;
   }
 
 proxyfail:
 restore_socket_timeout(fd, get_ok, tvtmp, vslen);
 /* Don't flush any potential buffer contents. Any input should cause a
-synchronization failure or we just don't want to speak SMTP to them */
+   synchronization failure */
 return FALSE;
 
 done:
 restore_socket_timeout(fd, get_ok, tvtmp, vslen);
 return FALSE;
 
 done:
 restore_socket_timeout(fd, get_ok, tvtmp, vslen);
-flush_input();
 DEBUG(D_receive)
 DEBUG(D_receive)
-  debug_printf("Valid %s sender from Proxy Protocol header\n",
-               iptype);
+  debug_printf("Valid %s sender from Proxy Protocol header\n", iptype);
 return proxy_session;
 }
 #endif
 return proxy_session;
 }
 #endif
@@ -1163,6 +1216,45 @@ return string_sprintf("SMTP connection from %s", hostname);
 
 
 
 
 
 
+#ifdef SUPPORT_TLS
+/* Append TLS-related information to a log line
+
+Arguments:
+  s            String under construction: allocated string to extend, or NULL
+  sizep                Pointer to current allocation size (update on return), or NULL
+  ptrp         Pointer to index for new entries in string (update on return), or NULL
+
+Returns:       Allocated string or NULL
+*/
+static uschar *
+s_tlslog(uschar * s, int * sizep, int * ptrp)
+{
+  int size = sizep ? *sizep : 0;
+  int ptr = ptrp ? *ptrp : 0;
+
+  if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
+    s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher);
+  if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
+       tls_in.cipher != NULL)
+    s = string_append(s, &size, &ptr, 2, US" CV=",
+      tls_in.certificate_verified? "yes":"no");
+  if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
+    s = string_append(s, &size, &ptr, 3, US" DN=\"",
+      string_printing(tls_in.peerdn), US"\"");
+  if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
+    s = string_append(s, &size, &ptr, 3, US" SNI=\"",
+      string_printing(tls_in.sni), US"\"");
+
+  if (s)
+    {
+    s[ptr] = '\0';
+    if (sizep) *sizep = size;
+    if (ptrp) *ptrp = ptr;
+    }
+  return s;
+}
+#endif
+
 /*************************************************
 *      Log lack of MAIL if so configured         *
 *************************************************/
 /*************************************************
 *      Log lack of MAIL if so configured         *
 *************************************************/
@@ -1195,18 +1287,7 @@ if (sender_host_authenticated != NULL)
   }
 
 #ifdef SUPPORT_TLS
   }
 
 #ifdef SUPPORT_TLS
-if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
-  s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher);
-if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
-     tls_in.cipher != NULL)
-  s = string_append(s, &size, &ptr, 2, US" CV=",
-    tls_in.certificate_verified? "yes":"no");
-if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
-  s = string_append(s, &size, &ptr, 3, US" DN=\"",
-    string_printing(tls_in.peerdn), US"\"");
-if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
-  s = string_append(s, &size, &ptr, 3, US" SNI=\"",
-    string_printing(tls_in.sni), US"\"");
+s = s_tlslog(s, &size, &ptr);
 #endif
 
 sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
 #endif
 
 sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
@@ -1230,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),
 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);
 }
 
 
 }
 
 
@@ -1411,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));
 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;
 authenticated_sender = NULL;
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 bmi_run = 0;
@@ -1755,8 +1842,12 @@ authenticated_by = NULL;
 
 #ifdef SUPPORT_TLS
 tls_in.cipher = tls_in.peerdn = NULL;
 
 #ifdef SUPPORT_TLS
 tls_in.cipher = tls_in.peerdn = NULL;
+tls_in.ourcert = tls_in.peercert = NULL;
+tls_in.sni = NULL;
+tls_in.ocsp = OCSP_NOT_REQ;
 tls_advertised = FALSE;
 #endif
 tls_advertised = FALSE;
 #endif
+dsn_advertised = FALSE;
 
 /* Reset ACL connection variables */
 
 
 /* Reset ACL connection variables */
 
@@ -2038,6 +2129,19 @@ if (!sender_host_unknown)
   set_process_info("handling incoming connection from %s",
     host_and_ident(FALSE));
 
   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. */
 
   /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
   smtps port for use with older style SSL MTAs. */
 
@@ -2571,7 +2675,7 @@ uschar *what =
 #endif
   (where == ACL_WHERE_PREDATA)? US"DATA" :
   (where == ACL_WHERE_DATA)? US"after DATA" :
 #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)?
   (where == ACL_WHERE_PRDR)? US"after DATA PRDR" :
 #endif
   (smtp_cmd_data == NULL)?
@@ -2694,9 +2798,17 @@ the connection is not forcibly to be dropped, return 0. Otherwise, log why it
 is closing if required and return 2.  */
 
 if (log_reject_target != 0)
 is closing if required and return 2.  */
 
 if (log_reject_target != 0)
-  log_write(0, log_reject_target, "%s %s%srejected %s%s",
-    host_and_ident(TRUE),
+  {
+#ifdef SUPPORT_TLS
+  uschar * s = s_tlslog(NULL, NULL, NULL);
+  if (!s) s = US"";
+#else
+  uschar * s = 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);
     sender_info, (rc == FAIL)? US"" : US"temporarily ", what, log_msg);
+  }
 
 if (!drop) return 0;
 
 
 if (!drop) return 0;
 
@@ -3020,7 +3132,7 @@ value. The values are 2 larger than the required yield of the function. */
 
 while (done <= 0)
   {
 
 while (done <= 0)
   {
-  uschar **argv;
+  const uschar **argv;
   uschar *etrn_command;
   uschar *etrn_serialize_key;
   uschar *errmess;
   uschar *etrn_command;
   uschar *etrn_serialize_key;
   uschar *errmess;
@@ -3028,7 +3140,7 @@ while (done <= 0)
   uschar *user_msg = NULL;
   uschar *recipient = NULL;
   uschar *hello = NULL;
   uschar *user_msg = NULL;
   uschar *recipient = NULL;
   uschar *hello = NULL;
-  uschar *set_id = NULL;
+  const uschar *set_id = NULL;
   uschar *s, *ss;
   BOOL was_rej_mail = FALSE;
   BOOL was_rcpt = FALSE;
   uschar *s, *ss;
   BOOL was_rej_mail = FALSE;
   BOOL was_rcpt = FALSE;
@@ -3038,6 +3150,8 @@ while (done <= 0)
   int ptr, size, rc;
   int c, i;
   auth_instance *au;
   int ptr, size, rc;
   int c, i;
   auth_instance *au;
+  uschar *orcpt = NULL;
+  int flags;
 
   switch(smtp_read_command(TRUE))
     {
 
   switch(smtp_read_command(TRUE))
     {
@@ -3310,7 +3424,7 @@ while (done <= 0)
 
       if (sender_host_name == NULL &&
            (deliver_domain = sender_helo_name,  /* set $domain */
 
       if (sender_host_name == NULL &&
            (deliver_domain = sender_helo_name,  /* set $domain */
-            match_isinlist(sender_helo_name, &helo_lookup_domains, 0,
+            match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
               &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK)
         (void)host_name_lookup();
 
               &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK)
         (void)host_name_lookup();
 
@@ -3382,6 +3496,7 @@ while (done <= 0)
     #ifdef SUPPORT_TLS
     tls_advertised = FALSE;
     #endif
     #ifdef SUPPORT_TLS
     tls_advertised = FALSE;
     #endif
+    dsn_advertised = FALSE;
 
     smtp_code = US"250 ";        /* Default response code plus space*/
     if (user_msg == NULL)
 
     smtp_code = US"250 ";        /* Default response code plus space*/
     if (user_msg == NULL)
@@ -3465,6 +3580,14 @@ while (done <= 0)
         s = string_cat(s, &size, &ptr, US"-8BITMIME\r\n", 11);
         }
 
         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. */
 
       /* 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. */
 
@@ -3554,12 +3677,13 @@ while (done <= 0)
         }
       #endif
 
         }
       #endif
 
-      #ifdef EXPERIMENTAL_PRDR
+      #ifndef DISABLE_PRDR
       /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
       /* 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);
         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. */
       #endif
 
       /* Finish off the multiline reply with one that is always available. */
@@ -3719,6 +3843,42 @@ while (done <= 0)
           arg_error = TRUE;
           break;
 
           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
         /* 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
@@ -3788,9 +3948,9 @@ while (done <= 0)
             }
             break;
 
             }
             break;
 
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
         case ENV_MAIL_OPT_PRDR:
         case ENV_MAIL_OPT_PRDR:
-          if ( prdr_enable )
+          if (prdr_enable)
             prdr_requested = TRUE;
           break;
 #endif
             prdr_requested = TRUE;
           break;
 #endif
@@ -3915,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. */
 
     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;
       }
       {
       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 (rc == OK || rc == DISCARD)
       {
-      if (user_msg == NULL) 
+      if (!user_msg)
         smtp_printf("%s%s%s", US"250 OK",
         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"",
                     US"",
+                  #endif
                     US"\r\n");
       else 
         {
                     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
            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);
         }
       smtp_delay_rcpt = smtp_rlr_base;
       recipients_discarded = (rc == DISCARD);
@@ -3993,6 +4156,89 @@ while (done <= 0)
       break;
       }
 
       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 */
 
     /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
     as a recipient address */
 
@@ -4106,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);
       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 */
       }
 
     /* The recipient was discarded */
@@ -4190,7 +4448,7 @@ while (done <= 0)
     ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
     to get the DATA command sent. */
 
     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;
       {
       uschar * acl= acl_smtp_predata ? acl_smtp_predata : US"accept";
       enable_dollar_recipients = TRUE;
@@ -4592,7 +4850,7 @@ while (done <= 0)
         break;
         }
       etrn_command = US"exim -R";
         break;
         }
       etrn_command = US"exim -R";
-      argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE, 2, US"-R",
+      argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE, 2, US"-R",
         smtp_cmd_data);
       }
 
         smtp_cmd_data);
       }
 
@@ -4777,4 +5035,6 @@ while (done <= 0)
 return done - 2;  /* Convert yield values */
 }
 
 return done - 2;  /* Convert yield values */
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of smtp_in.c */
 /* End of smtp_in.c */