Support gring as a first-class conversion specifier in internal string-formatting
[exim.git] / src / src / transports / appendfile.c
index efce526228cbc7e4f394432f288f4b4c2f7b3120..e49f46be4bc2c8269451519e93788994d8784044 100644 (file)
@@ -1,11 +1,11 @@
-/* $Cambridge: exim/src/src/transports/appendfile.c,v 1.10 2005/06/27 14:29:44 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) The Exim maintainers 2020 - 2022 */
+/* Copyright (c) University of Cambridge 1995 - 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 
 #include "../exim.h"
 #endif
 
 
-/* Encodings for mailbox formats, and their names. MBX format is actually
-supported only if SUPPORT_MBX is set. */
-
-enum { mbf_unix, mbf_mbx, mbf_smail, mbf_maildir, mbf_mailstore };
-
-static char *mailbox_formats[] = {
-  "unix", "mbx", "smail", "maildir", "mailstore" };
-
-
-/* Check warn threshold only if quota size set or not a percentage threshold
-   percentage check should only be done if quota > 0 */
-
-#define THRESHOLD_CHECK  (ob->quota_warn_threshold_value > 0 && \
-  (!ob->quota_warn_threshold_is_percent || ob->quota_value > 0))
-
-
 /* Options specific to the appendfile transport. They must be in alphabetic
 order (note that "_" comes before the lower case letters). Some of them are
 stored in the publicly visible instance block - these are flagged with the
 opt_public flag. */
+#define LOFF(field) OPT_OFF(appendfile_transport_options_block, field)
 
 optionlist appendfile_transport_options[] = {
-  { "*set_use_fcntl_lock",opt_bool | opt_hidden,
-      (void *)offsetof(appendfile_transport_options_block, set_use_fcntl) },
-  { "*set_use_flock_lock",opt_bool | opt_hidden,
-      (void *)offsetof(appendfile_transport_options_block, set_use_flock) },
-  { "*set_use_lockfile", opt_bool | opt_hidden,
-      (void *)offsetof(appendfile_transport_options_block, set_use_lockfile) },
+#ifdef SUPPORT_MAILDIR
+  { "*expand_maildir_use_size_file", opt_stringptr, LOFF(expand_maildir_use_size_file) },
+#endif
+  { "*set_use_fcntl_lock",opt_bool | opt_hidden, LOFF(set_use_fcntl) },
+  { "*set_use_flock_lock",opt_bool | opt_hidden, LOFF(set_use_flock) },
+  { "*set_use_lockfile", opt_bool | opt_hidden,        LOFF(set_use_lockfile) },
 #ifdef SUPPORT_MBX
-  { "*set_use_mbx_lock", opt_bool | opt_hidden,
-      (void *)offsetof(appendfile_transport_options_block, set_use_mbx_lock) },
+  { "*set_use_mbx_lock", opt_bool | opt_hidden,        LOFF(set_use_mbx_lock) },
 #endif
-  { "allow_fifo",        opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, allow_fifo) },
-  { "allow_symlink",     opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, allow_symlink) },
-  { "batch_id",          opt_stringptr | opt_public,
-      (void *)offsetof(transport_instance, batch_id) },
-  { "batch_max",         opt_int | opt_public,
-      (void *)offsetof(transport_instance, batch_max) },
-  { "check_group",       opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, check_group) },
-  { "check_owner",       opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, check_owner) },
-  { "check_string",      opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, check_string) },
-  { "create_directory",  opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, create_directory) },
-  { "create_file",       opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, create_file_string) },
-  { "directory",         opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, dirname) },
-  { "directory_file",    opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, dirfilename) },
-  { "directory_mode",    opt_octint,
-      (void *)offsetof(appendfile_transport_options_block, dirmode) },
-  { "escape_string",     opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, escape_string) },
-  { "file",              opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, filename) },
-  { "file_format",       opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, file_format) },
-  { "file_must_exist",   opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, file_must_exist) },
-  { "lock_fcntl_timeout", opt_time,
-      (void *)offsetof(appendfile_transport_options_block, lock_fcntl_timeout) },
-  { "lock_flock_timeout", opt_time,
-      (void *)offsetof(appendfile_transport_options_block, lock_flock_timeout) },
-  { "lock_interval",     opt_time,
-      (void *)offsetof(appendfile_transport_options_block, lock_interval) },
-  { "lock_retries",      opt_int,
-      (void *)offsetof(appendfile_transport_options_block, lock_retries) },
-  { "lockfile_mode",     opt_octint,
-      (void *)offsetof(appendfile_transport_options_block, lockfile_mode) },
-  { "lockfile_timeout",  opt_time,
-      (void *)offsetof(appendfile_transport_options_block, lockfile_timeout) },
-  { "mailbox_filecount", opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, mailbox_filecount_string) },
-  { "mailbox_size",      opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, mailbox_size_string) },
+  { "allow_fifo",        opt_bool,     LOFF(allow_fifo) },
+  { "allow_symlink",     opt_bool,     LOFF(allow_symlink) },
+  { "batch_id",          opt_stringptr | opt_public, OPT_OFF(transport_instance, batch_id) },
+  { "batch_max",         opt_int | opt_public, OPT_OFF(transport_instance, batch_max) },
+  { "check_group",       opt_bool,     LOFF(check_group) },
+  { "check_owner",       opt_bool,     LOFF(check_owner) },
+  { "check_string",      opt_stringptr,        LOFF(check_string) },
+  { "create_directory",  opt_bool,     LOFF(create_directory) },
+  { "create_file",       opt_stringptr,        LOFF(create_file_string) },
+  { "directory",         opt_stringptr,        LOFF(dirname) },
+  { "directory_file",    opt_stringptr,        LOFF(dirfilename) },
+  { "directory_mode",    opt_octint,   LOFF(dirmode) },
+  { "escape_string",     opt_stringptr,        LOFF(escape_string) },
+  { "file",              opt_stringptr,        LOFF(filename) },
+  { "file_format",       opt_stringptr,        LOFF(file_format) },
+  { "file_must_exist",   opt_bool,     LOFF(file_must_exist) },
+  { "lock_fcntl_timeout", opt_time,    LOFF(lock_fcntl_timeout) },
+  { "lock_flock_timeout", opt_time,    LOFF(lock_flock_timeout) },
+  { "lock_interval",     opt_time,     LOFF(lock_interval) },
+  { "lock_retries",      opt_int,      LOFF(lock_retries) },
+  { "lockfile_mode",     opt_octint,   LOFF(lockfile_mode) },
+  { "lockfile_timeout",  opt_time,     LOFF(lockfile_timeout) },
+  { "mailbox_filecount", opt_stringptr,        LOFF(mailbox_filecount_string) },
+  { "mailbox_size",      opt_stringptr,        LOFF(mailbox_size_string) },
 #ifdef SUPPORT_MAILDIR
-  { "maildir_format",    opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, maildir_format ) } ,
-  { "maildir_quota_directory_regex", opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, maildir_dir_regex) },
-  { "maildir_retries",   opt_int,
-      (void *)offsetof(appendfile_transport_options_block, maildir_retries) },
-  { "maildir_tag",       opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, maildir_tag) },
-  { "maildir_use_size_file", opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, maildir_use_size_file ) } ,
+  { "maildir_format",    opt_bool,     LOFF(maildir_format ) } ,
+  { "maildir_quota_directory_regex", opt_stringptr, LOFF(maildir_dir_regex) },
+  { "maildir_retries",   opt_int,      LOFF(maildir_retries) },
+  { "maildir_tag",       opt_stringptr,        LOFF(maildir_tag) },
+  { "maildir_use_size_file", opt_expand_bool, LOFF(maildir_use_size_file ) } ,
+  { "maildirfolder_create_regex", opt_stringptr, LOFF(maildirfolder_create_regex ) },
 #endif  /* SUPPORT_MAILDIR */
 #ifdef SUPPORT_MAILSTORE
-  { "mailstore_format",  opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, mailstore_format ) },
-  { "mailstore_prefix",  opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, mailstore_prefix ) },
-  { "mailstore_suffix",  opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, mailstore_suffix ) },
+  { "mailstore_format",  opt_bool,     LOFF(mailstore_format ) },
+  { "mailstore_prefix",  opt_stringptr,        LOFF(mailstore_prefix ) },
+  { "mailstore_suffix",  opt_stringptr,        LOFF(mailstore_suffix ) },
 #endif  /* SUPPORT_MAILSTORE */
 #ifdef SUPPORT_MBX
-  { "mbx_format",        opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, mbx_format ) } ,
+  { "mbx_format",        opt_bool,     LOFF(mbx_format ) } ,
 #endif  /* SUPPORT_MBX */
-  { "message_prefix",    opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, message_prefix) },
-  { "message_suffix",    opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, message_suffix) },
-  { "mode",              opt_octint,
-      (void *)offsetof(appendfile_transport_options_block, mode) },
-  { "mode_fail_narrower",opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, mode_fail_narrower) },
-  { "notify_comsat",     opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, notify_comsat) },
-  { "quota",             opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, quota) },
-  { "quota_directory",   opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, quota_directory) },
-  { "quota_filecount",   opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, quota_filecount) },
-  { "quota_is_inclusive", opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, quota_is_inclusive) },
-  { "quota_size_regex",   opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, quota_size_regex) },
-  { "quota_warn_message", opt_stringptr | opt_public,
-      (void *)offsetof(transport_instance, warn_message) },
-  { "quota_warn_threshold", opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, quota_warn_threshold) },
-  { "use_bsmtp",         opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, use_bsmtp) },
-  { "use_crlf",          opt_bool,
-      (void *)offsetof(appendfile_transport_options_block, use_crlf) },
-  { "use_fcntl_lock",    opt_bool_set,
-      (void *)offsetof(appendfile_transport_options_block, use_fcntl) },
-  { "use_flock_lock",    opt_bool_set,
-      (void *)offsetof(appendfile_transport_options_block, use_flock) },
-  { "use_lockfile",      opt_bool_set,
-      (void *)offsetof(appendfile_transport_options_block, use_lockfile) },
+  { "message_prefix",    opt_stringptr,        LOFF(message_prefix) },
+  { "message_suffix",    opt_stringptr,        LOFF(message_suffix) },
+  { "mode",              opt_octint,   LOFF(mode) },
+  { "mode_fail_narrower",opt_bool,     LOFF(mode_fail_narrower) },
+  { "notify_comsat",     opt_bool,     LOFF(notify_comsat) },
+  { "quota",             opt_stringptr,        LOFF(quota) },
+  { "quota_directory",   opt_stringptr,        LOFF(quota_directory) },
+  { "quota_filecount",   opt_stringptr,        LOFF(quota_filecount) },
+  { "quota_is_inclusive", opt_bool,    LOFF(quota_is_inclusive) },
+  { "quota_size_regex",   opt_stringptr, LOFF(quota_size_regex) },
+  { "quota_warn_message", opt_stringptr | opt_public, OPT_OFF(transport_instance, warn_message) },
+  { "quota_warn_threshold", opt_stringptr, LOFF(quota_warn_threshold) },
+  { "use_bsmtp",         opt_bool,     LOFF(use_bsmtp) },
+  { "use_crlf",          opt_bool,     LOFF(use_crlf) },
+  { "use_fcntl_lock",    opt_bool_set, LOFF(use_fcntl) },
+  { "use_flock_lock",    opt_bool_set, LOFF(use_flock) },
+  { "use_lockfile",      opt_bool_set, LOFF(use_lockfile) },
 #ifdef SUPPORT_MBX
-  { "use_mbx_lock",      opt_bool_set,
-      (void *)offsetof(appendfile_transport_options_block, use_mbx_lock) },
+  { "use_mbx_lock",      opt_bool_set, LOFF(use_mbx_lock) },
 #endif  /* SUPPORT_MBX */
 };
 
