debian experimental exim-daemon-heavy config
[exim.git] / src / src / transports / tf_maildir.c
CommitLineData
0756eb3c
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
f9ba5e22 5/* Copyright (c) University of Cambridge 1995 - 2018 */
1e1ddfac 6/* Copyright (c) The Exim Maintainers 2020 */
0756eb3c
PH
7/* See the file NOTICE for conditions of use and distribution. */
8
9/* Functions in support of the use of maildirsize files for handling quotas in
10maildir directories. Some of the rules are a bit baroque:
11
12http://www.inter7.com/courierimap/README.maildirquota.html
13
14We try to follow most of that, except that the directories to skip for quota
15calculations are not hard wired in, but are supplied as a regex. */
16
17
18#include "../exim.h"
19#include "appendfile.h"
20#include "tf_maildir.h"
21
22#define MAX_FILE_SIZE 5120
23
24
25
26/*************************************************
27* Ensure maildir directories exist *
28*************************************************/
29
30/* This function is called at the start of a maildir delivery, to ensure that
d6629cdc
PH
31all the relevant directories exist. It also creates a maildirfolder file if the
32base directory matches a given pattern.
0756eb3c
PH
33
34Argument:
35 path the base directory name
36 addr the address item (for setting an error message)
37 create_directory true if we are allowed to create missing directories
38 dirmode the mode for created directories
d6629cdc
PH
39 maildirfolder_create_regex
40 the pattern to match for maildirfolder creation
0756eb3c
PH
41
42Returns: TRUE on success; FALSE on failure
43*/
44
45BOOL maildir_ensure_directories(uschar *path, address_item *addr,
d6629cdc 46 BOOL create_directory, int dirmode, uschar *maildirfolder_create_regex)
0756eb3c
PH
47{
48int i;
49struct stat statbuf;
1ba28e2b 50const char *subdirs[] = { "/tmp", "/new", "/cur" };
0756eb3c
PH
51
52DEBUG(D_transport)
53 debug_printf("ensuring maildir directories exist in %s\n", path);
54
55/* First ensure that the path we have is a directory; if it does not exist,
56create it. Then make sure the tmp, new & cur subdirs of the maildir are
d6629cdc 57there. If not, fail. This aborts the delivery (even though the cur subdir is
0756eb3c
PH
58not actually needed for delivery). Handle all 4 directory tests/creates in a
59loop so that code can be shared. */
60
61for (i = 0; i < 4; i++)
62 {
63 int j;
1ba28e2b 64 const uschar *dir, *mdir;
0756eb3c
PH
65
66 if (i == 0)
67 {
1ba28e2b 68 mdir = CUS"";
0756eb3c
PH
69 dir = path;
70 }
71 else
72 {
1ba28e2b 73 mdir = CUS subdirs[i-1];
0756eb3c
PH
74 dir = mdir + 1;
75 }
76
77 /* Check an existing path is a directory. This is inside a loop because
78 there is a potential race condition when creating the directory - some
79 other process may get there first. Give up after trying several times,
80 though. */
81
82 for (j = 0; j < 10; j++)
83 {
84 if (Ustat(dir, &statbuf) == 0)
85 {
86 if (S_ISDIR(statbuf.st_mode)) break; /* out of the race loop */
87 addr->message = string_sprintf("%s%s is not a directory", path,
88 mdir);
89 addr->basic_errno = ERRNO_NOTDIRECTORY;
90 return FALSE;
91 }
92
93 /* Try to make if non-existent and configured to do so */
94
95 if (errno == ENOENT && create_directory)
96 {
97 if (!directory_make(NULL, dir, dirmode, FALSE))
98 {
99 if (errno == EEXIST) continue; /* repeat the race loop */
100 addr->message = string_sprintf("cannot create %s%s", path, mdir);
101 addr->basic_errno = errno;
102 return FALSE;
103 }
104 DEBUG(D_transport)
105 debug_printf("created directory %s%s\n", path, mdir);
106 break; /* out of the race loop */
107 }
108
109 /* stat() error other than ENOENT, or ENOENT and not creatable */
110
111 addr->message = string_sprintf("stat() error for %s%s: %s", path, mdir,
112 strerror(errno));
113 addr->basic_errno = errno;
114 return FALSE;
115 }
116
117 /* If we went round the loop 10 times, the directory was flickering in
118 and out of existence like someone in a malfunctioning Star Trek
119 transporter. */
120
121 if (j >= 10)
122 {
123 addr->message = string_sprintf("existence of %s%s unclear\n", path,
124 mdir);
125 addr->basic_errno = errno;
126 addr->special_action = SPECIAL_FREEZE;
127 return FALSE;
128 }
129
130 /* First time through the directories loop, cd to the main directory */
131
132 if (i == 0 && Uchdir(path) != 0)
133 {
134 addr->message = string_sprintf ("cannot chdir to %s", path);
135 addr->basic_errno = errno;
136 return FALSE;
137 }
138 }
139
d6629cdc
PH
140/* If the basic path matches maildirfolder_create_regex, we are dealing with
141a subfolder, and should ensure that a maildirfolder file exists. */
142
143if (maildirfolder_create_regex != NULL)
144 {
145 const uschar *error;
146 int offset;
147 const pcre *regex;
148
149 DEBUG(D_transport) debug_printf("checking for maildirfolder requirement\n");
150
3d2e82c5
JH
151 if (!(regex = pcre_compile(CS maildirfolder_create_regex, PCRE_COPT,
152 CCSS &error, &offset, NULL)))
d6629cdc
PH
153 {
154 addr->message = string_sprintf("appendfile: regular expression "
155 "error: %s at offset %d while compiling %s", error, offset,
156 maildirfolder_create_regex);
157 return FALSE;
158 }
159
160 if (pcre_exec(regex, NULL, CS path, Ustrlen(path), 0, 0, NULL, 0) >= 0)
161 {
162 uschar *fname = string_sprintf("%s/maildirfolder", path);
163 if (Ustat(fname, &statbuf) == 0)
164 {
165 DEBUG(D_transport) debug_printf("maildirfolder already exists\n");
166 }
167 else
168 {
5f28a6e8 169 int fd = Uopen(fname, O_WRONLY|O_APPEND|O_CREAT, 0600);
d6629cdc
PH
170 if (fd < 0)
171 {
172 addr->message = string_sprintf("appendfile: failed to create "
173 "maildirfolder file in %s directory: %s", path, strerror(errno));
174 return FALSE;
175 }
176 (void)close(fd);
177 DEBUG(D_transport) debug_printf("created maildirfolder file\n");
178 }
179 }
180 else
181 {
182 DEBUG(D_transport) debug_printf("maildirfolder file not required\n");
183 }
184 }
185
186return TRUE; /* Everything exists that should exist */
0756eb3c
PH
187}
188
189
190
191
192/*************************************************
193* Update maildirsizefile for new file *
194*************************************************/
195
196/* This function is called to add a new line to the file, recording the length
197of the newly added message. There isn't much we can do on failure...
198
199Arguments:
200 fd the open file descriptor
201 size the size of the message
202
203Returns: nothing
204*/
205
206void
207maildir_record_length(int fd, int size)
208{
209int len;
210uschar buffer[256];
211sprintf(CS buffer, "%d 1\n", size);
212len = Ustrlen(buffer);
d315eda1
JH
213if (lseek(fd, 0, SEEK_END) >= 0)
214 {
215 len = write(fd, buffer, len);
216 DEBUG(D_transport)
217 debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
218 }
0756eb3c
PH
219}
220
221
222
223/*************************************************
224* Find the size of a maildir *
225*************************************************/
226
227/* This function is called when we have to recalculate the size of a maildir by
228scanning all the files and directories therein. There are rules and conventions
229about which files or directories are included. We support this by the use of a
230regex to match directories that are to be included.
231
232Maildirs can only be one level deep. However, this function recurses, so it
233might cope with deeper nestings. We use the existing check_dir_size() function
234to add up the sizes of the files in a directory that contains messages.
235
236The function returns the most recent timestamp encountered. It can also be run
237in a dummy mode in which it does not scan for sizes, but just returns the
238timestamp.
239
240Arguments:
241 path the path to the maildir
242 filecount where to store the count of messages
243 latest where to store the latest timestamp encountered
244 regex a regex for getting files sizes from file names
245 dir_regex a regex for matching directories to be included
246 timestamp_only don't actually compute any sizes
247
248Returns: the sum of the sizes of the messages
249*/
250
0d7eb84a 251off_t
0756eb3c
PH
252maildir_compute_size(uschar *path, int *filecount, time_t *latest,
253 const pcre *regex, const pcre *dir_regex, BOOL timestamp_only)
254{
255DIR *dir;
0d7eb84a 256off_t sum = 0;
0756eb3c 257
54a2a2a9 258if (!(dir = exim_opendir(path)))
f3ebb786 259 return 0;
0756eb3c 260
54a2a2a9 261for (struct dirent *ent; ent = readdir(dir); )
0756eb3c 262 {
f3ebb786 263 uschar * s, * name = US ent->d_name;
54a2a2a9 264 struct stat statbuf;
0756eb3c
PH
265
266 if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
267
268 /* We are normally supplied with a regex for choosing which directories to
269 scan. We do the regex match first, because that avoids a stat() for names
270 we aren't interested in. */
271
272 if (dir_regex != NULL &&
273 pcre_exec(dir_regex, NULL, CS name, Ustrlen(name), 0, 0, NULL, 0) < 0)
274 {
275 DEBUG(D_transport)
276 debug_printf("skipping %s/%s: dir_regex does not match\n", path, name);
277 continue;
278 }
279
280 /* The name is OK; stat it. */
281
f3ebb786
JH
282 s = string_sprintf("%s/%s", path, name);
283 if (Ustat(s, &statbuf) < 0)
0756eb3c
PH
284 {
285 DEBUG(D_transport)
286 debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
f3ebb786 287 s, strerror(errno));
0756eb3c
PH
288 continue;
289 }
290
291 if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
292 {
293 DEBUG(D_transport)
f3ebb786 294 debug_printf("skipping %s/%s: not a directory\n", s, name);
0756eb3c
PH
295 continue;
296 }
297
298 /* Keep the latest timestamp encountered */
299
300 if (statbuf.st_mtime > *latest) *latest = statbuf.st_mtime;
301
302 /* If this is a maildir folder, call this function recursively. */
303
304 if (name[0] == '.')
f3ebb786 305 sum += maildir_compute_size(s, filecount, latest, regex, dir_regex,
0756eb3c 306 timestamp_only);
0756eb3c
PH
307
308 /* Otherwise it must be a folder that contains messages (e.g. new or cur), so
309 we need to get its size, unless all we are interested in is the timestamp. */
310
311 else if (!timestamp_only)
f3ebb786 312 sum += check_dir_size(s, filecount, regex);
0756eb3c
PH
313 }
314
315closedir(dir);
316DEBUG(D_transport)
317 {
318 if (timestamp_only)
319 debug_printf("maildir_compute_size (timestamp_only): %ld\n",
320 (long int) *latest);
321 else
b1c749bb
PH
322 debug_printf("maildir_compute_size: path=%s\n sum=" OFF_T_FMT
323 " filecount=%d timestamp=%ld\n",
324 path, sum, *filecount, (long int) *latest);
0756eb3c
PH
325 }
326return sum;
327}
328
329
330
331/*************************************************
332* Create or update maildirsizefile *
333*************************************************/
334
335/* This function is called before a delivery if the option to use
336maildirsizefile is enabled. Its function is to create the file if it does not
337exist, or to update it if that is necessary.
338
339The logic in this function follows the rules that are described in
340
341 http://www.inter7.com/courierimap/README.maildirquota.html
342
343Or, at least, it is supposed to!
344
345Arguments:
346 path the path to the maildir directory; this is already backed-up
4c04137d 347 to the parent if the delivery directory is a maildirfolder
0756eb3c
PH
348 ob the appendfile options block
349 regex a compiled regex for getting a file's size from its name
350 dir_regex a compiled regex for selecting maildir directories
351 returned_size where to return the current size of the maildir, even if
352 the maildirsizefile is removed because of a race
353
354Returns: >=0 a file descriptor for an open maildirsize file
355 -1 there was an error opening or accessing the file
356 -2 the file was removed because of a race
357*/
358
359int
360maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
0d7eb84a 361 const pcre *regex, const pcre *dir_regex, off_t *returned_size,
0756eb3c
PH
362 int *returned_filecount)
363{
364int count, fd;
0d7eb84a 365off_t cached_quota = 0;
0756eb3c 366int cached_quota_filecount = 0;
0756eb3c
PH
367int filecount = 0;
368int linecount = 0;
0d7eb84a 369off_t size = 0;
0756eb3c
PH
370uschar *filename;
371uschar buffer[MAX_FILE_SIZE];
372uschar *ptr = buffer;
373uschar *endptr;
374
375/* Try a few times to open or create the file, in case another process is doing
376the same thing. */
377
378filename = string_sprintf("%s/maildirsize", path);
379
380DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
f3ebb786 381if ((fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600)) < 0)
0756eb3c
PH
382 {
383 if (errno != ENOENT) return -1;
384 DEBUG(D_transport)
385 debug_printf("%s does not exist: recalculating\n", filename);
386 goto RECALCULATE;
387 }
388
389/* The file has been successfully opened. Check that the cached quota value is
390still correct, and that the size of the file is still small enough. If so,
391compute the maildir size from the file. */
392
f3ebb786 393if ((count = read(fd, buffer, sizeof(buffer))) >= sizeof(buffer))
0756eb3c
PH
394 {
395 DEBUG(D_transport)
396 debug_printf("maildirsize file too big (%d): recalculating\n", count);
397 goto RECALCULATE;
398 }
399buffer[count] = 0; /* Ensure string terminated */
400
401/* Read the quota parameters from the first line of the data. */
402
403DEBUG(D_transport)
404 debug_printf("reading quota parameters from maildirsize data\n");
405
406for (;;)
407 {
0d7eb84a 408 off_t n = (off_t)Ustrtod(ptr, &endptr);
0756eb3c
PH
409
410 /* Only two data items are currently defined; ignore any others that
411 may be present. The spec is for a number followed by a letter. Anything
412 else we reject and recalculate. */
413
414 if (*endptr == 'S') cached_quota = n;
0d7eb84a 415 else if (*endptr == 'C') cached_quota_filecount = (int)n;
0756eb3c
PH
416 if (!isalpha(*endptr++))
417 {
418 DEBUG(D_transport)
419 debug_printf("quota parameter number not followed by letter in "
420 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
421 buffer);
422 goto RECALCULATE;
423 }
424 if (*endptr == '\n' || *endptr == 0) break;
425 if (*endptr++ != ',')
426 {
427 DEBUG(D_transport)
428 debug_printf("quota parameter not followed by comma in "
429 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
430 buffer);
431 goto RECALCULATE;
432 }
433 ptr = endptr;
434 }
435
436/* Check the cached values against the current settings */
437
438if (cached_quota != ob->quota_value ||
439 cached_quota_filecount != ob->quota_filecount_value)
440 {
441 DEBUG(D_transport)
442 debug_printf("cached quota is out of date: recalculating\n"
b1c749bb
PH
443 " quota=" OFF_T_FMT " cached_quota=" OFF_T_FMT " filecount_quota=%d "
444 "cached_quota_filecount=%d\n", ob->quota_value,
445 cached_quota, ob->quota_filecount_value, cached_quota_filecount);
0756eb3c
PH
446 goto RECALCULATE;
447 }
448
449/* Quota values agree; parse the rest of the data to get the sizes. At this
450stage, *endptr points either to 0 or to '\n'. */
451
452DEBUG(D_transport)
453 debug_printf("computing maildir size from maildirsize data\n");
454
455while (*endptr++ == '\n')
456 {
457 if (*endptr == 0) break;
458 linecount++;
459 ptr = endptr;
0d7eb84a 460 size += (off_t)Ustrtod(ptr, &endptr);
0756eb3c
PH
461 if (*endptr != ' ') break;
462 ptr = endptr + 1;
463 filecount += Ustrtol(ptr, &endptr, 10);
464 }
465
466/* If *endptr is zero, we have successfully parsed the file, and we now have
467the size of the mailbox as cached in the file. The "rules" say that if this
468value indicates that the mailbox is over quota, we must recalculate if there is
62c0818f
PH
469more than one entry in the file, or if the file is older than 15 minutes. Also,
470just in case there are weird values in the file, recalculate if either of the
471values is negative. */
0756eb3c
PH
472
473if (*endptr == 0)
474 {
8e669ac1 475 if (size < 0 || filecount < 0)
62c0818f
PH
476 {
477 DEBUG(D_transport) debug_printf("negative value in maildirsize "
b1c749bb 478 "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
8e669ac1
PH
479 goto RECALCULATE;
480 }
481
0756eb3c
PH
482 if (ob->quota_value > 0 &&
483 (size + (ob->quota_is_inclusive? message_size : 0) > ob->quota_value ||
484 (ob->quota_filecount_value > 0 &&
485 filecount + (ob->quota_is_inclusive ? 1:0) >
486 ob->quota_filecount_value)
487 ))
488 {
489 struct stat statbuf;
490 if (linecount > 1)
491 {
62c0818f 492 DEBUG(D_transport) debug_printf("over quota and maildirsize has "
0756eb3c
PH
493 "more than 1 entry: recalculating\n");
494 goto RECALCULATE;
495 }
496
497 if (fstat(fd, &statbuf) < 0) goto RECALCULATE; /* Should never occur */
498
499 if (time(NULL) - statbuf.st_mtime > 15*60)
500 {
501 DEBUG(D_transport) debug_printf("over quota and maildirsize is older "
502 "than 15 minutes: recalculating\n");
503 goto RECALCULATE;
504 }
505 }
506 }
507
508
509/* If *endptr is not zero, there was a syntax error in the file. */
510
511else
512 {
513 int len;
514 time_t old_latest, new_latest;
515 uschar *tempname;
516 struct timeval tv;
517
518 DEBUG(D_transport)
519 {
520 uschar *p = endptr;
521 while (p > buffer && p[-1] != '\n') p--;
522 endptr[1] = 0;
523
524 debug_printf("error in maildirsizefile: unexpected character %d in "
525 "line %d (starting '%s'): recalculating\n",
526 *endptr, linecount + 1, string_printing(p));
527 }
528
529 /* Either there is no file, or the quota value has changed, or the file has
530 got too big, or there was some format error in the file. Recalculate the size
531 and write new contents to a temporary file; then rename it. After any
532 error, just return -1 as the file descriptor. */
533
534 RECALCULATE:
535
f1e894f3 536 if (fd >= 0) (void)close(fd);
0756eb3c
PH
537 old_latest = 0;
538 filecount = 0;
539 size = maildir_compute_size(path, &filecount, &old_latest, regex, dir_regex,
540 FALSE);
541
542 (void)gettimeofday(&tv, NULL);
d0291a0a
JH
543 tempname = string_sprintf("%s/tmp/" TIME_T_FMT ".H%luP%lu.%s",
544 path, tv.tv_sec, tv.tv_usec, (long unsigned) getpid(), primary_hostname);
0756eb3c 545
1da77999 546 fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
0756eb3c
PH
547 if (fd >= 0)
548 {
b1c749bb
PH
549 (void)sprintf(CS buffer, OFF_T_FMT "S,%dC\n" OFF_T_FMT " %d\n",
550 ob->quota_value, ob->quota_filecount_value, size, filecount);
0756eb3c
PH
551 len = Ustrlen(buffer);
552 if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
553 {
f1e894f3 554 (void)close(fd);
0756eb3c
PH
555 fd = -1;
556 }
557 }
558
559 /* If any of the directories have been modified since the last timestamp we
560 saw, we have to junk this maildirsize file. */
561
562 DEBUG(D_transport) debug_printf("checking subdirectory timestamps\n");
563 new_latest = 0;
564 (void)maildir_compute_size(path, NULL, &new_latest , NULL, dir_regex, TRUE);
565 if (new_latest > old_latest)
566 {
567 DEBUG(D_transport) debug_printf("abandoning maildirsize because of "
568 "a later subdirectory modification\n");
569 (void)Uunlink(filename);
f1e894f3 570 (void)close(fd);
660242ad 571 fd = -2;
0756eb3c
PH
572 }
573 }
574
575/* Return the sizes and the file descriptor, if any */
576
b1c749bb
PH
577DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
578 " filecount=%d\n", size, filecount);
0756eb3c
PH
579*returned_size = size;
580*returned_filecount = filecount;
581return fd;
582}
583
584/* End of tf_maildir.c */