Copyright updates:
[exim.git] / src / src / spool_in.c
index cbd2751acbdb3d71b5f69accc5e922c426188051..737a01cddd346194dab548ebcf6151b8b5b753cd 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* Functions for reading spool files. When compiling for a utility (eximon),
 not all are needed, and some functionality can be cut out. */
@@ -34,18 +36,18 @@ Side effect: message_subdir is set for the (possibly split) spool directory
 */
 
 int
-spool_open_datafile(uschar *id)
+spool_open_datafile(uschar * id)
 {
 struct stat statbuf;
 flock_t lock_data;
 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
-directory itself, to pick up leftovers from before the splitting. If split_
-spool_directory is not set, first look in the main input directory. If it is
-not found there, try the split sub-directory, in case it is left over from a
-splitting state. */
+/* If split_spool_directory is set (handled by set_subdir_str()), first look for
+the file in the appropriate sub-directory of the input directory. If it is not
+found there, try the input directory itself, to pick up leftovers from before
+the splitting. If split_ spool_directory is not set, first look in the main
+input directory. If it is not found there, try the split sub-directory, in case
+it is left over from a splitting state. */
 
 for (int i = 0; i < 2; i++)
   {
@@ -54,21 +56,15 @@ for (int i = 0; i < 2; i++)
 
   set_subdir_str(message_subdir, id, i);
   fname = spool_fname(US"input", message_subdir, id, US"-D");
-  DEBUG(D_deliver) debug_printf("Trying spool file %s\n", fname);
+  DEBUG(D_deliver) debug_printf_indent("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.
-   */
+  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)
+                 EXIM_CLOEXEC | EXIM_NOFOLLOW | O_RDWR | O_APPEND, 0)) >= 0)
     break;
   save_errno = errno;
   if (errno == ENOENT)
@@ -79,6 +75,11 @@ for (int i = 0; i < 2; i++)
        *queue_name ? US" Q=" : US"",
        *queue_name ? queue_name : US"",
        id);
+    else DEBUG(D_deliver)
+      debug_printf("Spool%s%s file %s-D not found\n",
+       *queue_name ? US" Q=" : US"",
+       *queue_name ? queue_name : US"",
+       id);
     }
   else
     log_write(0, LOG_MAIN, "Spool error for %s: %s", fname, strerror(errno));
@@ -101,13 +102,13 @@ what it locks. */
 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_len = spool_data_start_offset(id);
 
 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 for %s is locked (another process is handling this message)",
+      id);
   (void)close(fd);
   errno = 0;
   return -1;
@@ -118,7 +119,7 @@ in the count, but add one for the newline before the data. */
 
 if (fstat(fd, &statbuf) == 0)
   {
-  message_body_size = statbuf.st_size - SPOOL_DATA_START_OFFSET;
+  message_body_size = statbuf.st_size - spool_data_start_offset(id);
   message_size = message_body_size + 1;
   }
 
@@ -185,7 +186,7 @@ BOOL right = buffer[1] == 'Y';
 
 if (n < 5) return FALSE;    /* malformed line */
 buffer[n-1] = 0;            /* Remove \n */
-node = store_get(sizeof(tree_node) + n - 3, TRUE);     /* rcpt names tainted */
+node = store_get(sizeof(tree_node) + n - 3, GET_TAINTED);      /* rcpt names tainted */
 *connect = node;
 Ustrcpy(node->name, buffer + 3);
 node->data.ptr = NULL;
@@ -252,7 +253,7 @@ sender_helo_name = NULL;
 sender_host_address = NULL;
 sender_host_name = NULL;
 sender_host_port = 0;
-sender_host_authenticated = NULL;
+sender_host_authenticated = sender_host_auth_pubname = NULL;
 sender_ident = NULL;
 f.sender_local = FALSE;
 f.sender_set_untrusted = FALSE;
@@ -299,10 +300,42 @@ message_smtputf8 = FALSE;
 message_utf8_downconvert = 0;
 #endif
 
