Merge branch 'debug_fork'
authorJeremy Harris <jgh146exb@wizmail.org>
Sun, 22 Mar 2020 20:15:33 +0000 (20:15 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 22 Mar 2020 20:25:36 +0000 (20:25 +0000)
1  2 
doc/doc-docbook/spec.xfpt
src/src/exim.c
src/src/functions.h
src/src/queue.c
src/src/receive.c
src/src/sieve.c
src/src/transports/autoreply.c
test/stderr/0002

index cf15942dbfa8d02cfd4623a275260ca83dbff3bc,b9f012aef9a4f37ab732581010efa336753c093c..2a3fb6c51c117a071871445b53256c24a9bc168d
@@@ -3871,6 -3871,14 +3871,14 @@@ This option is not intended for use by 
  by Exim in conjunction with the &%-MC%& option. It signifies that the
  remote host supports the ESMTP &_DSN_& extension.
  
+ .new
+ .vitem &%-MCd%&
+ .oindex "&%-MCd%&"
+ This option is not intended for use by external callers. It is used internally
+ by Exim in conjunction with the &%-d%& option
+ to pass on an information string on the purpose of the process.
+ .wen
  .vitem &%-MCG%&&~<&'queue&~name'&>
  .oindex "&%-MCG%&"
  This option is not intended for use by external callers. It is used internally
@@@ -6762,12 -6770,7 +6770,12 @@@ by default, but has an option to omit t
  whose name is the key by calling the &[lstat()]& function. The key may not
  contain any forward slash characters. If &[lstat()]& succeeds, the result of
  the lookup is the name of the entry, which may be a file, directory,
 -symbolic link, or any other kind of directory entry. An example of how this
 +symbolic link, or any other kind of directory entry.
 +.new
 +.cindex "tainted data" "dsearch result"
 +It is regarded as untainted.
 +.wen
 +An example of how this
  lookup can be used to support virtual domains is given in section
  &<<SECTvirtualdomains>>&.
  .next
@@@ -36776,18 -36779,12 +36784,18 @@@ to a router of this form
  virtual:
    driver = redirect
    domains = dsearch;/etc/mail/virtual
 -  data = ${lookup{$local_part}lsearch{/etc/mail/virtual/$domain}}
 +  data = ${lookup{$local_part}lsearch{/etc/mail/virtual/$domain_data}}
    no_more
  .endd
 +.new
  The &%domains%& option specifies that the router is to be skipped, unless there
  is a file in the &_/etc/mail/virtual_& directory whose name is the same as the
 -domain that is being processed. When the router runs, it looks up the local
 +domain that is being processed.
 +The &(dsearch)& lookup used results in an untainted version of &$domain$&
 +being placed into the &$domain_data$& variable.
 +.wen
 +
 +When the router runs, it looks up the local
  part in the file to find a new address (or list of addresses). The &%no_more%&
  setting ensures that if the lookup fails (leading to &%data%& being an empty
  string), Exim gives up on the address without trying any subsequent routers.
diff --combined src/src/exim.c
index 63ef40553acf18929c59f39ab7a72bb5b8a8326f,7b1c8ead972c8a33ac8ad5758cc37328d55699c7..10526ba5f6c0c2745b49edc148c205fc145ba9c8
@@@ -345,11 -345,9 +345,9 @@@ Returns:     nothin
  void
  millisleep(int msec)
  {
- struct itimerval itval;
- itval.it_interval.tv_sec = 0;
- itval.it_interval.tv_usec = 0;
- itval.it_value.tv_sec = msec/1000;
- itval.it_value.tv_usec = (msec % 1000) * 1000;
+ struct itimerval itval = {.it_interval = {.tv_sec = 0, .tv_usec = 0},
+                         .it_value = {.tv_sec = msec/1000,
+                                      .tv_usec = (msec % 1000) * 1000}};
  milliwait(&itval);
  }
  
@@@ -717,26 -715,26 +715,26 @@@ Returns:     does not retur
  */
  
  void
- exim_exit(int rc, const uschar * process)
+ exim_exit(int rc)
  {
  search_tidyup();
  store_exit();
  DEBUG(D_any)
-   debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d %s%s%sterminating with rc=%d "
-     ">>>>>>>>>>>>>>>>\n", (int)getpid(),
-     process ? "(" : "", process, process ? ") " : "", rc);
+   debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d (%s) terminating with rc=%d "
+     ">>>>>>>>>>>>>>>>\n",
+     (int)getpid(), process_purpose, rc);
  exit(rc);
  }
  
  
  void
- exim_underbar_exit(int rc, const uschar * process)
+ exim_underbar_exit(int rc)
  {
  store_exit();
  DEBUG(D_any)
-   debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d %s%s%sterminating with rc=%d "
-     ">>>>>>>>>>>>>>>>\n", (int)getpid(),
-     process ? "(" : "", process, process ? ") " : "", rc);
+   debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d (%s) terminating with rc=%d "
+     ">>>>>>>>>>>>>>>>\n",
+     (int)getpid(), process_purpose, rc);
  _exit(rc);
  }
  