@@ -166,72 +100,59 @@ address can appear in the tables drtables.c. */
 int appendfile_transport_options_count =
   sizeof(appendfile_transport_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+appendfile_transport_options_block appendfile_transport_option_defaults = {0};
+void appendfile_transport_init(transport_instance *tblock) {}
+BOOL appendfile_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else  /*!MACRO_PREDEF*/
+
 /* Default private options block for the appendfile transport. */
 
 appendfile_transport_options_block appendfile_transport_option_defaults = {
-  NULL,           /* filename */
-  NULL,           /* dirname */
-  US"q${base62:$tod_epoch}-$inode", /* dirfilename */
-  NULL,           /* message_prefix (default reset in init if not bsmtp) */
-  NULL,           /* message_suffix (ditto) */
-  US"anywhere",   /* create_file_string (string value for create_file) */
-  NULL,           /* quota */
-  NULL,           /* quota_directory */
-  NULL,           /* quota_filecount */
-  NULL,           /* quota_size_regex */
-  NULL,           /* quota_warn_threshold */
-  NULL,           /* mailbox_size_string */
-  NULL,           /* mailbox_filecount_string */
-  US"^(?:cur|new|\\..*)$",  /* maildir_dir_regex */
-  NULL,           /* maildir_tag */
-  NULL,           /* mailstore_prefix */
-  NULL,           /* mailstore_suffix */
-  NULL,           /* check_string (default changed for non-bsmtp file)*/
-  NULL,           /* escape_string (ditto) */
-  NULL,           /* file_format */
-  0,              /* quota_value */
-  0,              /* quota_warn_threshold_value */
-  -1,             /* mailbox_size_value */
-  -1,             /* mailbox_filecount_value */
-  0,              /* quota_filecount_value */
-  APPENDFILE_MODE,           /* mode */
-  APPENDFILE_DIRECTORY_MODE, /* dirmode */
-  APPENDFILE_LOCKFILE_MODE,  /* lockfile_mode */
-  30*60,          /* lockfile_timeout */
-  0,              /* lock_fcntl_timeout */
-  0,              /* lock_flock_timeout */
-  10,             /* lock_retries */
-   3,             /* lock_interval */
-  10,             /* maildir_retries */
-  create_anywhere,/* create_file */
-  0,              /* options */
-  FALSE,          /* allow_fifo */
-  FALSE,          /* allow_symlink */
-  FALSE,          /* check_group */
-  TRUE,           /* check_owner */
-  TRUE,           /* create_directory */
-  FALSE,          /* notify_comsat */
-  TRUE,           /* use_lockfile */
-  FALSE,          /* set_use_lockfile */
-  TRUE,           /* use_fcntl */
-  FALSE,          /* set_use_fcntl */
-  FALSE,          /* use_flock */
-  FALSE,          /* set_use_flock */
-  FALSE,          /* use_mbx_lock */
-  FALSE,          /* set_use_mbx_lock */
-  FALSE,          /* use_bsmtp */
-  FALSE,          /* use_crlf */
-  FALSE,          /* file_must_exist */
-  TRUE,           /* mode_fail_narrower */
-  FALSE,          /* maildir_format */
-  FALSE,          /* maildir_use_size_file */
-  FALSE,          /* mailstore_format */
-  FALSE,          /* mbx_format */
-  FALSE,          /* quota_warn_threshold_is_percent */
-  TRUE            /* quota_is_inclusive */
+  /* all non-mentioned members zero/null/false */
+  .dirfilename = US"q${base62:$tod_epoch}-$inode",
+  .create_file_string = US"anywhere",
+  .maildir_dir_regex = US"^(?:cur|new|\\..*)$",
+  .mailbox_size_value = -1,
+  .mailbox_filecount_value = -1,
+  .mode = APPENDFILE_MODE,
+  .dirmode = APPENDFILE_DIRECTORY_MODE,
+  .lockfile_mode = APPENDFILE_LOCKFILE_MODE,
+  .lockfile_timeout = 30*60,
+  .lock_retries = 10,
+   .lock_interval = 3,
+  .maildir_retries = 10,
+  .create_file = create_anywhere,
+  .check_owner = TRUE,
+  .create_directory = TRUE,
+  .notify_comsat = FALSE,
+  .use_lockfile = TRUE,
+  .use_fcntl = TRUE,
+  .mode_fail_narrower = TRUE,
+  .quota_is_inclusive = TRUE,
 };
 
 
+/* Encodings for mailbox formats, and their names. MBX format is actually
+supported only if SUPPORT_MBX is set. */
+
+enum { mbf_unix, mbf_mbx, mbf_smail, mbf_maildir, mbf_mailstore };
+
+static const char *mailbox_formats[] = {
+  "unix", "mbx", "smail", "maildir", "mailstore" };
+
+
+/* Check warn threshold only if quota size set or not a percentage threshold
+   percentage check should only be done if quota > 0 */
+
+#define THRESHOLD_CHECK  (ob->quota_warn_threshold_value > 0 && \
+  (!ob->quota_warn_threshold_is_percent || ob->quota_value > 0))
+
+
 
 /*************************************************
 *              Setup entry point                 *
@@ -247,6 +168,8 @@ Arguments:
   tblock     points to the transport instance
   addrlist   addresses about to be delivered (not used)
   dummy      not used (doesn't pass back data)
+  uid        the uid that will be set (not used)
+  gid        the gid that will be set (not used)
   errmsg     where to put an error message
 
 Returns:     OK, FAIL, or DEFER
@@ -254,34 +177,35 @@ Returns:     OK, FAIL, or DEFER
 
 static int
 appendfile_transport_setup(transport_instance *tblock, address_item *addrlist,
-  transport_feedback *dummy, uschar **errmsg)
+  transport_feedback *dummy, uid_t uid, gid_t gid, uschar **errmsg)
 {
 appendfile_transport_options_block *ob =
   (appendfile_transport_options_block *)(tblock->options_block);
 uschar *q = ob->quota;
 double default_value = 0.0;
-int i;
 
-addrlist = addrlist;    /* Keep picky compilers happy */
-dummy = dummy;
+if (ob->expand_maildir_use_size_file)
+       ob->maildir_use_size_file = expand_check_condition(ob->expand_maildir_use_size_file,
+               US"`maildir_use_size_file` in transport", tblock->name);
 
 /* Loop for quota, quota_filecount, quota_warn_threshold, mailbox_size,
 mailbox_filecount */
 
-for (i = 0; i < 5; i++)
+for (int i = 0; i < 5; i++)
   {
-  double d;
+  double d = default_value;
+  int no_check = 0;
+  uschar *which = NULL;
 
-  if (q == NULL) d = default_value; else
+  if (q)
     {
-    uschar *rest;
-    uschar *s = expand_string(q);
+    uschar * rest, * s;
 
-    if (s == NULL)
+    if (!(s =  expand_string(q)))
       {
       *errmsg = string_sprintf("Expansion of \"%s\" in %s transport failed: "
         "%s", q, tblock->name, expand_string_message);
-      return search_find_defer? DEFER : FAIL;
+      return f.search_find_defer ? DEFER : FAIL;
       }
 
     d = Ustrtod(s, &rest);
@@ -295,7 +219,8 @@ for (i = 0; i < 5; i++)
     else if (tolower(*rest) == 'g') { d *= 1024.0*1024.0*1024.0; rest++; }
     else if (*rest == '%' && i == 2)
       {
-      if (ob->quota_value <= 0 && !ob->maildir_use_size_file) d = 0;
+      if (ob->quota_value <= 0 && !ob->maildir_use_size_file)
+       d = 0;
       else if ((int)d < 0 || (int)d > 100)
         {
         *errmsg = string_sprintf("Invalid quota_warn_threshold percentage (%d)"
@@ -306,9 +231,18 @@ for (i = 0; i < 5; i++)
       rest++;
       }
 
-    while (isspace(*rest)) rest++;
 
-    if (*rest != 0)
+    /* For quota and quota_filecount there may be options
+    appended. Currently only "no_check", so we can be lazy parsing it */
+    if (i < 2 && Ustrstr(rest, "/no_check") == rest)
+      {
+       no_check = 1;
+       rest += sizeof("/no_check") - 1;
+      }
+
+    Uskip_whitespace(&rest);
+
+    if (*rest)
       {
       *errmsg = string_sprintf("Malformed value \"%s\" (expansion of \"%s\") "
         "in %s transport", s, q, tblock->name);
@@ -316,32 +250,53 @@ for (i = 0; i < 5; i++)
       }
     }
 
+  /* Set each value, checking for possible overflow. */
+
   switch (i)
     {
     case 0:
-    ob->quota_value = (off_t)d;
-    q = ob->quota_filecount;
-    break;
+      if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
+       which = US"quota";
+      ob->quota_value = (off_t)d;
+      ob->quota_no_check = no_check;
+      q = ob->quota_filecount;
+      break;
 
     case 1:
-    ob->quota_filecount_value = (int)d;
-    q = ob->quota_warn_threshold;
-    break;
+      if (d >= 2.0*1024.0*1024.0*1024.0)
+       which = US"quota_filecount";
+      ob->quota_filecount_value = (int)d;
+      ob->quota_filecount_no_check = no_check;
+      q = ob->quota_warn_threshold;
+      break;
 
     case 2:
-    ob->quota_warn_threshold_value = (off_t)d;
-    q = ob->mailbox_size_string;
-    default_value = -1.0;
-    break;
+      if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
+         which = US"quota_warn_threshold";
+      ob->quota_warn_threshold_value = (off_t)d;
+      q = ob->mailbox_size_string;
+      default_value = -1.0;
+      break;
 
     case 3:
-    ob->mailbox_size_value = (off_t)d;
-    q = ob->mailbox_filecount_string;
-    break;
+      if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
+       which = US"mailbox_size";;
+      ob->mailbox_size_value = (off_t)d;
+      q = ob->mailbox_filecount_string;
+      break;
 
     case 4:
-    ob->mailbox_filecount_value = (int)d;
-    break;
+      if (d >= 2.0*1024.0*1024.0*1024.0)
+       which = US"mailbox_filecount";
+      ob->mailbox_filecount_value = (int)d;
+      break;
+    }
+
+  if (which)
+    {
+    *errmsg = string_sprintf("%s value %.10g is too large (overflow) in "
+      "%s transport", which, d, tblock->name);
+    return FAIL;
     }
   }
 
@@ -363,6 +318,7 @@ appendfile_transport_init(transport_instance *tblock)
 {
 appendfile_transport_options_block *ob =
   (appendfile_transport_options_block *)(tblock->options_block);
+uschar * s;
 
 /* Set up the setup entry point, to be called in the privileged state */
 
@@ -374,19 +330,19 @@ if (ob->lock_retries == 0) ob->lock_retries = 1;
 
 /* Only one of a file name or directory name must be given. */
 
-if (ob->filename != NULL && ob->dirname != NULL)
+if (ob->filename && ob->dirname)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n  "
   "only one of \"file\" or \"directory\" can be specified", tblock->name);
 
 /* If a file name was specified, neither quota_filecount nor quota_directory
 must be given. */
 
-if (ob->filename != NULL)
+if (ob->filename)
   {
-  if (ob->quota_filecount != NULL)
+  if (ob->quota_filecount)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n  "
       "quota_filecount must not be set without \"directory\"", tblock->name);
-  if (ob->quota_directory != NULL)
+  if (ob->quota_directory)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n  "
       "quota_directory must not be set without \"directory\"", tblock->name);
   }
@@ -411,7 +367,6 @@ if (ob->use_flock)
 
 #ifdef SUPPORT_MBX
 if (ob->mbx_format)
-  {
   if (!ob->set_use_lockfile && !ob->set_use_fcntl && !ob->set_use_flock &&
       !ob->set_use_mbx_lock)
     {
@@ -425,7 +380,6 @@ if (ob->mbx_format)
     if (!ob->set_use_flock) ob->use_flock = FALSE;
     if (!ob->use_fcntl && !ob->use_flock) ob->use_fcntl = TRUE;
     }
-  }
 #endif  /* SUPPORT_MBX */
 
 if (!ob->use_fcntl && !ob->use_flock && !ob->use_lockfile && !ob->use_mbx_lock)
@@ -441,7 +395,7 @@ if (!ob->use_flock) ob->lock_flock_timeout = 0;
 specified, and if quota_filecount or quota_directory is given, quota must
 be set. */
 
