appendfile
[exim.git] / src / src / transports / appendfile.c
index 1c3499c998a27cf8f997efd6f3f41e9341f9a1e3..5cefd147a29262600e14821e2fd7b98fbf8de06f 100644 (file)
@@ -2,7 +2,8 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) University of Cambridge 1995 - 2020 */
+/* Copyright (c) The Exim maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 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[] = {
 #ifdef SUPPORT_MAILDIR
-  { "*expand_maildir_use_size_file", opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, expand_maildir_use_size_file) },
+  { "*expand_maildir_use_size_file", opt_stringptr, LOFF(expand_maildir_use_size_file) },
 #endif
-  { "*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) },
+  { "*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_expand_bool,
-      (void *)offsetof(appendfile_transport_options_block, maildir_use_size_file ) } ,
-  { "maildirfolder_create_regex", opt_stringptr,
-      (void *)offsetof(appendfile_transport_options_block, maildirfolder_create_regex ) },
+  { "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 */
 };
 
@@ -167,70 +112,27 @@ BOOL appendfile_transport_entry(transport_instance *tblock, address_item *addr)
 /* 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 */
-  NULL,           /* expand_maildir_use_size_file */
-  US"^(?:cur|new|\\..*)$",  /* maildir_dir_regex */
-  NULL,           /* maildir_tag */
-  NULL,           /* maildirfolder_create_regex */
-  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 */
-  FALSE,          /* quota_no_check */
-  FALSE           /* quota_filecount_no_check */
+  /* 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,
 };
 
 
@@ -281,11 +183,6 @@ appendfile_transport_options_block *ob =
 uschar *q = ob->quota;
 double default_value = 0.0;
 
-addrlist = addrlist;    /* Keep picky compilers happy */
-dummy = dummy;
-uid = uid;
-gid = gid;
-
 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);
@@ -295,17 +192,15 @@ mailbox_filecount */
 
 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)
+    if (!(s =  expand_string(q)))
       {
       *errmsg = string_sprintf("Expansion of \"%s\" in %s transport failed: "
         "%s", q, tblock->name, expand_string_message);
@@ -344,9 +239,9 @@ for (int i = 0; i < 5; i++)
        rest += sizeof("/no_check") - 1;
       }
 
-    while (isspace(*rest)) rest++;
+    Uskip_whitespace(&rest);
 
-    if (*rest != 0)
+    if (*rest)
       {
       *errmsg = string_sprintf("Malformed value \"%s\" (expansion of \"%s\") "
         "in %s transport", s, q, tblock->name);
@@ -375,8 +270,8 @@ for (int i = 0; i < 5; i++)
       break;
 
     case 2:
-    if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
-       which = US"quota_warn_threshold";
+      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;
@@ -422,6 +317,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 */
 
@@ -520,20 +416,17 @@ if (tblock->uid_set && !tblock->gid_set && !tblock->expand_gid)
 /* If "create_file" is set, check that a valid option is given, and set the
 integer variable. */
 
-if (ob->create_file_string)
+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 \"file_create\" 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
@@ -704,7 +597,8 @@ if (len == 0) return tblock;
 
 /* Search the formats for a match */
 
-while ((s = string_nextinlist(&format,&sep,big_buffer,big_buffer_size)))
+/* 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;
@@ -769,14 +663,13 @@ check_dir_size(const uschar * dirname, int *countptr, const pcre *regex)
 DIR *dir;
 off_t sum = 0;
 int count = *countptr;
-struct dirent *ent;
-struct stat statbuf;
 
-if (!(dir = opendir(CS dirname))) return 0;
+if (!(dir = exim_opendir(dirname))) return 0;
 
-while ((ent = readdir(dir)))
+for (struct dirent *ent; ent = readdir(dir); )
   {
   uschar * path, * name = US ent->d_name;
+  struct stat statbuf;
 
   if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
 
@@ -996,28 +889,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  &&  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
@@ -1055,10 +948,10 @@ if (deliver_home  &&  create_file != create_anywhere)
     if (rp)
       {
       uschar hdbuffer[PATH_MAX+1];
-      uschar *rph = deliver_home;
+      const uschar * rph = deliver_dir;
       int rlen = Ustrlen(big_buffer);
 
-      if ((rp = US realpath(CS deliver_home, CS hdbuffer)))
+      if ((rp = US realpath(CS deliver_dir, CS hdbuffer)))
         {
         rph = hdbuffer;
         len = Ustrlen(rph);
@@ -1069,7 +962,7 @@ if (deliver_home  &&  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);
         }
       }
     }
@@ -1239,6 +1132,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;
@@ -1319,10 +1213,9 @@ if (!fdname)
     }
   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;
     }
   }
 
@@ -1330,21 +1223,27 @@ if (!fdname)
 
 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;
+  goto ret_panic;
   }
 
 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;
   }
+{ uschar *m;
+if (m = is_tainted2(path, 0, "Tainted '%s' (file or directory "
+          "name for %s transport) not permitted", path, tblock->name))
+  {
+  addr->message = m;
+  goto ret_panic;
+  }
+}
 
 if (path[0] != '/')
   {
@@ -1358,7 +1257,7 @@ if (path[0] != '/')
 to the true local part. */
 
 if (testflag(addr, af_file))
-  for (address_item * addr2 = addr; addr2 != NULL; addr2 = addr2->next)
+  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
@@ -1421,6 +1320,12 @@ if (f.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. */
 
@@ -1437,10 +1342,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, FALSE);
+      }
+
+  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
@@ -1456,7 +1371,7 @@ if (!isdirectory)
       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;
       }