+#ifndef COMPILE_UTILITY
+debuglog_name[0] = '\0';
+#endif
 dsn_ret = 0;
 dsn_envid = NULL;
 }
 
+static void *
+fgets_big_buffer(FILE *fp)
+{
+int len = 0;
+
+big_buffer[0] = 0;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) return NULL;
+
+while ((len = Ustrlen(big_buffer)) == big_buffer_size-1
+      && big_buffer[len-1] != '\n')
+  {
+  uschar *newbuffer;
+  int newsize;
+
+  if (big_buffer_size >= BIG_BUFFER_SIZE * 4) return NULL;
+  newsize = big_buffer_size * 2;
+  newbuffer = store_get_perm(newsize, FALSE);
+  memcpy(newbuffer, big_buffer, len);
+
+  big_buffer = newbuffer;
+  big_buffer_size = newsize;
+  if (Ufgets(big_buffer + len, big_buffer_size - len, fp) == NULL) return NULL;
+  }
+
+if (len <= 0 || big_buffer[len-1] != '\n') return NULL;
+return big_buffer;
+}
+
+
 
 /*************************************************
 *             Read spool header file             *
@@ -341,6 +374,7 @@ int n;
 int rcount = 0;
 long int uid, gid;
 BOOL inheader = FALSE;
+const uschar * where;
 
 /* 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
@@ -366,15 +400,21 @@ for (int n = 0; n < 2; n++)
 errno = 0;
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_printf("reading spool file %s\n", name);
+DEBUG(D_deliver) debug_printf_indent("reading spool file %s\n", name);
 #endif  /* COMPILE_UTILITY */
 
 /* 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. */
 
+where = US"first line read";
 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)
+where = US"first line length";
+if (  (  Ustrlen(big_buffer) != MESSAGE_ID_LENGTH + 3
+      && Ustrlen(big_buffer) != MESSAGE_ID_LENGTH_OLD + 3
+      )
+   || (  Ustrncmp(big_buffer, name, MESSAGE_ID_LENGTH + 2) != 0
+      && Ustrncmp(big_buffer, name, MESSAGE_ID_LENGTH_OLD + 2) != 0
+   )  )
   goto SPOOL_FORMAT_ERROR;
 
 /* The next three lines in the header file are in a fixed format. The first
@@ -384,51 +424,61 @@ 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. */
 
+where = US"2nd line read";
 if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
 
-{
-uschar *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 && (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;
-while (p > big_buffer && (isdigit(p[-1]) || '-' == p[-1])) p--;
-uid = Uatoi(p);
-if (p <= big_buffer || *(--p) != ' ') goto SPOOL_FORMAT_ERROR;
-*p = 0;
-}
+ {
+  uschar *p = big_buffer + Ustrlen(big_buffer);
+  while (p > big_buffer && isspace(p[-1])) p--;
+  *p = 0;
+  where = US"2nd line fmt 1";
+  if (!isdigit(p[-1])) goto SPOOL_FORMAT_ERROR;
+  while (p > big_buffer && (isdigit(p[-1]) || '-' == p[-1])) p--;
+  gid = Uatoi(p);
+  where = US"2nd line fmt 2";
+  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[-1])) p--;
+  uid = Uatoi(p);
+  where = US"2nd line fmt 3";
+  if (p <= big_buffer || *(--p) != ' ') goto SPOOL_FORMAT_ERROR;
+  *p = 0;
+ }
 
 originator_login = string_copy(big_buffer);
 originator_uid = (uid_t)uid;
 originator_gid = (gid_t)gid;
 
-/* envelope from */
+where = US"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;
 
-sender_address = store_get(n-2, TRUE); /* tainted */
+sender_address = store_get(n-2, GET_TAINTED);
 Ustrncpy(sender_address, big_buffer+1, n-3);
 sender_address[n-3] = 0;
 
-/* time */
+where = US"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;
 received_time.tv_usec = 0;
+received_time_complete = received_time;
+
 
 message_age = time(NULL) - received_time.tv_sec;