@@@ -833,7 -831,7 +831,7 @@@ int start, end, domain
  uschar *parse_error = NULL;
  uschar *address = parse_extract_address(s, &parse_error, &start, &end, &domain,
    FALSE);
 -if (address == NULL)
 +if (!address)
    {
    fprintf(stdout, "syntax error: %s\n", parse_error);
    *exit_value = 2;
@@@ -1957,8 -1955,8 +1955,8 @@@ on the second character (the one after 
  for (i = 1; i < argc; i++)
    {
    BOOL badarg = FALSE;
 -  uschar *arg = argv[i];
 -  uschar *argrest;
 +  uschar * arg = argv[i];
 +  uschar * argrest;
    int switchchar;
  
    /* An argument not starting with '-' is the start of a recipients list;
      /* sendmail uses -Ac and -Am to control which .cf file is used;
      we ignore them. */
      case 'A':
 -    if (*argrest == '\0') { badarg = TRUE; break; }
 +    if (!*argrest) { badarg = TRUE; break; }
      else
        {
        BOOL ignore = FALSE;
        /* -bF:  Run system filter test */
        case 'F':
          filter_test |= checking = FTEST_SYSTEM;
 -        if (*argrest) { badarg = TRUE; break; }
 -        if (++i < argc) filter_test_sfile = argv[i]; else
 -          exim_fail("exim: file name expected after %s\n", argv[i-1]);
 +        if (*argrest) badarg = TRUE;
 +        else if (++i < argc) filter_test_sfile = argv[i];
 +        else exim_fail("exim: file name expected after %s\n", argv[i-1]);
          break;
  
        /* -bf:  Run user filter test
          if (!*argrest || Ustrcmp(argrest, "c") == 0)
            {
            if (++i >= argc) { badarg = TRUE; break; }
 -          sender_host_address = argv[i];
 +          sender_host_address = string_copy_taint(argv[i], TRUE);
            host_checking = checking = f.log_testing_mode = TRUE;
            f.host_checking_callout = *argrest == 'c';
            message_logs = FALSE;
      a change! Enforce a prefix check if required. */
  
      case 'C':
 -    if (*argrest == 0)
 -      {
 -      if(++i < argc) argrest = argv[i]; else
 -        { badarg = TRUE; break; }
 -      }
 +    if (!*argrest)
 +      if (++i < argc) argrest = argv[i]; else { badarg = TRUE; break; }
      if (Ustrcmp(config_main_filelist, argrest) != 0)
        {
        #ifdef ALT_CONFIG_PREFIX
        const uschar *list = argrest;
        uschar *filename;
        while((filename = string_nextinlist(&list, &sep, big_buffer,
 -             big_buffer_size)) != NULL)
 -        {
 -        if ((Ustrlen(filename) < len ||
 -             Ustrncmp(filename, ALT_CONFIG_PREFIX, len) != 0 ||
 -             Ustrstr(filename, "/../") != NULL) &&
 -             (Ustrcmp(filename, "/dev/null") != 0 || real_uid != root_uid))
 +             big_buffer_size)))
 +        if (  (  Ustrlen(filename) < len
 +            || Ustrncmp(filename, ALT_CONFIG_PREFIX, len) != 0
 +            || Ustrstr(filename, "/../") != NULL
 +            )
 +         && (Ustrcmp(filename, "/dev/null") != 0 || real_uid != root_uid)
 +         )
            exim_fail("-C Permission denied\n");
 -        }
        #endif
        if (real_uid != root_uid)
          {
                  if (nl)
                    *nl = 0;
                  trusted_configs[nr_configs++] = string_copy(start);
 -                if (nr_configs == 32)
 +                if (nr_configs == nelem(trusted_configs))
                    break;
                  }
                fclose(trust_list);
                  const uschar *list = argrest;
                  uschar *filename;
                  while (f.trusted_config && (filename = string_nextinlist(&list,
 -                        &sep, big_buffer, big_buffer_size)) != NULL)
 +                        &sep, big_buffer, big_buffer_size)))
                    {
                    for (i=0; i < nr_configs; i++)
                      if (Ustrcmp(filename, trusted_configs[i]) == 0)
          f.debug_daemon = TRUE;
          argrest++;
          }
 -      if (*argrest != 0)
 +      if (*argrest)
          decode_bits(&selector, 1, debug_notall, argrest,
            debug_options, debug_options_count, US"debug", 0);
        debug_selector = selector;
      the -F or be in the next argument. */
  
      case 'F':
 -    if (*argrest == 0)
 -      {
 -      if(++i < argc) argrest = argv[i]; else
 -        { badarg = TRUE; break; }
 -      }
 -    originator_name = argrest;
 +    if (!*argrest)
 +      if (++i < argc) argrest = argv[i]; else { badarg = TRUE; break; }
 +    originator_name = string_copy_taint(argrest, TRUE);
      f.sender_name_forced = TRUE;
      break;
  
        {
        int dummy_start, dummy_end;
        uschar *errmess;
 -      if (*argrest == 0)
 -        {
 -        if (i+1 < argc) argrest = argv[++i]; else
 -          { badarg = TRUE; break; }
 -        }
 -      if (*argrest == 0)
 +      if (!*argrest)
 +        if (i+1 < argc) argrest = argv[++i]; else { badarg = TRUE; break; }
 +      if (!*argrest)
          *(sender_address = store_get(1, FALSE)) = '\0';  /* Ensure writeable memory */
        else
          {
 -        uschar *temp = argrest + Ustrlen(argrest) - 1;
 +        uschar * temp = argrest + Ustrlen(argrest) - 1;
          while (temp >= argrest && isspace(*temp)) temp--;
          if (temp >= argrest && *temp == '.') f_end_dot = TRUE;
          allow_domain_literals = TRUE;
  #ifdef SUPPORT_I18N
        allow_utf8_domains = TRUE;
  #endif
 -        sender_address = parse_extract_address(argrest, &errmess,
 -          &dummy_start, &dummy_end, &sender_address_domain, TRUE);
 +        if (!(sender_address = parse_extract_address(argrest, &errmess,
 +                &dummy_start, &dummy_end, &sender_address_domain, TRUE)))
 +          exim_fail("exim: bad -f address \"%s\": %s\n", argrest, errmess);
 +
        sender_address = string_copy_taint(sender_address, TRUE);
  #ifdef SUPPORT_I18N
        message_smtputf8 =  string_is_utf8(sender_address);
  #endif
          allow_domain_literals = FALSE;
          strip_trailing_dot = FALSE;
 -        if (!sender_address)
 -          exim_fail("exim: bad -f address \"%s\": %s\n", argrest, errmess);
          }
        f.sender_address_forced = TRUE;
        }
      To put it in will require a change to the spool header file format. */
  
      case 'h':
 -    if (*argrest == 0)
 -      {
 -      if(++i < argc) argrest = argv[i]; else
 -        { badarg = TRUE; break; }
 -      }
 +    if (!*argrest)
 +      if (++i < argc) argrest = argv[i]; else { badarg = TRUE; break; }
      if (!isdigit(*argrest)) badarg = TRUE;
      break;
  
      not to be documented for sendmail but mailx (at least) uses it) */
  
      case 'i':
 -    if (*argrest == 0) f.dot_ends = FALSE; else badarg = TRUE;
 +    if (!*argrest) f.dot_ends = FALSE; else badarg = TRUE;
      break;
  
  
      syslog_processname in the config file, but needs to be an admin option. */
  
      case 'L':
 -    if (*argrest == '\0')
 -      {
 -      if(++i < argc) argrest = argv[i]; else
 -        { badarg = TRUE; break; }
 -      }
 +    if (!*argrest)
 +      if (++i < argc) argrest = argv[i]; else { badarg = TRUE; break; }
      if ((sz = Ustrlen(argrest)) > 32)
        exim_fail("exim: the -L syslog name is too long: \"%s\"\n", argrest);
      if (sz < 1)
        exim_fail("exim: the -L syslog name is too short\n");
 -    cmdline_syslog_name = argrest;
 +    cmdline_syslog_name = string_copy_taint(argrest, TRUE);
      break;
  
      case 'M':
        if (msg_action_arg >= 0)
          exim_fail("exim: incompatible arguments\n");
  
 -      continue_transport = argv[++i];
 -      continue_hostname = argv[++i];
 -      continue_host_address = argv[++i];
 +      continue_transport = string_copy_taint(argv[++i], TRUE);
 +      continue_hostname = string_copy_taint(argv[++i], TRUE);
 +      continue_host_address = string_copy_taint(argv[++i], TRUE);
        continue_sequence = Uatoi(argv[++i]);
        msg_action = MSG_DELIVER;
        msg_action_arg = ++i;
  
        case 'D': smtp_peer_options |= OPTION_DSN; break;
  
