Events: raise msg:fail:internal & msg:complete for -Mrm. Bug 2310
[exim.git] / src / src / queue.c
index e7ae019d2f1c0892d166ddab14d6297bfa946acf..ac1bb760411eed4b93477223fb41ef99fa5eeaaf 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions that operate on the input queue. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions that operate on the input queue. */
@@ -12,7 +12,7 @@
 
 
 
 
 
 
-/* Routines with knowlege of spool layout */
+/* Routines with knowledge of spool layout */
 
 #ifndef COMPILE_UTILITY
 static void
 
 #ifndef COMPILE_UTILITY
 static void
@@ -80,7 +80,11 @@ queue_filename *first = NULL;
 queue_filename **append = &first;
 
 while (a && b)
 queue_filename **append = &first;
 
 while (a && b)
-  if (Ustrcmp(a->text, b->text) < 0)
+  {
+  int d;
+  if ((d = Ustrncmp(a->text, b->text, 6)) == 0)
+    d = Ustrcmp(a->text + 14, b->text + 14);
+  if (d < 0)
     {
     *append = a;
     append= &a->next;
     {
     *append = a;
     append= &a->next;
@@ -92,6 +96,7 @@ while (a && b)
     append= &b->next;
     b = b->next;
     }
     append= &b->next;
     b = b->next;
     }
+  }
 
 *append = a ? a : b;
 return first;
 
 *append = a ? a : b;
 return first;