-if (ob->dirname != NULL)
+if (ob->dirname)
   {
   if (ob->maildir_format && ob->mailstore_format)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n  "
@@ -456,35 +410,33 @@ if (ob->dirname != NULL)
 
 /* If a fixed uid field is set, then a gid field must also be set. */
 
-if (tblock->uid_set && !tblock->gid_set && tblock->expand_gid == NULL)
+if (tblock->uid_set && !tblock->gid_set && !tblock->expand_gid)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "user set without group for the %s transport", tblock->name);
 
 /* If "create_file" is set, check that a valid option is given, and set the
 integer variable. */
 
-if (ob->create_file_string != NULL)
+if ((s = ob->create_file_string ) && *s)
   {
-  int value = 0;
-  if (Ustrcmp(ob->create_file_string, "anywhere") == 0) value = create_anywhere;
-  else if (Ustrcmp(ob->create_file_string, "belowhome") == 0) value =
-    create_belowhome;
-  else if (Ustrcmp(ob->create_file_string, "inhome") == 0)
-    value = create_inhome;
+  int val = 0;
+  if (Ustrcmp(s, "anywhere") == 0)                     val = create_anywhere;
+  else if (*s == '/' || Ustrcmp(s, "belowhome") == 0)  val = create_belowhome;
+  else if (Ustrcmp(s, "inhome") == 0)                  val = create_inhome;
   else
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
-      "invalid value given for \"file_create\" for the %s transport: %s",
-        tblock->name, ob->create_file_string);
-  ob->create_file = value;
+      "invalid value given for \"create_file\" for the %s transport: '%s'",
+      tblock->name, s);
+  ob->create_file = val;
   }
 
 /* If quota_warn_threshold is set, set up default for warn_message. It may
 not be used if the actual threshold for a given delivery ends up as zero,
 of if it's given as a percentage and there's no quota setting. */
 
-if (ob->quota_warn_threshold != NULL)
+if (ob->quota_warn_threshold)
   {
-  if (tblock->warn_message == NULL) tblock->warn_message = US
+  if (!tblock->warn_message) tblock->warn_message = US
     "To: $local_part@$domain\n"
     "Subject: Your mailbox\n\n"
     "This message is automatically created by mail delivery software (Exim).\n\n"
@@ -505,13 +457,13 @@ if (ob->use_bsmtp)
 /* If not batch SMTP, not maildir, not mailstore, and directory is not set,
 insert default values for for the affixes and the check/escape strings. */
 
-else if (ob->dirname == NULL && !ob->maildir_format && !ob->mailstore_format)
+else if (!ob->dirname && !ob->maildir_format && !ob->mailstore_format)
   {
-  if (ob->message_prefix == NULL) ob->message_prefix =
+  if (!ob->message_prefix) ob->message_prefix =
     US"From ${if def:return_path{$return_path}{MAILER-DAEMON}} ${tod_bsdinbox}\n";
-  if (ob->message_suffix == NULL) ob->message_suffix = US"\n";
-  if (ob->check_string == NULL) ob->check_string = US"From ";
-  if (ob->escape_string == NULL) ob->escape_string = US">From ";
+  if (!ob->message_suffix) ob->message_suffix = US"\n";
+  if (!ob->check_string) ob->check_string = US"From ";
+  if (!ob->escape_string) ob->escape_string = US">From ";
 
   }
 
@@ -519,12 +471,12 @@ else if (ob->dirname == NULL && !ob->maildir_format && !ob->mailstore_format)
 driver options. Only one of body_only and headers_only can be set. */
 
 ob->options |=
-  (tblock->body_only? topt_no_headers : 0) |
-  (tblock->headers_only? topt_no_body : 0) |
-  (tblock->return_path_add? topt_add_return_path : 0) |
-  (tblock->delivery_date_add? topt_add_delivery_date : 0) |
-  (tblock->envelope_to_add? topt_add_envelope_to : 0) |
-  ((ob->use_crlf || ob->mbx_format)? topt_use_crlf : 0);
+  (tblock->body_only ? topt_no_headers : 0) |
+  (tblock->headers_only ? topt_no_body : 0) |
+  (tblock->return_path_add ? topt_add_return_path : 0) |
+  (tblock->delivery_date_add ? topt_add_delivery_date : 0) |
+  (tblock->envelope_to_add ? topt_add_envelope_to : 0) |
+  ((ob->use_crlf || ob->mbx_format) ? topt_use_crlf : 0);
 }
 
 