@@ -1836,8 +1751,8 @@ if (!isdirectory)
       if (statbuf.st_nlink != 1)
         {
         addr->basic_errno = ERRNO_NOTREGULAR;
-        addr->message = string_sprintf("mailbox %s%s has too many links (%d)",
-          filename, islink ? " (symlink)" : "", statbuf.st_nlink);
+        addr->message = string_sprintf("mailbox %s%s has too many links (%lu)",
+          filename, islink ? " (symlink)" : "", (unsigned long)statbuf.st_nlink);
         goto RETURN;
 
         }
@@ -2257,10 +2172,11 @@ scanning is expensive; for maildirs some fudges have been invented:
 
 else
   {
-  uschar *check_path = path;    /* Default quota check path */
+  uschar *check_path;          /* Default quota check path */
   const pcre *regex = 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 "
@@ -2268,6 +2184,20 @@ 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, FALSE);
+      }
+    else
+      goto tainted_ret_panic;
+
+  check_path = path;
+
   #ifdef SUPPORT_MAILDIR
   /* For a maildir delivery, ensure that all the relevant directories exist,
   and a maildirfolder file if necessary. */
@@ -2311,11 +2241,10 @@ else
       {
       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] != '/')
@@ -2341,14 +2270,14 @@ 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;
@@ -2403,7 +2332,7 @@ else
         {
         uschar *s = path + check_path_len;
         while (*s == '/') s++;
-        s = (*s == 0) ? US "new" : string_sprintf("%s/new", s);
+        s = *s ? string_sprintf("%s/new", s) : US"new";
         if (pcre_exec(dir_regex, NULL, CS s, Ustrlen(s), 0, 0, NULL, 0) < 0)
           {
           disable_quota = TRUE;
@@ -2462,10 +2391,11 @@ else
   count. Note that ob->quota_filecount_value cannot be set without
   ob->quota_value being set. */
 
-  if (!disable_quota &&
-      (ob->quota_value > 0 || THRESHOLD_CHECK) &&
-      (mailbox_size < 0 ||
-        (mailbox_filecount < 0 && ob->quota_filecount_value > 0)))
+  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;
@@ -2519,11 +2449,10 @@ else
 
     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
@@ -2539,7 +2468,7 @@ else
       uschar *basename;
 
       (void)gettimeofday(&msg_tv, NULL);
-      basename = string_sprintf(TIME_T_FMT ".H%luP" PID_T_FMT ".%s",
+      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);
@@ -2611,11 +2540,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);
@@ -2638,12 +2568,11 @@ else
     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. */
@@ -2655,13 +2584,12 @@ else
         {
         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
@@ -2684,13 +2612,12 @@ else
         {
         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
@@ -2797,6 +2724,18 @@ if (!disable_quota && ob->quota_value > 0)
 
   }
 
+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
 to a temporary file, and then copy it to the real file once we know its size.
 This is the most straightforward way of getting the correct length in the
@@ -3366,6 +3305,13 @@ 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*/