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