Docs: document ancillary info for more event types. Bug 2313
[users/heiko/exim.git] / src / src / spool_in.c
index a546b65216c57bd88c3a4774bfdaf2e9fda10ca5..2d349778ccc66f23aa50ed08f872a1b20a5eed6c 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for reading spool files. When compiling for a utility (eximon),
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for reading spool files. When compiling for a utility (eximon),
@@ -25,19 +25,21 @@ fact it won't be written to. Just in case there's a major disaster (e.g.
 overwriting some other file descriptor with the value of this one), open it
 with append.
 
 overwriting some other file descriptor with the value of this one), open it
 with append.
 
+As called by deliver_message() (at least) we are operating as root.
+
 Argument: the id of the message
 Argument: the id of the message
-Returns:  TRUE if file successfully opened and locked
+Returns:  fd if file successfully opened and locked, else -1
 
 
-Side effect: deliver_datafile is set to the fd of the open file.
+Side effect: message_subdir is set for the (possibly split) spool directory
 */
 
 */
 
-BOOL
+int
 spool_open_datafile(uschar *id)
 {
 int i;
 struct stat statbuf;
 flock_t lock_data;
 spool_open_datafile(uschar *id)
 {
 int i;
 struct stat statbuf;
 flock_t lock_data;
-uschar spoolname[256];
+int fd;
 
 /* If split_spool_directory is set, first look for the file in the appropriate
 sub-directory of the input directory. If it is not found there, try the input
 
 /* If split_spool_directory is set, first look for the file in the appropriate
 sub-directory of the input directory. If it is not found there, try the input
@@ -48,22 +50,41 @@ splitting state. */
 
 for (i = 0; i < 2; i++)
   {
 
 for (i = 0; i < 2; i++)
   {
+  uschar * fname;
   int save_errno;
   int save_errno;
-  message_subdir[0] = (split_spool_directory == (i == 0))? id[5] : 0;
-  sprintf(CS spoolname, "%s/input/%s/%s-D", spool_directory, message_subdir, id);
-  deliver_datafile = Uopen(spoolname, O_RDWR | O_APPEND, 0);
-  if (deliver_datafile >= 0) break;
+
+  message_subdir[0] = split_spool_directory == i ? '\0' : id[5];
+  fname = spool_fname(US"input", message_subdir, id, US"-D");
+  DEBUG(D_deliver) debug_printf("Trying spool file %s\n", fname);
+
+  /* We protect against symlink attacks both in not propagating the
+   * file-descriptor to other processes as we exec, and also ensuring that we
+   * don't even open symlinks.
+   * No -D file inside the spool area should be a symlink.
+   */
+  if ((fd = Uopen(fname,
+#ifdef O_CLOEXEC
+                     O_CLOEXEC |
+#endif
+#ifdef O_NOFOLLOW
+                     O_NOFOLLOW |
+#endif
+                     O_RDWR | O_APPEND, 0)) >= 0)
+    break;
   save_errno = errno;
   if (errno == ENOENT)
     {
     if (i == 0) continue;
   save_errno = errno;
   if (errno == ENOENT)
     {
     if (i == 0) continue;
-    if (!queue_running)
-      log_write(0, LOG_MAIN, "Spool file %s-D not found", id);
+    if (!f.queue_running)
+      log_write(0, LOG_MAIN, "Spool%s%s file %s-D not found",
+       *queue_name ? US" Q=" : US"",
+       *queue_name ? queue_name : US"",
+       id);
     }
     }
-  else log_write(0, LOG_MAIN, "Spool error for %s: %s", spoolname,
-    strerror(errno));
+  else
+    log_write(0, LOG_MAIN, "Spool error for %s: %s", fname, strerror(errno));
   errno = save_errno;
   errno = save_errno;
-  return FALSE;
+  return -1;
   }
 
 /* File is open and message_subdir is set. Set the close-on-exec flag, and lock
   }
 
 /* File is open and message_subdir is set. Set the close-on-exec flag, and lock
@@ -74,35 +95,35 @@ an open file descriptor (at least, I think that's the Cygwin story). On real
 Unix systems it doesn't make any difference as long as Exim is consistent in
 what it locks. */
 
 Unix systems it doesn't make any difference as long as Exim is consistent in
 what it locks. */
 
-(void)fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) |
-  FD_CLOEXEC);
+#ifndef O_CLOEXEC
+(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
 
 lock_data.l_type = F_WRLCK;
 lock_data.l_whence = SEEK_SET;
 lock_data.l_start = 0;
 lock_data.l_len = SPOOL_DATA_START_OFFSET;
 
 
 lock_data.l_type = F_WRLCK;
 lock_data.l_whence = SEEK_SET;
 lock_data.l_start = 0;
 lock_data.l_len = SPOOL_DATA_START_OFFSET;
 
-if (fcntl(deliver_datafile, F_SETLK, &lock_data) < 0)
+if (fcntl(fd, F_SETLK, &lock_data) < 0)
   {
   log_write(L_skip_delivery,
             LOG_MAIN,
             "Spool file is locked (another process is handling this message)");
   {
   log_write(L_skip_delivery,
             LOG_MAIN,
             "Spool file is locked (another process is handling this message)");
-  (void)close(deliver_datafile);
-  deliver_datafile = -1;
+  (void)close(fd);
   errno = 0;
   errno = 0;
-  return FALSE;
+  return -1;
   }
 
 /* Get the size of the data; don't include the leading filename line
 in the count, but add one for the newline before the data. */
 
   }
 
 /* Get the size of the data; don't include the leading filename line
 in the count, but add one for the newline before the data. */
 
-if (fstat(deliver_datafile, &statbuf) == 0)
+if (fstat(fd, &statbuf) == 0)
   {
   message_body_size = statbuf.st_size - SPOOL_DATA_START_OFFSET;
   message_size = message_body_size + 1;
   }
 
   {
   message_body_size = statbuf.st_size - SPOOL_DATA_START_OFFSET;
   message_size = message_body_size + 1;
   }
 
-return TRUE;
+return fd;
 }
 #endif  /* COMPILE_UTILITY */
 
 }
 #endif  /* COMPILE_UTILITY */
 
