Expansions: new ${authresults {mch}} for an Authentication-Results header
[exim.git] / src / src / spool_in.c
index fb03d58b04bce3d31cb43b42e66e7d6c4030e1aa..c8ddffe4135b068d1d8a2c9322ad6d3de4c87148 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/spool_in.c,v 1.9 2005/03/08 15:32:02 tom Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* 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),
@@ -27,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
@@ -50,22 +50,33 @@ 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);
+
+  if ((fd = Uopen(fname,
+#ifdef O_CLOEXEC
+                     O_CLOEXEC |
+#endif
+                     O_RDWR | O_APPEND, 0)) >= 0)
+    break;
   save_errno = errno;
   if (errno == ENOENT)
     {
     if (i == 0) continue;
     if (!queue_running)
   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);
+      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
@@ -76,35 +87,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. */
 
-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)");
-  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 */
 
@@ -211,6 +222,8 @@ 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.
 
 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
 Arguments:
   name          name of the header file, including the -H
   read_headers  TRUE if in-store header structures are to be built
@@ -219,7 +232,7 @@ Arguments:
 Returns:        spool_read_OK        success
                 spool_read_notopen   open failed
                 spool_read_enverror  error in the envelope portion
 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
+                spool_read_hdrerror  error in the header portion
 */
 
 int
 */
 
 int
@@ -236,8 +249,7 @@ uschar *p;
 one exception. DO NOT change the default value of dont_deliver, because it may
 be forced by an external setting. */
 
 one exception. DO NOT change the default value of dont_deliver, because it may
 be forced by an external setting. */
 
-for (n = 0; n < ACL_C_MAX + ACL_M_MAX; n++) acl_var[n] = NULL;
-
+acl_var_c = acl_var_m = NULL;
 authenticated_id = NULL;
 authenticated_sender = NULL;
 allow_unqualified_recipient = FALSE;
 authenticated_id = NULL;
 authenticated_sender = NULL;
 allow_unqualified_recipient = FALSE;
@@ -256,6 +268,7 @@ interface_address = NULL;
 interface_port = 0;
 local_error_message = FALSE;
 local_scan_data = NULL;
 interface_port = 0;
 local_error_message = FALSE;
 local_scan_data = NULL;
+max_received_linelength = 0;
 message_linecount = 0;
 received_protocol = NULL;
 received_count = 0;
 message_linecount = 0;
 received_protocol = NULL;
 received_count = 0;
@@ -271,6 +284,9 @@ sender_ident = NULL;
 sender_local = FALSE;
 sender_set_untrusted = FALSE;
 smtp_active_hostname = primary_hostname;
 sender_local = FALSE;
 sender_set_untrusted = FALSE;
 smtp_active_hostname = primary_hostname;
+#ifndef COMPILE_UTILITY
+spool_file_wireformat = FALSE;
+#endif
 tree_nonrecipients = NULL;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 tree_nonrecipients = NULL;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
@@ -278,20 +294,41 @@ bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
 
 bmi_verdicts = NULL;
 #endif
 
-#ifdef EXPERIMENTAL_DOMAINKEYS
-dk_do_verify = 0;
+#ifndef DISABLE_DKIM
+dkim_signers = NULL;
+dkim_disable_verify = FALSE;
+dkim_collect_input = FALSE;
 #endif
 
 #ifdef SUPPORT_TLS
 #endif
 
 #ifdef SUPPORT_TLS
-tls_certificate_verified = FALSE;
-tls_cipher = NULL;
-tls_peerdn = NULL;
+tls_in.certificate_verified = FALSE;
+# ifdef SUPPORT_DANE
+tls_in.dane_verified = FALSE;
+# endif
+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.ocsp = OCSP_NOT_REQ;
 #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;
+
 /* 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. */
