1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2014 */
6 /* See the file NOTICE for conditions of use and distribution. */
8 /* Functions in support of the use of maildirsize files for handling quotas in
9 maildir directories. Some of the rules are a bit baroque:
11 http://www.inter7.com/courierimap/README.maildirquota.html
13 We try to follow most of that, except that the directories to skip for quota
14 calculations are not hard wired in, but are supplied as a regex. */
18 #include "appendfile.h"
19 #include "tf_maildir.h"
21 #define MAX_FILE_SIZE 5120
25 /*************************************************
26 * Ensure maildir directories exist *
27 *************************************************/
29 /* This function is called at the start of a maildir delivery, to ensure that
30 all the relevant directories exist. It also creates a maildirfolder file if the
31 base directory matches a given pattern.
34 path the base directory name
35 addr the address item (for setting an error message)
36 create_directory true if we are allowed to create missing directories
37 dirmode the mode for created directories
38 maildirfolder_create_regex
39 the pattern to match for maildirfolder creation
41 Returns: TRUE on success; FALSE on failure
44 BOOL maildir_ensure_directories(uschar *path, address_item *addr,
45 BOOL create_directory, int dirmode, uschar *maildirfolder_create_regex)
49 const char *subdirs[] = { "/tmp", "/new", "/cur" };
52 debug_printf("ensuring maildir directories exist in %s\n", path);
54 /* First ensure that the path we have is a directory; if it does not exist,
55 create it. Then make sure the tmp, new & cur subdirs of the maildir are
56 there. If not, fail. This aborts the delivery (even though the cur subdir is
57 not actually needed for delivery). Handle all 4 directory tests/creates in a
58 loop so that code can be shared. */
60 for (i = 0; i < 4; i++)
63 const uschar *dir, *mdir;
72 mdir = CUS subdirs[i-1];
76 /* Check an existing path is a directory. This is inside a loop because
77 there is a potential race condition when creating the directory - some
78 other process may get there first. Give up after trying several times,
81 for (j = 0; j < 10; j++)
83 if (Ustat(dir, &statbuf) == 0)
85 if (S_ISDIR(statbuf.st_mode)) break; /* out of the race loop */
86 addr->message = string_sprintf("%s%s is not a directory", path,
88 addr->basic_errno = ERRNO_NOTDIRECTORY;
92 /* Try to make if non-existent and configured to do so */
94 if (errno == ENOENT && create_directory)
96 if (!directory_make(NULL, dir, dirmode, FALSE))
98 if (errno == EEXIST) continue; /* repeat the race loop */
99 addr->message = string_sprintf("cannot create %s%s", path, mdir);
100 addr->basic_errno = errno;
104 debug_printf("created directory %s%s\n", path, mdir);
105 break; /* out of the race loop */
108 /* stat() error other than ENOENT, or ENOENT and not creatable */
110 addr->message = string_sprintf("stat() error for %s%s: %s", path, mdir,
112 addr->basic_errno = errno;
116 /* If we went round the loop 10 times, the directory was flickering in
117 and out of existence like someone in a malfunctioning Star Trek
122 addr->message = string_sprintf("existence of %s%s unclear\n", path,
124 addr->basic_errno = errno;
125 addr->special_action = SPECIAL_FREEZE;
129 /* First time through the directories loop, cd to the main directory */
131 if (i == 0 && Uchdir(path) != 0)
133 addr->message = string_sprintf ("cannot chdir to %s", path);
134 addr->basic_errno = errno;
139 /* If the basic path matches maildirfolder_create_regex, we are dealing with
140 a subfolder, and should ensure that a maildirfolder file exists. */
142 if (maildirfolder_create_regex != NULL)
148 DEBUG(D_transport) debug_printf("checking for maildirfolder requirement\n");
150 regex = pcre_compile(CS maildirfolder_create_regex, PCRE_COPT,
151 (const char **)&error, &offset, NULL);
155 addr->message = string_sprintf("appendfile: regular expression "
156 "error: %s at offset %d while compiling %s", error, offset,
157 maildirfolder_create_regex);
161 if (pcre_exec(regex, NULL, CS path, Ustrlen(path), 0, 0, NULL, 0) >= 0)
163 uschar *fname = string_sprintf("%s/maildirfolder", path);
164 if (Ustat(fname, &statbuf) == 0)
166 DEBUG(D_transport) debug_printf("maildirfolder already exists\n");
170 int fd = Uopen(fname, O_WRONLY|O_APPEND|O_CREAT, 0600);
173 addr->message = string_sprintf("appendfile: failed to create "
174 "maildirfolder file in %s directory: %s", path, strerror(errno));
178 DEBUG(D_transport) debug_printf("created maildirfolder file\n");
183 DEBUG(D_transport) debug_printf("maildirfolder file not required\n");
187 return TRUE; /* Everything exists that should exist */
193 /*************************************************
194 * Update maildirsizefile for new file *
195 *************************************************/
197 /* This function is called to add a new line to the file, recording the length
198 of the newly added message. There isn't much we can do on failure...
201 fd the open file descriptor
202 size the size of the message
208 maildir_record_length(int fd, int size)
212 sprintf(CS buffer, "%d 1\n", size);
213 len = Ustrlen(buffer);
214 (void)lseek(fd, 0, SEEK_END);
215 len = write(fd, buffer, len);
217 debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
222 /*************************************************
223 * Find the size of a maildir *
224 *************************************************/
226 /* This function is called when we have to recalculate the size of a maildir by
227 scanning all the files and directories therein. There are rules and conventions
228 about which files or directories are included. We support this by the use of a
229 regex to match directories that are to be included.
231 Maildirs can only be one level deep. However, this function recurses, so it
232 might cope with deeper nestings. We use the existing check_dir_size() function
233 to add up the sizes of the files in a directory that contains messages.
235 The function returns the most recent timestamp encountered. It can also be run
236 in a dummy mode in which it does not scan for sizes, but just returns the
240 path the path to the maildir
241 filecount where to store the count of messages
242 latest where to store the latest timestamp encountered
243 regex a regex for getting files sizes from file names
244 dir_regex a regex for matching directories to be included
245 timestamp_only don't actually compute any sizes
247 Returns: the sum of the sizes of the messages
251 maildir_compute_size(uschar *path, int *filecount, time_t *latest,
252 const pcre *regex, const pcre *dir_regex, BOOL timestamp_only)
259 dir = opendir(CS path);
260 if (dir == NULL) return 0;
262 while ((ent = readdir(dir)) != NULL)
264 uschar *name = US ent->d_name;
267 if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
269 /* We are normally supplied with a regex for choosing which directories to
270 scan. We do the regex match first, because that avoids a stat() for names
271 we aren't interested in. */
273 if (dir_regex != NULL &&
274 pcre_exec(dir_regex, NULL, CS name, Ustrlen(name), 0, 0, NULL, 0) < 0)
277 debug_printf("skipping %s/%s: dir_regex does not match\n", path, name);
281 /* The name is OK; stat it. */
283 if (!string_format(buffer, sizeof(buffer), "%s/%s", path, name))
286 debug_printf("maildir_compute_size: name too long: dir=%s name=%s\n",
291 if (Ustat(buffer, &statbuf) < 0)
294 debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
295 buffer, strerror(errno));
299 if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
302 debug_printf("skipping %s/%s: not a directory\n", path, name);
306 /* Keep the latest timestamp encountered */
308 if (statbuf.st_mtime > *latest) *latest = statbuf.st_mtime;
310 /* If this is a maildir folder, call this function recursively. */
314 sum += maildir_compute_size(buffer, filecount, latest, regex, dir_regex,
318 /* Otherwise it must be a folder that contains messages (e.g. new or cur), so
319 we need to get its size, unless all we are interested in is the timestamp. */
321 else if (!timestamp_only)
323 sum += check_dir_size(buffer, filecount, regex);
331 debug_printf("maildir_compute_size (timestamp_only): %ld\n",
334 debug_printf("maildir_compute_size: path=%s\n sum=" OFF_T_FMT
335 " filecount=%d timestamp=%ld\n",
336 path, sum, *filecount, (long int) *latest);
343 /*************************************************
344 * Create or update maildirsizefile *
345 *************************************************/
347 /* This function is called before a delivery if the option to use
348 maildirsizefile is enabled. Its function is to create the file if it does not
349 exist, or to update it if that is necessary.
351 The logic in this function follows the rules that are described in
353 http://www.inter7.com/courierimap/README.maildirquota.html
355 Or, at least, it is supposed to!
358 path the path to the maildir directory; this is already backed-up
359 to the parent if the delivery diretory is a maildirfolder
360 ob the appendfile options block
361 regex a compiled regex for getting a file's size from its name
362 dir_regex a compiled regex for selecting maildir directories
363 returned_size where to return the current size of the maildir, even if
364 the maildirsizefile is removed because of a race
366 Returns: >=0 a file descriptor for an open maildirsize file
367 -1 there was an error opening or accessing the file
368 -2 the file was removed because of a race
372 maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
373 const pcre *regex, const pcre *dir_regex, off_t *returned_size,
374 int *returned_filecount)
377 off_t cached_quota = 0;
378 int cached_quota_filecount = 0;
383 uschar buffer[MAX_FILE_SIZE];
384 uschar *ptr = buffer;
387 /* Try a few times to open or create the file, in case another process is doing
390 filename = string_sprintf("%s/maildirsize", path);
392 DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
393 fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600);
396 if (errno != ENOENT) return -1;
398 debug_printf("%s does not exist: recalculating\n", filename);
402 /* The file has been successfully opened. Check that the cached quota value is
403 still correct, and that the size of the file is still small enough. If so,
404 compute the maildir size from the file. */
406 count = read(fd, buffer, sizeof(buffer));
407 if (count >= sizeof(buffer))
410 debug_printf("maildirsize file too big (%d): recalculating\n", count);
413 buffer[count] = 0; /* Ensure string terminated */
415 /* Read the quota parameters from the first line of the data. */
418 debug_printf("reading quota parameters from maildirsize data\n");
422 off_t n = (off_t)Ustrtod(ptr, &endptr);
424 /* Only two data items are currently defined; ignore any others that
425 may be present. The spec is for a number followed by a letter. Anything
426 else we reject and recalculate. */
428 if (*endptr == 'S') cached_quota = n;
429 else if (*endptr == 'C') cached_quota_filecount = (int)n;
430 if (!isalpha(*endptr++))
433 debug_printf("quota parameter number not followed by letter in "
434 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
438 if (*endptr == '\n' || *endptr == 0) break;
439 if (*endptr++ != ',')
442 debug_printf("quota parameter not followed by comma in "
443 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
450 /* Check the cached values against the current settings */
452 if (cached_quota != ob->quota_value ||
453 cached_quota_filecount != ob->quota_filecount_value)
456 debug_printf("cached quota is out of date: recalculating\n"
457 " quota=" OFF_T_FMT " cached_quota=" OFF_T_FMT " filecount_quota=%d "
458 "cached_quota_filecount=%d\n", ob->quota_value,
459 cached_quota, ob->quota_filecount_value, cached_quota_filecount);
463 /* Quota values agree; parse the rest of the data to get the sizes. At this
464 stage, *endptr points either to 0 or to '\n'. */
467 debug_printf("computing maildir size from maildirsize data\n");
469 while (*endptr++ == '\n')
471 if (*endptr == 0) break;
474 size += (off_t)Ustrtod(ptr, &endptr);
475 if (*endptr != ' ') break;
477 filecount += Ustrtol(ptr, &endptr, 10);
480 /* If *endptr is zero, we have successfully parsed the file, and we now have
481 the size of the mailbox as cached in the file. The "rules" say that if this
482 value indicates that the mailbox is over quota, we must recalculate if there is
483 more than one entry in the file, or if the file is older than 15 minutes. Also,
484 just in case there are weird values in the file, recalculate if either of the
485 values is negative. */
489 if (size < 0 || filecount < 0)
491 DEBUG(D_transport) debug_printf("negative value in maildirsize "
492 "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
496 if (ob->quota_value > 0 &&
497 (size + (ob->quota_is_inclusive? message_size : 0) > ob->quota_value ||
498 (ob->quota_filecount_value > 0 &&
499 filecount + (ob->quota_is_inclusive ? 1:0) >
500 ob->quota_filecount_value)
506 DEBUG(D_transport) debug_printf("over quota and maildirsize has "
507 "more than 1 entry: recalculating\n");
511 if (fstat(fd, &statbuf) < 0) goto RECALCULATE; /* Should never occur */
513 if (time(NULL) - statbuf.st_mtime > 15*60)
515 DEBUG(D_transport) debug_printf("over quota and maildirsize is older "
516 "than 15 minutes: recalculating\n");
523 /* If *endptr is not zero, there was a syntax error in the file. */
528 time_t old_latest, new_latest;
535 while (p > buffer && p[-1] != '\n') p--;
538 debug_printf("error in maildirsizefile: unexpected character %d in "
539 "line %d (starting '%s'): recalculating\n",
540 *endptr, linecount + 1, string_printing(p));
543 /* Either there is no file, or the quota value has changed, or the file has
544 got too big, or there was some format error in the file. Recalculate the size
545 and write new contents to a temporary file; then rename it. After any
546 error, just return -1 as the file descriptor. */
550 if (fd >= 0) (void)close(fd);
553 size = maildir_compute_size(path, &filecount, &old_latest, regex, dir_regex,
556 (void)gettimeofday(&tv, NULL);
557 tempname = string_sprintf("%s/tmp/" TIME_T_FMT ".H%luP%lu.%s",
558 path, tv.tv_sec, tv.tv_usec, (long unsigned) getpid(), primary_hostname);
560 fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
563 (void)sprintf(CS buffer, OFF_T_FMT "S,%dC\n" OFF_T_FMT " %d\n",
564 ob->quota_value, ob->quota_filecount_value, size, filecount);
565 len = Ustrlen(buffer);
566 if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
573 /* If any of the directories have been modified since the last timestamp we
574 saw, we have to junk this maildirsize file. */
576 DEBUG(D_transport) debug_printf("checking subdirectory timestamps\n");
578 (void)maildir_compute_size(path, NULL, &new_latest , NULL, dir_regex, TRUE);
579 if (new_latest > old_latest)
581 DEBUG(D_transport) debug_printf("abandoning maildirsize because of "
582 "a later subdirectory modification\n");
583 (void)Uunlink(filename);
589 /* Return the sizes and the file descriptor, if any */
591 DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
592 " filecount=%d\n", size, filecount);
593 *returned_size = size;
594 *returned_filecount = filecount;
598 /* End of tf_maildir.c */