Testsuite: output changes resulting
[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 */
0756eb3c
PH
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
9maildir directories. Some of the rules are a bit baroque:
10
11http://www.inter7.com/courierimap/README.maildirquota.html
12
13We try to follow most of that, except that the directories to skip for quota
14calculations 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
d6629cdc
PH
30all the relevant directories exist. It also creates a maildirfolder file if the
31base directory matches a given pattern.
0756eb3c
PH
32
33Argument:
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
d6629cdc
PH
38 maildirfolder_create_regex
39 the pattern to match for maildirfolder creation
0756eb3c
PH
40
41Returns: TRUE on success; FALSE on failure
42*/
43
44BOOL maildir_ensure_directories(uschar *path, address_item *addr,
d6629cdc 45 BOOL create_directory, int dirmode, uschar *maildirfolder_create_regex)
0756eb3c
PH
46{
47int i;
48struct stat statbuf;
1ba28e2b 49const char *subdirs[] = { "/tmp", "/new", "/cur" };
0756eb3c
PH
50
51DEBUG(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,
55create it. Then make sure the tmp, new & cur subdirs of the maildir are
d6629cdc 56there. If not, fail. This aborts the delivery (even though the cur subdir is
0756eb3c
PH
57not actually needed for delivery). Handle all 4 directory tests/creates in a
58loop so that code can be shared. */
59
60for (i = 0; i < 4; i++)
61 {
62 int j;
1ba28e2b 63 const uschar *dir, *mdir;
0756eb3c
PH
64
65 if (i == 0)
66 {
1ba28e2b 67 mdir = CUS"";
0756eb3c
PH
68 dir = path;
69 }
70 else
71 {
1ba28e2b 72 mdir = CUS subdirs[i-1];
0756eb3c
PH
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
d6629cdc
PH
139/* If the basic path matches maildirfolder_create_regex, we are dealing with
140a subfolder, and should ensure that a maildirfolder file exists. */
141
142if (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
3d2e82c5
JH
150 if (!(regex = pcre_compile(CS maildirfolder_create_regex, PCRE_COPT,
151 CCSS &error, &offset, NULL)))
d6629cdc
PH
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 {
5f28a6e8 168 int fd = Uopen(fname, O_WRONLY|O_APPEND|O_CREAT, 0600);
d6629cdc
PH
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
185return TRUE; /* Everything exists that should exist */
0756eb3c
PH
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
196of the newly added message. There isn't much we can do on failure...
197
198Arguments:
199 fd the open file descriptor
200 size the size of the message
201
202Returns: nothing
203*/
204
205void
206maildir_record_length(int fd, int size)
207{
208int len;
209uschar buffer[256];
210sprintf(CS buffer, "%d 1\n", size);
211len = Ustrlen(buffer);
d315eda1
JH
212if (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 }
0756eb3c
PH
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
227scanning all the files and directories therein. There are rules and conventions
228about which files or directories are included. We support this by the use of a
229regex to match directories that are to be included.
230
231Maildirs can only be one level deep. However, this function recurses, so it
232might cope with deeper nestings. We use the existing check_dir_size() function
233to add up the sizes of the files in a directory that contains messages.
234
235The function returns the most recent timestamp encountered. It can also be run
236in a dummy mode in which it does not scan for sizes, but just returns the
237timestamp.
238
239Arguments:
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
247Returns: the sum of the sizes of the messages
248*/
249
0d7eb84a 250off_t
0756eb3c
PH
251maildir_compute_size(uschar *path, int *filecount, time_t *latest,
252 const pcre *regex, const pcre *dir_regex, BOOL timestamp_only)
253{
254DIR *dir;
0d7eb84a 255off_t sum = 0;
0756eb3c 256
54a2a2a9 257if (!(dir = exim_opendir(path)))
f3ebb786 258 return 0;
0756eb3c 259
54a2a2a9 260for (struct dirent *ent; ent = readdir(dir); )
0756eb3c 261 {
f3ebb786 262 uschar * s, * name = US ent->d_name;
54a2a2a9 263 struct stat statbuf;
0756eb3c
PH
264
265 if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
266
267 /* We are normally supplied with a regex for choosing which directories to
268 scan. We do the regex match first, because that avoids a stat() for names
269 we aren't interested in. */
270
271 if (dir_regex != NULL &&
272 pcre_exec(dir_regex, NULL, CS name, Ustrlen(name), 0, 0, NULL, 0) < 0)
273 {
274 DEBUG(D_transport)
275 debug_printf("skipping %s/%s: dir_regex does not match\n", path, name);
276 continue;
277 }
278
279 /* The name is OK; stat it. */
280
f3ebb786
JH
281 s = string_sprintf("%s/%s", path, name);
282 if (Ustat(s, &statbuf) < 0)
0756eb3c
PH
283 {
284 DEBUG(D_transport)
285 debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
f3ebb786 286 s, strerror(errno));
0756eb3c
PH
287 continue;
288 }
289
290 if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
291 {
292 DEBUG(D_transport)
f3ebb786 293 debug_printf("skipping %s/%s: not a directory\n", s, name);
0756eb3c
PH
294 continue;
295 }
296
297 /* Keep the latest timestamp encountered */
298
299 if (statbuf.st_mtime > *latest) *latest = statbuf.st_mtime;
300
301 /* If this is a maildir folder, call this function recursively. */
302
303 if (name[0] == '.')
f3ebb786 304 sum += maildir_compute_size(s, filecount, latest, regex, dir_regex,
0756eb3c 305 timestamp_only);
0756eb3c
PH
306
307 /* Otherwise it must be a folder that contains messages (e.g. new or cur), so
308 we need to get its size, unless all we are interested in is the timestamp. */
309
310 else if (!timestamp_only)
f3ebb786 311 sum += check_dir_size(s, filecount, regex);
0756eb3c
PH
312 }
313
314closedir(dir);
315DEBUG(D_transport)
316 {
317 if (timestamp_only)
318 debug_printf("maildir_compute_size (timestamp_only): %ld\n",
319 (long int) *latest);
320 else
b1c749bb
PH
321 debug_printf("maildir_compute_size: path=%s\n sum=" OFF_T_FMT
322 " filecount=%d timestamp=%ld\n",
323 path, sum, *filecount, (long int) *latest);
0756eb3c
PH
324 }
325return sum;
326}
327
328
329
330/*************************************************
331* Create or update maildirsizefile *
332*************************************************/
333
334/* This function is called before a delivery if the option to use
335maildirsizefile is enabled. Its function is to create the file if it does not
336exist, or to update it if that is necessary.
337
338The logic in this function follows the rules that are described in
339
340 http://www.inter7.com/courierimap/README.maildirquota.html
341
342Or, at least, it is supposed to!
343
344Arguments:
345 path the path to the maildir directory; this is already backed-up
4c04137d 346 to the parent if the delivery directory is a maildirfolder
0756eb3c
PH
347 ob the appendfile options block
348 regex a compiled regex for getting a file's size from its name
349 dir_regex a compiled regex for selecting maildir directories
350 returned_size where to return the current size of the maildir, even if
351 the maildirsizefile is removed because of a race
352
353Returns: >=0 a file descriptor for an open maildirsize file
354 -1 there was an error opening or accessing the file
355 -2 the file was removed because of a race
356*/
357
358int
359maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
0d7eb84a 360 const pcre *regex, const pcre *dir_regex, off_t *returned_size,
0756eb3c
PH
361 int *returned_filecount)
362{
363int count, fd;
0d7eb84a 364off_t cached_quota = 0;
0756eb3c 365int cached_quota_filecount = 0;
0756eb3c
PH
366int filecount = 0;
367int linecount = 0;
0d7eb84a 368off_t size = 0;
0756eb3c
PH
369uschar *filename;
370uschar buffer[MAX_FILE_SIZE];
371uschar *ptr = buffer;
372uschar *endptr;
373
374/* Try a few times to open or create the file, in case another process is doing
375the same thing. */
376
377filename = string_sprintf("%s/maildirsize", path);
378
379DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
f3ebb786 380if ((fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600)) < 0)
0756eb3c
PH
381 {
382 if (errno != ENOENT) return -1;
383 DEBUG(D_transport)
384 debug_printf("%s does not exist: recalculating\n", filename);
385 goto RECALCULATE;
386 }
387
388/* The file has been successfully opened. Check that the cached quota value is
389still correct, and that the size of the file is still small enough. If so,
390compute the maildir size from the file. */
391
f3ebb786 392if ((count = read(fd, buffer, sizeof(buffer))) >= sizeof(buffer))
0756eb3c
PH
393 {
394 DEBUG(D_transport)
395 debug_printf("maildirsize file too big (%d): recalculating\n", count);
396 goto RECALCULATE;
397 }
398buffer[count] = 0; /* Ensure string terminated */
399
400/* Read the quota parameters from the first line of the data. */
401
402DEBUG(D_transport)
403 debug_printf("reading quota parameters from maildirsize data\n");
404
405for (;;)
406 {
0d7eb84a 407 off_t n = (off_t)Ustrtod(ptr, &endptr);
0756eb3c
PH
408
409 /* Only two data items are currently defined; ignore any others that
410 may be present. The spec is for a number followed by a letter. Anything
411 else we reject and recalculate. */
412
413 if (*endptr == 'S') cached_quota = n;
0d7eb84a 414 else if (*endptr == 'C') cached_quota_filecount = (int)n;
0756eb3c
PH
415 if (!isalpha(*endptr++))
416 {
417 DEBUG(D_transport)
418 debug_printf("quota parameter number not followed by letter in "
419 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
420 buffer);
421 goto RECALCULATE;
422 }
423 if (*endptr == '\n' || *endptr == 0) break;
424 if (*endptr++ != ',')
425 {
426 DEBUG(D_transport)
427 debug_printf("quota parameter not followed by comma in "
428 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
429 buffer);
430 goto RECALCULATE;
431 }
432 ptr = endptr;
433 }
434
435/* Check the cached values against the current settings */
436
437if (cached_quota != ob->quota_value ||
438 cached_quota_filecount != ob->quota_filecount_value)
439 {
440 DEBUG(D_transport)
441 debug_printf("cached quota is out of date: recalculating\n"
b1c749bb
PH
442 " quota=" OFF_T_FMT " cached_quota=" OFF_T_FMT " filecount_quota=%d "
443 "cached_quota_filecount=%d\n", ob->quota_value,
444 cached_quota, ob->quota_filecount_value, cached_quota_filecount);
0756eb3c
PH
445 goto RECALCULATE;
446 }
447
448/* Quota values agree; parse the rest of the data to get the sizes. At this
449stage, *endptr points either to 0 or to '\n'. */
450
451DEBUG(D_transport)
452 debug_printf("computing maildir size from maildirsize data\n");
453
454while (*endptr++ == '\n')
455 {
456 if (*endptr == 0) break;
457 linecount++;
458 ptr = endptr;
0d7eb84a 459 size += (off_t)Ustrtod(ptr, &endptr);
0756eb3c
PH
460 if (*endptr != ' ') break;
461 ptr = endptr + 1;
462 filecount += Ustrtol(ptr, &endptr, 10);
463 }
464
465/* If *endptr is zero, we have successfully parsed the file, and we now have
466the size of the mailbox as cached in the file. The "rules" say that if this
467value indicates that the mailbox is over quota, we must recalculate if there is
62c0818f
PH
468more than one entry in the file, or if the file is older than 15 minutes. Also,
469just in case there are weird values in the file, recalculate if either of the
470values is negative. */
0756eb3c
PH
471
472if (*endptr == 0)
473 {
8e669ac1 474 if (size < 0 || filecount < 0)
62c0818f
PH
475 {
476 DEBUG(D_transport) debug_printf("negative value in maildirsize "
b1c749bb 477 "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
8e669ac1
PH
478 goto RECALCULATE;
479 }
480
0756eb3c
PH
481 if (ob->quota_value > 0 &&
482 (size + (ob->quota_is_inclusive? message_size : 0) > ob->quota_value ||
483 (ob->quota_filecount_value > 0 &&
484 filecount + (ob->quota_is_inclusive ? 1:0) >
485 ob->quota_filecount_value)
486 ))
487 {
488 struct stat statbuf;
489 if (linecount > 1)
490 {
62c0818f 491 DEBUG(D_transport) debug_printf("over quota and maildirsize has "
0756eb3c
PH
492 "more than 1 entry: recalculating\n");
493 goto RECALCULATE;
494 }
495
496 if (fstat(fd, &statbuf) < 0) goto RECALCULATE; /* Should never occur */
497
498 if (time(NULL) - statbuf.st_mtime > 15*60)
499 {
500 DEBUG(D_transport) debug_printf("over quota and maildirsize is older "
501 "than 15 minutes: recalculating\n");
502 goto RECALCULATE;
503 }
504 }
505 }
506
507
508/* If *endptr is not zero, there was a syntax error in the file. */
509
510else
511 {
512 int len;
513 time_t old_latest, new_latest;
514 uschar *tempname;
515 struct timeval tv;
516
517 DEBUG(D_transport)
518 {
519 uschar *p = endptr;
520 while (p > buffer && p[-1] != '\n') p--;
521 endptr[1] = 0;
522
523 debug_printf("error in maildirsizefile: unexpected character %d in "
524 "line %d (starting '%s'): recalculating\n",
525 *endptr, linecount + 1, string_printing(p));
526 }
527
528 /* Either there is no file, or the quota value has changed, or the file has
529 got too big, or there was some format error in the file. Recalculate the size
530 and write new contents to a temporary file; then rename it. After any
531 error, just return -1 as the file descriptor. */
532
533 RECALCULATE:
534
f1e894f3 535 if (fd >= 0) (void)close(fd);
0756eb3c
PH
536 old_latest = 0;
537 filecount = 0;
538 size = maildir_compute_size(path, &filecount, &old_latest, regex, dir_regex,
539 FALSE);
540
541 (void)gettimeofday(&tv, NULL);
d0291a0a
JH
542 tempname = string_sprintf("%s/tmp/" TIME_T_FMT ".H%luP%lu.%s",
543 path, tv.tv_sec, tv.tv_usec, (long unsigned) getpid(), primary_hostname);
0756eb3c 544
1da77999 545 fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
0756eb3c
PH
546 if (fd >= 0)
547 {
b1c749bb
PH
548 (void)sprintf(CS buffer, OFF_T_FMT "S,%dC\n" OFF_T_FMT " %d\n",
549 ob->quota_value, ob->quota_filecount_value, size, filecount);
0756eb3c
PH
550 len = Ustrlen(buffer);
551 if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
552 {
f1e894f3 553 (void)close(fd);
0756eb3c
PH
554 fd = -1;
555 }
556 }
557
558 /* If any of the directories have been modified since the last timestamp we
559 saw, we have to junk this maildirsize file. */
560
561 DEBUG(D_transport) debug_printf("checking subdirectory timestamps\n");
562 new_latest = 0;
563 (void)maildir_compute_size(path, NULL, &new_latest , NULL, dir_regex, TRUE);
564 if (new_latest > old_latest)
565 {
566 DEBUG(D_transport) debug_printf("abandoning maildirsize because of "
567 "a later subdirectory modification\n");
568 (void)Uunlink(filename);
f1e894f3 569 (void)close(fd);
660242ad 570 fd = -2;
0756eb3c
PH
571 }
572 }
573
574/* Return the sizes and the file descriptor, if any */
575
b1c749bb
PH
576DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
577 " filecount=%d\n", size, filecount);
0756eb3c
PH
578*returned_size = size;
579*returned_filecount = filecount;
580return fd;
581}
582
583/* End of tf_maildir.c */