+     /* -MCd: for debug, set a process-purpose string */
+       case 'd': if (++i < argc)
+                   process_purpose = string_copy_taint(argv[i], TRUE);
+                 else badarg = TRUE;
+                 break;
      /* -MCG: set the queue name, to a non-default value */
  
 -      case 'G': if (++i < argc) queue_name = string_copy(argv[i]);
 +      case 'G': if (++i < argc) queue_name = string_copy_taint(argv[i], TRUE);
                  else badarg = TRUE;
                  break;
  
      Require three arguments for the proxied local address and port,
      and the TLS cipher.  */
  
 -      case 't': if (++i < argc) sending_ip_address = argv[i];
 +      case 't': if (++i < argc)
 +                  sending_ip_address = string_copy_taint(argv[i], TRUE);
                  else badarg = TRUE;
 -                if (++i < argc) sending_port = (int)(Uatol(argv[i]));
 +                if (++i < argc)
 +                  sending_port = (int)(Uatol(argv[i]));
                  else badarg = TRUE;
 -                if (++i < argc) continue_proxy_cipher = argv[i];
 +                if (++i < argc)
 +                  continue_proxy_cipher = string_copy_taint(argv[i], TRUE);
                  else badarg = TRUE;
                  /*FALLTHROUGH*/
  
         -Mvl  show log
      */
  
 -    else if (*argrest == 0)
 +    else if (!*argrest)
        {
        msg_action = MSG_DELIVER;
        forced_delivery = f.deliver_force_thaw = TRUE;
     else if (Ustrcmp(argrest, "G") == 0)
        {
        msg_action = MSG_SETQUEUE;
 -      queue_name_dest = argv[++i];
 +      queue_name_dest = string_copy_taint(argv[++i], TRUE);
        }
      else if (Ustrcmp(argrest, "mad") == 0)
        {
      for sendmail it askes for "me too". Exim always does this. */
  
      case 'm':
 -    if (*argrest != 0) badarg = TRUE;
 +    if (*argrest) badarg = TRUE;
      break;
  
  
      their thing. It implies debugging at the D_v level. */
  
      case 'N':
 -    if (*argrest == 0)
 +    if (!*argrest)
        {
        f.dont_deliver = TRUE;
        debug_selector |= D_v;
      -O option=value and -Ooption=value. */
  
      case 'O':
 -    if (*argrest == 0)
 +    if (!*argrest)
        if (++i >= argc)
          exim_fail("exim: string expected after -O\n");
      break;
  
        /* -oMa: Set sender host address */
  
 -      if (Ustrcmp(argrest, "a") == 0) sender_host_address = argv[++i];
 +      if (Ustrcmp(argrest, "a") == 0)
 +        sender_host_address = string_copy_taint(argv[++i], TRUE);
  
        /* -oMaa: Set authenticator name */
  
        else if (Ustrcmp(argrest, "aa") == 0)
 -        sender_host_authenticated = argv[++i];
 +        sender_host_authenticated = string_copy_taint(argv[++i], TRUE);
  
        /* -oMas: setting authenticated sender */
  
  
        /* -oMi: Set incoming interface address */
  
 -      else if (Ustrcmp(argrest, "i") == 0) interface_address = argv[++i];
 +      else if (Ustrcmp(argrest, "i") == 0)
 +        interface_address = string_copy_taint(argv[++i], TRUE);
  
        /* -oMm: Message reference */
  
          if (received_protocol)
            exim_fail("received_protocol is set already\n");
          else
 -          received_protocol = argv[++i];
 +          received_protocol = string_copy_taint(argv[++i], TRUE);
  
        /* -oMs: Set sender host name */
  
        else if (Ustrcmp(argrest, "t") == 0)
          {
          sender_ident_set = TRUE;
 -        sender_ident = argv[++i];
 +        sender_ident = string_copy_taint(argv[++i], TRUE);
          }
  
        /* Else a bad argument */
  
        case 'X':
        if (*argrest) badarg = TRUE;
 -      else override_local_interfaces = argv[++i];
 +      else override_local_interfaces = string_copy_taint(argv[++i], TRUE);
        break;
  
        /* Unknown -o argument */
      /* -panythingelse is taken as the Sendmail-compatible argument -prval:sval,
      which sets the host protocol and host name */
  
 -    if (*argrest == 0)
 -      if (i+1 < argc)
 -      argrest = argv[++i];
 -      else
 -        { badarg = TRUE; break; }
 +    if (!*argrest)
 +      if (i+1 < argc) argrest = argv[++i]; else { badarg = TRUE; break; }
  
 -    if (*argrest != 0)
 +    if (*argrest)
        {
        uschar *hn;
  
          exim_fail("received_protocol is set already\n");
  
        hn = Ustrchr(argrest, ':');
 -      if (hn == NULL)
 -        received_protocol = argrest;
 +      if (!hn)
 +        received_protocol = string_copy_taint(argrest, TRUE);
        else
          {
        int old_pool = store_pool;
        store_pool = POOL_PERM;
 -        received_protocol = string_copyn(argrest, hn - argrest);
 +        received_protocol = string_copyn_taint(argrest, hn - argrest, TRUE);
        store_pool = old_pool;
 -        sender_host_name = hn + 1;
 +        sender_host_name = string_copy_taint(hn + 1, TRUE);
          }
        }
      break;
      only, optionally named, optionally starting from a given message id. */
  
      if (!(list_queue || count_queue))
 -      if (*argrest == 0
 +      if (  !*argrest
         && (i + 1 >= argc || argv[i+1][0] == '-' || mac_ismsgid(argv[i+1])))
        {
        queue_interval = 0;
        if (i+1 < argc && mac_ismsgid(argv[i+1]))
 -        start_queue_run_id = argv[++i];
 +        start_queue_run_id = string_copy_taint(argv[++i], TRUE);
        if (i+1 < argc && mac_ismsgid(argv[i+1]))
 -        stop_queue_run_id = argv[++i];
 +        stop_queue_run_id = string_copy_taint(argv[++i], TRUE);
        }
  
      /* -q[f][f][l][G<name>/]<n>: Run the queue at regular intervals, optionally
      in all cases provided there are no further characters in this
      argument. */
  
 -    if (*argrest != 0)
 +    if (*argrest)
        for (int i = 0; i < nelem(rsopts); i++)
          if (Ustrcmp(argrest, rsopts[i]) == 0)
            {
      pick out particular messages. */
  
      if (*argrest)
 -      deliver_selectstring = argrest;
 +      deliver_selectstring = string_copy_taint(argrest, TRUE);
      else if (i+1 < argc)
 -      deliver_selectstring = argv[++i];
 +      deliver_selectstring = string_copy_taint(argv[++i], TRUE);
      else
        exim_fail("exim: string expected after -R\n");
      break;
      pick out particular messages. */
  
      if (*argrest)
 -      deliver_selectstring_sender = argrest;
 +      deliver_selectstring_sender = string_copy_taint(argrest, TRUE);
      else if (i+1 < argc)
 -      deliver_selectstring_sender = argv[++i];
 +      deliver_selectstring_sender = string_copy_taint(argv[++i], TRUE);
      else
        exim_fail("exim: string expected after -S\n");
      break;
  
      case 'T':
      if (f.running_in_test_harness && Ustrcmp(argrest, "qt") == 0)
 -      fudged_queue_times = argv[++i];
 +      fudged_queue_times = string_copy_taint(argv[++i], TRUE);
      else badarg = TRUE;
      break;
  
      /* -t: Set flag to extract recipients from body of message. */
  
      case 't':
 -    if (*argrest == 0) extract_recipients = TRUE;
 +    if (!*argrest) extract_recipients = TRUE;
  
      /* -ti: Set flag to extract recipients from body of message, and also
      specify that dot does not end the message. */
      /* -v: verify things - this is a very low-level debugging */
  
      case 'v':
 -    if (*argrest == 0)
 +    if (!*argrest)
        {
        debug_selector |= D_v;
        debug_file = stderr;
      As Exim is 8-bit clean, it just ignores this flag. */
  
      case 'x':
 -    if (*argrest != 0) badarg = TRUE;
 +    if (*argrest) badarg = TRUE;
      break;
  
      /* -X: in sendmail: takes one parameter, logfile, and sends debugging
      logs to that file.  We swallow the parameter and otherwise ignore it. */
  
      case 'X':
 -    if (*argrest == '\0')
 +    if (!*argrest)
        if (++i >= argc)
          exim_fail("exim: string expected after -X\n");
      break;
  
      case 'z':
 -    if (*argrest == '\0')
 +    if (!*argrest)
        if (++i < argc)
 -      log_oneline = argv[i];
 +      log_oneline = string_copy_taint(argv[i], TRUE);
        else
          exim_fail("exim: file name expected after %s\n", argv[i-1]);
      break;
@@@ -4065,12 -4083,11 +4070,12 @@@ if (  (debug_selector & D_any  ||  LOGG
        p = big_buffer + 3;
        }
      printing = string_printing(argv[i]);
 -    if (printing[0] == 0) quote = US"\""; else
 +    if (!*printing) quote = US"\"";
 +    else
        {
        const uschar *pp = printing;
        quote = US"";
 -      while (*pp != 0) if (isspace(*pp++)) { quote = US"\""; break; }
 +      while (*pp) if (isspace(*pp++)) { quote = US"\""; break; }
        }
      p += sprintf(CS p, " %s%.*s%s", quote, (int)(big_buffer_size -
        (p - big_buffer) - 4), printing, quote);
@@@ -4106,19 -4123,19 +4111,19 @@@ script. *
  if (bi_option)
    {
    (void)fclose(config_file);
 -  if (bi_command != NULL)
 +  if (bi_command)
      {
      int i = 0;
      uschar *argv[3];
      argv[i++] = bi_command;
 -    if (alias_arg != NULL) argv[i++] = alias_arg;
 +    if (alias_arg) argv[i++] = alias_arg;
      argv[i++] = NULL;
  
      setgroups(group_count, group_list);
      exim_setugid(real_uid, real_gid, FALSE, US"running bi_command");
  
      DEBUG(D_exec) debug_printf("exec %.256s %.256s\n", argv[0],
 -      (argv[1] == NULL)? US"" : argv[1]);
 +      argv[1] ? argv[1] : US"");
  
      execv(CS argv[0], (char *const *)argv);
      exim_fail("exim: exec failed: %s\n", strerror(errno));
@@@ -4424,7 -4441,7 +4429,7 @@@ if (test_retry_arg >= 0
    if (test_retry_arg >= argc)
      {
      printf("-brt needs a domain or address argument\n");
-     exim_exit(EXIT_FAILURE, US"main");
+     exim_exit(EXIT_FAILURE);
      }
    s1 = argv[test_retry_arg++];
    s2 = NULL;
  
      printf("\n");
      }
-   exim_exit(EXIT_SUCCESS, US"main");
+   exim_exit(EXIT_SUCCESS);
    }
  
  /* Handle a request to list one or more configuration options */
@@@ -4556,14 -4573,14 +4561,14 @@@ if (list_options
      else
        fail = !readconf_print(argv[i], NULL, flag_n);
      }
-   exim_exit(fail ? EXIT_FAILURE : EXIT_SUCCESS, US"main");
+   exim_exit(fail ? EXIT_FAILURE : EXIT_SUCCESS);
    }
  
  if (list_config)
    {
    set_process_info("listing config");
    exim_exit(readconf_print(US"config", NULL, flag_n)
-               ? EXIT_SUCCESS : EXIT_FAILURE, US"main");
+               ? EXIT_SUCCESS : EXIT_FAILURE);
    }
  
  
@@@ -4589,7 -4606,7 +4594,7 @@@ if (msg_action_arg > 0 && msg_action !
    if (prod_requires_admin && !f.admin_user)
      {
      fprintf(stderr, "exim: Permission denied\n");
-     exim_exit(EXIT_FAILURE, US"main");
+     exim_exit(EXIT_FAILURE);
      }
    set_process_info("delivering specified messages");
    if (deliver_give_up) forced_delivery = f.deliver_force_thaw = TRUE;
      {
      int status;
      pid_t pid;
 +    /*XXX This use of argv[i] for msg_id should really be tainted, but doing
 +    that runs into a later copy into the untainted global message_id[] */
      if (i == argc - 1)
        (void)deliver_message(argv[i], forced_delivery, deliver_give_up);
-     else if ((pid = fork()) == 0)
+     else if ((pid = exim_fork(US"cmdline-delivery")) == 0)
        {
        (void)deliver_message(argv[i], forced_delivery, deliver_give_up);
-       exim_underbar_exit(EXIT_SUCCESS, US"cmdline-delivery");
+       exim_underbar_exit(EXIT_SUCCESS);
        }
      else if (pid < 0)
        {
        fprintf(stderr, "failed to fork delivery process for %s: %s\n", argv[i],
          strerror(errno));
-       exim_exit(EXIT_FAILURE, US"main");
+       exim_exit(EXIT_FAILURE);
        }
      else wait(&status);
      }
-   exim_exit(EXIT_SUCCESS, US"main");
+   exim_exit(EXIT_SUCCESS);
    }
  
  
@@@ -4633,7 -4648,7 +4638,7 @@@ if (queue_interval == 0 && !f.daemon_li
    else
      set_process_info("running the queue (single queue run)");
    queue_run(start_queue_run_id, stop_queue_run_id, FALSE);
-   exim_exit(EXIT_SUCCESS, US"main");
+   exim_exit(EXIT_SUCCESS);
    }
  
  
@@@ -4803,10 -4818,10 +4808,10 @@@ if (test_rewrite_arg >= 0
    if (test_rewrite_arg >= argc)
      {
      printf("-brw needs an address argument\n");
-     exim_exit(EXIT_FAILURE, US"main");
+     exim_exit(EXIT_FAILURE);
      }
    rewrite_test(argv[test_rewrite_arg]);
-   exim_exit(EXIT_SUCCESS, US"main");
+   exim_exit(EXIT_SUCCESS);
    }
  
  /* A locally-supplied message is considered to be coming from a local user
@@@ -4921,7 -4936,7 +4926,7 @@@ if (verify_address_mode || f.address_te
      }
  
    route_tidyup();
-   exim_exit(exit_value, US"main");
+   exim_exit(exit_value);
    }
  
  /* Handle expansion checking. Either expand items on the command line, or read
@@@ -5007,7 -5022,7 +5012,7 @@@ if (expansion_test
      deliver_datafile = -1;
      }
  
-   exim_exit(EXIT_SUCCESS, US"main: expansion test");
+   exim_exit(EXIT_SUCCESS);
    }
  
  
@@@ -5101,7 -5116,7 +5106,7 @@@ if (host_checking
        }
      smtp_log_no_mail();
      }
-   exim_exit(EXIT_SUCCESS, US"main");
+   exim_exit(EXIT_SUCCESS);
    }
  
  
@@@ -5264,7 -5279,7 +5269,7 @@@ if (smtp_input
    if (!smtp_start_session())
      {
      mac_smtp_fflush();
-     exim_exit(EXIT_SUCCESS, US"smtp_start toplevel");
+     exim_exit(EXIT_SUCCESS);
      }
    }
  
@@@ -5379,14 -5394,14 +5384,14 @@@ while (more
        cancel_cutthrough_connection(TRUE, US"receive dropped");
          if (more) goto moreloop;
          smtp_log_no_mail();               /* Log no mail if configured */
-         exim_exit(EXIT_FAILURE, US"receive toplevel");
+         exim_exit(EXIT_FAILURE);
          }
        }
      else
        {
        cancel_cutthrough_connection(TRUE, US"message setup dropped");
        smtp_log_no_mail();               /* Log no mail if configured */
-       exim_exit(rc ? EXIT_FAILURE : EXIT_SUCCESS, US"msg setup toplevel");
+       exim_exit(rc ? EXIT_FAILURE : EXIT_SUCCESS);
        }
      }
  
            if (error_handling == ERRORS_STDERR)
              {
              fprintf(stderr, "exim: too many recipients\n");
-             exim_exit(EXIT_FAILURE, US"main");
+             exim_exit(EXIT_FAILURE);
              }
            else
              return
            errmess = US"unqualified recipient address not allowed";
            }
  
 -        if (recipient == NULL)
 -          {
 +        if (!recipient)
            if (error_handling == ERRORS_STDERR)
              {
              fprintf(stderr, "exim: bad recipient address \"%s\": %s\n",
                string_printing(list[i]), errmess);
-             exim_exit(EXIT_FAILURE, US"main");
+             exim_exit(EXIT_FAILURE);
              }
            else
              {
                moan_to_sender(ERRMESS_BADARGADDRESS, &eblock, NULL, stdin, TRUE)?
                  errors_sender_rc : EXIT_FAILURE;
              }
 -          }
  
          receive_add_recipient(string_copy_taint(recipient, TRUE), -1);
          s = ss;
  
      DEBUG(D_receive)
        {
 -      if (sender_address != NULL) debug_printf("Sender: %s\n", sender_address);
 -      if (recipients_list != NULL)
 +      if (sender_address) debug_printf("Sender: %s\n", sender_address);
 +      if (recipients_list)
          {
          debug_printf("Recipients:\n");
          for (int i = 0; i < recipients_count; i++)
      for real; when reading the headers of a message for filter testing,
      it is TRUE if the headers were terminated by '.' and FALSE otherwise. */
  
-     if (message_id[0] == 0) exim_exit(EXIT_FAILURE, US"main");
+     if (message_id[0] == 0) exim_exit(EXIT_FAILURE);
      }  /* Non-SMTP message reception */
  
    /* If this is a filter testing run, there are headers in store, but
      if (chdir("/"))   /* Get away from wherever the user is running this from */
        {
        DEBUG(D_receive) debug_printf("chdir(\"/\") failed\n");
-       exim_exit(EXIT_FAILURE, US"main");
+       exim_exit(EXIT_FAILURE);
        }
  
      /* Now we run either a system filter test, or a user filter test, or both.
  
      if ((filter_test & FTEST_SYSTEM) != 0)
        if (!filter_runtest(filter_sfd, filter_test_sfile, TRUE, more))
-         exim_exit(EXIT_FAILURE, US"main");
+         exim_exit(EXIT_FAILURE);
  
      memcpy(filter_sn, filter_n, sizeof(filter_sn));
  
      if ((filter_test & FTEST_USER) != 0)
        if (!filter_runtest(filter_ufd, filter_test_ufile, FALSE, more))
-         exim_exit(EXIT_FAILURE, US"main");
+         exim_exit(EXIT_FAILURE);
  
-     exim_exit(EXIT_SUCCESS, US"main");
+     exim_exit(EXIT_SUCCESS);
      }
  
    /* Else act on the result of message reception. We should not get here unless
      pid_t pid;
      search_tidyup();
  
-     if ((pid = fork()) == 0)
+     if ((pid = exim_fork(US"local-accept-delivery")) == 0)
        {
        int rc;
        close_unwanted();      /* Close unwanted file descriptors and TLS */
        rc = deliver_message(message_id, FALSE, FALSE);
        search_tidyup();
        exim_underbar_exit(!mua_wrapper || rc == DELIVER_MUA_SUCCEEDED
-         ? EXIT_SUCCESS : EXIT_FAILURE, US"cmdline-delivery");
+         ? EXIT_SUCCESS : EXIT_FAILURE);
        }
  
      if (pid < 0)
          log_write(0, LOG_MAIN|LOG_PANIC,
            "process %d crashed with signal %d while delivering %s",
            (int)pid, status & 0x00ff, message_id);
