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