4d5c0c1a9a1960c0aa42e0c2c34f7cb5b6edd689
[exim.git] / src / src / transports / tf_maildir.c
1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
7
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:
10
11 http://www.inter7.com/courierimap/README.maildirquota.html
12
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. */
15
16
17 #include "../exim.h"
18 #include "appendfile.h"
19 #include "tf_maildir.h"
20
21 #define MAX_FILE_SIZE 5120
22
23
24
25 /*************************************************
26 * Ensure maildir directories exist *
27 *************************************************/
28
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.
32
33 Argument:
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
40
41 Returns: TRUE on success; FALSE on failure
42 */
43
44 BOOL maildir_ensure_directories(uschar *path, address_item *addr,
45 BOOL create_directory, int dirmode, uschar *maildirfolder_create_regex)
46 {
47 int i;
48 struct stat statbuf;
49 const char *subdirs[] = { "/tmp", "/new", "/cur" };
50
51 DEBUG(D_transport)
52 debug_printf("ensuring maildir directories exist in %s\n", path);
53
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. */
59
60 for (i = 0; i < 4; i++)
61 {
62 int j;
63 const uschar *dir, *mdir;
64
65 if (i == 0)
66 {
67 mdir = CUS"";
68 dir = path;
69 }
70 else
71 {
72 mdir = CUS subdirs[i-1];
73 dir = mdir + 1;
74 }
75
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,
79 though. */
80
81 for (j = 0; j < 10; j++)
82 {
83 if (Ustat(dir, &statbuf) == 0)
84 {
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,
87 mdir);
88 addr->basic_errno = ERRNO_NOTDIRECTORY;
89 return FALSE;
90 }
91
92 /* Try to make if non-existent and configured to do so */
93
94 if (errno == ENOENT && create_directory)
95 {
96 if (!directory_make(NULL, dir, dirmode, FALSE))
97 {
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;
101 return FALSE;
102 }
103 DEBUG(D_transport)
104 debug_printf("created directory %s%s\n", path, mdir);
105 break; /* out of the race loop */
106 }
107
108 /* stat() error other than ENOENT, or ENOENT and not creatable */
109
110 addr->message = string_sprintf("stat() error for %s%s: %s", path, mdir,
111 strerror(errno));
112 addr->basic_errno = errno;
113 return FALSE;
114 }
115
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
118 transporter. */
119
120 if (j >= 10)
121 {
122 addr->message = string_sprintf("existence of %s%s unclear\n", path,
123 mdir);
124 addr->basic_errno = errno;
125 addr->special_action = SPECIAL_FREEZE;
126 return FALSE;
127 }
128
129 /* First time through the directories loop, cd to the main directory */
130
131 if (i == 0 && Uchdir(path) != 0)
132 {
133 addr->message = string_sprintf ("cannot chdir to %s", path);
134 addr->basic_errno = errno;
135 return FALSE;
136 }
137 }
138
139 /* If the basic path matches maildirfolder_create_regex, we are dealing with
140 a subfolder, and should ensure that a maildirfolder file exists. */
141
142 if (maildirfolder_create_regex != NULL)
143 {
144 const uschar *error;
145 int offset;
146 const pcre *regex;
147
148 DEBUG(D_transport) debug_printf("checking for maildirfolder requirement\n");
149
150 if (!(regex = pcre_compile(CS maildirfolder_create_regex, PCRE_COPT,
151 CCSS &error, &offset, NULL)))
152 {
153 addr->message = string_sprintf("appendfile: regular expression "
154 "error: %s at offset %d while compiling %s", error, offset,
155 maildirfolder_create_regex);
156 return FALSE;
157 }
158
159 if (pcre_exec(regex, NULL, CS path, Ustrlen(path), 0, 0, NULL, 0) >= 0)
160 {
161 uschar *fname = string_sprintf("%s/maildirfolder", path);
162 if (Ustat(fname, &statbuf) == 0)
163 {
164 DEBUG(D_transport) debug_printf("maildirfolder already exists\n");
165 }
166 else
167 {
168 int fd = Uopen(fname, O_WRONLY|O_APPEND|O_CREAT, 0600);
169 if (fd < 0)
170 {
171 addr->message = string_sprintf("appendfile: failed to create "
172 "maildirfolder file in %s directory: %s", path, strerror(errno));
173 return FALSE;
174 }
175 (void)close(fd);
176 DEBUG(D_transport) debug_printf("created maildirfolder file\n");
177 }
178 }
179 else
180 {
181 DEBUG(D_transport) debug_printf("maildirfolder file not required\n");
182 }
183 }
184
185 return TRUE; /* Everything exists that should exist */
186 }
187
188
189
190
191 /*************************************************
192 * Update maildirsizefile for new file *
193 *************************************************/
194
195 /* This function is called to add a new line to the file, recording the length
196 of the newly added message. There isn't much we can do on failure...
197
198 Arguments:
199 fd the open file descriptor
200 size the size of the message
201
202 Returns: nothing
203 */
204
205 void
206 maildir_record_length(int fd, int size)
207 {
208 int len;
209 uschar buffer[256];
210 sprintf(CS buffer, "%d 1\n", size);
211 len = Ustrlen(buffer);
212 if (lseek(fd, 0, SEEK_END) >= 0)
213 {
214 len = write(fd, buffer, len);
215 DEBUG(D_transport)
216 debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
217 }
218 }
219
220
221
222 /*************************************************
223 * Find the size of a maildir *
224 *************************************************/
225
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.
230
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.
234
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
237 timestamp.
238
239 Arguments:
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
246
247 Returns: the sum of the sizes of the messages
248 */
249
250 off_t
251 maildir_compute_size(uschar *path, int *filecount, time_t *latest,
252 const pcre *regex, const pcre *dir_regex, BOOL timestamp_only)
253 {
254 DIR *dir;
255 off_t sum = 0;
256 struct dirent *ent;
257 struct stat statbuf;
258
259 if (!(dir = opendir(CS path)))
260 return 0;
261
262 while ((ent = readdir(dir)))
263 {
264 uschar * s, * name = US ent->d_name;
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
282 s = string_sprintf("%s/%s", path, name);
283 if (Ustat(s, &statbuf) < 0)
284 {
285 DEBUG(D_transport)
286 debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
287 s, strerror(errno));
288 continue;
289 }
290
291 if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
292 {
293 DEBUG(D_transport)
294 debug_printf("skipping %s/%s: not a directory\n", s, name);
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] == '.')
305 sum += maildir_compute_size(s, filecount, latest, regex, dir_regex,
306 timestamp_only);
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)
312 sum += check_dir_size(s, filecount, regex);
313 }
314
315 closedir(dir);
316 DEBUG(D_transport)
317 {
318 if (timestamp_only)
319 debug_printf("maildir_compute_size (timestamp_only): %ld\n",
320 (long int) *latest);
321 else
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);
325 }
326 return 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
336 maildirsizefile is enabled. Its function is to create the file if it does not
337 exist, or to update it if that is necessary.
338
339 The logic in this function follows the rules that are described in
340
341 http://www.inter7.com/courierimap/README.maildirquota.html
342
343 Or, at least, it is supposed to!
344
345 Arguments:
346 path the path to the maildir directory; this is already backed-up
347 to the parent if the delivery directory is a maildirfolder
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
354 Returns: >=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
359 int
360 maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
361 const pcre *regex, const pcre *dir_regex, off_t *returned_size,
362 int *returned_filecount)
363 {
364 int count, fd;
365 off_t cached_quota = 0;
366 int cached_quota_filecount = 0;
367 int filecount = 0;
368 int linecount = 0;
369 off_t size = 0;
370 uschar *filename;
371 uschar buffer[MAX_FILE_SIZE];
372 uschar *ptr = buffer;
373 uschar *endptr;
374
375 /* Try a few times to open or create the file, in case another process is doing
376 the same thing. */
377
378 filename = string_sprintf("%s/maildirsize", path);
379
380 DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
381 if ((fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600)) < 0)
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
390 still correct, and that the size of the file is still small enough. If so,
391 compute the maildir size from the file. */
392
393 if ((count = read(fd, buffer, sizeof(buffer))) >= sizeof(buffer))
394 {
395 DEBUG(D_transport)
396 debug_printf("maildirsize file too big (%d): recalculating\n", count);
397 goto RECALCULATE;
398 }
399 buffer[count] = 0; /* Ensure string terminated */
400
401 /* Read the quota parameters from the first line of the data. */
402
403 DEBUG(D_transport)
404 debug_printf("reading quota parameters from maildirsize data\n");
405
406 for (;;)
407 {
408 off_t n = (off_t)Ustrtod(ptr, &endptr);
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;
415 else if (*endptr == 'C') cached_quota_filecount = (int)n;
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
438 if (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"
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);
446 goto RECALCULATE;
447 }
448
449 /* Quota values agree; parse the rest of the data to get the sizes. At this
450 stage, *endptr points either to 0 or to '\n'. */
451
452 DEBUG(D_transport)
453 debug_printf("computing maildir size from maildirsize data\n");
454
455 while (*endptr++ == '\n')
456 {
457 if (*endptr == 0) break;
458 linecount++;
459 ptr = endptr;
460 size += (off_t)Ustrtod(ptr, &endptr);
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
467 the size of the mailbox as cached in the file. The "rules" say that if this
468 value indicates that the mailbox is over quota, we must recalculate if there is
469 more than one entry in the file, or if the file is older than 15 minutes. Also,
470 just in case there are weird values in the file, recalculate if either of the
471 values is negative. */
472
473 if (*endptr == 0)
474 {
475 if (size < 0 || filecount < 0)
476 {
477 DEBUG(D_transport) debug_printf("negative value in maildirsize "
478 "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
479 goto RECALCULATE;
480 }
481
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 {
492 DEBUG(D_transport) debug_printf("over quota and maildirsize has "
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
511 else
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
536 if (fd >= 0) (void)close(fd);
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);
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);
545
546 fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
547 if (fd >= 0)
548 {
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);
551 len = Ustrlen(buffer);
552 if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
553 {
554 (void)close(fd);
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);
570 (void)close(fd);
571 fd = -2;
572 }
573 }
574
575 /* Return the sizes and the file descriptor, if any */
576
577 DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
578 " filecount=%d\n", size, filecount);
579 *returned_size = size;
580 *returned_filecount = filecount;
581 return fd;
582 }
583
584 /* End of tf_maildir.c */