-       if (mua_wrapper && (status & 0xffff) != 0) exim_exit(EXIT_FAILURE, US"main");
+       if (mua_wrapper && (status & 0xffff) != 0) exim_exit(EXIT_FAILURE);
        }
        }
      }
@@@ -5761,7 -5778,7 +5766,7 @@@ moreloop
    store_reset(reset_point);
    }
  
- exim_exit(EXIT_SUCCESS, US"main");   /* Never returns */
+ exim_exit(EXIT_SUCCESS);   /* Never returns */
  return 0;                  /* To stop compiler warning */
  }
  
diff --combined src/src/functions.h
index 0e38030d0286d0f9318af6b450f53b976b3ff344,b8bf2a490a8306255c2ed08ee5856a2ab7d05f27..03596064678024e7cd0bcab74592b661f463d2e9
@@@ -150,8 -150,14 +150,13 @@@ extern void    bits_set(unsigned int *
  extern void    cancel_cutthrough_connection(BOOL, const uschar *);
  extern int     check_host(void *, const uschar *, const uschar **, uschar **);
  extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...);
 -extern pid_t   child_open_function(uschar **argv, uschar **envp, int newumask,
 -               int *infdptr, int *outfdptr, BOOL make_leader,
 -               const uschar * purpose);
+ extern pid_t   child_open_exim_function(int *, const uschar *);
+ extern pid_t   child_open_exim2_function(int *, uschar *, uschar *,
+                const uschar *);
++extern pid_t   child_open_function(uschar **, uschar **, int,
++               int *, int *, BOOL, const uschar *);
  extern pid_t   child_open_uid(const uschar **, const uschar **, int,
-                uid_t *, gid_t *, int *, int *, uschar *, BOOL);
+                uid_t *, gid_t *, int *, int *, uschar *, BOOL, const uschar *);
  extern BOOL    cleanup_environment(void);
  extern void    cutthrough_data_puts(uschar *, int);
  extern void    cutthrough_data_put_nl(void);