+#ifndef COMPILE_UTILITY
+if (f.running_in_test_harness)
+  message_age = test_harness_fudged_queue_time(message_age);
+#endif
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_printf("user=%s uid=%ld gid=%ld sender=%s\n",
+DEBUG(D_deliver) debug_printf_indent("user=%s uid=%ld gid=%ld sender=%s\n",
   originator_login, (long int)originator_uid, (long int)originator_gid,
   sender_address);
-#endif  /* COMPILE_UTILITY */
+#endif
 
 /* 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.
@@ -443,33 +493,41 @@ 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.
 
-If the line starts with "--" the content of the variable is tainted.  */
+If the line starts with "--" the content of the variable is tainted.
+If the line start "--(<lookuptype>)" it is also quoted for the given <lookuptype>.
+*/
 
 for (;;)
   {
-  int len;
-  BOOL tainted;
+  const void * proto_mem;
   uschar * var;
   const uschar * p;
 
-  if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
+  if (fgets_big_buffer(fp) == 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, FALSE);
-    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;
+  big_buffer[Ustrlen(big_buffer)-1] = 0;
 
-  tainted = big_buffer[1] == '-';
-  var =  big_buffer + (tainted ? 2 : 1);
+  proto_mem = big_buffer[1] == '-' ? GET_TAINTED : GET_UNTAINTED;
+  var =  big_buffer + (proto_mem == GET_UNTAINTED ? 1 : 2);
+  if (*var == '(')                             /* marker for quoted value */
+    {
+    uschar * s;
+    for (s = ++var; *s != ')'; ) s++;
+#ifndef COMPILE_UTILITY
+      {
+      int idx;
+      if ((idx = search_findtype(var, s - var)) < 0)
+       {
+       DEBUG(D_any)
+         debug_printf("Unrecognised quoter %.*s\n", (int)(s - var), var+1);
+       where = NULL;
+       goto SPOOL_FORMAT_ERROR;
+       }
+      proto_mem = store_get_quoted(1, GET_TAINTED, idx);
+      }
+#endif  /* COMPILE_UTILITY */
+    var = s + 1;
+    }
   p = var + 1;
 
   switch(*var)
@@ -488,12 +546,13 @@ for (;;)
       int count;
       tree_node *node;
       endptr = Ustrchr(var + 5, ' ');
-      if (endptr == NULL) goto SPOOL_FORMAT_ERROR;
+      where = US"-aclXn";
+      if (!endptr) goto SPOOL_FORMAT_ERROR;
       name = string_sprintf("%c%.*s", var[3],
         (int)(endptr - var - 5), var + 5);
       if (sscanf(CS endptr, " %d", &count) != 1) goto SPOOL_FORMAT_ERROR;
       node = acl_var_create(name);
-      node->data.ptr = store_get(count + 1, tainted);
+      node->data.ptr = store_get(count + 1, proto_mem);
       if (fread(node->data.ptr, 1, count+1, fp) < count) goto SPOOL_READ_ERROR;
       ((uschar*)node->data.ptr)[count] = 0;
       }
@@ -504,11 +563,11 @@ for (;;)
       f.allow_unqualified_sender = TRUE;
 
     else if (Ustrncmp(p, "uth_id", 6) == 0)
-      authenticated_id = string_copy_taint(var + 8, tainted);
+      authenticated_id = string_copy_taint(var + 8, proto_mem);
     else if (Ustrncmp(p, "uth_sender", 10) == 0)
-      authenticated_sender = string_copy_taint(var + 12, tainted);
+      authenticated_sender = string_copy_taint(var + 12, proto_mem);
     else if (Ustrncmp(p, "ctive_hostname", 14) == 0)
-      smtp_active_hostname = string_copy_taint(var + 16, tainted);
+      smtp_active_hostname = string_copy_taint(var + 16, proto_mem);
 
     /* For long-term backward compatibility, we recognize "-acl", which was
     used before the number of ACL variables changed from 10 to 20. This was
@@ -522,6 +581,7 @@ for (;;)
       unsigned index, count;
       uschar name[20];   /* Need plenty of space for %u format */
       tree_node * node;
+      where = US"-acl (old)";
       if (  sscanf(CS var + 4, "%u %u", &index, &count) != 2
         || index >= 20
         || count > 16384       /* arbitrary limit on variable size */
@@ -532,7 +592,7 @@ for (;;)
       else
         (void) string_format(name, sizeof(name), "%c%u", 'm', index - 10);
       node = acl_var_create(name);
-      node->data.ptr = store_get(count + 1, tainted);
+      node->data.ptr = store_get(count + 1, proto_mem);
       /* 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;
@@ -547,18 +607,23 @@ for (;;)
       body_zerocount = Uatoi(var + 14);
 #ifdef EXPERIMENTAL_BRIGHTMAIL
     else if (Ustrncmp(p, "mi_verdicts ", 12) == 0)
-      bmi_verdicts = string_copy_taint(var + 13, tainted);
+      bmi_verdicts = string_copy_taint(var + 13, proto_mem);
 #endif
     break;
 
     case 'd':
     if (Ustrcmp(p, "eliver_firsttime") == 0)
       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 var + 7);
     else if (Ustrncmp(p, "sn_envid", 8) == 0)
-      dsn_envid = string_copy_taint(var + 10, tainted);
+      dsn_envid = string_copy_taint(var + 10, proto_mem);
+#ifndef COMPILE_UTILITY
+    else if (Ustrncmp(p, "ebug_selector ", 14) == 0)
+      debug_selector = strtol(CS var + 15, NULL, 0);
+    else if (Ustrncmp(p, "ebuglog_name ", 13) == 0)
+      debug_logging_from_spool(var + 14);
+#endif
     break;
 
     case 'f':
@@ -575,12 +640,14 @@ for (;;)
       host_lookup_deferred = TRUE;
     else if (Ustrcmp(p, "ost_lookup_failed") == 0)
       host_lookup_failed = TRUE;
+    else if (Ustrncmp(p, "ost_auth_pubname", 16) == 0)
+      sender_host_auth_pubname = string_copy_taint(var + 18, proto_mem);
     else if (Ustrncmp(p, "ost_auth", 8) == 0)
-      sender_host_authenticated = string_copy_taint(var + 10, tainted);
+      sender_host_authenticated = string_copy_taint(var + 10, proto_mem);
     else if (Ustrncmp(p, "ost_name", 8) == 0)
-      sender_host_name = string_copy_taint(var + 10, tainted);
+      sender_host_name = string_copy_taint(var + 10, proto_mem);
     else if (Ustrncmp(p, "elo_name", 8) == 0)
-      sender_helo_name = string_copy_taint(var + 10, tainted);
+      sender_helo_name = string_copy_taint(var + 10, proto_mem);
 
     /* We now record the port number after the address, separated by a
     dot. For compatibility during upgrading, do nothing if there
@@ -589,7 +656,7 @@ for (;;)
     else if (Ustrncmp(p, "ost_address", 11) == 0)
       {
       sender_host_port = host_address_extract_port(var + 13);
-      sender_host_address = string_copy_taint(var + 13, tainted);
+      sender_host_address = string_copy_taint(var + 13, proto_mem);
       }
     break;
 
@@ -597,10 +664,10 @@ for (;;)
     if (Ustrncmp(p, "nterface_address", 16) == 0)
       {
       interface_port = host_address_extract_port(var + 18);
-      interface_address = string_copy_taint(var + 18, tainted);
+      interface_address = string_copy_taint(var + 18, proto_mem);
       }
     else if (Ustrncmp(p, "dent", 4) == 0)
-      sender_ident = string_copy_taint(var + 6, tainted);
+      sender_ident = string_copy_taint(var + 6, proto_mem);
     break;
 
     case 'l':
@@ -610,7 +677,7 @@ for (;;)
       f.local_error_message = TRUE;
 #ifdef HAVE_LOCAL_SCAN
     else if (Ustrncmp(p, "ocal_scan ", 10) == 0)
-      local_scan_data = string_copy_taint(var + 11, tainted);
+      local_scan_data = string_copy_taint(var + 11, proto_mem);
 #endif
     break;
 
@@ -627,12 +694,24 @@ for (;;)
 
     case 'r':
     if (Ustrncmp(p, "eceived_protocol", 16) == 0)
-      received_protocol = string_copy_taint(var + 18, tainted);
+      received_protocol = string_copy_taint(var + 18, proto_mem);
     else if (Ustrncmp(p, "eceived_time_usec", 17) == 0)
       {
       unsigned usec;
       if (sscanf(CS var + 20, "%u", &usec) == 1)
+       {
        received_time.tv_usec = usec;
+       if (!received_time_complete.tv_sec) received_time_complete.tv_usec = usec;
+       }
+      }
+    else if (Ustrncmp(p, "eceived_time_complete", 21) == 0)
+      {
+      unsigned sec, usec;
+      if (sscanf(CS var + 23, "%u.%u", &sec, &usec) == 2)
+       {
+       received_time_complete.tv_sec = sec;
+       received_time_complete.tv_usec = usec;
+       }
       }
     break;
 
@@ -641,11 +720,11 @@ for (;;)
       f.sender_set_untrusted = TRUE;
 #ifdef WITH_CONTENT_SCAN
     else if (Ustrncmp(p, "pam_bar ", 8) == 0)
-      spam_bar = string_copy_taint(var + 9, tainted);
+      spam_bar = string_copy_taint(var + 9, proto_mem);
     else if (Ustrncmp(p, "pam_score ", 10) == 0)
-      spam_score = string_copy_taint(var + 11, tainted);
+      spam_score = string_copy_taint(var + 11, proto_mem);
     else if (Ustrncmp(p, "pam_score_int ", 14) == 0)
-      spam_score_int = string_copy_taint(var + 15, tainted);
+      spam_score_int = string_copy_taint(var + 15, proto_mem);
 #endif
 #ifndef COMPILE_UTILITY
     else if (Ustrncmp(p, "pool_file_wireformat", 20) == 0)
@@ -665,7 +744,7 @@ for (;;)
       if (Ustrncmp(q, "certificate_verified", 20) == 0)
        tls_in.certificate_verified = TRUE;
       else if (Ustrncmp(q, "cipher", 6) == 0)
-       tls_in.cipher = string_copy_taint(q+7, tainted);
+       tls_in.cipher = string_copy_taint(q+7, proto_mem);
 # ifndef COMPILE_UTILITY       /* tls support fns not built in */
       else if (Ustrncmp(q, "ourcert", 7) == 0)
        (void) tls_import_cert(q+8, &tls_in.ourcert);
@@ -673,17 +752,17 @@ for (;;)
        (void) tls_import_cert(q+9, &tls_in.peercert);
 # endif
       else if (Ustrncmp(q, "peerdn", 6) == 0)
-       tls_in.peerdn = string_unprinting(string_copy_taint(q+7, tainted));
+       tls_in.peerdn = string_unprinting(string_copy_taint(q+7, proto_mem));
       else if (Ustrncmp(q, "sni", 3) == 0)
-       tls_in.sni = string_unprinting(string_copy_taint(q+4, tainted));
+       tls_in.sni = string_unprinting(string_copy_taint(q+4, proto_mem));
       else if (Ustrncmp(q, "ocsp", 4) == 0)
        tls_in.ocsp = q[5] - '0';
-# ifdef EXPERIMENTAL_TLS_RESUME
+# ifndef DISABLE_TLS_RESUME
       else if (Ustrncmp(q, "resumption", 10) == 0)
        tls_in.resumption = q[11] - 'A';
 # endif
       else if (Ustrncmp(q, "ver", 3) == 0)
-       tls_in.ver = string_copy_taint(q+4, tainted);
+       tls_in.ver = string_copy_taint(q+4, proto_mem);
       }
     break;
 #endif
@@ -710,44 +789,43 @@ host_build_sender_fullhost();
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver)
-  debug_printf("sender_local=%d ident=%s\n", f.sender_local,
+  debug_printf_indent("sender_local=%d ident=%s\n", f.sender_local,
     sender_ident ? sender_ident : US"unset");
 #endif  /* COMPILE_UTILITY */
 
 /* We now have the tree of addresses NOT to deliver to, or a line
 containing "XX", indicating no tree. */
 
+where = US"nondeliver";
 if (Ustrncmp(big_buffer, "XX\n", 3) != 0 &&
   !read_nonrecipients_tree(&tree_nonrecipients, fp, big_buffer, big_buffer_size))
     goto SPOOL_FORMAT_ERROR;
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver)
-  {
-  debug_printf("Non-recipients:\n");
-  debug_print_tree(tree_nonrecipients);
-  }
+DEBUG(D_deliver) debug_print_tree("Non-recipients", tree_nonrecipients);
 #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.
 Apply an arbitrary sanity check.*/
 
