X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/54a2a2a9983913a91ccef3aac107a159434a4714..HEAD:/src/src/transports/appendfile.c diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c index 426a8c1ed..08a59543c 100644 --- a/src/src/transports/appendfile.c +++ b/src/src/transports/appendfile.c @@ -2,12 +2,15 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2018 */ -/* Copyright (c) The Exim maintainers 2020 */ +/* Copyright (c) The Exim maintainers 2020 - 2024 */ +/* 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" + +#ifdef TRANSPORT_APPENDFILE /* Remainder of file */ #include "appendfile.h" #ifdef SUPPORT_MAILDIR @@ -104,7 +107,7 @@ int appendfile_transport_options_count = /* Dummy values */ appendfile_transport_options_block appendfile_transport_option_defaults = {0}; -void appendfile_transport_init(transport_instance *tblock) {} +void appendfile_transport_init(driver_instance *tblock) {} BOOL appendfile_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;} #else /*!MACRO_PREDEF*/ @@ -112,70 +115,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, }; @@ -195,7 +155,6 @@ static const char *mailbox_formats[] = { (!ob->quota_warn_threshold_is_percent || ob->quota_value > 0)) - /************************************************* * Setup entry point * *************************************************/ @@ -221,39 +180,38 @@ static int appendfile_transport_setup(transport_instance *tblock, address_item *addrlist, 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; +appendfile_transport_options_block * ob = tblock->drinst.options_block; +const uschar * trname = tblock->drinst.name; +uschar * q; 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); + { + GET_OPTION("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", trname); + } /* Loop for quota, quota_filecount, quota_warn_threshold, mailbox_size, mailbox_filecount */ +GET_OPTION("quota"); +q = ob->quota; 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); + "%s", q, trname, expand_string_message); return f.search_find_defer ? DEFER : FAIL; } @@ -273,7 +231,7 @@ for (int i = 0; i < 5; i++) else if ((int)d < 0 || (int)d > 100) { *errmsg = string_sprintf("Invalid quota_warn_threshold percentage (%d)" - " for %s transport", (int)d, tblock->name); + " for %s transport", (int)d, trname); return FAIL; } ob->quota_warn_threshold_is_percent = TRUE; @@ -289,12 +247,12 @@ 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); + "in %s transport", s, q, trname); return FAIL; } } @@ -308,6 +266,7 @@ for (int i = 0; i < 5; i++) which = US"quota"; ob->quota_value = (off_t)d; ob->quota_no_check = no_check; + GET_OPTION("quota_filecount"); q = ob->quota_filecount; break; @@ -316,13 +275,15 @@ for (int i = 0; i < 5; i++) which = US"quota_filecount"; ob->quota_filecount_value = (int)d; ob->quota_filecount_no_check = no_check; + GET_OPTION("quota_warn_threshold"); q = ob->quota_warn_threshold; 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; + GET_OPTION("mailbox_size"); q = ob->mailbox_size_string; default_value = -1.0; break; @@ -331,6 +292,7 @@ for (int i = 0; i < 5; i++) 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; + GET_OPTION("mailbox_filecount"); q = ob->mailbox_filecount_string; break; @@ -344,7 +306,7 @@ for (int i = 0; i < 5; i++) if (which) { *errmsg = string_sprintf("%s value %.10g is too large (overflow) in " - "%s transport", which, d, tblock->name); + "%s transport", which, d, trname); return FAIL; } } @@ -363,10 +325,12 @@ enable consistency checks to be done, or anything else that needs to be set up. */ void -appendfile_transport_init(transport_instance *tblock) +appendfile_transport_init(driver_instance * t) { -appendfile_transport_options_block *ob = - (appendfile_transport_options_block *)(tblock->options_block); +transport_instance * tblock = (transport_instance *)t; +appendfile_transport_options_block * ob = tblock->drinst.options_block; +const uschar * trname = tblock->drinst.name; +uschar * s; /* Set up the setup entry point, to be called in the privileged state */ @@ -380,7 +344,7 @@ if (ob->lock_retries == 0) ob->lock_retries = 1; 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); + "only one of \"file\" or \"directory\" can be specified", trname); /* If a file name was specified, neither quota_filecount nor quota_directory must be given. */ @@ -389,10 +353,10 @@ if (ob->filename) { 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); + "quota_filecount must not be set without \"directory\"", trname); 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); + "quota_directory must not be set without \"directory\"", trname); } /* The default locking depends on whether MBX is set or not. Change the @@ -408,7 +372,7 @@ if (ob->use_flock) #ifdef NO_FLOCK log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n " "flock() support was not available in the operating system when this " - "binary was built", tblock->name); + "binary was built", trname); #endif /* NO_FLOCK */ if (!ob->set_use_fcntl) ob->use_fcntl = FALSE; } @@ -432,7 +396,7 @@ if (ob->mbx_format) if (!ob->use_fcntl && !ob->use_flock && !ob->use_lockfile && !ob->use_mbx_lock) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n " - "no locking configured", tblock->name); + "no locking configured", trname); /* Unset timeouts for non-used locking types */ @@ -447,38 +411,35 @@ if (ob->dirname) { if (ob->maildir_format && ob->mailstore_format) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n " - "only one of maildir and mailstore may be specified", tblock->name); - if (ob->quota_filecount != NULL && ob->quota == NULL) + "only one of maildir and mailstore may be specified", trname); + if (ob->quota_filecount != NULL && !ob->quota) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n " - "quota must be set if quota_filecount is set", tblock->name); - if (ob->quota_directory != NULL && ob->quota == NULL) + "quota must be set if quota_filecount is set", trname); + if (ob->quota_directory != NULL && !ob->quota) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n " - "quota must be set if quota_directory is set", tblock->name); + "quota must be set if quota_directory is set", trname); } /* 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) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, - "user set without group for the %s transport", tblock->name); + "user set without group for the %s transport", trname); /* 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 \"create_file\" for the %s transport: '%s'", + trname, s); + ob->create_file = val; } /* If quota_warn_threshold is set, set up default for warn_message. It may @@ -553,7 +514,7 @@ Returns: nothing */ static void -notify_comsat(uschar *user, off_t offset) +notify_comsat(const uschar * user, off_t offset) { struct servent *sp; host_item host; @@ -634,8 +595,8 @@ Returns: pointer to the required transport, or NULL transport_instance * check_file_format(int cfd, transport_instance *tblock, address_item *addr) { -const uschar *format = - ((appendfile_transport_options_block *)(tblock->options_block))->file_format; +appendfile_transport_options_block * ob = tblock->drinst.options_block; +const uschar * format = ob->file_format; uschar data[256]; int len = read(cfd, data, sizeof(data)); int sep = 0; @@ -649,7 +610,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; @@ -657,11 +619,11 @@ while ((s = string_nextinlist(&format,&sep,big_buffer,big_buffer_size))) if (match && tp) { - for (transport_instance * tt = transports; tt; tt = tt->next) - if (Ustrcmp(tp, tt->name) == 0) + for (transport_instance * tt = transports; tt; tt = tt->drinst.next) + if (Ustrcmp(tp, tt->drinst.name) == 0) { DEBUG(D_transport) - debug_printf("file format -> %s transport\n", tt->name); + debug_printf("file format -> %s transport\n", tt->drinst.name); return tt; } addr->basic_errno = ERRNO_BADTRANSPORT; @@ -702,22 +664,23 @@ 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(const uschar * dirname, int *countptr, const pcre *regex) +check_dir_size(const uschar * dirname, int * countptr, const pcre2_code * re) { -DIR *dir; +DIR * dir; off_t sum = 0; -int count = *countptr; +int count = *countptr, lcount = REGEX_LOOPCOUNT_STORE_RESET; +rmark reset_point = store_mark(); if (!(dir = exim_opendir(dirname))) return 0; -for (struct dirent *ent; ent = readdir(dir); ) +for (struct dirent * ent; ent = readdir(dir); ) { uschar * path, * name = US ent->d_name; struct stat statbuf; @@ -725,25 +688,36 @@ for (struct dirent *ent; ent = readdir(dir); ) if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue; count++; + if (--lcount == 0) + { + store_reset(reset_point); reset_point = store_mark(); + lcount = REGEX_LOOPCOUNT_STORE_RESET; + } /* If there's a regex, try to find the size using it */ - if (regex) + 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); } @@ -762,7 +736,7 @@ for (struct dirent *ent; ent = readdir(dir); ) 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, regex); + sum += check_dir_size(path, &count, re); } closedir(dir); @@ -770,6 +744,7 @@ DEBUG(D_transport) debug_printf("check_dir_size: dir=%s sum=" OFF_T_FMT " count=%d\n", dirname, sum, count); +store_reset(reset_point); *countptr = count; return sum; } @@ -940,28 +915,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 @@ -999,10 +974,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); @@ -1013,7 +988,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); } } } @@ -1180,36 +1155,27 @@ appendfile_transport_entry( transport_instance *tblock, /* data for this instantiation */ address_item *addr) /* address we are working on */ { -appendfile_transport_options_block *ob = - (appendfile_transport_options_block *)(tblock->options_block); +appendfile_transport_options_block * ob = tblock->drinst.options_block; +const uschar * trname = tblock->drinst.name; struct stat statbuf; -uschar *fdname = NULL; -uschar *filename = NULL; -uschar *hitchname = NULL; -uschar *dataname = NULL; -uschar *lockname = NULL; -uschar *newname = NULL; -uschar *nametag = NULL; -uschar *cr = US""; -uschar *filecount_msg = US""; -uschar *path; +const uschar * deliver_dir; +uschar * fdname = NULL, * filename = NULL; +uschar * hitchname = NULL, * dataname = NULL; +uschar * lockname = NULL, * newname = NULL; +uschar * nametag = NULL, * cr = US""; +uschar * filecount_msg = US""; +uschar * path; struct utimbuf times; struct timeval msg_tv; -BOOL disable_quota = FALSE; -BOOL isdirectory = FALSE; -BOOL isfifo = FALSE; +BOOL disable_quota = FALSE, isdirectory = FALSE, 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; -off_t saved_size = -1; -off_t mailbox_size = ob->mailbox_size_value; +int mode = addr->mode > 0 ? addr->mode : ob->mode; +off_t saved_size = -1, mailbox_size = ob->mailbox_size_value; int mailbox_filecount = ob->mailbox_filecount_value; -int hd = -1; -int fd = -1; -int yield = FAIL; -int i; +int hd = -1, fd = -1, yield = FAIL, i; #ifdef SUPPORT_MBX int save_fd = 0; @@ -1258,13 +1224,14 @@ if (!fdname) { if (!(fdname = ob->filename)) { + GET_OPTION("directory"); fdname = ob->dirname; isdirectory = TRUE; } if (!fdname) { addr->message = string_sprintf("Mandatory file or directory option " - "missing from %s transport", tblock->name); + "missing from %s transport", trname); goto ret_panic; } } @@ -1275,23 +1242,17 @@ if ((ob->maildir_format || ob->mailstore_format) && !isdirectory) { addr->message = string_sprintf("mail%s_format requires \"directory\" " "to be specified for the %s transport", - ob->maildir_format ? "dir" : "store", tblock->name); + ob->maildir_format ? "dir" : "store", trname); goto ret_panic; } if (!(path = expand_string(fdname))) { addr->message = string_sprintf("Expansion of \"%s\" (file or directory " - "name for %s transport) failed: %s", fdname, tblock->name, + "name for %s transport) failed: %s", fdname, trname, expand_string_message); goto ret_panic; } -if (is_tainted(path)) - { - addr->message = string_sprintf("Tainted '%s' (file or directory " - "name for %s transport) not permitted", path, tblock->name); - goto ret_panic; - } if (path[0] != '/') { @@ -1363,11 +1324,17 @@ if (f.dont_deliver) { DEBUG(D_transport) debug_printf("*** delivery by %s transport bypassed by -N option\n", - tblock->name); + trname); addr->transport_return = OK; 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. */ @@ -1384,10 +1351,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("below-home: 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 @@ -1397,17 +1374,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)); - DEBUG(D_transport) debug_printf("%s transport: %s\n", tblock->name, path); + exim_errstr(errno)); + DEBUG(D_transport) debug_printf("%s transport: %s\n", trname, path); return FALSE; } - *p = '/'; } /* If file_format is set we must check that any existing file matches one of @@ -1433,11 +1409,12 @@ if (!isdirectory) { if (tt) { + transport_info * ti = tt->drinst.info; set_process_info("delivering %s to %s using %s", message_id, - addr->local_part, tt->name); + addr->local_part, tt->drinst.name); debug_print_string(tt->debug_string); addr->transport = tt; - (tt->info->code)(tt, addr); + (ti->code)(tt, addr); } return FALSE; } @@ -1783,8 +1760,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; } @@ -2204,10 +2181,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 " @@ -2215,6 +2193,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("below-home: 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, and a maildirfolder file if necessary. */ @@ -2233,34 +2225,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. */ if (ob->quota_size_regex) { - if (!(regex = pcre_compile(CS ob->quota_size_regex, PCRE_COPT, - CCSS &error, &offset, 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; - } + DEBUG(D_transport) debug_printf("using regex for file sizes: %s\n", ob->quota_size_regex); } /* Use an explicitly configured directory if set */ + GET_OPTION("quota_directory"); if (ob->quota_directory) { if (!(check_path = expand_string(ob->quota_directory))) { addr->message = string_sprintf("Expansion of \"%s\" (quota_directory " "name for %s transport) failed: %s", ob->quota_directory, - tblock->name, expand_string_message); + trname, expand_string_message); goto ret_panic; } @@ -2287,14 +2273,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; @@ -2319,22 +2305,15 @@ 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) { int check_path_len = Ustrlen(check_path); - if (!(dir_regex = pcre_compile(CS ob->maildir_dir_regex, PCRE_COPT, - CCSS &error, &offset, NULL))) - { - addr->message = string_sprintf("appendfile: regular expression " - "error: %s at offset %d while compiling %s", error, offset, - ob->maildir_dir_regex); + if (!(dir_regex = regex_compile(ob->maildir_dir_regex, + MCS_NOFLAGS, &addr->message, pcre_gen_cmp_ctx))) return FALSE; - } DEBUG(D_transport) debug_printf("using regex for maildir directory selection: %s\n", @@ -2349,8 +2328,8 @@ else { uschar *s = path + check_path_len; while (*s == '/') s++; - s = (*s == 0) ? US "new" : string_sprintf("%s/new", s); - if (pcre_exec(dir_regex, NULL, CS s, Ustrlen(s), 0, 0, NULL, 0) < 0) + 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 " @@ -2371,7 +2350,7 @@ else off_t size; int filecount; - if ((maildirsize_fd = maildir_ensure_sizefile(check_path, ob, regex, dir_regex, + if ((maildirsize_fd = maildir_ensure_sizefile(check_path, ob, re, dir_regex, &size, &filecount)) == -1) { addr->basic_errno = errno; @@ -2396,7 +2375,7 @@ 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); * } */ @@ -2408,16 +2387,17 @@ 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; 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; } @@ -2457,6 +2437,7 @@ else DEBUG(D_transport) debug_printf("delivering in maildir format in %s\n", path); + GET_OPTION("maildir_tag"); nametag = ob->maildir_tag; /* Check that nametag expands successfully; a hard failure causes a panic @@ -2466,7 +2447,7 @@ else if (nametag && !expand_string(nametag) && !f.expand_string_forcedfail) { addr->message = string_sprintf("Expansion of \"%s\" (maildir_tag " - "for %s transport) failed: %s", nametag, tblock->name, + "for %s transport) failed: %s", nametag, trname, expand_string_message); goto ret_panic; } @@ -2484,7 +2465,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); @@ -2544,9 +2525,9 @@ else else { - FILE *env_file; + 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); @@ -2556,11 +2537,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); @@ -2584,7 +2566,7 @@ else { addr->basic_errno = errno; addr->message = string_sprintf("fdopen of %s (" - "for %s transport) failed", filename, tblock->name); + "for %s transport) failed", filename, trname); (void)close(fd); Uunlink(filename); goto ret_panic; @@ -2592,16 +2574,17 @@ else /* Write the envelope file, then close it. */ + GET_OPTION("mailstore_prefix"); if (ob->mailstore_prefix) { - uschar *s = expand_string(ob->mailstore_prefix); + uschar * s = expand_string(ob->mailstore_prefix); if (!s) { if (!f.expand_string_forcedfail) { addr->message = string_sprintf("Expansion of \"%s\" (mailstore " "prefix for %s transport) failed: %s", ob->mailstore_prefix, - tblock->name, expand_string_message); + trname, expand_string_message); (void)fclose(env_file); Uunlink(filename); goto ret_panic; @@ -2620,16 +2603,17 @@ else for (address_item * taddr = addr; taddr; taddr = taddr->next) fprintf(env_file, "%s@%s\n", taddr->local_part, taddr->domain); + GET_OPTION("mailstore_suffix"); if (ob->mailstore_suffix) { - uschar *s = expand_string(ob->mailstore_suffix); + uschar * s = expand_string(ob->mailstore_suffix); if (!s) { if (!f.expand_string_forcedfail) { addr->message = string_sprintf("Expansion of \"%s\" (mailstore " "suffix for %s transport) failed: %s", ob->mailstore_suffix, - tblock->name, expand_string_message); + trname, expand_string_message); (void)fclose(env_file); Uunlink(filename); goto ret_panic; @@ -2739,6 +2723,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 @@ -2770,18 +2766,21 @@ transport_newlines = 0; /* Write any configured prefix text first */ -if (yield == OK && ob->message_prefix && *ob->message_prefix) +if (yield == OK) { - uschar *prefix = expand_string(ob->message_prefix); - if (!prefix) - { - errno = ERRNO_EXPANDFAIL; - addr->transport_return = PANIC; - addr->message = string_sprintf("Expansion of \"%s\" (prefix for %s " - "transport) failed", ob->message_prefix, tblock->name); - yield = DEFER; - } - else if (!transport_write_string(fd, "%s", prefix)) yield = DEFER; + uschar * prefix = ob->message_prefix; + GET_OPTION("message_prefix"); + if (prefix && *prefix) + if (!(prefix = expand_string(prefix))) + { + errno = ERRNO_EXPANDFAIL; + addr->transport_return = PANIC; + addr->message = string_sprintf("Expansion of \"%s\" (prefix for %s " + "transport) failed", ob->message_prefix, trname); + yield = DEFER; + } + else if (!transport_write_string(fd, "%s", prefix)) + yield = DEFER; } /* If the use_bsmtp option is on, we need to write SMTP prefix information. The @@ -2833,18 +2832,21 @@ if (yield == OK) /* Now a configured suffix. */ -if (yield == OK && ob->message_suffix && *ob->message_suffix) +if (yield == OK) { - uschar *suffix = expand_string(ob->message_suffix); - if (!suffix) - { - errno = ERRNO_EXPANDFAIL; - addr->transport_return = PANIC; - addr->message = string_sprintf("Expansion of \"%s\" (suffix for %s " - "transport) failed", ob->message_suffix, tblock->name); - yield = DEFER; - } - else if (!transport_write_string(fd, "%s", suffix)) yield = DEFER; + uschar * suffix = ob->message_suffix; + GET_OPTION("message_suffix"); + if (suffix && *suffix) + if (!(suffix = expand_string(suffix))) + { + errno = ERRNO_EXPANDFAIL; + addr->transport_return = PANIC; + addr->message = string_sprintf("Expansion of \"%s\" (suffix for %s " + "transport) failed", ob->message_suffix, trname); + yield = DEFER; + } + else if (!transport_write_string(fd, "%s", suffix)) + yield = DEFER; } /* If batch smtp, write the terminating dot. */ @@ -3148,7 +3150,7 @@ else addr->transport_return = PANIC; addr->message = string_sprintf("Expansion of \"%s\" " "(directory_file for %s transport) failed: %s", - ob->dirfilename, tblock->name, expand_string_message); + ob->dirfilename, trname, expand_string_message); goto RETURN; } @@ -3195,7 +3197,7 @@ else uschar *iptr = expand_string(nametag); if (iptr) { - uschar *etag = store_get(Ustrlen(iptr) + 2, is_tainted(iptr)); + uschar *etag = store_get(Ustrlen(iptr) + 2, iptr); uschar *optr = etag; for ( ; *iptr; iptr++) if (mac_isgraph(*iptr) && *iptr != '/') @@ -3309,10 +3311,39 @@ 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, trname); ret_panic: addr->transport_return = PANIC; return FALSE; } + + + +# ifdef DYNLOOKUP +# define appendfile_transport_info _transport_info +# endif + +transport_info appendfile_transport_info = { +.drinfo = { + .driver_name = US"appendfile", + .options = appendfile_transport_options, + .options_count = &appendfile_transport_options_count, + .options_block = &appendfile_transport_option_defaults, /* private options defaults */ + .options_len = sizeof(appendfile_transport_options_block), + .init = appendfile_transport_init, +# ifdef DYNLOOKUP + .dyn_magic = TRANSPORT_MAGIC, +# endif + }, +.code = appendfile_transport_entry, +.tidyup = NULL, +.closedown = NULL, +.local = TRUE +}; + #endif /*!MACRO_PREDEF*/ +#endif /*TRANSPORT_APPENDFILE*/ /* End of transport/appendfile.c */