@@ -299,12 +336,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 ((f = 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;
@@ -323,9 +360,10 @@ if (Ustrlen(big_buffer) != MESSAGE_ID_LENGTH + 3 ||
 
 /* The next three lines in the header file are in a fixed format. The first
 contains the login, uid, and gid of the user who caused the file to be written.
 
 /* The next three lines in the header file are in a fixed format. The first
 contains the login, uid, and gid of the user who caused the file to be written.
-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. */
+There are known cases where a negative gid is used, so we allow for both
+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. */
 
 if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
 
 
 if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
 
@@ -333,12 +371,12 @@ p = big_buffer + Ustrlen(big_buffer);
 while (p > big_buffer && isspace(p[-1])) p--;
 *p = 0;
 if (!isdigit(p[-1])) goto SPOOL_FORMAT_ERROR;
 while (p > big_buffer && isspace(p[-1])) p--;
 *p = 0;
 if (!isdigit(p[-1])) goto SPOOL_FORMAT_ERROR;
-while (p > big_buffer && isdigit(p[-1])) p--;
+while (p > big_buffer && (isdigit(p[-1]) || '-' == p[-1])) p--;
 gid = Uatoi(p);
 if (p <= big_buffer || *(--p) != ' ') goto SPOOL_FORMAT_ERROR;
 *p = 0;
 if (!isdigit(p[-1])) goto SPOOL_FORMAT_ERROR;
 gid = Uatoi(p);
 if (p <= big_buffer || *(--p) != ' ') goto SPOOL_FORMAT_ERROR;
 *p = 0;
 if (!isdigit(p[-1])) goto SPOOL_FORMAT_ERROR;
-while (p > big_buffer && isdigit(p[-1])) p--;
+while (p > big_buffer && (isdigit(p[-1]) || '-' == p[-1])) p--;
 uid = Uatoi(p);
 if (p <= big_buffer || *(--p) != ' ') goto SPOOL_FORMAT_ERROR;
 *p = 0;
 uid = Uatoi(p);
 if (p <= big_buffer || *(--p) != ' ') goto SPOOL_FORMAT_ERROR;
 *p = 0;
@@ -347,6 +385,7 @@ 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;
 
+/* envelope from */
 if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
 n = Ustrlen(big_buffer);
 if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>')
 if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
 n = Ustrlen(big_buffer);
 if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>')
@@ -356,11 +395,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;
 
+/* time */
 if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
 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)
+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",
@@ -368,117 +409,254 @@ DEBUG(D_deliver) debug_printf("user=%s uid=%ld gid=%ld sender=%s\n",
   sender_address);
 #endif  /* COMPILE_UTILITY */
 
   sender_address);
 #endif  /* COMPILE_UTILITY */
 
-/* Now there may be a number of optional lines, each starting with "-".
-If you add a new setting here, make sure you set the default above. */
+/* Now there may be a number of optional lines, each starting with "-". If you
+add a new setting here, make sure you set the default above.
+
+Because there are now quite a number of different possibilities, we use a
+switch on the first character to avoid too many failing tests. Thanks to Nico
+Erfurth for the patch that implemented this. I have made it even more efficient
+by not re-scanning the first two characters.
+
+To allow new versions of Exim that add additional flags to interwork with older
+versions that do not understand them, just ignore any lines starting with "-"
+that we don't recognize. Otherwise it wouldn't be possible to back off a new
+version that left new-style flags written on the spool. */
 
 
+p = big_buffer + 2;
 for (;;)
   {
 for (;;)
   {
+  int len;
   if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
   if (big_buffer[0] != '-') break;
   if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
   if (big_buffer[0] != '-') break;
+  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, f) == NULL)
+      goto SPOOL_READ_ERROR;
+    }
+  big_buffer[len-1] = 0;
 
 
-  big_buffer[Ustrlen(big_buffer) - 1] = 0;
-  if (Ustrncmp(big_buffer, "-acl ", 5) == 0)
+  switch(big_buffer[1])
     {
     {
-    int index, count;
-    if (sscanf(CS big_buffer + 5, "%d %d", &index, &count) != 2)
-      goto SPOOL_FORMAT_ERROR;
-    /* Ignore if index too big - might be if a later release with more
-    variables built this spool file. */
-    if (index < ACL_C_MAX + ACL_M_MAX)
+    case 'a':
+
+    /* Nowadays we use "-aclc" and "-aclm" for the different types of ACL
+    variable, because Exim allows any number of them, with arbitrary names.
+    The line in the spool file is "-acl[cm] <name> <length>". The name excludes
+    the c or m. */
+
+    if (Ustrncmp(p, "clc ", 4) == 0 ||
+        Ustrncmp(p, "clm ", 4) == 0)
       {
       {
-      acl_var[index] = store_get(count + 1);
-      if (fread(acl_var[index], 1, count+1, f) < count) goto SPOOL_READ_ERROR;
-      acl_var[index][count] = 0;
+      uschar *name, *endptr;
+      int count;
+      tree_node *node;
+      endptr = Ustrchr(big_buffer + 6, ' ');
+      if (endptr == NULL) goto SPOOL_FORMAT_ERROR;
+      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 (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
+      ((uschar*)node->data.ptr)[count] = 0;
       }
       }
-    }
-  else if (Ustrcmp(big_buffer, "-local") == 0) sender_local = TRUE;
-  else if (Ustrcmp(big_buffer, "-localerror") == 0)
-    local_error_message = TRUE;
-  else if (Ustrncmp(big_buffer, "-local_scan ", 12) == 0)
-    local_scan_data = string_copy(big_buffer + 12);
+
+    else if (Ustrcmp(p, "llow_unqualified_recipient") == 0)
+      allow_unqualified_recipient = TRUE;
+    else if (Ustrcmp(p, "llow_unqualified_sender") == 0)
+      allow_unqualified_sender = TRUE;
+
+    else if (Ustrncmp(p, "uth_id", 6) == 0)
+      authenticated_id = string_copy(big_buffer + 9);
+    else if (Ustrncmp(p, "uth_sender", 10) == 0)
+      authenticated_sender = string_copy(big_buffer + 13);
+    else if (Ustrncmp(p, "ctive_hostname", 14) == 0)
+      smtp_active_hostname = string_copy(big_buffer + 17);
+
+    /* For long-term backward compatibility, we recognize "-acl", which was
+    used before the number of ACL variables changed from 10 to 20. This was
+    before the subsequent change to an arbitrary number of named variables.
+    This code is retained so that upgrades from very old versions can still
+    handle old-format spool files. The value given after "-acl" is a number
+    that is 0-9 for connection variables, and 10-19 for message variables. */
+
+    else if (Ustrncmp(p, "cl ", 3) == 0)
+      {
+      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)
+        (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);
+      /* We sanity-checked the count, so disable the Coverity error */
+      /* coverity[tainted_data] */
+      if (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
+      (US node->data.ptr)[count] = '\0';
+      }
+    break;
+
+    case 'b':
+    if (Ustrncmp(p, "ody_linecount", 13) == 0)
+      body_linecount = Uatoi(big_buffer + 15);
+    else if (Ustrncmp(p, "ody_zerocount", 13) == 0)
+      body_zerocount = Uatoi(big_buffer + 15);
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+    else if (Ustrncmp(p, "mi_verdicts ", 12) == 0)
+      bmi_verdicts = string_copy(big_buffer + 14);
+#endif
+    break;
+
+    case 'd':
+    if (Ustrcmp(p, "eliver_firsttime") == 0)
+      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)
+      {
+      deliver_freeze = TRUE;
+      if (sscanf(CS big_buffer+7, TIME_T_FMT, &deliver_frozen_at) != 1)
+       goto SPOOL_READ_ERROR;
+      }
+    break;
+
+    case 'h':
+    if (Ustrcmp(p, "ost_lookup_deferred") == 0)
+      host_lookup_deferred = TRUE;
+    else if (Ustrcmp(p, "ost_lookup_failed") == 0)
+      host_lookup_failed = TRUE;
+    else if (Ustrncmp(p, "ost_auth", 8) == 0)
+      sender_host_authenticated = string_copy(big_buffer + 11);
+    else if (Ustrncmp(p, "ost_name", 8) == 0)
+      sender_host_name = string_copy(big_buffer + 11);
+    else if (Ustrncmp(p, "elo_name", 8) == 0)
+      sender_helo_name = string_copy(big_buffer + 11);
+
+    /* We now record the port number after the address, separated by a
+    dot. For compatibility during upgrading, do nothing if there
+    isn't a value (it gets left at zero). */
+
+    else if (Ustrncmp(p, "ost_address", 11) == 0)
+      {
+      sender_host_port = host_address_extract_port(big_buffer + 14);
+      sender_host_address = string_copy(big_buffer + 14);
+      }
+    break;
+
+    case 'i':
+    if (Ustrncmp(p, "nterface_address", 16) == 0)
+      {
+      interface_port = host_address_extract_port(big_buffer + 19);
+      interface_address = string_copy(big_buffer + 19);
+      }
+    else if (Ustrncmp(p, "dent", 4) == 0)
+      sender_ident = string_copy(big_buffer + 7);
+    break;
+
+    case 'l':
+    if (Ustrcmp(p, "ocal") == 0)
+      sender_local = TRUE;
+    else if (Ustrcmp(big_buffer, "-localerror") == 0)
+      local_error_message = TRUE;
+    else if (Ustrncmp(p, "ocal_scan ", 10) == 0)
+      local_scan_data = string_copy(big_buffer + 12);
+    break;
+
+    case 'm':
+    if (Ustrcmp(p, "anual_thaw") == 0) deliver_manual_thaw = TRUE;
+    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 */
+    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)
+      sender_set_untrusted = TRUE;
 #ifdef WITH_CONTENT_SCAN
 #ifdef WITH_CONTENT_SCAN
