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