@@@ -222,10 -228,10 +227,10 @@@ extern void    msg_event_raise(const us
  
  extern int     exim_chown_failure(int, const uschar*, uid_t, gid_t);
  extern const uschar * exim_errstr(int);
- extern void    exim_exit(int, const uschar *) NORETURN;
+ extern void    exim_exit(int) NORETURN;
  extern void    exim_nullstd(void);
  extern void    exim_setugid(uid_t, gid_t, BOOL, uschar *);
- extern void    exim_underbar_exit(int, const uschar *);
+ extern void    exim_underbar_exit(int) NORETURN;
  extern void    exim_wait_tick(struct timeval *, int);
  extern int     exp_bool(address_item *addr,
    uschar *mtype, uschar *mname, unsigned dgb_opt, uschar *oname, BOOL bvalue,
@@@ -706,44 -712,28 +711,44 @@@ return chown(CCS name, owner, group
  *************************************************/
  
  /* This function assumes that memcpy() is faster than strcpy().
 +The result is explicitly nul-terminated.
  */
  
  static inline uschar *
 -string_copy_taint_trc(const uschar *s, BOOL tainted, const char * func, int line)
 +string_copyn_taint_trc(const uschar * s, unsigned len,
 +      BOOL tainted, const char * func, int line)
  {
 -int len = Ustrlen(s) + 1;
 -uschar *ss = store_get_3(len, tainted, func, line);
 +uschar * ss = store_get_3(len + 1, tainted, func, line);
  memcpy(ss, s, len);
 +ss[len] = '\0';
  return ss;
  }
  
 -#define string_copy_taint(s, tainted) \
 -      string_copy_taint_trc((s), tainted, __FUNCTION__, __LINE__)
 +static inline uschar *
 +string_copy_taint_trc(const uschar * s, BOOL tainted, const char * func, int line)
 +{ return string_copyn_taint_trc(s, Ustrlen(s), tainted, func, line); }
  
 +static inline uschar *
 +string_copyn_trc(const uschar * s, unsigned len, const char * func, int line)
 +{ return string_copyn_taint_trc(s, len, is_tainted(s), func, line); }
  static inline uschar *
  string_copy_trc(const uschar * s, const char * func, int line)
 -{
 -return string_copy_taint_trc((s), is_tainted(s), func, line);
 -}
 +{ return string_copy_taint_trc(s, is_tainted(s), func, line); }
 +
 +
 +/* String-copy functions explicitly setting the taint status */
  
 +#define string_copyn_taint(s, len, tainted) \
 +      string_copyn_taint_trc((s), (len), (tainted), __FUNCTION__, __LINE__)
 +#define string_copy_taint(s, tainted) \
 +      string_copy_taint_trc((s), (tainted), __FUNCTION__, __LINE__)
 +
 +/* Simple string-copy functions maintaining the taint */
 +
 +#define string_copyn(s, len) \
 +      string_copyn_taint_trc((s), (len), is_tainted(s), __FUNCTION__, __LINE__)
  #define string_copy(s) \
 -      string_copy_trc((s), __FUNCTION__, __LINE__)
 +      string_copy_taint_trc((s), is_tainted(s), __FUNCTION__, __LINE__)
  
  
  /*************************************************
@@@ -767,6 -757,31 +772,6 @@@ return ss
  
  
  
 -/*************************************************
 -*       Copy and save string, given length       *
 -*************************************************/
 -
 -/* It is assumed the data contains no zeros. A zero is added
 -onto the end.
 -
 -Arguments:
 -  s         string to copy
 -  n         number of characters
 -
 -Returns:    copy of string in new store
 -
 -This is an API for local_scan hence not static.
 -*/
 -
 -static inline uschar *
 -string_copyn(const uschar *s, int n)
 -{
 -uschar *ss = store_get(n + 1, is_tainted(s));
 -Ustrncpy(ss, s, n);
 -ss[n] = 0;
 -return ss;
 -}
 -
  /*************************************************
  * Copy, lowercase, and save string, given length *
  *************************************************/
@@@ -1099,6 -1114,46 +1104,46 @@@ errno = EACCES
  return NULL;
  }
  
+ /******************************************************************************/
+ /* Process manipulation */
+ static inline pid_t
+ exim_fork(const unsigned char * purpose)
+ {
+ pid_t pid;
+ DEBUG(D_any) debug_printf("%s forking for %s\n", process_purpose, purpose);
+ if ((pid = fork()) == 0)
+   {
+   process_purpose = purpose;
+   DEBUG(D_any) debug_printf("postfork: %s\n", purpose);
+   }
+ else
+   {
+   testharness_pause_ms(100); /* let child work */
+   DEBUG(D_any) debug_printf("%s forked for %s: %d\n", process_purpose, purpose, (int)pid);
+   }
+ return pid;
+ }
+ static inline pid_t
+ child_open_exim(int * fdptr, const uschar * purpose)
+ { return child_open_exim_function(fdptr, purpose); }
+ static inline pid_t
+ child_open_exim2(int * fdptr, uschar * sender,
+   uschar * sender_auth, const uschar * purpose)
+ { return child_open_exim2_function(fdptr, sender, sender_auth, purpose); }
+ static inline pid_t
+ child_open(uschar **argv, uschar **envp, int newumask, int *infdptr,
+   int *outfdptr, BOOL make_leader, const uschar * purpose)
+ { return child_open_function(argv, envp, newumask, infdptr,
+   outfdptr, make_leader, purpose);
+ }
+ /******************************************************************************/
  #endif        /* !MACRO_PREDEF */
  
  #endif  /* _FUNCTIONS_H_ */
diff --combined src/src/queue.c
index 2d2772c1a458487020bbf4f03deba1d27c235623,3235a41ba582b2dda860045f6f816fd73b8e5ce5..bb75c99cd13c393006327958057d01b162ce91ab
@@@ -496,10 -496,8 +496,8 @@@ for (int i = queue_run_in_order ? -1 : 
        }
        else
        for (i = 0; qpid[i]; ) i++;
-       DEBUG(D_queue_run) debug_printf("q2stage forking\n");
-       if ((qpid[i] = fork()))
+       if ((qpid[i] = exim_fork(US"qrun-phase-one")))
        continue;       /* parent loops around */
-       DEBUG(D_queue_run) debug_printf("q2stage child\n");
        }
  
      /* Skip this message unless it's within the ID limits */
  #endif
  
  single_item_retry:
-     if ((pid = fork()) == 0)
+     if ((pid = exim_fork(US"qrun-delivery")) == 0)
        {
        int rc;
-       testharness_pause_ms(100);
        (void)close(pfd[pipe_read]);
        rc = deliver_message(fq->text, force_delivery, FALSE);
-       exim_underbar_exit(rc == DELIVER_NOT_ATTEMPTED, US"qrun-delivery");
+       exim_underbar_exit(rc == DELIVER_NOT_ATTEMPTED
+               ? EXIT_FAILURE : EXIT_SUCCESS);
        }
      if (pid < 0)
        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork of delivery process from "
  
      /* If initial of a 2-phase run, we are a child - so just exit */
      if (f.queue_2stage && !queue_run_in_order)
-       exim_exit(EXIT_SUCCESS, US"2-phase child");
+       exim_exit(EXIT_SUCCESS);
  
      /* If we are in the test harness, and this is not the first of a 2-stage
      queue run, update fudged queue times. */
    go_around:
      /* If initial of a 2-phase run, we are a child - so just exit */
      if (f.queue_2stage && !queue_run_in_order)
-       exim_exit(EXIT_SUCCESS, US"2-phase child");
+       exim_exit(EXIT_SUCCESS);
      }                                  /* End loop for list of messages */
  
    tree_nonrecipients = NULL;