@@ -193,66 +214,34 @@ return TRUE;
 
 
 
 
 
 
-/*************************************************
-*             Read spool header file             *
-*************************************************/
-
-/* This function reads a spool header file and places the data into the
-appropriate global variables. The header portion is always read, but header
-structures are built only if read_headers is set true. It isn't, for example,
-while generating -bp output.
-
-It may be possible for blocks of nulls (binary zeroes) to get written on the
-end of a file if there is a system crash during writing. It was observed on an
-earlier version of Exim that omitted to fsync() the files - this is thought to
-have been the cause of that incident, but in any case, this code must be robust
-against such an event, and if such a file is encountered, it must be treated as
-malformed.
-
-Arguments:
-  name          name of the header file, including the -H
-  read_headers  TRUE if in-store header structures are to be built
-  subdir_set    TRUE is message_subdir is already set
-
-Returns:        spool_read_OK        success
-                spool_read_notopen   open failed
-                spool_read_enverror  error in the envelope portion
-                spool_read_hdrdrror  error in the header portion
-*/
-
-int
-spool_read_header(uschar *name, BOOL read_headers, BOOL subdir_set)
-{
-FILE *f = NULL;
-int n;
-int rcount = 0;
-long int uid, gid;
-BOOL inheader = FALSE;
-uschar *p;
-
 /* Reset all the global variables to their default values. However, there is
 one exception. DO NOT change the default value of dont_deliver, because it may
 be forced by an external setting. */
 
 /* Reset all the global variables to their default values. However, there is
 one exception. DO NOT change the default value of dont_deliver, because it may
 be forced by an external setting. */
 
+void
+spool_clear_header_globals(void)
+{
 acl_var_c = acl_var_m = NULL;
 authenticated_id = NULL;
 authenticated_sender = NULL;
 acl_var_c = acl_var_m = NULL;
 authenticated_id = NULL;
 authenticated_sender = NULL;
-allow_unqualified_recipient = FALSE;
-allow_unqualified_sender = FALSE;
+f.allow_unqualified_recipient = FALSE;
+f.allow_unqualified_sender = FALSE;
 body_linecount = 0;
 body_zerocount = 0;
 body_linecount = 0;
 body_zerocount = 0;
-deliver_firsttime = FALSE;
-deliver_freeze = FALSE;
+f.deliver_firsttime = FALSE;
+f.deliver_freeze = FALSE;
 deliver_frozen_at = 0;
 deliver_frozen_at = 0;
-deliver_manual_thaw = FALSE;
-/* dont_deliver must NOT be reset */
+f.deliver_manual_thaw = FALSE;
+/* f.dont_deliver must NOT be reset */
 header_list = header_last = NULL;
 host_lookup_deferred = FALSE;
 host_lookup_failed = FALSE;
 interface_address = NULL;
 interface_port = 0;
 header_list = header_last = NULL;
 host_lookup_deferred = FALSE;
 host_lookup_failed = FALSE;
 interface_address = NULL;
 interface_port = 0;
-local_error_message = FALSE;
+f.local_error_message = FALSE;
+#ifdef HAVE_LOCAL_SCAN
 local_scan_data = NULL;
 local_scan_data = NULL;
+#endif
 max_received_linelength = 0;
 message_linecount = 0;
 received_protocol = NULL;
 max_received_linelength = 0;
 message_linecount = 0;
 received_protocol = NULL;
@@ -266,9 +255,12 @@ sender_host_name = NULL;
 sender_host_port = 0;
 sender_host_authenticated = NULL;
 sender_ident = NULL;
 sender_host_port = 0;
 sender_host_authenticated = NULL;
 sender_ident = NULL;
-sender_local = FALSE;
-sender_set_untrusted = FALSE;
+f.sender_local = FALSE;
+f.sender_set_untrusted = FALSE;
 smtp_active_hostname = primary_hostname;
 smtp_active_hostname = primary_hostname;
+#ifndef COMPILE_UTILITY
+f.spool_file_wireformat = FALSE;
+#endif
 tree_nonrecipients = NULL;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 tree_nonrecipients = NULL;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
@@ -278,21 +270,89 @@ bmi_verdicts = NULL;
 
 #ifndef DISABLE_DKIM
 dkim_signers = NULL;
 
 #ifndef DISABLE_DKIM
 dkim_signers = NULL;
-dkim_disable_verify = FALSE;
-dkim_collect_input = FALSE;
+f.dkim_disable_verify = FALSE;
+dkim_collect_input = 0;
 #endif
 
 #ifdef SUPPORT_TLS
 tls_in.certificate_verified = FALSE;
 #endif
 
 #ifdef SUPPORT_TLS
 tls_in.certificate_verified = FALSE;
+# ifdef SUPPORT_DANE
+tls_in.dane_verified = FALSE;
+# endif
 tls_in.cipher = NULL;
 tls_in.cipher = NULL;
+# ifndef COMPILE_UTILITY       /* tls support fns not built in */
+tls_free_cert(&tls_in.ourcert);
+tls_free_cert(&tls_in.peercert);
+# endif
 tls_in.peerdn = NULL;
 tls_in.sni = NULL;
 tls_in.peerdn = NULL;
 tls_in.sni = NULL;
+tls_in.ocsp = OCSP_NOT_REQ;
+# if defined(EXPERIMENTAL_REQUIRETLS) && !defined(COMPILE_UTILITY)
+tls_requiretls = 0;
+# endif
 #endif
 
 #ifdef WITH_CONTENT_SCAN
 #endif
 
 #ifdef WITH_CONTENT_SCAN
+spam_bar = NULL;
+spam_score = NULL;
 spam_score_int = NULL;
 #endif
 
 spam_score_int = NULL;
 #endif
 
+#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
+message_smtputf8 = FALSE;
+message_utf8_downconvert = 0;
+#endif
+
+dsn_ret = 0;
+dsn_envid = NULL;
+}
+
+
+/*************************************************
+*             Read spool header file             *
+*************************************************/
+
+/* This function reads a spool header file and places the data into the
+appropriate global variables. The header portion is always read, but header
+structures are built only if read_headers is set true. It isn't, for example,
+while generating -bp output.
+
+It may be possible for blocks of nulls (binary zeroes) to get written on the
+end of a file if there is a system crash during writing. It was observed on an
+earlier version of Exim that omitted to fsync() the files - this is thought to
+have been the cause of that incident, but in any case, this code must be robust
+against such an event, and if such a file is encountered, it must be treated as
+malformed.
+
+As called from deliver_message() (at least) we are running as root.
+
+Arguments:
+  name          name of the header file, including the -H
+  read_headers  TRUE if in-store header structures are to be built
+  subdir_set    TRUE is message_subdir is already set
+
+Returns:        spool_read_OK        success
+                spool_read_notopen   open failed
+                spool_read_enverror  error in the envelope portion
+                spool_read_hdrerror  error in the header portion
+*/
+
+int
+spool_read_header(uschar *name, BOOL read_headers, BOOL subdir_set)
+{
+FILE * fp = NULL;
+int n;
+int rcount = 0;
+long int uid, gid;
+BOOL inheader = FALSE;
+uschar *p;
+
+/* Reset all the global variables to their default values. However, there is
+one exception. DO NOT change the default value of dont_deliver, because it may
+be forced by an external setting. */
+
+spool_clear_header_globals();
+
 /* Generate the full name and open the file. If message_subdir is already
 set, just look in the given directory. Otherwise, look in both the split
 and unsplit directories, as for the data file above. */
 /* Generate the full name and open the file. If message_subdir is already
 set, just look in the given directory. Otherwise, look in both the split
 and unsplit directories, as for the data file above. */
@@ -300,12 +360,12 @@ and unsplit directories, as for the data file above. */
 for (n = 0; n < 2; n++)
   {
   if (!subdir_set)
 for (n = 0; n < 2; n++)
   {
   if (!subdir_set)
-    message_subdir[0] = (split_spool_directory == (n == 0))? name[5] : 0;
-  sprintf(CS big_buffer, "%s/input/%s/%s", spool_directory, message_subdir,
-    name);
-  f = Ufopen(big_buffer, "rb");
-  if (f != NULL) break;
-  if (n != 0 || subdir_set || errno != ENOENT) return spool_read_notopen;
+    message_subdir[0] = split_spool_directory == (n == 0) ? name[5] : 0;
+
+  if ((fp = Ufopen(spool_fname(US"input", message_subdir, name, US""), "rb")))
+    break;
+  if (n != 0 || subdir_set || errno != ENOENT)
+    return spool_read_notopen;
   }
 
 errno = 0;
   }
 
 errno = 0;
@@ -317,7 +377,7 @@ DEBUG(D_deliver) debug_printf("reading spool file %s\n", name);
 /* The first line of a spool file contains the message id followed by -H (i.e.
 the file name), in order to make the file self-identifying. */
 
 /* The first line of a spool file contains the message id followed by -H (i.e.
 the file name), in order to make the file self-identifying. */
 
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
 if (Ustrlen(big_buffer) != MESSAGE_ID_LENGTH + 3 ||
     Ustrncmp(big_buffer, name, MESSAGE_ID_LENGTH + 2) != 0)
   goto SPOOL_FORMAT_ERROR;
 if (Ustrlen(big_buffer) != MESSAGE_ID_LENGTH + 3 ||
     Ustrncmp(big_buffer, name, MESSAGE_ID_LENGTH + 2) != 0)
   goto SPOOL_FORMAT_ERROR;
@@ -329,7 +389,7 @@ negative uids and gids. The second contains the mail address of the message's
 sender, enclosed in <>. The third contains the time the message was received,
 and the number of warning messages for delivery delays that have been sent. */
 
 sender, enclosed in <>. The third contains the time the message was received,
 and the number of warning messages for delivery delays that have been sent. */
 
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
 
 p = big_buffer + Ustrlen(big_buffer);
 while (p > big_buffer && isspace(p[-1])) p--;
 
 p = big_buffer + Ustrlen(big_buffer);
 while (p > big_buffer && isspace(p[-1])) p--;
@@ -349,7 +409,8 @@ originator_login = string_copy(big_buffer);
 originator_uid = (uid_t)uid;
 originator_gid = (gid_t)gid;
 
 originator_uid = (uid_t)uid;
 originator_gid = (gid_t)gid;
 
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+/* envelope from */
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
 n = Ustrlen(big_buffer);
 if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>')
   goto SPOOL_FORMAT_ERROR;
 n = Ustrlen(big_buffer);
 if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>')
   goto SPOOL_FORMAT_ERROR;
@@ -358,11 +419,13 @@ sender_address = store_get(n-2);
 Ustrncpy(sender_address, big_buffer+1, n-3);
 sender_address[n-3] = 0;
 
 Ustrncpy(sender_address, big_buffer+1, n-3);
 sender_address[n-3] = 0;
 
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
-if (sscanf(CS big_buffer, "%d %d", &received_time, &warning_count) != 2)
+/* time */
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
+if (sscanf(CS big_buffer, TIME_T_FMT " %d", &received_time.tv_sec, &warning_count) != 2)
   goto SPOOL_FORMAT_ERROR;
   goto SPOOL_FORMAT_ERROR;
+received_time.tv_usec = 0;
 
 
-message_age = time(NULL) - received_time;
+message_age = time(NULL) - received_time.tv_sec;
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver) debug_printf("user=%s uid=%ld gid=%ld sender=%s\n",
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver) debug_printf("user=%s uid=%ld gid=%ld sender=%s\n",
@@ -386,9 +449,22 @@ version that left new-style flags written on the spool. */
 p = big_buffer + 2;
 for (;;)
   {
 p = big_buffer + 2;
 for (;;)
   {
-  if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+  int len;
+  if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
   if (big_buffer[0] != '-') break;
   if (big_buffer[0] != '-') break;
-  big_buffer[Ustrlen(big_buffer) - 1] = 0;
+  while (  (len = Ustrlen(big_buffer)) == big_buffer_size-1
+       && big_buffer[len-1] != '\n'
+       )
+    {  /* buffer not big enough for line; certs make this possible */
+    uschar * buf;
+    if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR;
+    buf = store_get_perm(big_buffer_size *= 2);
+    memcpy(buf, big_buffer, --len);
+    big_buffer = buf;
+    if (Ufgets(big_buffer+len, big_buffer_size-len, fp) == NULL)
+      goto SPOOL_READ_ERROR;
+    }
+  big_buffer[len-1] = 0;
 
   switch(big_buffer[1])
     {
 
   switch(big_buffer[1])
     {
@@ -407,19 +483,19 @@ for (;;)
       tree_node *node;
       endptr = Ustrchr(big_buffer + 6, ' ');
       if (endptr == NULL) goto SPOOL_FORMAT_ERROR;
       tree_node *node;
       endptr = Ustrchr(big_buffer + 6, ' ');
       if (endptr == NULL) goto SPOOL_FORMAT_ERROR;
-      name = string_sprintf("%c%.*s", big_buffer[4], endptr - big_buffer - 6,
-        big_buffer + 6);
+      name = string_sprintf("%c%.*s", big_buffer[4],
+        (int)(endptr - big_buffer - 6), big_buffer + 6);
       if (sscanf(CS endptr, " %d", &count) != 1) goto SPOOL_FORMAT_ERROR;
       node = acl_var_create(name);
       node->data.ptr = store_get(count + 1);
       if (sscanf(CS endptr, " %d", &count) != 1) goto SPOOL_FORMAT_ERROR;
       node = acl_var_create(name);
       node->data.ptr = store_get(count + 1);
-      if (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
+      if (fread(node->data.ptr, 1, count+1, fp) < count) goto SPOOL_READ_ERROR;
       ((uschar*)node->data.ptr)[count] = 0;
       }
 
     else if (Ustrcmp(p, "llow_unqualified_recipient") == 0)
       ((uschar*)node->data.ptr)[count] = 0;
       }
 
     else if (Ustrcmp(p, "llow_unqualified_recipient") == 0)
-      allow_unqualified_recipient = TRUE;
+      f.allow_unqualified_recipient = TRUE;
     else if (Ustrcmp(p, "llow_unqualified_sender") == 0)
     else if (Ustrcmp(p, "llow_unqualified_sender") == 0)
-      allow_unqualified_sender = TRUE;
+      f.allow_unqualified_sender = TRUE;
 
     else if (Ustrncmp(p, "uth_id", 6) == 0)
       authenticated_id = string_copy(big_buffer + 9);
 
     else if (Ustrncmp(p, "uth_id", 6) == 0)
       authenticated_id = string_copy(big_buffer + 9);
@@ -437,19 +513,24 @@ for (;;)
 
     else if (Ustrncmp(p, "cl ", 3) == 0)
       {
 
     else if (Ustrncmp(p, "cl ", 3) == 0)
       {
-      int index, count;
-      uschar name[20];   /* Need plenty of space for %d format */
-      tree_node *node;
-      if (sscanf(CS big_buffer + 5, "%d %d", &index, &count) != 2)
+      unsigned index, count;
+      uschar name[20];   /* Need plenty of space for %u format */
+      tree_node * node;
+      if (  sscanf(CS big_buffer + 5, "%u %u", &index, &count) != 2
+        || index >= 20
+        || count > 16384       /* arbitrary limit on variable size */
+         )
         goto SPOOL_FORMAT_ERROR;
       if (index < 10)
         goto SPOOL_FORMAT_ERROR;
       if (index < 10)
-        (void) string_format(name, sizeof(name), "%c%d", 'c', index);
-      else if (index < 20) /* ignore out-of-range index */
-        (void) string_format(name, sizeof(name), "%c%d", 'm', index - 10);
+        (void) string_format(name, sizeof(name), "%c%u", 'c', index);
+      else
+        (void) string_format(name, sizeof(name), "%c%u", 'm', index - 10);
       node = acl_var_create(name);
       node->data.ptr = store_get(count + 1);
       node = acl_var_create(name);
       node->data.ptr = store_get(count + 1);
-      if (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
-      ((uschar*)node->data.ptr)[count] = 0;
+      /* We sanity-checked the count, so disable the Coverity error */
+      /* coverity[tainted_data] */
+      if (fread(node->data.ptr, 1, count+1, fp) < count) goto SPOOL_READ_ERROR;
+      (US node->data.ptr)[count] = '\0';
       }
     break;
 
       }
     break;
 
@@ -458,22 +539,28 @@ for (;;)
       body_linecount = Uatoi(big_buffer + 15);
     else if (Ustrncmp(p, "ody_zerocount", 13) == 0)
       body_zerocount = Uatoi(big_buffer + 15);
       body_linecount = Uatoi(big_buffer + 15);
     else if (Ustrncmp(p, "ody_zerocount", 13) == 0)
       body_zerocount = Uatoi(big_buffer + 15);
-    #ifdef EXPERIMENTAL_BRIGHTMAIL
+#ifdef EXPERIMENTAL_BRIGHTMAIL
     else if (Ustrncmp(p, "mi_verdicts ", 12) == 0)
       bmi_verdicts = string_copy(big_buffer + 14);
     else if (Ustrncmp(p, "mi_verdicts ", 12) == 0)
       bmi_verdicts = string_copy(big_buffer + 14);
-    #endif
+#endif
     break;
 
     case 'd':
     if (Ustrcmp(p, "eliver_firsttime") == 0)
     break;
 
     case 'd':
     if (Ustrcmp(p, "eliver_firsttime") == 0)
-      deliver_firsttime = TRUE;
+      f.deliver_firsttime = TRUE;
+    /* Check if the dsn flags have been set in the header file */
+    else if (Ustrncmp(p, "sn_ret", 6) == 0)
+      dsn_ret= atoi(CS big_buffer + 8);
+    else if (Ustrncmp(p, "sn_envid", 8) == 0)
+      dsn_envid = string_copy(big_buffer + 11);
     break;
 
     case 'f':
     if (Ustrncmp(p, "rozen", 5) == 0)
       {
     break;
 
     case 'f':
     if (Ustrncmp(p, "rozen", 5) == 0)
       {
-      deliver_freeze = TRUE;
-      deliver_frozen_at = Uatoi(big_buffer + 7);
+      f.deliver_freeze = TRUE;
+      if (sscanf(CS big_buffer+7, TIME_T_FMT, &deliver_frozen_at) != 1)
+       goto SPOOL_READ_ERROR;
       }
     break;
 
       }
     break;
 
@@ -511,49 +598,95 @@ for (;;)
     break;
 
     case 'l':
     break;
 
     case 'l':
-    if (Ustrcmp(p, "ocal") == 0) sender_local = TRUE;
+    if (Ustrcmp(p, "ocal") == 0)
+      f.sender_local = TRUE;
     else if (Ustrcmp(big_buffer, "-localerror") == 0)
     else if (Ustrcmp(big_buffer, "-localerror") == 0)
-      local_error_message = TRUE;
+      f.local_error_message = TRUE;
+#ifdef HAVE_LOCAL_SCAN
     else if (Ustrncmp(p, "ocal_scan ", 10) == 0)
       local_scan_data = string_copy(big_buffer + 12);
     else if (Ustrncmp(p, "ocal_scan ", 10) == 0)
       local_scan_data = string_copy(big_buffer + 12);
+#endif
     break;
 
     case 'm':
     break;
 
     case 'm':
-    if (Ustrcmp(p, "anual_thaw") == 0) deliver_manual_thaw = TRUE;
+    if (Ustrcmp(p, "anual_thaw") == 0) f.deliver_manual_thaw = TRUE;
     else if (Ustrncmp(p, "ax_received_linelength", 22) == 0)
       max_received_linelength = Uatoi(big_buffer + 24);
     break;
 
     case 'N':
     else if (Ustrncmp(p, "ax_received_linelength", 22) == 0)
       max_received_linelength = Uatoi(big_buffer + 24);
     break;
 
     case 'N':
-    if (*p == 0) dont_deliver = TRUE;   /* -N */
+    if (*p == 0) f.dont_deliver = TRUE;   /* -N */
     break;
 
     case 'r':
     if (Ustrncmp(p, "eceived_protocol", 16) == 0)
       received_protocol = string_copy(big_buffer + 19);
     break;
 
     case 'r':
     if (Ustrncmp(p, "eceived_protocol", 16) == 0)
       received_protocol = string_copy(big_buffer + 19);
+    else if (Ustrncmp(p, "eceived_time_usec", 17) == 0)
+      {
+      unsigned usec;
+      if (sscanf(CS big_buffer + 21, "%u", &usec) == 1)
+       received_time.tv_usec = usec;
+      }
     break;
 
     case 's':
     if (Ustrncmp(p, "ender_set_untrusted", 19) == 0)
     break;
 
     case 's':
     if (Ustrncmp(p, "ender_set_untrusted", 19) == 0)
-      sender_set_untrusted = TRUE;
-    #ifdef WITH_CONTENT_SCAN
+      f.sender_set_untrusted = TRUE;
+#ifdef WITH_CONTENT_SCAN
+    else if (Ustrncmp(p, "pam_bar ", 8) == 0)
+      spam_bar = string_copy(big_buffer + 10);
+    else if (Ustrncmp(p, "pam_score ", 10) == 0)
+      spam_score = string_copy(big_buffer + 12);
     else if (Ustrncmp(p, "pam_score_int ", 14) == 0)
       spam_score_int = string_copy(big_buffer + 16);
     else if (Ustrncmp(p, "pam_score_int ", 14) == 0)
       spam_score_int = string_copy(big_buffer + 16);
-    #endif
+#endif
+#ifndef COMPILE_UTILITY
+    else if (Ustrncmp(p, "pool_file_wireformat", 20) == 0)
+      f.spool_file_wireformat = TRUE;
+#endif
+#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
+    else if (Ustrncmp(p, "mtputf8", 7) == 0)
+      message_smtputf8 = TRUE;
+#endif
     break;
 
     break;
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     case 't':
     case 't':
-    if (Ustrncmp(p, "ls_certificate_verified", 23) == 0)
-      tls_in.certificate_verified = TRUE;
-    else if (Ustrncmp(p, "ls_cipher", 9) == 0)
-      tls_in.cipher = string_copy(big_buffer + 12);
-    else if (Ustrncmp(p, "ls_peerdn", 9) == 0)
-      tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
-    else if (Ustrncmp(p, "ls_sni", 6) == 0)
-      tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
+    if (Ustrncmp(p, "ls_", 3) == 0)
+      {
+      uschar * q = p + 3;
+      if (Ustrncmp(q, "certificate_verified", 20) == 0)
+       tls_in.certificate_verified = TRUE;
+      else if (Ustrncmp(q, "cipher", 6) == 0)
+       tls_in.cipher = string_copy(big_buffer + 12);
+# ifndef COMPILE_UTILITY       /* tls support fns not built in */
+      else if (Ustrncmp(q, "ourcert", 7) == 0)
+       (void) tls_import_cert(big_buffer + 13, &tls_in.ourcert);
+      else if (Ustrncmp(q, "peercert", 8) == 0)
+       (void) tls_import_cert(big_buffer + 14, &tls_in.peercert);
+# endif
+      else if (Ustrncmp(q, "peerdn", 6) == 0)
+       tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
+      else if (Ustrncmp(q, "sni", 3) == 0)
+       tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
+      else if (Ustrncmp(q, "ocsp", 4) == 0)
+       tls_in.ocsp = big_buffer[10] - '0';
+# if defined(EXPERIMENTAL_REQUIRETLS) && !defined(COMPILE_UTILITY)
+      else if (Ustrncmp(q, "requiretls", 10) == 0)
+       tls_requiretls = strtol(CS big_buffer+16, NULL, 0);
+# endif
+      }
     break;
     break;
-    #endif
+#endif
+
+#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
+    case 'u':
+    if (Ustrncmp(p, "tf8_downcvt", 11) == 0)
+      message_utf8_downconvert = 1;
+    else if (Ustrncmp(p, "tf8_optdowncvt", 15) == 0)
+      message_utf8_downconvert = -1;
+    break;
+#endif
 
     default:    /* Present because some compilers complain if all */
     break;      /* possibilities are not covered. */
 
     default:    /* Present because some compilers complain if all */
     break;      /* possibilities are not covered. */
@@ -568,7 +701,7 @@ host_build_sender_fullhost();
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver)
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver)
-  debug_printf("sender_local=%d ident=%s\n", sender_local,
+  debug_printf("sender_local=%d ident=%s\n", f.sender_local,
     (sender_ident == NULL)? US"unset" : sender_ident);
 #endif  /* COMPILE_UTILITY */
 
     (sender_ident == NULL)? US"unset" : sender_ident);
 #endif  /* COMPILE_UTILITY */
 
@@ -576,7 +709,7 @@ DEBUG(D_deliver)
 containing "XX", indicating no tree. */
 
 if (Ustrncmp(big_buffer, "XX\n", 3) != 0 &&
 containing "XX", indicating no tree. */
 
 if (Ustrncmp(big_buffer, "XX\n", 3) != 0 &&
-  !read_nonrecipients_tree(&tree_nonrecipients, f, big_buffer, big_buffer_size))
+  !read_nonrecipients_tree(&tree_nonrecipients, fp, big_buffer, big_buffer_size))
     goto SPOOL_FORMAT_ERROR;
 
 #ifndef COMPILE_UTILITY
     goto SPOOL_FORMAT_ERROR;
 
 #ifndef COMPILE_UTILITY
@@ -588,10 +721,12 @@ DEBUG(D_deliver)
 #endif  /* COMPILE_UTILITY */
 
 /* After reading the tree, the next line has not yet been read into the
 #endif  /* COMPILE_UTILITY */
 
 /* After reading the tree, the next line has not yet been read into the
-buffer. It contains the count of recipients which follow on separate lines. */
+buffer. It contains the count of recipients which follow on separate lines.
+Apply an arbitrary sanity check.*/
 
 
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
-if (sscanf(CS big_buffer, "%d", &rcount) != 1) goto SPOOL_FORMAT_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
+if (sscanf(CS big_buffer, "%d", &rcount) != 1 || rcount > 16384)
+  goto SPOOL_FORMAT_ERROR;
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount);
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount);
@@ -600,14 +735,20 @@ DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount);
 recipients_list_max = rcount;
 recipients_list = store_get(rcount * sizeof(recipient_item));
 
 recipients_list_max = rcount;
 recipients_list = store_get(rcount * sizeof(recipient_item));
 
+/* We sanitised the count and know we have enough memory, so disable
+the Coverity error on recipients_count */
+/* coverity[tainted_data] */
+
 for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   {
   int nn;
   int pno = -1;
 for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   {
   int nn;
   int pno = -1;
+  int dsn_flags = 0;
+  uschar *orcpt = NULL;
   uschar *errors_to = NULL;
   uschar *p;
 
   uschar *errors_to = NULL;
   uschar *p;
 
-  if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+  if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
   nn = Ustrlen(big_buffer);
   if (nn < 2) goto SPOOL_FORMAT_ERROR;
 
   nn = Ustrlen(big_buffer);
   if (nn < 2) goto SPOOL_FORMAT_ERROR;
 
@@ -646,6 +787,9 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
       ends with <errors_to address><space><len>,<pno> where pno is
       the parent number for one_time addresses, and len is the length
       of the errors_to address (zero meaning none).
       ends with <errors_to address><space><len>,<pno> where pno is
       the parent number for one_time addresses, and len is the length
       of the errors_to address (zero meaning none).
+
+    Bit 02 indicates that, again reading from right to left, the data continues
+     with orcpt len(orcpt),dsn_flags
    */
 
   while (isdigit(*p)) p--;
    */
 
   while (isdigit(*p)) p--;
@@ -676,6 +820,11 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   else if (*p == '#')
     {
     int flags;
   else if (*p == '#')
     {
     int flags;
+
+#if !defined (COMPILE_UTILITY)
+    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim 4 standard format spoolfile\n");
+#endif
+
     (void)sscanf(CS p+1, "%d", &flags);
 
     if ((flags & 0x01) != 0)      /* one_time data exists */
     (void)sscanf(CS p+1, "%d", &flags);
 
     if ((flags & 0x01) != 0)      /* one_time data exists */
@@ -691,12 +840,43 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
         }
       }
 
         }
       }
 
+    *(--p) = 0;   /* Terminate address */
+    if ((flags & 0x02) != 0)      /* one_time data exists */
+      {
+      int len;
+      while (isdigit(*(--p)) || *p == ',' || *p == '-');
+      (void)sscanf(CS p+1, "%d,%d", &len, &dsn_flags);
+      *p = 0;
+      if (len > 0)
+        {
+        p -= len;
+        orcpt = string_copy(p);
+        }
+      }
+
     *(--p) = 0;   /* Terminate address */
     }
     *(--p) = 0;   /* Terminate address */
     }