-  else if (Ustrncmp(big_buffer, "-spam_score_int ", 16) == 0)
-    spam_score_int = string_copy(big_buffer + 16);
+    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);
 #endif
 #endif
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  else if (Ustrncmp(big_buffer, "-bmi_verdicts ", 14) == 0)
-    bmi_verdicts = string_copy(big_buffer + 14);
+#ifndef COMPILE_UTILITY
+    else if (Ustrncmp(p, "pool_file_wireformat", 20) == 0)
+      spool_file_wireformat = TRUE;
 #endif
 #endif
-  else if (Ustrcmp(big_buffer, "-host_lookup_deferred") == 0)
-    host_lookup_deferred = TRUE;
-  else if (Ustrcmp(big_buffer, "-host_lookup_failed") == 0)
-    host_lookup_failed = TRUE;
-  else if (Ustrncmp(big_buffer, "-body_linecount", 15) == 0)
-    body_linecount = Uatoi(big_buffer + 15);
-  else if (Ustrncmp(big_buffer, "-body_zerocount", 15) == 0)
-    body_zerocount = Uatoi(big_buffer + 15);
-  else if (Ustrncmp(big_buffer, "-frozen", 7) == 0)
-    {
-    deliver_freeze = TRUE;
-    deliver_frozen_at = Uatoi(big_buffer + 7);
-    }
-  else if (Ustrcmp(big_buffer, "-allow_unqualified_recipient") == 0)
-    allow_unqualified_recipient = TRUE;
-  else if (Ustrcmp(big_buffer, "-allow_unqualified_sender") == 0)
-    allow_unqualified_sender = TRUE;
-  else if (Ustrcmp(big_buffer, "-deliver_firsttime") == 0)
-    deliver_firsttime = TRUE;
-  else if (Ustrcmp(big_buffer, "-manual_thaw") == 0)
-    deliver_manual_thaw = TRUE;
-  else if (Ustrncmp(big_buffer, "-auth_id", 8) == 0)
-    authenticated_id = string_copy(big_buffer + 9);
-  else if (Ustrncmp(big_buffer, "-auth_sender", 12) == 0)
-    authenticated_sender = string_copy(big_buffer + 13);
-  else if (Ustrncmp(big_buffer, "-sender_set_untrusted", 21) == 0)
-    sender_set_untrusted = TRUE;
-
-  #ifdef SUPPORT_TLS
-  else if (Ustrncmp(big_buffer, "-tls_certificate_verified", 25) == 0)
-    tls_certificate_verified = TRUE;
-  else if (Ustrncmp(big_buffer, "-tls_cipher", 11) == 0)
-    tls_cipher = string_copy(big_buffer + 12);
-  else if (Ustrncmp(big_buffer, "-tls_peerdn", 11) == 0)
-    tls_peerdn = string_copy(big_buffer + 12);
-  #endif
-
-  /* We now record the port number after the address, separated by a
-  dot. For compatibility during upgrading, do nothing if there
-  isn't a value (it gets left at zero). */
-
-  else if (Ustrncmp(big_buffer, "-host_address", 13) == 0)
-    {
-    sender_host_port = host_extract_port(big_buffer + 14);
-    sender_host_address = string_copy(big_buffer + 14);
-    }
+#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
+    else if (Ustrncmp(p, "mtputf8", 7) == 0)
+      message_smtputf8 = TRUE;
+#endif
+    break;
 
 
-  else if (Ustrncmp(big_buffer, "-interface_address", 18) == 0)
-    {
-    interface_port = host_extract_port(big_buffer + 19);
-    interface_address = string_copy(big_buffer + 19);
-    }
+#ifdef SUPPORT_TLS
+    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);
+# ifndef COMPILE_UTILITY       /* tls support fns not built in */
+    else if (Ustrncmp(p, "ls_ourcert", 10) == 0)
+      (void) tls_import_cert(big_buffer + 13, &tls_in.ourcert);
+    else if (Ustrncmp(p, "ls_peercert", 11) == 0)
+      (void) tls_import_cert(big_buffer + 14, &tls_in.peercert);
+# endif
+    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));
+    else if (Ustrncmp(p, "ls_ocsp", 7) == 0)
+      tls_in.ocsp = big_buffer[10] - '0';
+    break;
+#endif
 
 
-  else if (Ustrncmp(big_buffer, "-active_hostname", 16) == 0)
-    smtp_active_hostname = string_copy(big_buffer + 17);
-  else if (Ustrncmp(big_buffer, "-host_auth", 10) == 0)
-    sender_host_authenticated = string_copy(big_buffer + 11);
-  else if (Ustrncmp(big_buffer, "-host_name", 10) == 0)
-    sender_host_name = string_copy(big_buffer + 11);
-  else if (Ustrncmp(big_buffer, "-helo_name", 10) == 0)
-    sender_helo_name = string_copy(big_buffer + 11);
-  else if (Ustrncmp(big_buffer, "-ident", 6) == 0)
-    sender_ident = string_copy(big_buffer + 7);
-  else if (Ustrncmp(big_buffer, "-received_protocol", 18) == 0)
-    received_protocol = string_copy(big_buffer + 19);
-  else if (Ustrncmp(big_buffer, "-N", 2) == 0)
-    dont_deliver = TRUE;
-
-  /* To allow new versions of Exim that add additional flags to interwork
-  with older versions that do not understand them, just ignore any flagged
-  lines that we don't recognize. Otherwise it wouldn't be possible to back
-  off a new version that left new-style flags written on the spool. That's
-  why the following line is commented out. */
-
-    /* else goto SPOOL_FORMAT_ERROR; */
+#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. */
+    }
   }
 
 /* Build sender_fullhost if required */
   }
 
 /* Build sender_fullhost if required */
