-/* $Cambridge: exim/src/src/transports/tf_maildir.c,v 1.1 2004/10/07 13:10:02 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Functions in support of the use of maildirsize files for handling quotas in
maildir directories. Some of the rules are a bit baroque:
*************************************************/
/* This function is called at the start of a maildir delivery, to ensure that
-all the relevant directories exist.
+all the relevant directories exist. It also creates a maildirfolder file if the
+base directory matches a given pattern.
Argument:
path the base directory name
addr the address item (for setting an error message)
create_directory true if we are allowed to create missing directories
dirmode the mode for created directories
+ maildirfolder_create_regex
+ the pattern to match for maildirfolder creation
Returns: TRUE on success; FALSE on failure
*/
BOOL maildir_ensure_directories(uschar *path, address_item *addr,
- BOOL create_directory, int dirmode)
+ BOOL create_directory, int dirmode, uschar *maildirfolder_create_regex)
{
int i;
struct stat statbuf;
-char *subdirs[] = { "/tmp", "/new", "/cur" };
+const char *subdirs[] = { "/tmp", "/new", "/cur" };
DEBUG(D_transport)
debug_printf("ensuring maildir directories exist in %s\n", path);
/* First ensure that the path we have is a directory; if it does not exist,
create it. Then make sure the tmp, new & cur subdirs of the maildir are
-there. If not, fail which aborts the delivery (even though the cur subdir is
+there. If not, fail. This aborts the delivery (even though the cur subdir is
not actually needed for delivery). Handle all 4 directory tests/creates in a
loop so that code can be shared. */
for (i = 0; i < 4; i++)
{
int j;
- uschar *dir, *mdir;
+ const uschar *dir, *mdir;
if (i == 0)
{
- mdir = US"";
+ mdir = CUS"";
dir = path;
}
else
{
- mdir = US subdirs[i-1];
+ mdir = CUS subdirs[i-1];
dir = mdir + 1;
}
}
}
-return TRUE; /* All directories exist */
+/* If the basic path matches maildirfolder_create_regex, we are dealing with
+a subfolder, and should ensure that a maildirfolder file exists. */
+
+if (maildirfolder_create_regex)
+ {
+ const pcre2_code * re;
+
+ DEBUG(D_transport) debug_printf("checking for maildirfolder requirement\n");
+
+ if (!(re = regex_compile(maildirfolder_create_regex,
+ MCS_NOFLAGS, &addr->message, pcre_gen_cmp_ctx)))
+ return FALSE;
+
+ if (regex_match(re, path, -1, NULL))
+ {
+ uschar *fname = string_sprintf("%s/maildirfolder", path);
+ if (Ustat(fname, &statbuf) == 0)
+ {
+ DEBUG(D_transport) debug_printf("maildirfolder already exists\n");
+ }
+ else
+ {
+ int fd = Uopen(fname, O_WRONLY|O_APPEND|O_CREAT, 0600);
+ if (fd < 0)
+ {
+ addr->message = string_sprintf("appendfile: failed to create "
+ "maildirfolder file in %s directory: %s", path, strerror(errno));
+ return FALSE;
+ }
+ (void)close(fd);
+ DEBUG(D_transport) debug_printf("created maildirfolder file\n");
+ }
+ }
+ else
+ {
+ DEBUG(D_transport) debug_printf("maildirfolder file not required\n");
+ }
+ }
+
+return TRUE; /* Everything exists that should exist */
}
uschar buffer[256];
sprintf(CS buffer, "%d 1\n", size);
len = Ustrlen(buffer);
-(void)lseek(fd, 0, SEEK_END);
-(void)write(fd, buffer, len);
-DEBUG(D_transport)
- debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
+if (lseek(fd, 0, SEEK_END) >= 0)
+ {
+ len = write(fd, buffer, len);
+ DEBUG(D_transport)
+ debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
+ }
}
Returns: the sum of the sizes of the messages
*/
-int
+off_t
maildir_compute_size(uschar *path, int *filecount, time_t *latest,
- const pcre *regex, const pcre *dir_regex, BOOL timestamp_only)
+ const pcre2_code *regex, const pcre2_code *dir_regex, BOOL timestamp_only)
{
DIR *dir;
-int sum = 0;
-struct dirent *ent;
-struct stat statbuf;
+off_t sum = 0;
-dir = opendir(CS path);
-if (dir == NULL) return 0;
+if (!(dir = exim_opendir(path)))
+ return 0;
-while ((ent = readdir(dir)) != NULL)
+for (struct dirent *ent; ent = readdir(dir); )
{
- uschar *name = US ent->d_name;
- uschar buffer[1024];
+ uschar * s, * name = US ent->d_name;
+ struct stat statbuf;
if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
scan. We do the regex match first, because that avoids a stat() for names
we aren't interested in. */
- if (dir_regex != NULL &&
- pcre_exec(dir_regex, NULL, CS name, Ustrlen(name), 0, 0, NULL, 0) < 0)
+ if (dir_regex && !regex_match(dir_regex, name, -1, NULL))
{
DEBUG(D_transport)
debug_printf("skipping %s/%s: dir_regex does not match\n", path, name);
/* The name is OK; stat it. */
- if (!string_format(buffer, sizeof(buffer), "%s/%s", path, name))
- {
- DEBUG(D_transport)
- debug_printf("maildir_compute_size: name too long: dir=%s name=%s\n",
- path, name);
- continue;
- }
-
- if (Ustat(buffer, &statbuf) < 0)
+ s = string_sprintf("%s/%s", path, name);
+ if (Ustat(s, &statbuf) < 0)
{
DEBUG(D_transport)
debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
- buffer, strerror(errno));
+ s, strerror(errno));
continue;
}
if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
{
DEBUG(D_transport)
- debug_printf("skipping %s/%s: not a directory\n", path, name);
+ debug_printf("skipping %s/%s: not a directory\n", s, name);
continue;
}
/* If this is a maildir folder, call this function recursively. */
if (name[0] == '.')
- {
- sum += maildir_compute_size(buffer, filecount, latest, regex, dir_regex,
+ sum += maildir_compute_size(s, filecount, latest, regex, dir_regex,
timestamp_only);
- }
/* Otherwise it must be a folder that contains messages (e.g. new or cur), so
we need to get its size, unless all we are interested in is the timestamp. */
else if (!timestamp_only)
- {
- sum += check_dir_size(buffer, filecount, regex);
- }
+ sum += check_dir_size(s, filecount, regex);
}
closedir(dir);
debug_printf("maildir_compute_size (timestamp_only): %ld\n",
(long int) *latest);
else
- debug_printf("maildir_compute_size: path=%s\n sum=%d filecount=%d "
- "timestamp=%ld\n", path, sum, *filecount, (long int) *latest);
+ debug_printf("maildir_compute_size: path=%s\n sum=" OFF_T_FMT
+ " filecount=%d timestamp=%ld\n",
+ path, sum, *filecount, (long int) *latest);
}
return sum;
}
Arguments:
path the path to the maildir directory; this is already backed-up
- to the parent if the delivery diretory is a maildirfolder
+ to the parent if the delivery directory is a maildirfolder
ob the appendfile options block
regex a compiled regex for getting a file's size from its name
dir_regex a compiled regex for selecting maildir directories
int
maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
- const pcre *regex, const pcre *dir_regex, int *returned_size,
+ const pcre2_code *regex, const pcre2_code *dir_regex, off_t *returned_size,
int *returned_filecount)
{
int count, fd;
-int cached_quota = 0;
+off_t cached_quota = 0;
int cached_quota_filecount = 0;
-int size = 0;
int filecount = 0;
int linecount = 0;
+off_t size = 0;
uschar *filename;
uschar buffer[MAX_FILE_SIZE];
uschar *ptr = buffer;
filename = string_sprintf("%s/maildirsize", path);
DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
-fd = Uopen(filename, O_RDWR|O_APPEND, 0);
-if (fd < 0)
+if ((fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600)) < 0)
{
if (errno != ENOENT) return -1;
DEBUG(D_transport)
still correct, and that the size of the file is still small enough. If so,
compute the maildir size from the file. */
-count = read(fd, buffer, sizeof(buffer));
-if (count >= sizeof(buffer))
+if ((count = read(fd, buffer, sizeof(buffer))) >= sizeof(buffer))
{
DEBUG(D_transport)
debug_printf("maildirsize file too big (%d): recalculating\n", count);
for (;;)
{
- long int n = Ustrtol(ptr, &endptr, 10);
+ off_t n = (off_t)Ustrtod(ptr, &endptr);
/* Only two data items are currently defined; ignore any others that
may be present. The spec is for a number followed by a letter. Anything
else we reject and recalculate. */
if (*endptr == 'S') cached_quota = n;
- else if (*endptr == 'C') cached_quota_filecount = n;
+ else if (*endptr == 'C') cached_quota_filecount = (int)n;
if (!isalpha(*endptr++))
{
DEBUG(D_transport)
{
DEBUG(D_transport)
debug_printf("cached quota is out of date: recalculating\n"
- " quota=%d cached_quota=%d filecount_quota=%d "
- "cached_quota_filecount=%d\n", ob->quota_value, cached_quota,
- ob->quota_filecount_value, cached_quota_filecount);
+ " quota=" OFF_T_FMT " cached_quota=" OFF_T_FMT " filecount_quota=%d "
+ "cached_quota_filecount=%d\n", ob->quota_value,
+ cached_quota, ob->quota_filecount_value, cached_quota_filecount);
goto RECALCULATE;
}
if (*endptr == 0) break;
linecount++;
ptr = endptr;
- size += Ustrtol(ptr, &endptr, 10);
+ size += (off_t)Ustrtod(ptr, &endptr);
if (*endptr != ' ') break;
ptr = endptr + 1;
filecount += Ustrtol(ptr, &endptr, 10);
/* If *endptr is zero, we have successfully parsed the file, and we now have
the size of the mailbox as cached in the file. The "rules" say that if this
value indicates that the mailbox is over quota, we must recalculate if there is
-more than one entry in the file, or if the file is older than 15 minutes. */
+more than one entry in the file, or if the file is older than 15 minutes. Also,
+just in case there are weird values in the file, recalculate if either of the
+values is negative. */
if (*endptr == 0)
{
+ if (size < 0 || filecount < 0)
+ {
+ DEBUG(D_transport) debug_printf("negative value in maildirsize "
+ "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
+ goto RECALCULATE;
+ }
+
if (ob->quota_value > 0 &&
(size + (ob->quota_is_inclusive? message_size : 0) > ob->quota_value ||
(ob->quota_filecount_value > 0 &&
struct stat statbuf;
if (linecount > 1)
{
- DEBUG(D_transport) debug_printf("over quota and maildirsizefile has "
+ DEBUG(D_transport) debug_printf("over quota and maildirsize has "
"more than 1 entry: recalculating\n");
goto RECALCULATE;
}
RECALCULATE:
- if (fd >= 0) close(fd);
+ if (fd >= 0) (void)close(fd);
old_latest = 0;
filecount = 0;
size = maildir_compute_size(path, &filecount, &old_latest, regex, dir_regex,
FALSE);
(void)gettimeofday(&tv, NULL);
- tempname = string_sprintf("%s/tmp/%lu.H%luP%lu.%s", path, tv.tv_sec,
- tv.tv_usec, getpid(), primary_hostname);
+ tempname = string_sprintf("%s/tmp/" TIME_T_FMT ".H%luP%lu.%s",
+ path, tv.tv_sec, tv.tv_usec, (long unsigned) getpid(), primary_hostname);
- fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, 0600);
+ fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
if (fd >= 0)
{
- (void)sprintf(CS buffer, "%dS,%dC\n%d %d\n", ob->quota_value,
- ob->quota_filecount_value, size, filecount);
+ (void)sprintf(CS buffer, OFF_T_FMT "S,%dC\n" OFF_T_FMT " %d\n",
+ ob->quota_value, ob->quota_filecount_value, size, filecount);
len = Ustrlen(buffer);
if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
{
- close(fd);
+ (void)close(fd);
fd = -1;
}
}
DEBUG(D_transport) debug_printf("abandoning maildirsize because of "
"a later subdirectory modification\n");
(void)Uunlink(filename);
- close(fd);
- fd = -1;
+ (void)close(fd);
+ fd = -2;
}
}
/* Return the sizes and the file descriptor, if any */
-DEBUG(D_transport) debug_printf("returning maildir size=%d filecount=%d\n",
- size, filecount);
+DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
+ " filecount=%d\n", size, filecount);
*returned_size = size;
*returned_filecount = filecount;
return fd;