+#if !defined(COMPILE_UTILITY)
+  else
+    { DEBUG(D_deliver) debug_printf("**** SPOOL_IN - No additional fields\n"); }
+
+  if ((orcpt != NULL) || (dsn_flags != 0))
+    {
+    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: |%s| orcpt: |%s| dsn_flags: %d\n",
+      big_buffer, orcpt, dsn_flags);
+    }
+  if (errors_to != NULL)
+    {
+    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: |%s| errorsto: |%s|\n",
+      big_buffer, errors_to);
+    }
+#endif
 
   recipients_list[recipients_count].address = string_copy(big_buffer);
   recipients_list[recipients_count].pno = pno;
   recipients_list[recipients_count].errors_to = errors_to;
 
   recipients_list[recipients_count].address = string_copy(big_buffer);
   recipients_list[recipients_count].pno = pno;
   recipients_list[recipients_count].errors_to = errors_to;
+  recipients_list[recipients_count].orcpt = orcpt;
+  recipients_list[recipients_count].dsn_flags = dsn_flags;
   }
 
 /* The remainder of the spool header file contains the headers for the message,
   }
 
 /* The remainder of the spool header file contains the headers for the message,
@@ -708,17 +888,17 @@ always, in order to check on the format of the file, but only create a header
 list if requested to do so. */
 
 inheader = TRUE;
 list if requested to do so. */
 
 inheader = TRUE;
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
 if (big_buffer[0] != '\n') goto SPOOL_FORMAT_ERROR;
 
 if (big_buffer[0] != '\n') goto SPOOL_FORMAT_ERROR;
 