@@@ -1412,13 -1410,13 +1410,13 @@@ switch(action
        parse_extract_address(argv[recipients_arg], &errmess, &start, &end,
          &domain, (action == MSG_EDIT_SENDER));
  
 -    if (recipient == NULL)
 +    if (!recipient)
        {
        yield = FALSE;
        printf("- error while %s:\n  bad address %s: %s\n",
          doing, argv[recipients_arg], errmess);
        }
 -    else if (recipient[0] != 0 && domain == 0)
 +    else if (*recipient && domain == 0)
        {
        yield = FALSE;
        printf("- error while %s:\n  bad address %s: "
@@@ -1548,6 -1546,7 +1546,6 @@@ memcpy(buf+1, msgid, MESSAGE_ID_LENGTH+
  if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) >= 0)
    {
    struct sockaddr_un sa_un = {.sun_family = AF_UNIX};
 -  int slen;
  
  #ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
    int len = offsetof(struct sockaddr_un, sun_path) + 1
diff --combined src/src/receive.c
index 2745df6d1be723d671b63d1798f3a86e0f5a0b39,398250ebbbbbc8d563187e79cfd38be9b45e4edb..0afb72b8cfd315c9a409f8dd38aea4904faa82fa
@@@ -216,7 -216,7 +216,7 @@@ if (STATVFS(CS path, &statbuf) != 0
      log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
        "%s directory %s: %s", name, path, strerror(errno));
      smtp_closedown(US"spool or log directory problem");
-     exim_exit(EXIT_FAILURE, NULL);
+     exim_exit(EXIT_FAILURE);
      }
  
  *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
@@@ -372,7 -372,7 +372,7 @@@ if (!already_bombing_out
  
  /* Exit from the program (non-BSMTP cases) */
  
- exim_exit(EXIT_FAILURE, NULL);
+ exim_exit(EXIT_FAILURE);
  }
  
  
@@@ -1172,7 -1172,7 +1172,7 @@@ if (error_handling == ERRORS_SENDER
  else
    fprintf(stderr, "exim: %s%s\n", text2, text1);  /* Sic */
  (void)fclose(f);
- exim_exit(error_rc, US"");
+ exim_exit(error_rc);
  }
  
  
@@@ -2568,7 -2568,7 +2568,7 @@@ if (extract_recip
  
          If there are no recipients at all, an error will occur later. */
  
 -        if (recipient == NULL && Ustrcmp(errmess, "empty address") != 0)
 +        if (!recipient && Ustrcmp(errmess, "empty address") != 0)
            {
            int len = Ustrlen(s);
            error_block *b = store_get(sizeof(error_block), FALSE);
@@@ -3340,7 -3340,7 +3340,7 @@@ if (extract_recip && (bad_addresses || 
      {
      Uunlink(spool_name);
      (void)fclose(spool_data_file);
-     exim_exit(error_rc, US"receiving");
+     exim_exit(error_rc);
      }
    }
  
diff --combined src/src/sieve.c
index 286be789a173d25eb40a3623cb21ffeb1ce8f5c1,e07b7da1860cf310e0b7ed13200dc965d8c4294f..4467665340030a48a6fee47290e98efc2a1cbfd3
@@@ -328,7 -328,7 +328,7 @@@ if (address->length>0
    {
    ss = parse_extract_address(address->character, &error, &start, &end, &domain,
      FALSE);
 -  if (ss == NULL)
 +  if (!ss)
      {
      filter->errmsg=string_sprintf("malformed address \"%s\" (%s)",
        address->character, error);
@@@ -3084,7 -3084,8 +3084,8 @@@ while (*filter->pc
              {
              int pid, fd;
  
-             if ((pid = child_open_exim2(&fd,envelope_from,envelope_from))>=1)
+             if ((pid = child_open_exim2(&fd, envelope_from, envelope_from,
+                       US"sieve-notify")) >= 1)
                {
                FILE *f;
                uschar *buffer;
  
                f = fdopen(fd, "wb");
                fprintf(f,"From: %s\n", from.length == -1
-               ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : from.character);
+               ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain")
+               : from.character);
                for (string_item * p = recipient; p; p=p->next)
                fprintf(f,"To: %s\n",p->text);
                fprintf(f,"Auto-Submitted: auto-notified; %s\n",filter->enotify_mailto_owner);
                  message.length=Ustrlen(message.character);
                  }
                /* Allocation is larger than necessary, but enough even for split MIME words */
-               buffer_capacity=32+4*message.length;
+               buffer_capacity = 32 + 4*message.length;
                buffer=store_get(buffer_capacity, TRUE);
-               if (message.length!=-1) fprintf(f,"Subject: %s\n",parse_quote_2047(message.character, message.length, US"utf-8", buffer, buffer_capacity, TRUE));
+               if (message.length != -1)
+               fprintf(f, "Subject: %s\n", parse_quote_2047(message.character,
+                 message.length, US"utf-8", buffer, buffer_capacity, TRUE));
                fprintf(f,"\n");
                if (body.length>0) fprintf(f,"%s\n",body.character);
                fflush(f);
                (void)child_close(pid, 0);
                }
              }
-           if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
-             {
+           if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
              debug_printf("Notification to `%s': '%s'.\n",method.character,message.length!=-1 ? message.character : CUS "");
-             }
  #endif
            }
          else
-           {
-           if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
-             {
+           if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
              debug_printf("Repeated notification to `%s' ignored.\n",method.character);
-             }
-           }
          }
        else
-         {
-         if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
-           {
+         if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
            debug_printf("Ignoring notification, triggering message contains Auto-submitted: field.\n");
-           }
-         }
        }
      }
  #endif
index e75349ed9b7eb9ca928d3226f548bb16b94ea6bc,2b487b435a62b66f1f61701bf775f42e44a9520f..90a5aa4be6f3aa58d6d082247e9ea4a4bda06f48
@@@ -202,7 -202,7 +202,7 @@@ while (*s != 0
    /* If there is some kind of syntax error, just give up on this header
    line. */
  
 -  if (next == NULL) break;
 +  if (!next) break;
  
    /* See if the address is on the never_mail list */
  
@@@ -567,12 -567,10 +567,10 @@@ if (file
  
  /* Make a subprocess to send the message */
  
- pid = child_open_exim(&fd);
- /* Creation of child failed; defer this delivery. */
- if (pid < 0)
+ if ((pid = child_open_exim(&fd, US"autoreply")) < 0)
    {
+   /* Creation of child failed; defer this delivery. */
    addr->transport_return = DEFER;
    addr->basic_errno = errno;
    addr->message = string_sprintf("Failed to create child process to send "
diff --combined test/stderr/0002
index 97fc299016950453491b02ebf8f6c50988fe50d0,f2261428440b012c1feb2d9a513ce5e00dbc23a9..995a193fefb25a8164feb875793d21604db0d237
@@@ -85,7 -85,7 +85,7 @@@ LOG: MAIN PANI
    ╰─────result: no
   ├──expanding: match_address:   ${if match_address{a.b.c}{a.b.c}{yes}{no}}
   ╰─────result: match_address:   no
- >>>>>>>>>>>>>>>> Exim pid=pppp (main: expansion test) terminating with rc=0 >>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>> Exim pid=pppp (fresh-exec) terminating with rc=0 >>>>>>>>>>>>>>>>
  Exim version x.yz ....
  configuration file is TESTSUITE/test-config
  admin user
@@@ -169,7 -169,7 +169,7 @@@ LOG: MAIN PANI
    \_____result: no
   |--expanding: match_address:   ${if match_address{a.b.c}{a.b.c}{yes}{no}}
   \_____result: match_address:   no
- >>>>>>>>>>>>>>>> Exim pid=pppp (main: expansion test) terminating with rc=0 >>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>> Exim pid=pppp (fresh-exec) terminating with rc=0 >>>>>>>>>>>>>>>>
  Exim version x.yz ....
  configuration file is TESTSUITE/test-config
  admin user
@@@ -177,14 -177,12 +177,14 @@@ dropping to exim gid; retaining priv ui
   ╭considering: -oMa  sender_host_address = $sender_host_address
   ├──expanding: -oMa  sender_host_address = $sender_host_address
   ╰─────result: -oMa  sender_host_address = V4NET.0.0.1
 +            ╰──(tainted)
   ╭considering:       sender_host_port = $sender_host_port
   ├──expanding:       sender_host_port = $sender_host_port
   ╰─────result:       sender_host_port = 1234
   ╭considering: -oMaa sender_host_authenticated = $sender_host_authenticated
   ├──expanding: -oMaa sender_host_authenticated = $sender_host_authenticated
   ╰─────result: -oMaa sender_host_authenticated = AAA
 +            ╰──(tainted)
   ╭considering: -oMai authenticated_id = $authenticated_id
   ├──expanding: -oMai authenticated_id = $authenticated_id
   ╰─────result: -oMai authenticated_id = philip
   ╭considering: -oMi  interface_address = $interface_address
   ├──expanding: -oMi  interface_address = $interface_address
   ╰─────result: -oMi  interface_address = 1.1.1.1
 +            ╰──(tainted)
   ╭considering:       interface_port = $interface_port
   ├──expanding:       interface_port = $interface_port
   ╰─────result:       interface_port = 99
   ╭considering: -oMr  received_protocol = $received_protocol
   ├──expanding: -oMr  received_protocol = $received_protocol
   ╰─────result: -oMr  received_protocol = special
 +            ╰──(tainted)
   ╭considering: -oMt  sender_ident = $sender_ident
   ├──expanding: -oMt  sender_ident = $sender_ident
   ╰─────result: -oMt  sender_ident = me
- >>>>>>>>>>>>>>>> Exim pid=pppp (main: expansion test) terminating with rc=0 >>>>>>>>>>>>>>>>
 +            ╰──(tainted)
+ >>>>>>>>>>>>>>>> Exim pid=pppp (fresh-exec) terminating with rc=0 >>>>>>>>>>>>>>>>
  1999-03-02 09:44:33 no host name found for IP address V4NET.11.12.13
  Exim version x.yz ....
  configuration file is TESTSUITE/test-config
@@@ -217,14 -212,12 +217,14 @@@ dropping to exim gid; retaining priv ui
   ╭considering: -oMa  sender_host_address = $sender_host_address
   ├──expanding: -oMa  sender_host_address = $sender_host_address
   ╰─────result: -oMa  sender_host_address = V4NET.0.0.1
 +            ╰──(tainted)
   ╭considering:       sender_host_port = $sender_host_port
   ├──expanding:       sender_host_port = $sender_host_port
   ╰─────result:       sender_host_port = 1234
   ╭considering: -oMaa sender_host_authenticated = $sender_host_authenticated
   ├──expanding: -oMaa sender_host_authenticated = $sender_host_authenticated
   ╰─────result: -oMaa sender_host_authenticated = AAA
 +            ╰──(tainted)
   ╭considering: -oMai authenticated_id = $authenticated_id
   ├──expanding: -oMai authenticated_id = $authenticated_id
   ╰─────result: -oMai authenticated_id = philip
   ╭considering: -oMi  interface_address = $interface_address
   ├──expanding: -oMi  interface_address = $interface_address
   ╰─────result: -oMi  interface_address = 1.1.1.1
 +            ╰──(tainted)
   ╭considering:       interface_port = $interface_port
   ├──expanding:       interface_port = $interface_port
   ╰─────result:       interface_port = 99
   ╭considering: -oMr  received_protocol = $received_protocol
   ├──expanding: -oMr  received_protocol = $received_protocol
   ╰─────result: -oMr  received_protocol = special
 +            ╰──(tainted)
   ╭considering: ----> No lookup yet: ${if eq{black}{white}{$sender_host_name}{No}}
    ╭considering: black}{white}{$sender_host_name}{No}}
    ├──expanding: black
   ╰─────result: ----> No lookup yet: No
   ╭considering: -oMs  sender_host_name = $sender_host_name
  looking up host name for V4NET.0.0.1
+ fresh-exec forking for fakens-search
+ postfork: fakens-search
+ fresh-exec forked for fakens-search: npppp
  IP address lookup yielded "ten-1.test.ex"
+ fresh-exec forking for fakens-search
+ postfork: fakens-search
+ fresh-exec forked for fakens-search: npppp
+ fresh-exec forking for fakens-search
+ postfork: fakens-search
+ fresh-exec forked for fakens-search: npppp
  ten-1.test.ex V4NET.0.0.1 mx=-1 sort=xx 
  checking addresses for ten-1.test.ex
    V4NET.0.0.1 OK
@@@ -276,8 -276,7 +285,8 @@@ sender_rcvhost = ten-1.test.ex ([V4NET.
   ╭considering: -oMt  sender_ident = $sender_ident
   ├──expanding: -oMt  sender_ident = $sender_ident
   ╰─────result: -oMt  sender_ident = me
- >>>>>>>>>>>>>>>> Exim pid=pppp (main: expansion test) terminating with rc=0 >>>>>>>>>>>>>>>>
 +            ╰──(tainted)
+ >>>>>>>>>>>>>>>> Exim pid=pppp (fresh-exec) terminating with rc=0 >>>>>>>>>>>>>>>>
  Exim version x.yz ....
  changed uid/gid: forcing real = effective
    uid=uuuu gid=CALLER_GID pid=pppp
@@@ -306,9 -305,18 +315,18 @@@ check hosts = <\n partial-lsearch;TESTS
  sender host name required, to match against partial-lsearch;TESTSUITE/aux-fixed/0002.lsearch
  looking up host name for V4NET.0.0.1
  DNS lookup of 1.0.0.V4NET.in-addr.arpa (PTR) using fakens
+ fresh-exec forking for fakens-search
+ postfork: fakens-search
+ fresh-exec forked for fakens-search: npppp
  DNS lookup of 1.0.0.V4NET.in-addr.arpa (PTR) succeeded
  IP address lookup yielded "ten-1.test.ex"
+ fresh-exec forking for fakens-search
+ postfork: fakens-search
+ fresh-exec forked for fakens-search: npppp
  DNS lookup of ten-1.test.ex (A) using fakens
+ fresh-exec forking for fakens-search
+ postfork: fakens-search
+ fresh-exec forked for fakens-search: npppp
  DNS lookup of ten-1.test.ex (A) succeeded
  ten-1.test.ex V4NET.0.0.1 mx=-1 sort=xx 
  checking addresses for ten-1.test.ex
@@@ -336,7 -344,7 +354,7 @@@ SMTP>> 550 Administrative prohibitio
  LOG: connection_reject MAIN REJECT
    H=ten-1.test.ex [V4NET.0.0.1] rejected connection in "connect" ACL
  search_tidyup called
- >>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>> Exim pid=pppp (fresh-exec) terminating with rc=0 >>>>>>>>>>>>>>>>
  Exim version x.yz ....
  changed uid/gid: forcing real = effective
    uid=uuuu gid=CALLER_GID pid=pppp
@@@ -380,7 -388,7 +398,7 @@@ SMTP>> 550 Administrative prohibitio
  LOG: connection_reject MAIN REJECT
    H=[V4NET.0.0.2] rejected connection in "connect" ACL
  search_tidyup called
- >>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>> Exim pid=pppp (fresh-exec) terminating with rc=0 >>>>>>>>>>>>>>>>
  >>> host in hosts_connection_nolog? no (option unset)
  >>> host in host_lookup? no (option unset)
  >>> host in host_reject_connection? no (option unset)
@@@ -532,4 -540,4 +550,4 @@@ sender address = CALLER@myhost.test.e
  1.2.3.4 in "1.2.3"? no (malformed IPv4 address or address mask)
  1.2.3.4 in "1.2.3.4/abc"? no (malformed IPv4 address or address mask)
  search_tidyup called
- >>>>>>>>>>>>>>>> Exim pid=pppp (main: expansion test) terminating with rc=0 >>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>> Exim pid=pppp (fresh-exec) terminating with rc=0 >>>>>>>>>>>>>>>>