@@ -278,7 +283,7 @@ for (; i <= *subcount; i++)
           if (root[j])
             {
             next = merge_queue_lists(next, root[j]);
           if (root[j])
             {
             next = merge_queue_lists(next, root[j]);
-            root[j] = (j == LOG2_MAXNODES - 1)? next : NULL;
+            root[j] = j == LOG2_MAXNODES - 1 ? next : NULL;
             }
           else
             {
             }
           else
             {
@@ -368,7 +373,7 @@ Returns:     nothing
 void
 queue_run(uschar *start_id, uschar *stop_id, BOOL recurse)
 {
 void
 queue_run(uschar *start_id, uschar *stop_id, BOOL recurse)
 {
-BOOL force_delivery = queue_run_force || deliver_selectstring != NULL ||
+BOOL force_delivery = f.queue_run_force || deliver_selectstring != NULL ||
   deliver_selectstring_sender != NULL;
 const pcre *selectstring_regex = NULL;
 const pcre *selectstring_regex_sender = NULL;
   deliver_selectstring_sender != NULL;
 const pcre *selectstring_regex = NULL;
 const pcre *selectstring_regex_sender = NULL;
@@ -385,10 +390,10 @@ on TCP/IP channels have queue_run_pid set, but not queue_running. */
 
 queue_domains = NULL;
 queue_smtp_domains = NULL;
 
 queue_domains = NULL;
 queue_smtp_domains = NULL;
-queue_smtp = queue_2stage;
+f.queue_smtp = f.queue_2stage;
 
 queue_run_pid = getpid();
 
 queue_run_pid = getpid();
-queue_running = TRUE;
+f.queue_running = TRUE;
 
 /* Log the true start of a queue run, and fancy options */
 
 
 /* Log the true start of a queue run, and fancy options */
 
@@ -397,36 +402,26 @@ if (!recurse)
   uschar extras[8];
   uschar *p = extras;
 
   uschar extras[8];
   uschar *p = extras;
 
-  if (queue_2stage) *p++ = 'q';
-  if (queue_run_first_delivery) *p++ = 'i';
-  if (queue_run_force) *p++ = 'f';
-  if (deliver_force_thaw) *p++ = 'f';
-  if (queue_run_local) *p++ = 'l';
+  if (f.queue_2stage) *p++ = 'q';
+  if (f.queue_run_first_delivery) *p++ = 'i';
+  if (f.queue_run_force) *p++ = 'f';
+  if (f.deliver_force_thaw) *p++ = 'f';
+  if (f.queue_run_local) *p++ = 'l';
   *p = 0;
 
   p = big_buffer;
   *p = 0;
 
   p = big_buffer;
-  sprintf(CS p, "pid=%d", (int)queue_run_pid);
-  while (*p != 0) p++;
+  p += sprintf(CS p, "pid=%d", (int)queue_run_pid);
 
   if (extras[0] != 0)
 
   if (extras[0] != 0)
-    {
-    sprintf(CS p, " -q%s", extras);
-    while (*p != 0) p++;
-    }
+    p += sprintf(CS p, " -q%s", extras);
 
 
-  if (deliver_selectstring != NULL)
-    {
-    sprintf(CS p, " -R%s %s", deliver_selectstring_regex? "r" : "",
+  if (deliver_selectstring)
+    p += sprintf(CS p, " -R%s %s", f.deliver_selectstring_regex? "r" : "",
       deliver_selectstring);
       deliver_selectstring);
-    while (*p != 0) p++;
-    }
 
 
-  if (deliver_selectstring_sender != NULL)
-    {
-    sprintf(CS p, " -S%s %s", deliver_selectstring_sender_regex? "r" : "",
+  if (deliver_selectstring_sender)
+    p += sprintf(CS p, " -S%s %s", f.deliver_selectstring_sender_regex? "r" : "",
       deliver_selectstring_sender);
       deliver_selectstring_sender);
-    while (*p != 0) p++;
-    }
 
   log_detail = string_copy(big_buffer);
   if (*queue_name)
 
   log_detail = string_copy(big_buffer);
   if (*queue_name)
@@ -438,10 +433,10 @@ if (!recurse)
 
 /* If deliver_selectstring is a regex, compile it. */
 
 
 /* If deliver_selectstring is a regex, compile it. */
 
-if (deliver_selectstring != NULL && deliver_selectstring_regex)
+if (deliver_selectstring && f.deliver_selectstring_regex)
   selectstring_regex = regex_must_compile(deliver_selectstring, TRUE, FALSE);
 
   selectstring_regex = regex_must_compile(deliver_selectstring, TRUE, FALSE);
 
-if (deliver_selectstring_sender != NULL && deliver_selectstring_sender_regex)
+if (deliver_selectstring_sender && f.deliver_selectstring_sender_regex)
   selectstring_regex_sender =
     regex_must_compile(deliver_selectstring_sender, TRUE, FALSE);
 
   selectstring_regex_sender =
     regex_must_compile(deliver_selectstring_sender, TRUE, FALSE);
 
@@ -455,16 +450,16 @@ any messages therein), and then repeats for any subdirectories that were found.
 When the first argument of queue_get_spool_list() is 0, it scans the top
 directory, fills in subdirs, and sets subcount. The order of the directories is
 then randomized after the first time through, before they are scanned in
 When the first argument of queue_get_spool_list() is 0, it scans the top
 directory, fills in subdirs, and sets subcount. The order of the directories is
 then randomized after the first time through, before they are scanned in
-subsqeuent iterations.
+subsequent iterations.
 
 When the first argument of queue_get_spool_list() is -1 (for queue_run_in_
 order), it scans all directories and makes a single message list. */
 
 
 When the first argument of queue_get_spool_list() is -1 (for queue_run_in_
 order), it scans all directories and makes a single message list. */
 
-for (i  = (queue_run_in_order? -1 : 0);
-     i <= (queue_run_in_order? -1 : subcount);
+for (i = queue_run_in_order ? -1 : 0;
+     i <= (queue_run_in_order ? -1 : subcount);
      i++)
   {
      i++)
   {
-  queue_filename *f;
+  queue_filename * fq;
   void *reset_point1 = store_get(0);
 
   DEBUG(D_queue_run)
   void *reset_point1 = store_get(0);
 
   DEBUG(D_queue_run)
@@ -477,9 +472,9 @@ for (i  = (queue_run_in_order? -1 : 0);
       debug_printf("queue running subdirectory '%c'\n", subdirs[i]);
     }
 
       debug_printf("queue running subdirectory '%c'\n", subdirs[i]);
     }
 
-  for (f = queue_get_spool_list(i, subdirs, &subcount, !queue_run_in_order);
-       f;
-       f = f->next)
+  for (fq = queue_get_spool_list(i, subdirs, &subcount, !queue_run_in_order);
+       fq;
+       fq = fq->next)
     {
     pid_t pid;
     int status;
     {
     pid_t pid;
     int status;
@@ -490,7 +485,7 @@ for (i  = (queue_run_in_order? -1 : 0);
     /* Unless deliveries are forced, if deliver_queue_load_max is non-negative,
     check that the load average is low enough to permit deliveries. */
 
     /* Unless deliveries are forced, if deliver_queue_load_max is non-negative,
     check that the load average is low enough to permit deliveries. */
 
-    if (!queue_run_force && deliver_queue_load_max >= 0)
+    if (!f.queue_run_force && deliver_queue_load_max >= 0)
       if ((load_average = os_getloadavg()) > deliver_queue_load_max)
         {
         log_write(L_queue_run, LOG_MAIN, "Abandon queue run: %s (load %.2f, max %.2f)",
       if ((load_average = os_getloadavg()) > deliver_queue_load_max)
         {
         log_write(L_queue_run, LOG_MAIN, "Abandon queue run: %s (load %.2f, max %.2f)",
@@ -507,15 +502,15 @@ for (i  = (queue_run_in_order? -1 : 0);
 
     /* Skip this message unless it's within the ID limits */
 
 
     /* Skip this message unless it's within the ID limits */
 
-    if (stop_id && Ustrncmp(f->text, stop_id, MESSAGE_ID_LENGTH) > 0)
+    if (stop_id && Ustrncmp(fq->text, stop_id, MESSAGE_ID_LENGTH) > 0)
       continue;
       continue;
-    if (start_id && Ustrncmp(f->text, start_id, MESSAGE_ID_LENGTH) < 0)
+    if (start_id && Ustrncmp(fq->text, start_id, MESSAGE_ID_LENGTH) < 0)
       continue;
 
     /* Check that the message still exists */
 
       continue;
 
     /* Check that the message still exists */
 
-    message_subdir[0] = f->dir_uschar;
-    if (Ustat(spool_fname(US"input", message_subdir, f->text, US""), &statbuf) < 0)
+    message_subdir[0] = fq->dir_uschar;
+    if (Ustat(spool_fname(US"input", message_subdir, fq->text, US""), &statbuf) < 0)
       continue;
 
     /* There are some tests that require the reading of the header file. Ensure
       continue;
 
     /* There are some tests that require the reading of the header file. Ensure
@@ -525,10 +520,10 @@ for (i  = (queue_run_in_order? -1 : 0);
     message when many are not going to be delivered. */
 
     if (deliver_selectstring || deliver_selectstring_sender ||
     message when many are not going to be delivered. */
 
     if (deliver_selectstring || deliver_selectstring_sender ||
-        queue_run_first_delivery)
+        f.queue_run_first_delivery)
       {
       BOOL wanted = TRUE;
       {
       BOOL wanted = TRUE;
-      BOOL orig_dont_deliver = dont_deliver;
+      BOOL orig_dont_deliver = f.dont_deliver;
       void *reset_point2 = store_get(0);
 
       /* Restore the original setting of dont_deliver after reading the header,
       void *reset_point2 = store_get(0);
 
       /* Restore the original setting of dont_deliver after reading the header,
@@ -536,14 +531,14 @@ for (i  = (queue_run_in_order? -1 : 0);
       follow. If the message is chosen for delivery, the header is read again
       in the deliver_message() function, in a subprocess. */
 
       follow. If the message is chosen for delivery, the header is read again
       in the deliver_message() function, in a subprocess. */
 
-      if (spool_read_header(f->text, FALSE, TRUE) != spool_read_OK) continue;
-      dont_deliver = orig_dont_deliver;
+      if (spool_read_header(fq->text, FALSE, TRUE) != spool_read_OK) continue;
+      f.dont_deliver = orig_dont_deliver;
 
       /* Now decide if we want to deliver this message. As we have read the
       header file, we might as well do the freeze test now, and save forking
       another process. */
 
 
       /* Now decide if we want to deliver this message. As we have read the
       header file, we might as well do the freeze test now, and save forking
       another process. */
 
-      if (deliver_freeze && !deliver_force_thaw)
+      if (f.deliver_freeze && !f.deliver_force_thaw)
         {
         log_write(L_skip_delivery, LOG_MAIN, "Message is frozen");
         wanted = FALSE;
         {
         log_write(L_skip_delivery, LOG_MAIN, "Message is frozen");
         wanted = FALSE;
@@ -551,9 +546,9 @@ for (i  = (queue_run_in_order? -1 : 0);
 
       /* Check first_delivery in the case when there are no message logs. */
 
 
       /* Check first_delivery in the case when there are no message logs. */
 
-      else if (queue_run_first_delivery && !deliver_firsttime)
+      else if (f.queue_run_first_delivery && !f.deliver_firsttime)
         {
         {
-        DEBUG(D_queue_run) debug_printf("%s: not first delivery\n", f->text);
+        DEBUG(D_queue_run) debug_printf("%s: not first delivery\n", fq->text);
         wanted = FALSE;
         }
 
         wanted = FALSE;
         }
 
@@ -564,7 +559,7 @@ for (i  = (queue_run_in_order? -1 : 0);
       /* Sender matching */
 
       else if (  deliver_selectstring_sender
       /* Sender matching */
 
       else if (  deliver_selectstring_sender
-             && !(deliver_selectstring_sender_regex
+             && !(f.deliver_selectstring_sender_regex
                  ? (pcre_exec(selectstring_regex_sender, NULL,
                      CS sender_address, Ustrlen(sender_address), 0, PCRE_EOPT,
                      NULL, 0) >= 0)
                  ? (pcre_exec(selectstring_regex_sender, NULL,
                      CS sender_address, Ustrlen(sender_address), 0, PCRE_EOPT,
                      NULL, 0) >= 0)
@@ -573,7 +568,7 @@ for (i  = (queue_run_in_order? -1 : 0);
              )   )
         {
         DEBUG(D_queue_run) debug_printf("%s: sender address did not match %s\n",
              )   )
         {
         DEBUG(D_queue_run) debug_printf("%s: sender address did not match %s\n",
-          f->text, deliver_selectstring_sender);
+          fq->text, deliver_selectstring_sender);
         wanted = FALSE;
         }
 
         wanted = FALSE;
         }
 
@@ -585,7 +580,7 @@ for (i  = (queue_run_in_order? -1 : 0);
         for (i = 0; i < recipients_count; i++)
           {
           uschar *address = recipients_list[i].address;
         for (i = 0; i < recipients_count; i++)
           {
           uschar *address = recipients_list[i].address;
-          if (  (deliver_selectstring_regex
+          if (  (f.deliver_selectstring_regex
                ? (pcre_exec(selectstring_regex, NULL, CS address,
                     Ustrlen(address), 0, PCRE_EOPT, NULL, 0) >= 0)
                 : (strstric(address, deliver_selectstring, FALSE) != NULL)
                ? (pcre_exec(selectstring_regex, NULL, CS address,
                     Ustrlen(address), 0, PCRE_EOPT, NULL, 0) >= 0)
                 : (strstric(address, deliver_selectstring, FALSE) != NULL)
@@ -599,13 +594,14 @@ for (i  = (queue_run_in_order? -1 : 0);
           {
           DEBUG(D_queue_run)
             debug_printf("%s: no recipient address matched %s\n",
           {
           DEBUG(D_queue_run)
             debug_printf("%s: no recipient address matched %s\n",
-              f->text, deliver_selectstring);
+              fq->text, deliver_selectstring);
           wanted = FALSE;
           }
         }
 
       /* Recover store used when reading the header */
 
           wanted = FALSE;
           }
         }
 
       /* Recover store used when reading the header */
 
+      spool_clear_header_globals();
       store_reset(reset_point2);
       if (!wanted) continue;      /* With next message */
       }
       store_reset(reset_point2);
       if (!wanted) continue;      /* With next message */
       }
@@ -646,14 +642,14 @@ for (i  = (queue_run_in_order? -1 : 0);
     /* Now deliver the message; get the id by cutting the -H off the file
     name. The return of the process is zero if a delivery was attempted. */
 
     /* Now deliver the message; get the id by cutting the -H off the file
     name. The return of the process is zero if a delivery was attempted. */
 
-    set_process_info("running queue: %s", f->text);
-    f->text[SPOOL_NAME_LENGTH-2] = 0;
+    set_process_info("running queue: %s", fq->text);
+    fq->text[SPOOL_NAME_LENGTH-2] = 0;
     if ((pid = fork()) == 0)
       {
       int rc;
     if ((pid = fork()) == 0)
       {
       int rc;
-      if (running_in_test_harness) millisleep(100);
+      if (f.running_in_test_harness) millisleep(100);
       (void)close(pfd[pipe_read]);
       (void)close(pfd[pipe_read]);
-      rc = deliver_message(f->text, force_delivery, FALSE);
+      rc = deliver_message(fq->text, force_delivery, FALSE);
       _exit(rc == DELIVER_NOT_ATTEMPTED);
       }
     if (pid < 0)
       _exit(rc == DELIVER_NOT_ATTEMPTED);
       }
     if (pid < 0)
@@ -664,20 +660,20 @@ for (i  = (queue_run_in_order? -1 : 0);
     then wait for the first level process to terminate. */
 
     (void)close(pfd[pipe_write]);
     then wait for the first level process to terminate. */
 
     (void)close(pfd[pipe_write]);
-    set_process_info("running queue: waiting for %s (%d)", f->text, pid);
+    set_process_info("running queue: waiting for %s (%d)", fq->text, pid);
     while (wait(&status) != pid);
 
     /* A zero return means a delivery was attempted; turn off the force flag
     for any subsequent calls unless queue_force is set. */
 
     while (wait(&status) != pid);
 
     /* A zero return means a delivery was attempted; turn off the force flag
     for any subsequent calls unless queue_force is set. */
 
-    if ((status & 0xffff) == 0) force_delivery = queue_run_force;
+    if ((status & 0xffff) == 0) force_delivery = f.queue_run_force;
 
     /* If the process crashed, tell somebody */
 
     else if ((status & 0x00ff) != 0)
       log_write(0, LOG_MAIN|LOG_PANIC,
         "queue run: process %d crashed with signal %d while delivering %s",
 
     /* If the process crashed, tell somebody */
 
     else if ((status & 0x00ff) != 0)
       log_write(0, LOG_MAIN|LOG_PANIC,
         "queue run: process %d crashed with signal %d while delivering %s",
-        (int)pid, status & 0x00ff, f->text);
+        (int)pid, status & 0x00ff, fq->text);
 
     /* Before continuing, wait till the pipe gets closed at the far end. This
     tells us that any children created by the delivery to re-use any SMTP
 
     /* Before continuing, wait till the pipe gets closed at the far end. This
     tells us that any children created by the delivery to re-use any SMTP
@@ -694,13 +690,14 @@ for (i  = (queue_run_in_order? -1 : 0);
     /* If we are in the test harness, and this is not the first of a 2-stage
     queue run, update fudged queue times. */
 
     /* If we are in the test harness, and this is not the first of a 2-stage
     queue run, update fudged queue times. */
 
-    if (running_in_test_harness && !queue_2stage)
+    if (f.running_in_test_harness && !f.queue_2stage)
       {
       uschar *fqtnext = Ustrchr(fudged_queue_times, '/');
       if (fqtnext != NULL) fudged_queue_times = fqtnext + 1;
       }
     }                                  /* End loop for list of messages */
 
       {
       uschar *fqtnext = Ustrchr(fudged_queue_times, '/');
       if (fqtnext != NULL) fudged_queue_times = fqtnext + 1;
       }
     }                                  /* End loop for list of messages */
 
+  tree_nonrecipients = NULL;
   store_reset(reset_point1);           /* Scavenge list of messages */
 
   /* If this was the first time through for random order processing, and
   store_reset(reset_point1);           /* Scavenge list of messages */
 
   /* If this was the first time through for random order processing, and
@@ -723,9 +720,9 @@ for (i  = (queue_run_in_order? -1 : 0);
 /* If queue_2stage is true, we do it all again, with the 2stage flag
 turned off. */
 
 /* If queue_2stage is true, we do it all again, with the 2stage flag
 turned off. */
 
-if (queue_2stage)
+if (f.queue_2stage)
   {
   {
-  queue_2stage = FALSE;
+  f.queue_2stage = FALSE;
   queue_run(start_id, stop_id, TRUE);
   }
 
   queue_run(start_id, stop_id, TRUE);
   }
 
@@ -823,7 +820,7 @@ int i;
 int subcount;
 int now = (int)time(NULL);
 void *reset_point;
 int subcount;
 int now = (int)time(NULL);
 void *reset_point;
-queue_filename *f = NULL;
+queue_filename * qf = NULL;
 uschar subdirs[64];
 
 /* If given a list of messages, build a chain containing their ids. */
 uschar subdirs[64];
 
 /* If given a list of messages, build a chain containing their ids. */
@@ -838,7 +835,7 @@ if (count > 0)
     sprintf(CS next->text, "%s-H", list[i]);
     next->dir_uschar = '*';
     next->next = NULL;
     sprintf(CS next->text, "%s-H", list[i]);
     next->dir_uschar = '*';
     next->next = NULL;
-    if (i == 0) f = next; else last->next = next;
+    if (i == 0) qf = next; else last->next = next;
     last = next;
     }
   }
     last = next;
     }
   }
@@ -846,7 +843,7 @@ if (count > 0)
 /* Otherwise get a list of the entire queue, in order if necessary. */
 
 else
 /* Otherwise get a list of the entire queue, in order if necessary. */
 
 else
-  f = queue_get_spool_list(
+  qf = queue_get_spool_list(
           -1,             /* entire queue */
           subdirs,        /* for holding sub list */
           &subcount,      /* for subcount */
           -1,             /* entire queue */
           subdirs,        /* for holding sub list */
           &subcount,      /* for subcount */
@@ -857,19 +854,20 @@ if (option >= 8) option -= 8;
 /* Now scan the chain and print information, resetting store used
 each time. */
 
 /* Now scan the chain and print information, resetting store used
 each time. */
 
-reset_point = store_get(0);
-
-for (; f != NULL; f = f->next)
+for (reset_point = store_get(0);
+    qf;
+    spool_clear_header_globals(), store_reset(reset_point), qf = qf->next
+    )
   {
   int rc, save_errno;
   int size = 0;
   BOOL env_read;
 
   {
   int rc, save_errno;
   int size = 0;
   BOOL env_read;
 
-  store_reset(reset_point);
   message_size = 0;
   message_size = 0;
-  message_subdir[0] = f->dir_uschar;
-  rc = spool_read_header(f->text, FALSE, count <= 0);
-  if (rc == spool_read_notopen && errno == ENOENT && count <= 0) continue;
+  message_subdir[0] = qf->dir_uschar;
+  rc = spool_read_header(qf->text, FALSE, count <= 0);
+  if (rc == spool_read_notopen && errno == ENOENT && count <= 0)
+    continue;
   save_errno = errno;
 
   env_read = (rc == spool_read_OK || rc == spool_read_hdrerror);
   save_errno = errno;
 
   env_read = (rc == spool_read_OK || rc == spool_read_hdrerror);
@@ -879,7 +877,7 @@ for (; f != NULL; f = f->next)
     int ptr;
     FILE *jread;
     struct stat statbuf;
     int ptr;
     FILE *jread;
     struct stat statbuf;
-    uschar * fname = spool_fname(US"input", message_subdir, f->text, US"");
+    uschar * fname = spool_fname(US"input", message_subdir, qf->text, US"");
 
     ptr = Ustrlen(fname)-1;
     fname[ptr] = 'D';
 
     ptr = Ustrlen(fname)-1;
     fname[ptr] = 'D';
@@ -890,7 +888,7 @@ for (; f != NULL; f = f->next)
 
     if (Ustat(fname, &statbuf) == 0)
       size = message_size + statbuf.st_size - SPOOL_DATA_START_OFFSET + 1;
 
     if (Ustat(fname, &statbuf) == 0)
       size = message_size + statbuf.st_size - SPOOL_DATA_START_OFFSET + 1;
-    i = (now - received_time)/60;  /* minutes on queue */
+    i = (now - received_time.tv_sec)/60;  /* minutes on queue */
     if (i > 90)
       {
       i = (i + 30)/60;
     if (i > 90)
       {
       i = (i + 30)/60;
@@ -901,8 +899,7 @@ for (; f != NULL; f = f->next)
     /* Collect delivered addresses from any J file */
 
     fname[ptr] = 'J';
     /* Collect delivered addresses from any J file */
 
     fname[ptr] = 'J';
-    jread = Ufopen(fname, "rb");
-    if (jread != NULL)
+    if ((jread = Ufopen(fname, "rb")))
       {
       while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
         {
       {
       while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
         {
@@ -915,12 +912,12 @@ for (; f != NULL; f = f->next)
     }
 
   fprintf(stdout, "%s ", string_format_size(size, big_buffer));
     }
 
   fprintf(stdout, "%s ", string_format_size(size, big_buffer));
-  for (i = 0; i < 16; i++) fputc(f->text[i], stdout);
+  for (i = 0; i < 16; i++) fputc(qf->text[i], stdout);
 
 
-  if (env_read && sender_address != NULL)
+  if (env_read && sender_address)
     {
     printf(" <%s>", sender_address);
     {
     printf(" <%s>", sender_address);
-    if (sender_set_untrusted) printf(" (%s)", originator_login);
+    if (f.sender_set_untrusted) printf(" (%s)", originator_login);
     }
 
   if (rc != spool_read_OK)
     }
 
   if (rc != spool_read_OK)
@@ -929,7 +926,7 @@ for (; f != NULL; f = f->next)
     if (save_errno == ERRNO_SPOOLFORMAT)
       {
       struct stat statbuf;
     if (save_errno == ERRNO_SPOOLFORMAT)
       {
       struct stat statbuf;
-      uschar * fname = spool_fname(US"input", message_subdir, f->text, US"");
+      uschar * fname = spool_fname(US"input", message_subdir, qf->text, US"");
 
       if (Ustat(fname, &statbuf) == 0)
         printf("*** spool format error: size=" OFF_T_FMT " ***",
 
       if (Ustat(fname, &statbuf) == 0)
         printf("*** spool format error: size=" OFF_T_FMT " ***",
@@ -944,22 +941,22 @@ for (; f != NULL; f = f->next)
       }
     }
 
       }
     }
 
-  if (deliver_freeze) printf(" *** frozen ***");
+  if (f.deliver_freeze) printf(" *** frozen ***");
 
   printf("\n");
 
 
   printf("\n");
 
-  if (recipients_list != NULL)
+  if (recipients_list)
     {
     for (i = 0; i < recipients_count; i++)
       {
       tree_node *delivered =
         tree_search(tree_nonrecipients, recipients_list[i].address);
       if (!delivered || option != 1)
     {
     for (i = 0; i < recipients_count; i++)
       {
       tree_node *delivered =
         tree_search(tree_nonrecipients, recipients_list[i].address);
       if (!delivered || option != 1)
-        printf("        %s %s\n", (delivered != NULL)? "D":" ",
-          recipients_list[i].address);
-      if (delivered != NULL) delivered->data.val = TRUE;
+        printf("        %s %s\n",
+         delivered ? "D" : " ", recipients_list[i].address);
+      if (delivered) delivered->data.val = TRUE;
       }
       }
-    if (option == 2 && tree_nonrecipients != NULL)
+    if (option == 2 && tree_nonrecipients)
       queue_list_extras(tree_nonrecipients);
     printf("\n");
     }
       queue_list_extras(tree_nonrecipients);
     printf("\n");
     }
@@ -1011,7 +1008,7 @@ if (action >= MSG_SHOW_BODY)
   int fd, i, rc;
   uschar *subdirectory, *suffix;
 
   int fd, i, rc;
   uschar *subdirectory, *suffix;
 
-  if (!admin_user)
+  if (!f.admin_user)
     {
     printf("Permission denied\n");
     return FALSE;
     {
     printf("Permission denied\n");
     return FALSE;
@@ -1073,7 +1070,7 @@ if ((deliver_datafile = spool_open_datafile(id)) < 0)
     {
     yield = FALSE;
     printf("Spool data file for %s does not exist\n", id);
     {
     yield = FALSE;
     printf("Spool data file for %s does not exist\n", id);
-    if (action != MSG_REMOVE || !admin_user) return FALSE;
+    if (action != MSG_REMOVE || !f.admin_user) return FALSE;
     printf("Continuing, to ensure all files removed\n");
     }
   else
     printf("Continuing, to ensure all files removed\n");
     }
   else
@@ -1098,7 +1095,7 @@ if (spool_read_header(spoolname, TRUE, FALSE) != spool_read_OK)
     printf("Spool read error for %s: %s\n", spoolname, strerror(errno));
   else
     printf("Spool format error for %s\n", spoolname);
     printf("Spool read error for %s: %s\n", spoolname, strerror(errno));
   else
     printf("Spool format error for %s\n", spoolname);
-  if (action != MSG_REMOVE || !admin_user)
+  if (action != MSG_REMOVE || !f.admin_user)
     {
     (void)close(deliver_datafile);
     deliver_datafile = -1;
     {
     (void)close(deliver_datafile);
     deliver_datafile = -1;
@@ -1112,7 +1109,7 @@ message. Only admin users may freeze/thaw, add/cancel recipients, or otherwise
 mess about, but the original sender is permitted to remove a message. That's
 why we leave this check until after the headers are read. */
 
 mess about, but the original sender is permitted to remove a message. That's
 why we leave this check until after the headers are read. */
 
-if (!admin_user && (action != MSG_REMOVE || real_uid != originator_uid))
+if (!f.admin_user && (action != MSG_REMOVE || real_uid != originator_uid))
   {
   printf("Permission denied\n");
   (void)close(deliver_datafile);
   {
   printf("Permission denied\n");
   (void)close(deliver_datafile);
@@ -1133,22 +1130,26 @@ if (action != MSG_SHOW_COPY) printf("Message %s ", id);
 switch(action)
   {
   case MSG_SHOW_COPY:
 switch(action)
   {
   case MSG_SHOW_COPY:
-  deliver_in_buffer = store_malloc(DELIVER_IN_BUFFER_SIZE);
-  deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
-  transport_write_message(1, NULL, 0);
-  break;
+    {
+    transport_ctx tctx = {{0}};
+    deliver_in_buffer = store_malloc(DELIVER_IN_BUFFER_SIZE);
+    deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
+    tctx.u.fd = 1;
+    transport_write_message(&tctx, 0);
+    break;
+    }
 
 
   case MSG_FREEZE:
 
 
   case MSG_FREEZE:
-  if (deliver_freeze)
+  if (f.deliver_freeze)
     {
     yield = FALSE;
     printf("is already frozen\n");
     }
   else
     {
     {
     yield = FALSE;
     printf("is already frozen\n");
     }
   else
     {
-    deliver_freeze = TRUE;
-    deliver_manual_thaw = FALSE;
+    f.deliver_freeze = TRUE;
+    f.deliver_manual_thaw = FALSE;
     deliver_frozen_at = time(NULL);
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       {
     deliver_frozen_at = time(NULL);
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       {
@@ -1165,15 +1166,15 @@ switch(action)
 
 
   case MSG_THAW:
 
 
   case MSG_THAW:
-  if (!deliver_freeze)
+  if (!f.deliver_freeze)
     {
     yield = FALSE;
     printf("is not frozen\n");
     }
   else
     {
     {
     yield = FALSE;
     printf("is not frozen\n");
     }
   else
     {
-    deliver_freeze = FALSE;
-    deliver_manual_thaw = TRUE;
+    f.deliver_freeze = FALSE;
+    f.deliver_manual_thaw = TRUE;
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       {
       printf("is no longer frozen\n");
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       {
       printf("is no longer frozen\n");
@@ -1254,6 +1255,38 @@ switch(action)
       else printf("has been removed or did not exist\n");
     if (removed)
       {
       else printf("has been removed or did not exist\n");
     if (removed)
       {
+#ifndef DISABLE_EVENT
+      for (i = 0; i < recipients_count; i++)
+       {
+       tree_node *delivered =
+         tree_search(tree_nonrecipients, recipients_list[i].address);
+       if (!delivered)
+         {
+         uschar * save_local = deliver_localpart;
+         const uschar * save_domain = deliver_domain;
+         uschar * addr = recipients_list[i].address, * errmsg = NULL;
+         int start, end, dom;
+
+         if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
+           log_write(0, LOG_MAIN|LOG_PANIC,
+             "failed to parse address '%.100s'\n: %s", addr, errmsg);
+         else
+           {
+           deliver_localpart =
+             string_copyn(addr+start, dom ? (dom-1) - start : end - start);
+           deliver_domain = dom
+             ? CUS string_copyn(addr+dom, end - dom) : CUS"";
+
+           event_raise(event_action, US"msg:fail:internal",
+             string_sprintf("message removed by %s", username));
+
+           deliver_localpart = save_local;
+           deliver_domain = save_domain;
+           }
+         }
+       }
+      (void) event_raise(event_action, US"msg:complete", NULL);
+#endif
       log_write(0, LOG_MAIN, "removed by %s", username);
       log_write(0, LOG_MAIN, "Completed");
       }
       log_write(0, LOG_MAIN, "removed by %s", username);
       log_write(0, LOG_MAIN, "Completed");
       }
@@ -1263,9 +1296,8 @@ switch(action)
 
   case MSG_MARK_ALL_DELIVERED:
   for (i = 0; i < recipients_count; i++)
 
   case MSG_MARK_ALL_DELIVERED:
   for (i = 0; i < recipients_count; i++)
-    {
     tree_add_nonrecipient(recipients_list[i].address);
     tree_add_nonrecipient(recipients_list[i].address);
-    }
+
   if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
     {
     printf("has been modified\n");
   if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
     {
     printf("has been modified\n");
@@ -1406,11 +1438,10 @@ Returns:    nothing
 void
 queue_check_only(void)
 {
 void
 queue_check_only(void)
 {
-BOOL *set;
 int sep = 0;
 struct stat statbuf;
 const uschar *s;
 int sep = 0;
 struct stat statbuf;
 const uschar *s;
-uschar *ss, *name;
+uschar *ss;
 uschar buffer[1024];
 
 if (queue_only_file == NULL) return;
 uschar buffer[1024];
 
 if (queue_only_file == NULL) return;
@@ -1420,20 +1451,20 @@ while ((ss = string_nextinlist(&s, &sep, buffer, sizeof(buffer))) != NULL)
   {
   if (Ustrncmp(ss, "smtp", 4) == 0)
     {
   {
   if (Ustrncmp(ss, "smtp", 4) == 0)
     {
-    name = US"queue_smtp";
-    set = &queue_smtp;
     ss += 4;
     ss += 4;
+    if (Ustat(ss, &statbuf) == 0)
+      {
+      f.queue_smtp = TRUE;
+      DEBUG(D_receive) debug_printf("queue_smtp set because %s exists\n", ss);
+      }
     }
   else
     {
     }
   else
     {
-    name = US"queue_only";
-    set = &queue_only;
-    }
-
-  if (Ustat(ss, &statbuf) == 0)
-    {
-    *set = TRUE;
-    DEBUG(D_receive) debug_printf("%s set because %s exists\n", name, ss);
+    if (Ustat(ss, &statbuf) == 0)
+      {
+      queue_only = TRUE;
+      DEBUG(D_receive) debug_printf("queue_only set because %s exists\n", ss);
+      }
     }
   }
 }
     }
   }
 }