update to pre-4.87 master
[exim.git] / src / src / transports / tf_maildir.c
CommitLineData
0756eb3c
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
c4ceed07 5/* Copyright (c) University of Cambridge 1995 - 2012 */
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);
214(void)lseek(fd, 0, SEEK_END);
1ac6b2e7 215len = write(fd, buffer, len);
0756eb3c
PH
216DEBUG(D_transport)
217 debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
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
PH
256struct dirent *ent;
257struct stat statbuf;
258
259dir = opendir(CS path);
260if (dir == NULL) return 0;
261
262while ((ent = readdir(dir)) != NULL)
263 {
264 uschar *name = US ent->d_name;
265 uschar buffer[1024];
266
267 if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
268
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. */
272
273 if (dir_regex != NULL &&
274 pcre_exec(dir_regex, NULL, CS name, Ustrlen(name), 0, 0, NULL, 0) < 0)
275 {
276 DEBUG(D_transport)
277 debug_printf("skipping %s/%s: dir_regex does not match\n", path, name);
278 continue;
279 }
280
281 /* The name is OK; stat it. */
282
283 if (!string_format(buffer, sizeof(buffer), "%s/%s", path, name))
284 {
285 DEBUG(D_transport)
286 debug_printf("maildir_compute_size: name too long: dir=%s name=%s\n",
287 path, name);
288 continue;
289 }
290
291 if (Ustat(buffer, &statbuf) < 0)
292 {
293 DEBUG(D_transport)
294 debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
295 buffer, strerror(errno));
296 continue;
297 }
298
299 if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
300 {
301 DEBUG(D_transport)
302 debug_printf("skipping %s/%s: not a directory\n", path, name);
303 continue;
304 }
305
306 /* Keep the latest timestamp encountered */
307
308 if (statbuf.st_mtime > *latest) *latest = statbuf.st_mtime;
309
310 /* If this is a maildir folder, call this function recursively. */
311
312 if (name[0] == '.')
313 {
314 sum += maildir_compute_size(buffer, filecount, latest, regex, dir_regex,
315 timestamp_only);
316 }
317
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. */
320
321 else if (!timestamp_only)
322 {
323 sum += check_dir_size(buffer, filecount, regex);
324 }
325 }
326
327closedir(dir);
328DEBUG(D_transport)
329 {
330 if (timestamp_only)
331 debug_printf("maildir_compute_size (timestamp_only): %ld\n",
332 (long int) *latest);
333 else
b1c749bb
PH
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);
0756eb3c
PH
337 }
338return sum;
339}
340
341
342
343/*************************************************
344* Create or update maildirsizefile *
345*************************************************/
346
347/* This function is called before a delivery if the option to use
348maildirsizefile is enabled. Its function is to create the file if it does not
349exist, or to update it if that is necessary.
350
351The logic in this function follows the rules that are described in
352
353 http://www.inter7.com/courierimap/README.maildirquota.html
354
355Or, at least, it is supposed to!
356
357Arguments:
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
365
366Returns: >=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
369*/
370
371int
372maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
0d7eb84a 373 const pcre *regex, const pcre *dir_regex, off_t *returned_size,
0756eb3c
PH
374 int *returned_filecount)
375{
376int count, fd;
0d7eb84a 377off_t cached_quota = 0;
0756eb3c 378int cached_quota_filecount = 0;
0756eb3c
PH
379int filecount = 0;
380int linecount = 0;
0d7eb84a 381off_t size = 0;
0756eb3c
PH
382uschar *filename;
383uschar buffer[MAX_FILE_SIZE];
384uschar *ptr = buffer;
385uschar *endptr;
386
387/* Try a few times to open or create the file, in case another process is doing
388the same thing. */
389
390filename = string_sprintf("%s/maildirsize", path);
391
392DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
dc988b7e 393fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600);
0756eb3c
PH
394if (fd < 0)
395 {
396 if (errno != ENOENT) return -1;
397 DEBUG(D_transport)
398 debug_printf("%s does not exist: recalculating\n", filename);
399 goto RECALCULATE;
400 }
401
402/* The file has been successfully opened. Check that the cached quota value is
403still correct, and that the size of the file is still small enough. If so,
404compute the maildir size from the file. */
405
406count = read(fd, buffer, sizeof(buffer));
407if (count >= sizeof(buffer))
408 {
409 DEBUG(D_transport)
410 debug_printf("maildirsize file too big (%d): recalculating\n", count);
411 goto RECALCULATE;
412 }
413buffer[count] = 0; /* Ensure string terminated */
414
415/* Read the quota parameters from the first line of the data. */
416
417DEBUG(D_transport)
418 debug_printf("reading quota parameters from maildirsize data\n");
419
420for (;;)
421 {
0d7eb84a 422 off_t n = (off_t)Ustrtod(ptr, &endptr);
0756eb3c
PH
423
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. */
427
428 if (*endptr == 'S') cached_quota = n;
0d7eb84a 429 else if (*endptr == 'C') cached_quota_filecount = (int)n;
0756eb3c
PH
430 if (!isalpha(*endptr++))
431 {
432 DEBUG(D_transport)
433 debug_printf("quota parameter number not followed by letter in "
434 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
435 buffer);
436 goto RECALCULATE;
437 }
438 if (*endptr == '\n' || *endptr == 0) break;
439 if (*endptr++ != ',')
440 {
441 DEBUG(D_transport)
442 debug_printf("quota parameter not followed by comma in "
443 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
444 buffer);
445 goto RECALCULATE;
446 }
447 ptr = endptr;
448 }
449
450/* Check the cached values against the current settings */
451
452if (cached_quota != ob->quota_value ||
453 cached_quota_filecount != ob->quota_filecount_value)
454 {
455 DEBUG(D_transport)
456 debug_printf("cached quota is out of date: recalculating\n"
b1c749bb
PH
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);
0756eb3c
PH
460 goto RECALCULATE;
461 }
462
463/* Quota values agree; parse the rest of the data to get the sizes. At this
464stage, *endptr points either to 0 or to '\n'. */
465
466DEBUG(D_transport)
467 debug_printf("computing maildir size from maildirsize data\n");
468
469while (*endptr++ == '\n')
470 {
471 if (*endptr == 0) break;
472 linecount++;
473 ptr = endptr;
0d7eb84a 474 size += (off_t)Ustrtod(ptr, &endptr);
0756eb3c
PH
475 if (*endptr != ' ') break;
476 ptr = endptr + 1;
477 filecount += Ustrtol(ptr, &endptr, 10);
478 }
479
480/* If *endptr is zero, we have successfully parsed the file, and we now have
481the size of the mailbox as cached in the file. The "rules" say that if this
482value indicates that the mailbox is over quota, we must recalculate if there is
62c0818f
PH
483more than one entry in the file, or if the file is older than 15 minutes. Also,
484just in case there are weird values in the file, recalculate if either of the
485values is negative. */
0756eb3c
PH
486
487if (*endptr == 0)
488 {
8e669ac1 489 if (size < 0 || filecount < 0)
62c0818f
PH
490 {
491 DEBUG(D_transport) debug_printf("negative value in maildirsize "
b1c749bb 492 "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
8e669ac1
PH
493 goto RECALCULATE;
494 }
495
0756eb3c
PH
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)
501 ))
502 {
503 struct stat statbuf;
504 if (linecount > 1)
505 {
62c0818f 506 DEBUG(D_transport) debug_printf("over quota and maildirsize has "
0756eb3c
PH
507 "more than 1 entry: recalculating\n");
508 goto RECALCULATE;
509 }
510
511 if (fstat(fd, &statbuf) < 0) goto RECALCULATE; /* Should never occur */
512
513 if (time(NULL) - statbuf.st_mtime > 15*60)
514 {
515 DEBUG(D_transport) debug_printf("over quota and maildirsize is older "
516 "than 15 minutes: recalculating\n");
517 goto RECALCULATE;
518 }
519 }
520 }
521
522
523/* If *endptr is not zero, there was a syntax error in the file. */
524
525else
526 {
527 int len;
528 time_t old_latest, new_latest;
529 uschar *tempname;
530 struct timeval tv;
531
532 DEBUG(D_transport)
533 {
534 uschar *p = endptr;
535 while (p > buffer && p[-1] != '\n') p--;
536 endptr[1] = 0;
537
538 debug_printf("error in maildirsizefile: unexpected character %d in "
539 "line %d (starting '%s'): recalculating\n",
540 *endptr, linecount + 1, string_printing(p));
541 }
542
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. */
547
548 RECALCULATE:
549
f1e894f3 550 if (fd >= 0) (void)close(fd);
0756eb3c
PH
551 old_latest = 0;
552 filecount = 0;
553 size = maildir_compute_size(path, &filecount, &old_latest, regex, dir_regex,
554 FALSE);
555
556 (void)gettimeofday(&tv, NULL);
bfe645c1
JH
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);
0756eb3c 559
1da77999 560 fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
0756eb3c
PH
561 if (fd >= 0)
562 {
b1c749bb
PH
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);
0756eb3c
PH
565 len = Ustrlen(buffer);
566 if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
567 {
f1e894f3 568 (void)close(fd);
0756eb3c
PH
569 fd = -1;
570 }
571 }
572
573 /* If any of the directories have been modified since the last timestamp we
574 saw, we have to junk this maildirsize file. */
575
576 DEBUG(D_transport) debug_printf("checking subdirectory timestamps\n");
577 new_latest = 0;
578 (void)maildir_compute_size(path, NULL, &new_latest , NULL, dir_regex, TRUE);
579 if (new_latest > old_latest)
580 {
581 DEBUG(D_transport) debug_printf("abandoning maildirsize because of "
582 "a later subdirectory modification\n");
583 (void)Uunlink(filename);
f1e894f3 584 (void)close(fd);
660242ad 585 fd = -2;
0756eb3c
PH
586 }
587 }
588
589/* Return the sizes and the file descriptor, if any */
590
b1c749bb
PH
591DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
592 " filecount=%d\n", size, filecount);
0756eb3c
PH
593*returned_size = size;
594*returned_filecount = filecount;
595return fd;
596}
597
598/* End of tf_maildir.c */