+where = US"rcpt cnt";
 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);
+DEBUG(D_deliver) debug_printf_indent("recipients_count=%d\n", rcount);
 #endif  /* COMPILE_UTILITY */
 
 recipients_list_max = rcount;
-recipients_list = store_get(rcount * sizeof(recipient_item), FALSE);
+recipients_list = store_get(rcount * sizeof(recipient_item), GET_UNTAINTED);
 
 /* We sanitised the count and know we have enough memory, so disable
 the Coverity error on recipients_count */
 /* coverity[tainted_data] */
 
+where = US"recipient";
 for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   {
   int nn;
@@ -757,7 +835,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   uschar *errors_to = NULL;
   uschar *p;
 
-  if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
+  if (fgets_big_buffer(fp) == NULL) goto SPOOL_READ_ERROR;
   nn = Ustrlen(big_buffer);
   if (nn < 2) goto SPOOL_FORMAT_ERROR;
 
@@ -809,7 +887,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     {
     int dummy;
 #if !defined (COMPILE_UTILITY)
-    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim 3 spool file\n");
+    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - Exim 3 spool file\n");
 #endif
     while (isdigit(*(--p)) || *p == ',');
     if (*p == ' ')
@@ -824,7 +902,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   else if (*p == ' ')
     {
 #if !defined (COMPILE_UTILITY)
-    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - early Exim 4 spool file\n");
+    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - early Exim 4 spool file\n");
 #endif
     *p++ = 0;
     (void)sscanf(CS p, "%d", &pno);
@@ -837,12 +915,12 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     int flags;
 
 #if !defined (COMPILE_UTILITY)
-    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim standard format spoolfile\n");
+    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - Exim standard format spoolfile\n");
 #endif
 
     (void)sscanf(CS p+1, "%d", &flags);
 
-    if ((flags & 0x01) != 0)      /* one_time data exists */
+    if (flags & 0x01)      /* one_time data exists */
       {
       int len;
       while (isdigit(*(--p)) || *p == ',' || *p == '-');
@@ -851,12 +929,12 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
       if (len > 0)
         {
         p -= len;
-        errors_to = string_copy_taint(p, TRUE);
+        errors_to = string_copy_taint(p, GET_TAINTED);
         }
       }
 
-    *(--p) = 0;   /* Terminate address */
-    if ((flags & 0x02) != 0)      /* one_time data exists */
+    *--p = 0;   /* Terminate address */
+    if (flags & 0x02)      /* one_time data exists */
       {
       int len;
       while (isdigit(*(--p)) || *p == ',' || *p == '-');
@@ -865,25 +943,25 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
       if (len > 0)
         {
         p -= len;
-        orcpt = string_copy_taint(p, TRUE);
+        orcpt = string_copy_taint(p, GET_TAINTED);
         }
       }
 
-    *(--p) = 0;   /* Terminate address */
+    *--p = 0;   /* Terminate address */
     }
 #if !defined(COMPILE_UTILITY)
   else
-    { DEBUG(D_deliver) debug_printf("**** SPOOL_IN - No additional fields\n"); }
+    { DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - No additional fields\n"); }
 
   if (orcpt || dsn_flags)
-    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: <%s> orcpt: <%s> dsn_flags: 0x%x\n",
+    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - address: <%s> orcpt: <%s> dsn_flags: 0x%x\n",
       big_buffer, orcpt, dsn_flags);
   if (errors_to)
-    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: <%s> errorsto: <%s>\n",
+    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - address: <%s> errorsto: <%s>\n",
       big_buffer, errors_to);
 #endif
 
-  recipients_list[recipients_count].address = string_copy_taint(big_buffer, TRUE);
+  recipients_list[recipients_count].address = string_copy_taint(big_buffer, GET_TAINTED);
   recipients_list[recipients_count].pno = pno;
   recipients_list[recipients_count].errors_to = errors_to;
   recipients_list[recipients_count].orcpt = orcpt;
@@ -899,12 +977,13 @@ always, in order to check on the format of the file, but only create a header
 list if requested to do so. */
 
 inheader = TRUE;
+where = US"headers";
 if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
 if (big_buffer[0] != '\n') goto SPOOL_FORMAT_ERROR;
 
 while ((n = fgetc(fp)) != EOF)
   {
-  header_line *h;
+  header_line * h;
   uschar flag[4];
   int i;
 
@@ -915,11 +994,11 @@ while ((n = fgetc(fp)) != EOF)
 
   if (read_headers)
     {
-    h = store_get(sizeof(header_line), FALSE);
+    h = store_get(sizeof(header_line), GET_UNTAINTED);
     h->next = NULL;
     h->type = flag[0];
     h->slen = n;
-    h->text = store_get(n+1, TRUE);    /* tainted */
+    h->text = store_get(n+1, GET_TAINTED);
 
     if (h->type == htype_received) received_count++;
 
@@ -951,7 +1030,7 @@ line count by adding the body linecount to the header linecount. Close the file
 and give a positive response. */
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_printf("body_linecount=%d message_linecount=%d\n",
+DEBUG(D_deliver) debug_printf_indent("body_linecount=%d message_linecount=%d\n",
   body_linecount, message_linecount);
 #endif  /* COMPILE_UTILITY */
 
@@ -982,7 +1061,8 @@ if (errno != 0)
 SPOOL_FORMAT_ERROR:
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_any) debug_printf("Format error in spool file %s\n", name);
+DEBUG(D_any) debug_printf("Format error in spool file %s%s%s\n", name,
+  where ? ": " : "", where ? where : US"");
 #endif  /* COMPILE_UTILITY */
 
 fclose(fp);
@@ -990,6 +1070,46 @@ errno = ERRNO_SPOOLFORMAT;
 return inheader? spool_read_hdrerror : spool_read_enverror;
 }
 
+
+#ifndef COMPILE_UTILITY
+/* Read out just the (envelope) sender string from the spool -H file.
+Remove the <> wrap and return it in allocated store.  Return NULL on error.
+
+We assume that message_subdir is already set.
+*/
+
+uschar *
+spool_sender_from_msgid(const uschar * id)
+{
+FILE * fp;
+int n;
+uschar * yield = NULL;
+
+if (!(fp = Ufopen(spool_fname(US"input", message_subdir, id, US"-H"), "rb")))
+  return NULL;
+
+DEBUG(D_deliver) debug_printf_indent("reading spool file %s-H\n", id);
+
+/* Skip the line with the copy of the filename, then the line with login/uid/gid.
+Read the next line, which should be the envelope sender.
+Do basic validation on that. */
+
+if (  Ufgets(big_buffer, big_buffer_size, fp) != NULL
+   && Ufgets(big_buffer, big_buffer_size, fp) != NULL
+   && Ufgets(big_buffer, big_buffer_size, fp) != NULL
+   && (n = Ustrlen(big_buffer)) >= 3
+   && big_buffer[0] == '<' && big_buffer[n-2] == '>'
+   )
+  {
+  yield = store_get(n-2, GET_TAINTED);
+  Ustrncpy(yield, big_buffer+1, n-3);
+  yield[n-3] = 0;
+  }
+fclose(fp);
+return yield;
+}
+#endif  /* COMPILE_UTILITY */
+
 /* vi: aw ai sw=2
 */
 /* End of spool_in.c */