@@ -509,10 +687,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 (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 (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);
@@ -521,10 +701,16 @@ 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;
 
@@ -567,6 +753,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--;
@@ -580,7 +769,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     if (*p == ' ')
       {
       *p++ = 0;
     if (*p == ' ')
       {
       *p++ = 0;
-      sscanf(CS p, "%d,%d", &dummy, &pno);
+      (void)sscanf(CS p, "%d,%d", &dummy, &pno);
       }
     }
 
       }
     }
 
@@ -589,7 +778,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   else if (*p == ' ')
     {
     *p++ = 0;
   else if (*p == ' ')
     {
     *p++ = 0;
-    sscanf(CS p, "%d", &pno);
+    (void)sscanf(CS p, "%d", &pno);
     }
 
   /* Handle current format Exim 4 spool files */
     }
 
   /* Handle current format Exim 4 spool files */
@@ -597,13 +786,18 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   else if (*p == '#')
     {
     int flags;
   else if (*p == '#')
     {
     int flags;
-    sscanf(CS p+1, "%d", &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 */
       {
       int len;
       while (isdigit(*(--p)) || *p == ',' || *p == '-');
 
     if ((flags & 0x01) != 0)      /* one_time data exists */
       {
       int len;
       while (isdigit(*(--p)) || *p == ',' || *p == '-');
-      sscanf(CS p+1, "%d,%d", &len, &pno);
+      (void)sscanf(CS p+1, "%d,%d", &len, &pno);
       *p = 0;
       if (len > 0)
         {
       *p = 0;
       if (len > 0)
         {
@@ -612,12 +806,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,
@@ -639,8 +864,8 @@ while ((n = fgetc(f)) != EOF)
   int i;
 
   if (!isdigit(n)) goto SPOOL_FORMAT_ERROR;
   int i;
 
   if (!isdigit(n)) goto SPOOL_FORMAT_ERROR;
-  ungetc(n, f);
-  fscanf(f, "%d%c ", &n, flag);
+  if(ungetc(n, f) == EOF  ||  fscanf(f, "%d%c ", &n, flag) == EOF)
+    goto SPOOL_READ_ERROR;
   if (flag[0] != '*') message_size += n;  /* Omit non-transmitted headers */
 
   if (read_headers)
   if (flag[0] != '*') message_size += n;  /* Omit non-transmitted headers */
 
   if (read_headers)
@@ -700,9 +925,9 @@ 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);
   errno = n;
 
   fclose(f);
   errno = n;
@@ -720,4 +945,6 @@ errno = ERRNO_SPOOLFORMAT;
 return inheader? spool_read_hdrerror : spool_read_enverror;
 }
 
 return inheader? spool_read_hdrerror : spool_read_enverror;
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of spool_in.c */
 /* End of spool_in.c */