@@ -554,12 +506,11 @@ notify_comsat(uschar *user, off_t offset)
 {
 struct servent *sp;
 host_item host;
-host_item *h;
-uschar buffer[256];
+uschar * s;
 
 DEBUG(D_transport) debug_printf("notify_comsat called\n");
 
-sprintf(CS buffer, "%.200s@" OFF_T_FMT "\n", user, offset);
+s = string_sprintf("%.200s@" OFF_T_FMT "\n", user, offset);
 
 if ((sp = getservbyname("biff", "udp")) == NULL)
   {
@@ -575,10 +526,10 @@ host.next = NULL;
 until one succeeds. However, it appears that at least on some systems, comsat
 doesn't listen on the ::1 address. So for the moment, just force the address to
 be 127.0.0.1. At some future stage, when IPv6 really is superseding IPv4, this
-can be changed. */
+can be changed. (But actually, comsat is probably dying out anyway.) */
 
 /******
-if (host_find_byname(&host, NULL, NULL, FALSE) == HOST_FIND_FAILED)
+if (host_find_byname(&host, NULL, 0, NULL, FALSE) == HOST_FIND_FAILED)
   {
   DEBUG(D_transport) debug_printf("\"localhost\" unknown\n");
   return;
@@ -588,20 +539,19 @@ if (host_find_byname(&host, NULL, NULL, FALSE) == HOST_FIND_FAILED)
 host.address = US"127.0.0.1";
 
 
-for (h = &host; h != NULL; h = h->next)
+for (host_item * h = &host; h; h = h->next)
   {
   int sock, rc;
-  int host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
+  int host_af = Ustrchr(h->address, ':') != NULL ? AF_INET6 : AF_INET;
 
   DEBUG(D_transport) debug_printf("calling comsat on %s\n", h->address);
 
-  sock = ip_socket(SOCK_DGRAM, host_af);
-  if (sock < 0) continue;
+  if ((sock = ip_socket(SOCK_DGRAM, host_af)) < 0) continue;
 
   /* Connect never fails for a UDP socket, so don't set a timeout. */
 
-  (void)ip_connect(sock, host_af, h->address, ntohs(sp->s_port), 0);
-  rc = send(sock, buffer, Ustrlen(buffer) + 1, 0);
+  (void)ip_connect(sock, host_af, h->address, ntohs(sp->s_port), 0, NULL);
+  rc = send(sock, s, Ustrlen(s) + 1, 0);
   (void)close(sock);
 
   if (rc >= 0) break;
@@ -633,7 +583,7 @@ Returns:       pointer to the required transport, or NULL
 transport_instance *
 check_file_format(int cfd, transport_instance *tblock, address_item *addr)
 {
-uschar *format =
+const uschar *format =
   ((appendfile_transport_options_block *)(tblock->options_block))->file_format;
 uschar data[256];
 int len = read(cfd, data, sizeof(data));
@@ -648,15 +598,16 @@ if (len == 0) return tblock;
 
 /* Search the formats for a match */
 
-while ((s = string_nextinlist(&format,&sep,big_buffer,big_buffer_size))!= NULL)
+/* not expanded so cannot be tainted */
+while ((s = string_nextinlist(&format, &sep, big_buffer, big_buffer_size)))
   {
   int slen = Ustrlen(s);
   BOOL match = len >= slen && Ustrncmp(data, s, slen) == 0;
   uschar *tp = string_nextinlist(&format, &sep, big_buffer, big_buffer_size);
-  if (match)
+
+  if (match && tp)
     {
-    transport_instance *tt;
-    for (tt = transports; tt != NULL; tt = tt->next)
+    for (transport_instance * tt = transports; tt; tt = tt->next)
       if (Ustrcmp(tp, tt->name) == 0)
         {
         DEBUG(D_transport)
@@ -701,28 +652,25 @@ the log, because we are running as an unprivileged user here.
 Arguments:
   dirname       the name of the directory
   countptr      where to add the file count (because this function recurses)
-  regex         a compiled regex to get the size from a name
+  re            a compiled regex to get the size from a name
 
 Returns:        the sum of the sizes of the stattable files
                 zero if the directory cannot be opened
 */
 
 off_t
-check_dir_size(uschar *dirname, int *countptr, const pcre *regex)
+check_dir_size(const uschar * dirname, int * countptr, const pcre2_code * re)
 {
 DIR *dir;
 off_t sum = 0;
 int count = *countptr;
-struct dirent *ent;
-struct stat statbuf;
 
-dir = opendir(CS dirname);
-if (dir == NULL) return 0;
+if (!(dir = exim_opendir(dirname))) return 0;
 
-while ((ent = readdir(dir)) != NULL)
+for (struct dirent *ent; ent = readdir(dir); )
   {
-  uschar *name = US ent->d_name;
-  uschar buffer[1024];
+  uschar * path, * name = US ent->d_name;
+  struct stat statbuf;
 
   if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
 
@@ -730,48 +678,47 @@ while ((ent = readdir(dir)) != NULL)
 
   /* If there's a regex, try to find the size using it */
 
-  if (regex != NULL)
+  if (re)
     {
-    int ovector[6];
-    if (pcre_exec(regex, NULL, CS name, Ustrlen(name), 0, 0, ovector,6) >= 2)
+    pcre2_match_data * md = pcre2_match_data_create(2, pcre_gen_ctx);
+    int rc = pcre2_match(re, (PCRE2_SPTR)name, PCRE2_ZERO_TERMINATED,
+                         0, 0, md, pcre_gen_mtc_ctx);
+    PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md);
+    if (  rc >= 0
+       && (rc = pcre2_get_ovector_count(md)) >= 2)
       {
       uschar *endptr;
-      off_t size = (off_t)Ustrtod(name + ovector[2], &endptr);
-      if (endptr == name + ovector[3])
+      off_t size = (off_t)Ustrtod(name + ovec[2], &endptr);
+      if (endptr == name + ovec[3])
         {
         sum += size;
         DEBUG(D_transport)
           debug_printf("check_dir_size: size from %s is " OFF_T_FMT "\n", name,
             size);
+       /* pcre2_match_data_free(md);   gen ctx needs no free */
         continue;
         }
       }
+    /* pcre2_match_data_free(md);      gen ctx needs no free */
     DEBUG(D_transport)
       debug_printf("check_dir_size: regex did not match %s\n", name);
     }
 
   /* No regex or no match for the regex, or captured non-digits */
 
-  if (!string_format(buffer, sizeof(buffer), "%s/%s", dirname, name))
-    {
-    DEBUG(D_transport)
-      debug_printf("check_dir_size: name too long: dir=%s name=%s\n", dirname,
-        name);
-    continue;
-    }
+  path = string_sprintf("%s/%s", dirname, name);
 
-  if (Ustat(buffer, &statbuf) < 0)
+  if (Ustat(path, &statbuf) < 0)
     {
     DEBUG(D_transport)
-      debug_printf("check_dir_size: stat error %d for %s: %s\n", errno, buffer,
+      debug_printf("check_dir_size: stat error %d for %s: %s\n", errno, path,
         strerror(errno));
-    continue;
     }
-
-  if ((statbuf.st_mode & S_IFMT) == S_IFREG)
-    sum += statbuf.st_size;
-  else if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
-    sum += check_dir_size(buffer, &count, regex);
+  else
+    if ((statbuf.st_mode & S_IFMT) == S_IFREG)
+      sum += statbuf.st_size / statbuf.st_nlink;
+    else if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
+      sum += check_dir_size(path, &count, re);
   }
 
 closedir(dir);
@@ -824,10 +771,10 @@ if (dofcntl)
   {
   if (fcntltime > 0)
     {
-    alarm(fcntltime);
+    ALARM(fcntltime);
     yield = fcntl(fd, F_SETLKW, &lock_data);
     save_errno = errno;
-    alarm(0);
+    ALARM_CLR(0);
     errno = save_errno;
     }
   else yield = fcntl(fd, F_SETLK, &lock_data);
@@ -836,13 +783,13 @@ if (dofcntl)
 #ifndef NO_FLOCK
 if (doflock && (yield >= 0))
   {
-  int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
+  int flocktype = (fcntltype == F_WRLCK) ? LOCK_EX : LOCK_SH;
   if (flocktime > 0)
     {
-    alarm(flocktime);
+    ALARM(flocktime);
     yield = flock(fd, flocktype);
     save_errno = errno;
-    alarm(0);
+    ALARM_CLR(0);
     errno = save_errno;
     }
   else yield = flock(fd, flocktype | LOCK_NB);
@@ -885,19 +832,19 @@ copy_mbx_message(int to_fd, int from_fd, off_t saved_size)
 int used;
 off_t size;
 struct stat statbuf;
+transport_ctx tctx = { .u={.fd = to_fd}, .options = topt_not_socket };
 
 /* If the current mailbox size is zero, write a header block */
 
 if (saved_size == 0)
   {
-  int i;
   uschar *s;
   memset (deliver_out_buffer, '\0', MBX_HDRSIZE);
   sprintf(CS(s = deliver_out_buffer), "*mbx*\015\012%08lx00000000\015\012",
     (long int)time(NULL));
-  for (i = 0; i < MBX_NUSERFLAGS; i++)
+  for (int i = 0; i < MBX_NUSERFLAGS; i++)
     sprintf (CS(s += Ustrlen(s)), "\015\012");
-  if (!transport_write_block (to_fd, deliver_out_buffer, MBX_HDRSIZE))
+  if (!transport_write_block (&tctx, deliver_out_buffer, MBX_HDRSIZE, FALSE))
     return DEFER;
   }
 
@@ -915,7 +862,7 @@ used = Ustrlen(deliver_out_buffer);
 
 /* Rewind the temporary file, and copy it over in chunks. */
 
-lseek(from_fd, 0 , SEEK_SET);
+if (lseek(from_fd, 0 , SEEK_SET) < 0) return DEFER;
 
 while (size > 0)
   {
@@ -926,7 +873,7 @@ while (size > 0)
     if (len == 0) errno = ERRNO_MBXLENGTH;
     return DEFER;
     }
-  if (!transport_write_block(to_fd, deliver_out_buffer, used + len))
+  if (!transport_write_block(&tctx, deliver_out_buffer, used + len, FALSE))
     return DEFER;
   size -= len;
   used = 0;
@@ -949,28 +896,28 @@ we can't do any tests.
 Arguments:
   filename     the file name
   create_file  the ob->create_file option
+  deliver_dir  the delivery directory
 
 Returns:       TRUE if creation is permitted
 */
 
 static BOOL
-check_creation(uschar *filename, int create_file)
+check_creation(uschar *filename, int create_file, const uschar * deliver_dir)
 {
 BOOL yield = TRUE;
 
-if (deliver_home != NULL && create_file != create_anywhere)
+if (deliver_dir  &&  create_file != create_anywhere)
   {
-  int len = Ustrlen(deliver_home);
+  int len = Ustrlen(deliver_dir);
   uschar *file = filename;
 
   while (file[0] == '/' && file[1] == '/') file++;
-  if (Ustrncmp(file, deliver_home, len) != 0 || file[len] != '/' ||
-       ( Ustrchr(file+len+2, '/') != NULL &&
-         (
-         create_file != create_belowhome ||
-         Ustrstr(file+len, "/../") != NULL
-         )
-       )
+  if (  Ustrncmp(file, deliver_dir, len) != 0
+     || file[len] != '/'
+     ||    Ustrchr(file+len+2, '/') != NULL
+       && (  create_file != create_belowhome
+          || Ustrstr(file+len, "/../") != NULL
+          )
      ) yield = FALSE;
 
   /* If yield is TRUE, the file name starts with the home directory, and does
@@ -986,10 +933,10 @@ if (deliver_home != NULL && create_file != create_anywhere)
   #ifndef NO_REALPATH
   if (yield && create_file == create_belowhome)
     {
-    uschar *slash, *next;
+    uschar *next;
     uschar *rp = NULL;
-    for (slash = Ustrrchr(file, '/');       /* There is known to be one */
-         rp == NULL && slash > file;        /* Stop if reached beginning */
+    for (uschar * slash = Ustrrchr(file, '/');  /* There is known to be one */
+         !rp && slash > file;                  /* Stop if reached beginning */
          slash = next)
       {
       *slash = 0;
@@ -1005,14 +952,13 @@ if (deliver_home != NULL && create_file != create_anywhere)
     contain symbolic links, so we have to "realpath" it as well, if
     possible. */
 
-    if (rp != NULL)
+    if (rp)
       {
       uschar hdbuffer[PATH_MAX+1];
-      uschar *rph = deliver_home;
+      const uschar * rph = deliver_dir;
       int rlen = Ustrlen(big_buffer);
 
-      rp = US realpath(CS deliver_home, CS hdbuffer);
-      if (rp != NULL)
+      if ((rp = US realpath(CS deliver_dir, CS hdbuffer)))
         {
         rph = hdbuffer;
         len = Ustrlen(rph);
@@ -1023,7 +969,7 @@ if (deliver_home != NULL && create_file != create_anywhere)
         {
         yield = FALSE;
         DEBUG(D_transport) debug_printf("Real path \"%s\" does not match \"%s\"\n",
-          big_buffer, deliver_home);
+          big_buffer, deliver_dir);
         }
       }
     }
@@ -1115,7 +1061,7 @@ directory name) is given, that is, when appending to a single file:
 
      Open with O_WRONLY + O_EXCL + O_CREAT with configured mode, unless we know
      this is via a symbolic link (only possible if allow_symlinks is set), in
-     which case don't use O_EXCL, as it dosn't work.
+     which case don't use O_EXCL, as it doesn't work.
 
      If open fails because the file already exists, go to (6f). To avoid
      looping for ever in a situation where the file is continuously being
@@ -1193,6 +1139,7 @@ appendfile_transport_entry(
 appendfile_transport_options_block *ob =
   (appendfile_transport_options_block *)(tblock->options_block);
 struct stat statbuf;
+const uschar * deliver_dir;
 uschar *fdname = NULL;
 uschar *filename = NULL;
 uschar *hitchname = NULL;
@@ -1205,13 +1152,14 @@ uschar *filecount_msg = US"";
 uschar *path;
 struct utimbuf times;
 struct timeval msg_tv;
+BOOL disable_quota = FALSE;
 BOOL isdirectory = FALSE;
 BOOL isfifo = FALSE;
 BOOL wait_for_tick = FALSE;
 uid_t uid = geteuid();     /* See note above */
 gid_t gid = getegid();
 int mbformat;
-int mode = (addr->mode > 0)? addr->mode : ob->mode;
+int mode = (addr->mode > 0) ? addr->mode : ob->mode;
 off_t saved_size = -1;
 off_t mailbox_size = ob->mailbox_size_value;
 int mailbox_filecount = ob->mailbox_filecount_value;
@@ -1250,9 +1198,7 @@ variable (that holds the parent local part). It is, however, in the
 $address_file variable. Below, we update the local part in the address if it
 changes by expansion, so that the final path ends up in the log. */
 
-if (testflag(addr, af_file) &&
-    ob->filename == NULL &&
-    ob->dirname == NULL)
+if (testflag(addr, af_file) && !ob->filename && !ob->dirname)
   {
   fdname = US"$address_file";
   if (address_file[Ustrlen(address_file)-1] == '/' ||
@@ -1265,20 +1211,18 @@ if (testflag(addr, af_file) &&
 explicitly set and (b) a non-address_file delivery, where one of "file" or
 "directory" must be set; initialization ensures that they are not both set. */
 
-if (fdname == NULL)
+if (!fdname)
   {
-  fdname = ob->filename;
-  if (fdname == NULL)
+  if (!(fdname = ob->filename))
     {
     fdname = ob->dirname;
     isdirectory = TRUE;
     }
-  if (fdname == NULL)
+  if (!fdname)
     {
-    addr->transport_return = PANIC;
     addr->message = string_sprintf("Mandatory file or directory option "
       "missing from %s transport", tblock->name);
-    return FALSE;
+    goto ret_panic;
     }
   }
 
@@ -1286,22 +1230,18 @@ if (fdname == NULL)
 
 if ((ob->maildir_format || ob->mailstore_format) && !isdirectory)
   {
-  addr->transport_return = PANIC;
   addr->message = string_sprintf("mail%s_format requires \"directory\" "
     "to be specified for the %s transport",
-    ob->maildir_format? "dir" : "store", tblock->name);
-  return FALSE;
+    ob->maildir_format ? "dir" : "store", tblock->name);
+  goto ret_panic;
   }
 
-path = expand_string(fdname);
-
-if (path == NULL)
+if (!(path = expand_string(fdname)))
   {
-  addr->transport_return = PANIC;
   addr->message = string_sprintf("Expansion of \"%s\" (file or directory "
     "name for %s transport) failed: %s", fdname, tblock->name,
     expand_string_message);
-  return FALSE;
+  goto ret_panic;
   }
 
 if (path[0] != '/')
@@ -1312,10 +1252,12 @@ if (path[0] != '/')
   return FALSE;
   }
 
-/* For a file delivery, make sure the local part in the address is updated to
-the true local part. */
+/* For a file delivery, make sure the local part in the address(es) is updated
+to the true local part. */
 
-if (testflag(addr, af_file)) addr->local_part = string_copy(path);
+if (testflag(addr, af_file))
+  for (address_item * addr2 = addr; addr2; addr2 = addr2->next)
+    addr2->local_part = string_copy(path);
 
 /* The available mailbox formats depend on whether it is a directory or a file
 delivery. */
@@ -1324,10 +1266,10 @@ if (isdirectory)
   {
   mbformat =
   #ifdef SUPPORT_MAILDIR
-    (ob->maildir_format)? mbf_maildir :
+    ob->maildir_format ? mbf_maildir :
   #endif
   #ifdef SUPPORT_MAILSTORE
-    (ob->mailstore_format)? mbf_mailstore :
+    ob->mailstore_format ? mbf_mailstore :
   #endif
     mbf_smail;
   }
@@ -1335,7 +1277,7 @@ else
   {
   mbformat =
   #ifdef SUPPORT_MBX
-    (ob->mbx_format)? mbf_mbx :
+    ob->mbx_format ? mbf_mbx :
   #endif
     mbf_unix;
   }
@@ -1343,29 +1285,32 @@ else
 DEBUG(D_transport)
   {
   debug_printf("appendfile: mode=%o notify_comsat=%d quota=" OFF_T_FMT
+    "%s%s"
     " warning=" OFF_T_FMT "%s\n"
     "  %s=%s format=%s\n  message_prefix=%s\n  message_suffix=%s\n  "
     "maildir_use_size_file=%s\n",
     mode, ob->notify_comsat, ob->quota_value,
+    ob->quota_no_check ? " (no_check)" : "",
+    ob->quota_filecount_no_check ? " (no_check_filecount)" : "",
     ob->quota_warn_threshold_value,
-    ob->quota_warn_threshold_is_percent? "%" : "",
-    isdirectory? "directory" : "file",
+    ob->quota_warn_threshold_is_percent ? "%" : "",
+    isdirectory ? "directory" : "file",
     path, mailbox_formats[mbformat],
-    (ob->message_prefix == NULL)? US"null" : string_printing(ob->message_prefix),
-    (ob->message_suffix == NULL)? US"null" : string_printing(ob->message_suffix),
-    (ob->maildir_use_size_file)? "yes" : "no");
+    !ob->message_prefix ? US"null" : string_printing(ob->message_prefix),
+    !ob->message_suffix ? US"null" : string_printing(ob->message_suffix),
+    ob->maildir_use_size_file ? "yes" : "no");
 
   if (!isdirectory) debug_printf("  locking by %s%s%s%s%s\n",
-    ob->use_lockfile? "lockfile " : "",
-    ob->use_mbx_lock? "mbx locking (" : "",
-    ob->use_fcntl? "fcntl " : "",
-    ob->use_flock? "flock" : "",
-    ob->use_mbx_lock? ")" : "");
+    ob->use_lockfile ? "lockfile " : "",
+    ob->use_mbx_lock ? "mbx locking (" : "",
+    ob->use_fcntl ? "fcntl " : "",
+    ob->use_flock ? "flock" : "",
+    ob->use_mbx_lock ? ")" : "");
   }
 
 /* If the -N option is set, can't do any more. */
 
-if (dont_deliver)
+if (f.dont_deliver)
   {
   DEBUG(D_transport)
     debug_printf("*** delivery by %s transport bypassed by -N option\n",
@@ -1374,6 +1319,12 @@ if (dont_deliver)
   return FALSE;
   }
 
+/* If an absolute path was given for create_file the it overrides deliver_home
+(here) and de-taints the filename (below, after check_creation() */
+
+deliver_dir = *ob->create_file_string == '/'
+  ? ob->create_file_string : deliver_home;
+
 /* Handle the case of a file name. If the file name is /dev/null, we can save
 ourselves some effort and just give a success return right away. */
 
@@ -1390,10 +1341,20 @@ if (!isdirectory)
     }
 
   /* Set the name of the file to be opened, and the file to which the data
-  is written, and find out if we are permitted to create a non-existent file. */
+  is written, and find out if we are permitted to create a non-existent file.
+  If the create_file option is an absolute path and the file was within it,
+  de-taint.  Chaeck for a tainted path. */
+
+  if (  (allow_creation_here = check_creation(path, ob->create_file, deliver_dir))
+     && ob->create_file == create_belowhome)
+    if (is_tainted(path))
+      {
+      DEBUG(D_transport) debug_printf("de-tainting path '%s'\n", path);
+      path = string_copy_taint(path, GET_UNTAINTED);
+      }
 
+  if (is_tainted(path)) goto tainted_ret_panic;
   dataname = filename = path;
-  allow_creation_here = check_creation(filename, ob->create_file);
 
   /* If ob->create_directory is set, attempt to create the directories in
   which this mailbox lives, but only if we are permitted to create the file
@@ -1403,17 +1364,16 @@ if (!isdirectory)
   if (ob->create_directory && allow_creation_here)
     {
     uschar *p = Ustrrchr(path, '/');
-    *p = '\0';
-    if (!directory_make(NULL, path, ob->dirmode, FALSE))
+    p = string_copyn(path, p - path);
+    if (!directory_make(NULL, p, ob->dirmode, FALSE))
       {
       addr->basic_errno = errno;
       addr->message =
         string_sprintf("failed to create directories for %s: %s", path,
-          strerror(errno));
+          exim_errstr(errno));
       DEBUG(D_transport) debug_printf("%s transport: %s\n", tblock->name, path);
       return FALSE;
       }
-    *p = '/';
     }
 
   /* If file_format is set we must check that any existing file matches one of
@@ -1424,7 +1384,7 @@ if (!isdirectory)
   failures because if an existing file fails to open here, it will also fail
   again later when O_RDWR is used. */
 
-  if (ob->file_format != NULL)
+  if (ob->file_format)
     {
     int cfd = Uopen(path, O_RDONLY, 0);
     if (cfd >= 0)
@@ -1437,7 +1397,7 @@ if (!isdirectory)
 
       if (tt != tblock)
         {
-        if (tt != NULL)
+        if (tt)
           {
           set_process_info("delivering %s to %s using %s", message_id,
             addr->local_part, tt->name);
@@ -1583,6 +1543,7 @@ if (!isdirectory)
 
   if (ob->use_lockfile)
     {
+    /* cf. exim_lock.c */
     lockname = string_sprintf("%s.lock", filename);
     hitchname = string_sprintf( "%s.%s.%08x.%08x", lockname, primary_hostname,
       (unsigned int)(time(NULL)), (unsigned int)getpid());
@@ -1595,8 +1556,8 @@ if (!isdirectory)
     for (i = 0; i < ob->lock_retries; sleep(ob->lock_interval), i++)
       {
       int rc;
-      hd = Uopen(hitchname, O_WRONLY | O_CREAT | O_EXCL, ob->lockfile_mode);
 
+      hd = Uopen(hitchname, O_WRONLY | O_CREAT | O_EXCL, ob->lockfile_mode);
       if (hd < 0)
         {
         addr->basic_errno = errno;
@@ -1671,7 +1632,7 @@ if (!isdirectory)
     int sleep_before_retry = TRUE;
     file_opened = FALSE;
 
-    if((use_lstat? Ulstat(filename, &statbuf) : Ustat(filename, &statbuf)) != 0)
+    if ((use_lstat ? Ulstat(filename, &statbuf) : Ustat(filename, &statbuf)) != 0)
       {
       /* Let's hope that failure to stat (other than non-existence) is a
       rare event. */
@@ -1718,7 +1679,7 @@ if (!isdirectory)
       get a shared lock. */
 
       fd = Uopen(filename, O_RDWR | O_APPEND | O_CREAT |
-        (use_lstat? O_EXCL : 0), mode);
+        (use_lstat ? O_EXCL : 0), mode);
       if (fd < 0)
         {
         if (errno == EEXIST) continue;
@@ -1734,8 +1695,14 @@ if (!isdirectory)
       /* We have successfully created and opened the file. Ensure that the group
       and the mode are correct. */
 
-      (void)Uchown(filename, uid, gid);
-      (void)Uchmod(filename, mode);
+      if (exim_chown(filename, uid, gid) || Uchmod(filename, mode))
+        {
+        addr->basic_errno = errno;
+        addr->message = string_sprintf("while setting perms on mailbox %s",
+          filename);
+        addr->transport_return = FAIL;
+        goto RETURN;
+        }
       }
 
 
@@ -1761,7 +1728,7 @@ if (!isdirectory)
         addr->basic_errno = ERRNO_BADUGID;
         addr->message = string_sprintf("mailbox %s%s has wrong uid "
           "(%ld != %ld)", filename,
-          islink? " (symlink)" : "",
+          islink ? " (symlink)" : "",
           (long int)(statbuf.st_uid), (long int)uid);
         goto RETURN;
         }
@@ -1772,10 +1739,22 @@ if (!isdirectory)
         {
         addr->basic_errno = ERRNO_BADUGID;
         addr->message = string_sprintf("mailbox %s%s has wrong gid (%d != %d)",
-          filename, islink? " (symlink)" : "", statbuf.st_gid, gid);
+          filename, islink ? " (symlink)" : "", statbuf.st_gid, gid);
         goto RETURN;
         }
 
+      /* Just in case this is a sticky-bit mail directory, we don't want
+      users to be able to create hard links to other users' files. */
+
+      if (statbuf.st_nlink != 1)
+        {
+        addr->basic_errno = ERRNO_NOTREGULAR;
+        addr->message = string_sprintf("mailbox %s%s has too many links (%lu)",
+          filename, islink ? " (symlink)" : "", (unsigned long)statbuf.st_nlink);
+        goto RETURN;
+
+        }
+
       /* If symlinks are permitted (not recommended), the lstat() above will
       have found the symlink. Its ownership has just been checked; go round
       the loop again, using stat() instead of lstat(). That will never yield a
@@ -1797,7 +1776,7 @@ if (!isdirectory)
         {
         addr->basic_errno = ERRNO_NOTREGULAR;
         addr->message = string_sprintf("mailbox %s is not a regular file%s",
-          filename, ob->allow_fifo? " or named pipe" : "");
+          filename, ob->allow_fifo ? " or named pipe" : "");
         goto RETURN;
         }
 
@@ -1807,7 +1786,7 @@ if (!isdirectory)
       permissions are greater than the existing permissions, don't change
       things when the mode is not from the address. */
 
-      if ((oldmode = (oldmode & 07777)) != mode)
+      if ((oldmode &= 07777) != mode)
         {
         int diffs = oldmode ^ mode;
         if (addr->mode > 0 || (diffs & oldmode) == diffs)
@@ -1846,7 +1825,7 @@ if (!isdirectory)
       a FIFO is opened WRONLY + NDELAY so that it fails if there is no process
       reading the pipe. */
 
-      fd = Uopen(filename, isfifo? (O_WRONLY|O_NDELAY) : (O_RDWR|O_APPEND),
+      fd = Uopen(filename, isfifo ? (O_WRONLY|O_NDELAY) : (O_RDWR|O_APPEND),
         mode);
       if (fd < 0)
         {
@@ -1857,14 +1836,10 @@ if (!isdirectory)
           }
         addr->basic_errno = errno;
         if (isfifo)
-          {
           addr->message = string_sprintf("while opening named pipe %s "
             "(could mean no process is reading it)", filename);
-          }
         else if (errno != EWOULDBLOCK)
-          {
           addr->message = string_sprintf("while opening mailbox %s", filename);
-          }
         goto RETURN;
         }
 
@@ -1893,7 +1868,7 @@ if (!isdirectory)
         {
         addr->basic_errno = ERRNO_INODECHANGED;
         addr->message = string_sprintf("opened mailbox %s inode number changed "
-          "from %d to %ld", filename, inode, statbuf.st_ino);
+          "from " INO_T_FMT " to " INO_T_FMT, filename, inode, statbuf.st_ino);
         addr->special_action = SPECIAL_FREEZE;
         goto RETURN;
         }
@@ -1907,7 +1882,7 @@ if (!isdirectory)
         addr->basic_errno = ERRNO_NOTREGULAR;
         addr->message =
           string_sprintf("opened mailbox %s is no longer a %s", filename,
-            isfifo? "named pipe" : "regular file");
+            isfifo ? "named pipe" : "regular file");
         addr->special_action = SPECIAL_FREEZE;
         goto RETURN;
         }
@@ -1934,8 +1909,7 @@ if (!isdirectory)
       /* If file_format is set, check that the format of the file has not
       changed. Error data is set by the testing function. */
 
-      if (ob->file_format != NULL &&
-          check_file_format(fd, tblock, addr) != tblock)
+      if (ob->file_format  &&  check_file_format(fd, tblock, addr) != tblock)
         {
         addr->message = US"open mailbox has changed format";
         goto RETURN;
@@ -1968,6 +1942,8 @@ if (!isdirectory)
     #ifdef SUPPORT_MBX
     else if (ob->use_mbx_lock)
       {
+      int mbx_tmp_oflags;
+      struct stat lstatbuf, statbuf2;
       if (apply_lock(fd, F_RDLCK, ob->use_fcntl, ob->lock_fcntl_timeout,
            ob->use_flock, ob->lock_flock_timeout) >= 0 &&
            fstat(fd, &statbuf) >= 0)
@@ -1975,6 +1951,21 @@ if (!isdirectory)
         sprintf(CS mbx_lockname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
           (long)statbuf.st_ino);
 
+        /*
+         * 2010-05-29: SECURITY
+         * Dan Rosenberg reported the presence of a race-condition in the
+         * original code here.  Beware that many systems still allow symlinks
+         * to be followed in /tmp so an attacker can create a symlink pointing
+         * elsewhere between a stat and an open, which we should avoid
+         * following.
+         *
+         * It's unfortunate that we can't just use all the heavily debugged
+         * locking from above.
+         *
+         * Also: remember to mirror changes into exim_lock.c */
+
+        /* first leave the old pre-check in place, it provides better
+         * diagnostics for common cases */
         if (Ulstat(mbx_lockname, &statbuf) >= 0)
           {
           if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
@@ -1993,7 +1984,19 @@ if (!isdirectory)
             }
           }
 
-        mbx_lockfd = Uopen(mbx_lockname, O_RDWR | O_CREAT, 0600);
+        /* If we could just declare "we must be the ones who create this
+         * file" then a hitching post in a subdir would work, since a
+         * subdir directly in /tmp/ which we create wouldn't follow links
+         * but this isn't our locking logic, so we can't safely change the
+         * file existence rules. */
+
+        /* On systems which support O_NOFOLLOW, it's the easiest and most
+         * obviously correct security fix */
+        mbx_tmp_oflags = O_RDWR | O_CREAT;
+#ifdef O_NOFOLLOW
+        mbx_tmp_oflags |= O_NOFOLLOW;
+#endif
+        mbx_lockfd = Uopen(mbx_lockname, mbx_tmp_oflags, ob->lockfile_mode);
         if (mbx_lockfd < 0)
           {
           addr->basic_errno = ERRNO_LOCKFAILED;
@@ -2002,7 +2005,61 @@ if (!isdirectory)
           goto RETURN;
           }
 
-        (void)Uchmod(mbx_lockname, 0600);
+        if (Ulstat(mbx_lockname, &lstatbuf) < 0)
+          {
+          addr->basic_errno = ERRNO_LOCKFAILED;
+          addr->message = string_sprintf("attempting to lstat open MBX "
+             "lock file %s: %s", mbx_lockname, strerror(errno));
+          goto RETURN;
+          }
+        if (fstat(mbx_lockfd, &statbuf2) < 0)
+          {
+          addr->basic_errno = ERRNO_LOCKFAILED;
+          addr->message = string_sprintf("attempting to stat fd of open MBX "
+              "lock file %s: %s", mbx_lockname, strerror(errno));
+          goto RETURN;
+          }
+
+        /*
+         * At this point:
+         *  statbuf: if exists, is file which existed prior to opening the
+         *           lockfile, might have been replaced since then
+         *  statbuf2: result of stat'ing the open fd, is what was actually
+         *            opened
+         *  lstatbuf: result of lstat'ing the filename immediately after
+         *            the open but there's a race condition again between
+         *            those two steps: before open, symlink to foo, after
+         *            open but before lstat have one of:
+         *             * was no symlink, so is the opened file
+         *               (we created it, no messing possible after that point)
+         *             * hardlink to foo
+         *             * symlink elsewhere
+         *             * hardlink elsewhere
+         *             * new file/other
+         * Don't want to compare to device of /tmp because some modern systems
+         * have regressed to having /tmp be the safe actual filesystem as
+         * valuable data, so is mostly worthless, unless we assume that *only*
+         * Linux systems do this and that all Linux has O_NOFOLLOW.  Something
+         * for further consideration.
+         * No point in doing a readlink on the lockfile as that will always be
+         * at a different point in time from when we open it, so tells us
+         * nothing; attempts to clean up and delete after ourselves would risk
+         * deleting a *third* filename.
+         */
+        if ((statbuf2.st_nlink > 1) ||
+            (lstatbuf.st_nlink > 1) ||
+            (!S_ISREG(lstatbuf.st_mode)) ||
+            (lstatbuf.st_dev != statbuf2.st_dev) ||
+            (lstatbuf.st_ino != statbuf2.st_ino))
+          {
+          addr->basic_errno = ERRNO_LOCKFAILED;
+          addr->message = string_sprintf("RACE CONDITION detected: "
+              "mismatch post-initial-checks between \"%s\" and opened "
+              "fd lead us to abort!", mbx_lockname);
+          goto RETURN;
+          }
+
+        (void)Uchmod(mbx_lockname, ob->lockfile_mode);
 
         if (apply_lock(mbx_lockfd, F_WRLCK, ob->use_fcntl,
             ob->lock_fcntl_timeout, ob->use_flock, ob->lock_flock_timeout) >= 0)
@@ -2113,10 +2170,11 @@ scanning is expensive; for maildirs some fudges have been invented:
 
 else
   {
-  uschar *check_path = path;    /* Default quota check path */
-  const pcre *regex = NULL;     /* Regex for file size from file name */
+  uschar *check_path;          /* Default quota check path */
+  const pcre2_code * re = NULL;     /* Regex for file size from file name */
 
-  if (!check_creation(string_sprintf("%s/any", path), ob->create_file))
+  if (!check_creation(string_sprintf("%s/any", path),
+                     ob->create_file, deliver_dir))
     {
     addr->basic_errno = ERRNO_BADCREATE;
     addr->message = string_sprintf("tried to create file in %s, but "
@@ -2124,11 +2182,26 @@ else
     goto RETURN;
     }
 
+  /* If the create_file option is an absolute path and the file was within
+  it, de-taint. Otherwise check for taint. */
+
+  if (is_tainted(path))
+    if (ob->create_file == create_belowhome)
+      {
+      DEBUG(D_transport) debug_printf("de-tainting path '%s'\n", path);
+      path = string_copy_taint(path, GET_UNTAINTED);
+      }
+    else
+      goto tainted_ret_panic;
+
+  check_path = path;
+
   #ifdef SUPPORT_MAILDIR
-  /* For a maildir delivery, ensure that all the relevant directories exist */
+  /* For a maildir delivery, ensure that all the relevant directories exist,
+  and a maildirfolder file if necessary. */
 
   if (mbformat == mbf_maildir && !maildir_ensure_directories(path, addr,
-    ob->create_directory, ob->dirmode))
+    ob->create_directory, ob->dirmode, ob->maildirfolder_create_regex))
       return FALSE;
   #endif  /* SUPPORT_MAILDIR */
 
@@ -2141,41 +2214,28 @@ else
 
   if (ob->quota_value > 0 || THRESHOLD_CHECK || ob->maildir_use_size_file)
     {
-    const uschar *error;
-    int offset;
-
-    /* Compile the regex if there is one */
+    /* Compile the regex if there is one. */
 
-    if (ob->quota_size_regex != NULL)
+    if (ob->quota_size_regex)
       {
-      regex = pcre_compile(CS ob->quota_size_regex, PCRE_COPT,
-        (const char **)&error, &offset, NULL);
-      if (regex == NULL)
-        {
-        addr->message = string_sprintf("appendfile: regular expression "
-          "error: %s at offset %d while compiling %s", error, offset,
-          ob->quota_size_regex);
+      if (!(re = regex_compile(ob->quota_size_regex,
+                 MCS_NOFLAGS, &addr->message, pcre_gen_cmp_ctx)))
         return FALSE;
-        }
-      else
-        {
-        DEBUG(D_transport) debug_printf("using regex for file sizes: %s\n",
-          ob->quota_size_regex);
-        }
+
+      DEBUG(D_transport) debug_printf("using regex for file sizes: %s\n",
+        ob->quota_size_regex);
       }
 
     /* Use an explicitly configured directory if set */
 
-    if (ob->quota_directory != NULL)
+    if (ob->quota_directory)
       {
-      check_path = expand_string(ob->quota_directory);
-      if (check_path == NULL)
+      if (!(check_path = expand_string(ob->quota_directory)))
         {
-        addr->transport_return = PANIC;
         addr->message = string_sprintf("Expansion of \"%s\" (quota_directory "
          "name for %s transport) failed: %s", ob->quota_directory,
           tblock->name, expand_string_message);
-        return FALSE;
+        goto ret_panic;
         }
 
       if (check_path[0] != '/')
@@ -2201,17 +2261,19 @@ else
         {
         uschar *new_check_path = string_copy(check_path);
         uschar *slash = Ustrrchr(new_check_path, '/');
-        if (slash != NULL)
+        if (slash)
           {
-          if (slash[1] == 0)
+          if (!slash[1])
             {
             *slash = 0;
             slash = Ustrrchr(new_check_path, '/');
             }
-          if (slash != NULL)
+          if (slash)
             {
             *slash = 0;
             check_path = new_check_path;
+            DEBUG(D_transport) debug_printf("maildirfolder file exists: "
+              "quota check directory changed to %s\n", check_path);
             }
           }
         }
@@ -2231,26 +2293,36 @@ else
   #ifdef SUPPORT_MAILDIR
   if (ob->maildir_use_size_file)
     {
-    const pcre *dir_regex = NULL;
-    const uschar *error;
-    int offset;
+    const pcre2_code * dir_regex = NULL;
 
-    if (ob->maildir_dir_regex != NULL)
+    if (ob->maildir_dir_regex)
       {
-      dir_regex = pcre_compile(CS ob->maildir_dir_regex, PCRE_COPT,
-        (const char **)&error, &offset, NULL);
-      if (dir_regex == NULL)
-        {
-        addr->message = string_sprintf("appendfile: regular expression "
-          "error: %s at offset %d while compiling %s", error, offset,
-          ob->maildir_dir_regex);
+      int check_path_len = Ustrlen(check_path);
+
+      if (!(dir_regex = regex_compile(ob->maildir_dir_regex,
+           MCS_NOFLAGS, &addr->message, pcre_gen_cmp_ctx)))
         return FALSE;
-        }
-      else
+
+      DEBUG(D_transport)
+        debug_printf("using regex for maildir directory selection: %s\n",
+          ob->maildir_dir_regex);
+
+      /* Check to see if we are delivering into an ignored directory, that is,
+      if the delivery path starts with the quota check path, and the rest
+      of the deliver path matches the regex; if so, set a flag to disable quota
+      checking and maildirsize updating. */
+
+      if (Ustrncmp(path, check_path, check_path_len) == 0)
         {
-        DEBUG(D_transport)
-          debug_printf("using regex for maildir directory selection: %s\n",
-            ob->maildir_dir_regex);
+        uschar *s = path + check_path_len;
+        while (*s == '/') s++;
+        s = *s ? string_sprintf("%s/new", s) : US"new";
+       if (!regex_match(dir_regex, s, -1, NULL))
+          {
+          disable_quota = TRUE;
+          DEBUG(D_transport) debug_printf("delivery directory does not match "
+            "maildir_quota_directory_regex: disabling quota\n");
+          }
         }
       }
 
@@ -2261,20 +2333,22 @@ else
 
 /*  if (???? || ob->quota_value > 0) */
 
+    if (!disable_quota)
       {
       off_t size;
       int filecount;
 
-      maildirsize_fd = maildir_ensure_sizefile(check_path, ob, regex, dir_regex,
-        &size, &filecount);
-
-      if (maildirsize_fd == -1)
+      if ((maildirsize_fd = maildir_ensure_sizefile(check_path, ob,  re, dir_regex,
+         &size, &filecount)) == -1)
         {
         addr->basic_errno = errno;
         addr->message = string_sprintf("while opening or reading "
           "%s/maildirsize", check_path);
         return FALSE;
         }
+      /* can also return -2, which means that the file was removed because of
+      raciness; but in this case, the size & filecount will still have been
+      updated. */
 
       if (mailbox_size < 0) mailbox_size = size;
       if (mailbox_filecount < 0) mailbox_filecount = filecount;
@@ -2289,26 +2363,29 @@ else
  *    (void)unlink(CS string_sprintf("%s/maildirsize", check_path));
  *    if (THRESHOLD_CHECK)
  *      mailbox_size = maildir_compute_size(check_path, &mailbox_filecount, &old_latest,
- *        regex, dir_regex, FALSE);
+ *         re, dir_regex, FALSE);
  *    }
 */
 
     }
   #endif  /* SUPPORT_MAILDIR */
 
-  /* Otherwise (mailbox_size is not yet set), if we are going to do a quota
-  check later on, find the current size of the mailbox. (We don't need to check
-  ob->quota_filecount_value, because it can only be set if ob->quota_value is
-  set.) */
+  /* Otherwise if we are going to do a quota check later on, and the mailbox
+  size is not set, find the current size of the mailbox. Ditto for the file
+  count. Note that ob->quota_filecount_value cannot be set without
+  ob->quota_value being set. */
 
-  if ((mailbox_size < 0 || mailbox_filecount < 0) &&
-      (ob->quota_value > 0 || THRESHOLD_CHECK))
+  if (  !disable_quota
+     && (ob->quota_value > 0 || THRESHOLD_CHECK)
+     && (  mailbox_size < 0
+       || mailbox_filecount < 0 && ob->quota_filecount_value > 0
+    )   )
     {
     off_t size;
     int filecount = 0;
     DEBUG(D_transport)
       debug_printf("quota checks on directory %s\n", check_path);
-    size = check_dir_size(check_path, &filecount, regex);
+    size = check_dir_size(check_path, &filecount,  re);
     if (mailbox_size < 0) mailbox_size = size;
     if (mailbox_filecount < 0) mailbox_filecount = filecount;
     }
@@ -2354,14 +2431,12 @@ else
     return. The actual expansion for use happens again later, when
     $message_size is accurately known. */
 
-    if (nametag != NULL && expand_string(nametag) == NULL &&
-        !expand_string_forcedfail)
+    if (nametag && !expand_string(nametag) && !f.expand_string_forcedfail)
       {
-      addr->transport_return = PANIC;
       addr->message = string_sprintf("Expansion of \"%s\" (maildir_tag "
         "for %s transport) failed: %s", nametag, tblock->name,
         expand_string_message);
-      return FALSE;
+      goto ret_panic;
       }
 
     /* We ensured the existence of all the relevant directories above. Attempt
@@ -2372,13 +2447,13 @@ else
     checked at the end, to make sure we don't release this process until the
     clock has ticked. */
 
-    for (i = 1;; i++)
+    for (int i = 1;; i++)
       {
       uschar *basename;
 
       (void)gettimeofday(&msg_tv, NULL);
-      basename = string_sprintf("%lu.H%luP%lu.%s", msg_tv.tv_sec,
-        msg_tv.tv_usec, getpid(), primary_hostname);
+      basename = string_sprintf(TIME_T_FMT ".M%luP" PID_T_FMT ".%s",
+               msg_tv.tv_sec, msg_tv.tv_usec, getpid(), primary_hostname);
 
       filename = dataname = string_sprintf("tmp/%s", basename);
       newname = string_sprintf("new/%s", basename);
@@ -2387,8 +2462,8 @@ else
         errno = EEXIST;
       else if (errno == ENOENT)
         {
-        fd = Uopen(filename, O_WRONLY | O_CREAT | O_EXCL, mode);
-        if (fd >= 0) break;
+        if ((fd = Uopen(filename, O_WRONLY | O_CREAT | O_EXCL, mode)) >= 0)
+         break;
         DEBUG (D_transport) debug_printf ("open failed for %s: %s\n",
           filename, strerror(errno));
         }
@@ -2398,8 +2473,10 @@ else
       if (i >= ob->maildir_retries)
         {
         addr->message = string_sprintf ("failed to open %s (%d tr%s)",
-          filename, i, (i == 1)? "y" : "ies");
+          filename, i, (i == 1) ? "y" : "ies");
         addr->basic_errno = errno;
+        if (errno == errno_quota || errno == ENOSPC)
+          addr->user_message = US"mailbox is full";
         return FALSE;
         }
 
@@ -2415,8 +2492,13 @@ else
     /* Why are these here? Put in because they are present in the non-maildir
     directory case above. */
 
-    (void)Uchown(filename, uid, gid);
-    (void)Uchmod(filename, mode);
+    if (exim_chown(filename, uid, gid) || Uchmod(filename, mode))
+      {
+      addr->basic_errno = errno;
+      addr->message = string_sprintf("while setting perms on maildir %s",
+        filename);
+      return FALSE;
+      }
     }
 
   #endif  /* SUPPORT_MAILDIR */
@@ -2430,10 +2512,9 @@ else
 
   else
     {
-    FILE *env_file;
-    address_item *taddr;
+    FILE * env_file;
     mailstore_basename = string_sprintf("%s/%s-%s", path, message_id,
-      string_base62((long int)getpid()));
+      string_base62_64((long int)getpid()));
 
     DEBUG(D_transport)
       debug_printf("delivering in mailstore format in %s\n", path);
@@ -2443,11 +2524,12 @@ else
     dataname = string_sprintf("%s.msg", mailstore_basename);
 
     fd = Uopen(filename, O_WRONLY|O_CREAT|O_EXCL, mode);
-    if (fd < 0 &&                                 /* failed to open, and */
-        (errno != ENOENT ||                       /* either not non-exist */
-         !ob->create_directory ||                 /* or not allowed to make */
-         !directory_make(NULL, path, ob->dirmode, FALSE) ||  /* or failed to create dir */
-         (fd = Uopen(filename, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0)) /* or then failed to open */
+    if (  fd < 0                               /* failed to open, and */
+       && (   errno != ENOENT                  /* either not non-exist */
+         || !ob->create_directory              /* or not allowed to make */
+         || !directory_make(NULL, path, ob->dirmode, FALSE)  /* or failed to create dir */
+         || (fd = Uopen(filename, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0 /* or then failed to open */
+       )  )
       {
       addr->basic_errno = errno;
       addr->message = string_sprintf("while creating file %s", filename);
@@ -2457,38 +2539,41 @@ else
     /* Why are these here? Put in because they are present in the non-maildir
     directory case above. */
 
-    (void)Uchown(filename, uid, gid);
-    (void)Uchmod(filename, mode);
+    if (exim_chown(filename, uid, gid) || Uchmod(filename, mode))
+      {
+      addr->basic_errno = errno;
+      addr->message = string_sprintf("while setting perms on file %s",
+        filename);
+      return FALSE;
+      }
 
     /* Built a C stream from the open file descriptor. */
 
-    if ((env_file = fdopen(fd, "wb")) == NULL)
+    if (!(env_file = fdopen(fd, "wb")))
       {
       addr->basic_errno = errno;
-      addr->transport_return = PANIC;
       addr->message = string_sprintf("fdopen of %s ("
         "for %s transport) failed", filename, tblock->name);
       (void)close(fd);
       Uunlink(filename);
-      return FALSE;
+      goto ret_panic;
       }
 
     /* Write the envelope file, then close it. */
 
-    if (ob->mailstore_prefix != NULL)
+    if (ob->mailstore_prefix)
       {
       uschar *s = expand_string(ob->mailstore_prefix);
-      if (s == NULL)
+      if (!s)
         {
-        if (!expand_string_forcedfail)
+        if (!f.expand_string_forcedfail)
           {
-          addr->transport_return = PANIC;
           addr->message = string_sprintf("Expansion of \"%s\" (mailstore "
             "prefix for %s transport) failed: %s", ob->mailstore_prefix,
             tblock->name, expand_string_message);
           (void)fclose(env_file);
           Uunlink(filename);
-          return FALSE;
+          goto ret_panic;
           }
         }
       else
@@ -2501,23 +2586,22 @@ else
 
     fprintf(env_file, "%s\n", sender_address);
 
-    for (taddr = addr; taddr!= NULL; taddr = taddr->next)
+    for (address_item * taddr = addr; taddr; taddr = taddr->next)
       fprintf(env_file, "%s@%s\n", taddr->local_part, taddr->domain);
 
-    if (ob->mailstore_suffix != NULL)
+    if (ob->mailstore_suffix)
       {
       uschar *s = expand_string(ob->mailstore_suffix);
-      if (s == NULL)
+      if (!s)
         {
-        if (!expand_string_forcedfail)
+        if (!f.expand_string_forcedfail)
           {
-          addr->transport_return = PANIC;
           addr->message = string_sprintf("Expansion of \"%s\" (mailstore "
             "suffix for %s transport) failed: %s", ob->mailstore_suffix,
             tblock->name, expand_string_message);
           (void)fclose(env_file);
           Uunlink(filename);
-          return FALSE;
+          goto ret_panic;
           }
         }
       else
@@ -2541,16 +2625,20 @@ else
     /* Now open the data file, and ensure that it has the correct ownership and
     mode. */
 
-    fd = Uopen(dataname, O_WRONLY|O_CREAT|O_EXCL, mode);
-    if (fd < 0)
+    if ((fd = Uopen(dataname, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0)
       {
       addr->basic_errno = errno;
       addr->message = string_sprintf("while creating file %s", dataname);
       Uunlink(filename);
       return FALSE;
       }
-    (void)Uchown(dataname, uid, gid);
-    (void)Uchmod(dataname, mode);
+    if (exim_chown(dataname, uid, gid) || Uchmod(dataname, mode))
+      {
+      addr->basic_errno = errno;
+      addr->message = string_sprintf("while setting perms on file %s",
+        dataname);
+      return FALSE;
+      }
     }
 
   #endif  /* SUPPORT_MAILSTORE */
@@ -2559,8 +2647,13 @@ else
   /* In all cases of writing to a new file, ensure that the file which is
   going to be renamed has the correct ownership and mode. */
 
-  (void)Uchown(filename, uid, gid);
-  (void)Uchmod(filename, mode);
+  if (exim_chown(filename, uid, gid) || Uchmod(filename, mode))
+    {
+    addr->basic_errno = errno;
+    addr->message = string_sprintf("while setting perms on file %s",
+      filename);
+    return FALSE;
+    }
   }
 
 
@@ -2578,32 +2671,53 @@ with this message if quota_is_inclusive is set; if it is not set, the check
 is for the mailbox already being over quota (i.e. the current message is not
 included in the check). */
 
-if (ob->quota_value > 0)
+if (!disable_quota && ob->quota_value > 0)
   {
   DEBUG(D_transport)
     {
     debug_printf("Exim quota = " OFF_T_FMT " old size = " OFF_T_FMT
       " this message = %d (%sincluded)\n",
       ob->quota_value, mailbox_size, message_size,
-      ob->quota_is_inclusive? "" : "not ");
+      ob->quota_is_inclusive ? "" : "not ");
     debug_printf("  file count quota = %d count = %d\n",
       ob->quota_filecount_value, mailbox_filecount);
     }
-  if (mailbox_size + (ob->quota_is_inclusive? message_size:0) > ob->quota_value)
-    {
-    DEBUG(D_transport) debug_printf("mailbox quota exceeded\n");
-    yield = DEFER;
-    errno = ERRNO_EXIMQUOTA;
-    }
-  else if (ob->quota_filecount_value > 0 &&
-           mailbox_filecount + (ob->quota_is_inclusive ? 1:0) >
-             ob->quota_filecount_value)
-    {
-    DEBUG(D_transport) debug_printf("mailbox file count quota exceeded\n");
-    yield = DEFER;
-    errno = ERRNO_EXIMQUOTA;
-    filecount_msg = US" filecount";
-    }
+
+  if (mailbox_size + (ob->quota_is_inclusive ? message_size:0) > ob->quota_value)
+    if (!ob->quota_no_check)
+      {
+      DEBUG(D_transport) debug_printf("mailbox quota exceeded\n");
+      yield = DEFER;
+      errno = ERRNO_EXIMQUOTA;
+      }
+    else
+      DEBUG(D_transport) debug_printf("mailbox quota exceeded but ignored\n");
+
+  if (ob->quota_filecount_value > 0
+           && mailbox_filecount + (ob->quota_is_inclusive ? 1:0) >
+              ob->quota_filecount_value)
+    if (!ob->quota_filecount_no_check)
+      {
+      DEBUG(D_transport) debug_printf("mailbox file count quota exceeded\n");
+      yield = DEFER;
+      errno = ERRNO_EXIMQUOTA;
+      filecount_msg = US" filecount";
+      }
+    else DEBUG(D_transport) if (ob->quota_filecount_no_check)
+      debug_printf("mailbox file count quota exceeded but ignored\n");
+
+  }
+
+if (verify_mode)
+  {
+  addr->basic_errno = errno;
+  addr->message = US"Over quota";
+  addr->transport_return = yield;
+  DEBUG(D_transport)
+    debug_printf("appendfile (verify) yields %d with errno=%d more_errno=%d\n",
+      yield, addr->basic_errno, addr->more_errno);
+
+  goto RETURN;
   }
 
 /* If we are writing in MBX format, what we actually do is to write the message
@@ -2616,8 +2730,7 @@ opened, so that it goes away on closure. */
 #ifdef SUPPORT_MBX
 if (yield == OK && ob->mbx_format)
   {
-  temp_file = tmpfile();
-  if (temp_file == NULL)
+  if (!(temp_file = tmpfile()))
     {
     addr->basic_errno = errno;
     addr->message = US"while setting up temporary file";
@@ -2634,13 +2747,14 @@ if (yield == OK && ob->mbx_format)
 functions. */
 
 transport_count = 0;
+transport_newlines = 0;
 
 /* Write any configured prefix text first */
 
-if (yield == OK && ob->message_prefix != NULL && ob->message_prefix[0] != 0)
+if (yield == OK && ob->message_prefix && *ob->message_prefix)
   {
   uschar *prefix = expand_string(ob->message_prefix);
-  if (prefix == NULL)
+  if (!prefix)
     {
     errno = ERRNO_EXPANDFAIL;
     addr->transport_return = PANIC;
@@ -2659,21 +2773,25 @@ file, use its parent in the RCPT TO. */
 if (yield == OK && ob->use_bsmtp)
   {
   transport_count = 0;
+  transport_newlines = 0;
   if (ob->use_crlf) cr = US"\r";
   if (!transport_write_string(fd, "MAIL FROM:<%s>%s\n", return_path, cr))
     yield = DEFER;
   else
     {
-    address_item *a;
-    for (a = addr; a != NULL; a = a->next)
+    transport_newlines++;
+    for (address_item * a = addr; a; a = a->next)
       {
-      address_item *b = testflag(a, af_pfr)? a->parent: a;
+      address_item * b = testflag(a, af_pfr) ? a->parent : a;
       if (!transport_write_string(fd, "RCPT TO:<%s>%s\n",
         transport_rcpt_address(b, tblock->rcpt_include_affixes), cr))
           { yield = DEFER; break; }
+      transport_newlines++;
       }
     if (yield == OK && !transport_write_string(fd, "DATA%s\n", cr))
       yield = DEFER;
+    else
+      transport_newlines++;
     }
   }
 
@@ -2682,18 +2800,24 @@ at initialization time. */
 
 if (yield == OK)
   {
-  if (!transport_write_message(addr, fd, ob->options, 0, tblock->add_headers,
-      tblock->remove_headers, ob->check_string, ob->escape_string,
-      tblock->rewrite_rules, tblock->rewrite_existflags))
+  transport_ctx tctx = {
+    .u = {.fd=fd},
+    .tblock = tblock,
+    .addr = addr,
+    .check_string = ob->check_string,
+    .escape_string = ob->escape_string,
+    .options =  ob->options | topt_not_socket
+  };
+  if (!transport_write_message(&tctx, 0))
     yield = DEFER;
   }
 
 /* Now a configured suffix. */
 
-if (yield == OK && ob->message_suffix != NULL && ob->message_suffix[0] != 0)
+if (yield == OK && ob->message_suffix && *ob->message_suffix)
   {
   uschar *suffix = expand_string(ob->message_suffix);
-  if (suffix == NULL)
+  if (!suffix)
     {
     errno = ERRNO_EXPANDFAIL;
     addr->transport_return = PANIC;
@@ -2706,8 +2830,9 @@ if (yield == OK && ob->message_suffix != NULL && ob->message_suffix[0] != 0)
 
 /* If batch smtp, write the terminating dot. */
 
-if (yield == OK && ob->use_bsmtp &&
-  !transport_write_string(fd, ".%s\n", cr)) yield = DEFER;
+if (yield == OK && ob->use_bsmtp)
+  if (!transport_write_string(fd, ".%s\n", cr)) yield = DEFER;
+  else transport_newlines++;
 
 /* If MBX format is being used, all that writing was to the temporary file.
 However, if there was an earlier failure (Exim quota exceeded, for example),
@@ -2717,7 +2842,7 @@ message in MBX format into the real file. Otherwise use the temporary name in
 any messages. */
 
 #ifdef SUPPORT_MBX
-if (temp_file != NULL && ob->mbx_format)
+if (temp_file && ob->mbx_format)
   {
   int mbx_save_errno;
   fd = save_fd;
@@ -2725,6 +2850,8 @@ if (temp_file != NULL && ob->mbx_format)
   if (yield == OK)
     {
     transport_count = 0;   /* Reset transport count for actual write */
+    /* No need to reset transport_newlines as we're just using a block copy
+     * routine so the number won't be affected */
     yield = copy_mbx_message(fd, fileno(temp_file), saved_size);
     }
   else if (errno >= 0) dataname = US"temporary file";
@@ -2740,30 +2867,37 @@ if (temp_file != NULL && ob->mbx_format)
 /* Force out the remaining data to check for any errors; some OS don't allow
 fsync() to be called for a FIFO. */
 
-if (yield == OK && !isfifo && fsync(fd) < 0) yield = DEFER;
+if (yield == OK && !isfifo && EXIMfsync(fd) < 0) yield = DEFER;
 
-/* Update message_size to the accurate count of bytes written, including
-added headers. */
+/* Update message_size and message_linecount to the accurate count of bytes
+written, including added headers. Note; we subtract 1 from message_linecount as
+this variable doesn't count the new line between the header and the body of the
+message. */
 
 message_size = transport_count;
+message_linecount = transport_newlines - 1;
 
 /* If using a maildir++ quota file, add this message's size to it, and
-close the file descriptor. */
+close the file descriptor, except when the quota has been disabled because we
+are delivering into an uncounted folder. */
 
 #ifdef SUPPORT_MAILDIR
-if (yield == OK && maildirsize_fd >= 0)
-  maildir_record_length(maildirsize_fd, message_size);
-
-maildir_save_errno = errno;       /* Preserve errno while closing the file */
-(void)close(maildirsize_fd);
-errno = maildir_save_errno;
+if (!disable_quota)
+  {
+  if (yield == OK && maildirsize_fd >= 0)
+    maildir_record_length(maildirsize_fd, message_size);
+  maildir_save_errno = errno;    /* Preserve errno while closing the file */
+  if (maildirsize_fd >= 0)
+    (void)close(maildirsize_fd);
+  errno = maildir_save_errno;
+  }
 #endif  /* SUPPORT_MAILDIR */
 
 /* If there is a quota warning threshold and we are have crossed it with this
 message, set the SPECIAL_WARN flag in the address, to cause a warning message
 to be sent. */
 
-if (THRESHOLD_CHECK)
+if (!disable_quota && THRESHOLD_CHECK)
   {
   off_t threshold = ob->quota_warn_threshold_value;
   if (ob->quota_warn_threshold_is_percent)
@@ -2826,10 +2960,9 @@ if (yield != OK)
           "stat error %d for \"new\": %s\n", errno, strerror(errno));
         }
       else   /* Want a repeatable time when in test harness */
-        {
-        addr->more_errno = running_in_test_harness? 10 :
+        addr->more_errno = f.running_in_test_harness ? 10 :
           (int)time(NULL) - statbuf.st_mtime;
-        }
+
       DEBUG(D_transport)
         debug_printf("maildir: time since \"new\" directory modified = %s\n",
         readconf_printtime(addr->more_errno));
@@ -2847,12 +2980,13 @@ if (yield != OK)
     addr->message = string_sprintf("mailbox is full "
       "(quota exceeded while writing to file %s)", filename);
     #else
-    addr->message = string_sprintf("mailbox is full");
+    addr->message = US"mailbox is full";
     #endif  /* EDQUOT */
+    addr->user_message = US"mailbox is full";
     DEBUG(D_transport) debug_printf("System quota exceeded for %s%s%s\n",
       dataname,
-      isdirectory? US"" : US": time since file read = ",
-      isdirectory? US"" : readconf_printtime(addr->more_errno));
+      isdirectory ? US"" : US": time since file read = ",
+      isdirectory ? US"" : readconf_printtime(addr->more_errno));
     }
 
   /* Handle Exim's own quota-imposition */
@@ -2865,8 +2999,8 @@ if (yield != OK)
     addr->user_message = US"mailbox is full";
     DEBUG(D_transport) debug_printf("Exim%s quota exceeded for %s%s%s\n",
       filecount_msg, dataname,
-      isdirectory? US"" : US": time since file read = ",
-      isdirectory? US"" : readconf_printtime(addr->more_errno));
+      isdirectory ? US"" : US": time since file read = ",
+      isdirectory ? US"" : readconf_printtime(addr->more_errno));
     }
 
   /* Handle a process failure while writing via a filter; the return
@@ -2877,7 +3011,7 @@ if (yield != OK)
     yield = PANIC;
     addr->message = string_sprintf("transport filter process failed (%d) "
       "while writing to %s%s", addr->more_errno, dataname,
-      (addr->more_errno == EX_EXECFAILED)? ": unable to execute command" : "");
+      (addr->more_errno == EX_EXECFAILED) ? ": unable to execute command" : "");
     }
 
   /* Handle failure to expand header changes */
@@ -2893,19 +3027,15 @@ if (yield != OK)
   /* Handle failure to complete writing of a data block */
 
   else if (errno == ERRNO_WRITEINCOMPLETE)
-    {
     addr->message = string_sprintf("failed to write data block while "
       "writing to %s", dataname);
-    }
 
   /* Handle length mismatch on MBX copying */
 
   #ifdef SUPPORT_MBX
   else if (errno == ERRNO_MBXLENGTH)
-    {
     addr->message = string_sprintf("length mismatch while copying MBX "
       "temporary file to %s", dataname);
-    }
   #endif  /* SUPPORT_MBX */
 
   /* For other errors, a general-purpose explanation, if the message is
@@ -2919,7 +3049,8 @@ if (yield != OK)
   investigated so far have ftruncate(), whereas not all have the F_FREESP
   fcntl() call (BSDI & FreeBSD do not). */
 
-  if (!isdirectory) (void)ftruncate(fd, saved_size);
+  if (!isdirectory && ftruncate(fd, saved_size))
+    DEBUG(D_transport) debug_printf("Error resetting file size\n");
   }
 
 /* Handle successful writing - we want the modification time to be now for
@@ -2950,7 +3081,7 @@ else
       {
       addr->basic_errno = errno;
       addr->message = string_sprintf("close() error for %s",
-        (ob->mailstore_format)? dataname : filename);
+        (ob->mailstore_format) ? dataname : filename);
       yield = DEFER;
       }
 
@@ -2982,19 +3113,18 @@ else
       This makes it possible to build values that are based on the time, and
       still cope with races from multiple simultaneous deliveries. */
 
-      if (newname == NULL)
+      if (!newname)
         {
-        int i;
         uschar *renameleaf;
         uschar *old_renameleaf = US"";
 
-        for (i = 0; ; sleep(1), i++)
+        for (int i = 0; ; sleep(1), i++)
           {
           deliver_inode = statbuf.st_ino;
           renameleaf = expand_string(ob->dirfilename);
           deliver_inode = 0;
 
-          if (renameleaf == NULL)
+          if (!renameleaf)
             {
             addr->transport_return = PANIC;
             addr->message = string_sprintf("Expansion of \"%s\" "
@@ -3041,22 +3171,19 @@ else
 
       else
         {
-        if (nametag != NULL)
+        if (nametag)
           {
           uschar *iptr = expand_string(nametag);
-          if (iptr != NULL)
+          if (iptr)
             {
-            uschar *etag = store_get(Ustrlen(iptr) + 2);
+            uschar *etag = store_get(Ustrlen(iptr) + 2, iptr);
             uschar *optr = etag;
-            while (*iptr != 0)
-              {
+            for ( ; *iptr; iptr++)
               if (mac_isgraph(*iptr) && *iptr != '/')
                 {
                 if (optr == etag && isalnum(*iptr)) *optr++ = ':';
                 *optr++ = *iptr;
                 }
-              iptr++;
-              }
             *optr = 0;
             renamename = string_sprintf("%s%s", newname, etag);
             }
@@ -3097,7 +3224,7 @@ if (!isdirectory) utime(CS filename, &times);
 /* Notify comsat if configured to do so. It only makes sense if the configured
 file is the one that the comsat daemon knows about. */
 
-if (ob->notify_comsat && yield == OK && deliver_localpart != NULL)
+if (ob->notify_comsat && yield == OK && deliver_localpart)
   notify_comsat(deliver_localpart, saved_size);
 
 /* Pass back the final return code in the address structure */
@@ -3145,7 +3272,7 @@ if (hd >= 0) Uunlink(lockname);
 
 /* We get here with isdirectory and filename set only in error situations. */
 
-if (isdirectory && filename != NULL)
+if (isdirectory && filename)
   {
   Uunlink(filename);
   if (dataname != filename) Uunlink(dataname);
@@ -3162,6 +3289,14 @@ if (wait_for_tick) exim_wait_tick(&msg_tv, 1);
 put in the first address of a batch. */
 
 return FALSE;
+
+tainted_ret_panic:
+  addr->message = string_sprintf("Tainted '%s' (file or directory "
+      "name for %s transport) not permitted", path, tblock->name);
+ret_panic:
+  addr->transport_return = PANIC;
+  return FALSE;
 }
 
+#endif /*!MACRO_PREDEF*/
 /* End of transport/appendfile.c */