-while ((n = fgetc(f)) != EOF)
+while ((n = fgetc(fp)) != EOF)
   {
   header_line *h;
   uschar flag[4];
   int i;
 
   if (!isdigit(n)) goto SPOOL_FORMAT_ERROR;
   {
   header_line *h;
   uschar flag[4];
   int i;
 
   if (!isdigit(n)) goto SPOOL_FORMAT_ERROR;
-  if(ungetc(n, f) == EOF  ||  fscanf(f, "%d%c ", &n, flag) == EOF)
+  if(ungetc(n, fp) == EOF  ||  fscanf(fp, "%d%c ", &n, flag) == EOF)
     goto SPOOL_READ_ERROR;
   if (flag[0] != '*') message_size += n;  /* Omit non-transmitted headers */
 
     goto SPOOL_READ_ERROR;
   if (flag[0] != '*') message_size += n;  /* Omit non-transmitted headers */
 
@@ -738,7 +918,7 @@ while ((n = fgetc(f)) != EOF)
 
     for (i = 0; i < n; i++)
       {
 
     for (i = 0; i < n; i++)
       {
-      int c = fgetc(f);
+      int c = fgetc(fp);
       if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
       if (c == '\n' && h->type != htype_old) message_linecount++;
       h->text[i] = c;
       if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
       if (c == '\n' && h->type != htype_old) message_linecount++;
       h->text[i] = c;
@@ -750,7 +930,7 @@ while ((n = fgetc(f)) != EOF)
 
   else for (i = 0; i < n; i++)
     {
 
   else for (i = 0; i < n; i++)
     {
-    int c = fgetc(f);
+    int c = fgetc(fp);
     if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
     }
   }
     if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
     }
   }
@@ -766,7 +946,7 @@ DEBUG(D_deliver) debug_printf("body_linecount=%d message_linecount=%d\n",
 
 message_linecount += body_linecount;
 
 
 message_linecount += body_linecount;
 
-fclose(f);
+fclose(fp);
 return spool_read_OK;
 
 
 return spool_read_OK;
 
 
@@ -779,11 +959,11 @@ if (errno != 0)
   {
   n = errno;
 
   {
   n = errno;
 
-  #ifndef COMPILE_UTILITY
+#ifndef COMPILE_UTILITY
   DEBUG(D_any) debug_printf("Error while reading spool file %s\n", name);
   DEBUG(D_any) debug_printf("Error while reading spool file %s\n", name);
-  #endif  /* COMPILE_UTILITY */
+#endif  /* COMPILE_UTILITY */
 
 
-  fclose(f);
+  fclose(fp);
   errno = n;
   return inheader? spool_read_hdrerror : spool_read_enverror;
   }
   errno = n;
   return inheader? spool_read_hdrerror : spool_read_enverror;
   }
@@ -794,9 +974,11 @@ SPOOL_FORMAT_ERROR:
 DEBUG(D_any) debug_printf("Format error in spool file %s\n", name);
 #endif  /* COMPILE_UTILITY */
 
 DEBUG(D_any) debug_printf("Format error in spool file %s\n", name);
 #endif  /* COMPILE_UTILITY */
 
-fclose(f);
+fclose(fp);
 errno = ERRNO_SPOOLFORMAT;
 return inheader? spool_read_hdrerror : spool_read_enverror;
 }
 
 errno = ERRNO_SPOOLFORMAT;
 return inheader? spool_read_hdrerror : spool_read_enverror;
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of spool_in.c */
 /* End of spool_in.c */