0fbb77243f7912b28e3a92fb55607a9575a870ed
[exim.git] / src / src / transports / tf_maildir.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2014 */
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
9 maildir directories. Some of the rules are a bit baroque:
10
11 http://www.inter7.com/courierimap/README.maildirquota.html
12
13 We try to follow most of that, except that the directories to skip for quota
14 calculations 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
30 all the relevant directories exist. It also creates a maildirfolder file if the
31 base directory matches a given pattern.
32
33 Argument:
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
38   maildirfolder_create_regex
39                     the pattern to match for maildirfolder creation
40
41 Returns:            TRUE on success; FALSE on failure
42 */
43
44 BOOL maildir_ensure_directories(uschar *path, address_item *addr,
45   BOOL create_directory, int dirmode, uschar *maildirfolder_create_regex)
46 {
47 int i;
48 struct stat statbuf;
49 const char *subdirs[] = { "/tmp", "/new", "/cur" };
50
51 DEBUG(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,
55 create it. Then make sure the tmp, new & cur subdirs of the maildir are
56 there. If not, fail. This aborts the delivery (even though the cur subdir is
57 not actually needed for delivery). Handle all 4 directory tests/creates in a
58 loop so that code can be shared. */
59
60 for (i = 0; i < 4; i++)
61   {
62   int j;
63   const uschar *dir, *mdir;
64
65   if (i == 0)
66     {
67     mdir = CUS"";
68     dir = path;
69     }
70   else
71     {
72     mdir = CUS subdirs[i-1];
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
139 /* If the basic path matches maildirfolder_create_regex, we are dealing with
140 a subfolder, and should ensure that a maildirfolder file exists. */
141
142 if (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       {
170       int fd = Uopen(fname, O_WRONLY|O_APPEND|O_CREAT, 0600);
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
187 return TRUE;   /* Everything exists that should exist */
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
198 of the newly added message. There isn't much we can do on failure...
199
200 Arguments:
201   fd           the open file descriptor
202   size         the size of the message
203
204 Returns:       nothing
205 */
206
207 void
208 maildir_record_length(int fd, int size)
209 {
210 int len;
211 uschar buffer[256];
212 sprintf(CS buffer, "%d 1\n", size);
213 len = Ustrlen(buffer);
214 (void)lseek(fd, 0, SEEK_END);
215 len = write(fd, buffer, len);
216 DEBUG(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
227 scanning all the files and directories therein. There are rules and conventions
228 about which files or directories are included. We support this by the use of a
229 regex to match directories that are to be included.
230
231 Maildirs can only be one level deep. However, this function recurses, so it
232 might cope with deeper nestings. We use the existing check_dir_size() function
233 to add up the sizes of the files in a directory that contains messages.
234
235 The function returns the most recent timestamp encountered. It can also be run
236 in a dummy mode in which it does not scan for sizes, but just returns the
237 timestamp.
238
239 Arguments:
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
247 Returns:      the sum of the sizes of the messages
248 */
249
250 off_t
251 maildir_compute_size(uschar *path, int *filecount, time_t *latest,
252   const pcre *regex, const pcre *dir_regex, BOOL timestamp_only)
253 {
254 DIR *dir;
255 off_t sum = 0;
256 struct dirent *ent;
257 struct stat statbuf;
258
259 dir = opendir(CS path);
260 if (dir == NULL) return 0;
261
262 while ((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
327 closedir(dir);
328 DEBUG(D_transport)
329   {
330   if (timestamp_only)
331     debug_printf("maildir_compute_size (timestamp_only): %ld\n",
332     (long int) *latest);
333   else
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);
337   }
338 return 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
348 maildirsizefile is enabled. Its function is to create the file if it does not
349 exist, or to update it if that is necessary.
350
351 The logic in this function follows the rules that are described in
352
353   http://www.inter7.com/courierimap/README.maildirquota.html
354
355 Or, at least, it is supposed to!
356
357 Arguments:
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
366 Returns:           >=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
371 int
372 maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
373   const pcre *regex, const pcre *dir_regex, off_t *returned_size,
374   int *returned_filecount)
375 {
376 int count, fd;
377 off_t cached_quota = 0;
378 int cached_quota_filecount = 0;
379 int filecount = 0;
380 int linecount = 0;
381 off_t size = 0;
382 uschar *filename;
383 uschar buffer[MAX_FILE_SIZE];
384 uschar *ptr = buffer;
385 uschar *endptr;
386
387 /* Try a few times to open or create the file, in case another process is doing
388 the same thing. */
389
390 filename = string_sprintf("%s/maildirsize", path);
391
392 DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
393 fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600);
394 if (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
403 still correct, and that the size of the file is still small enough. If so,
404 compute the maildir size from the file. */
405
406 count = read(fd, buffer, sizeof(buffer));
407 if (count >= sizeof(buffer))
408   {
409   DEBUG(D_transport)
410     debug_printf("maildirsize file too big (%d): recalculating\n", count);
411   goto RECALCULATE;
412   }
413 buffer[count] = 0;   /* Ensure string terminated */
414
415 /* Read the quota parameters from the first line of the data. */
416
417 DEBUG(D_transport)
418   debug_printf("reading quota parameters from maildirsize data\n");
419
420 for (;;)
421   {
422   off_t n = (off_t)Ustrtod(ptr, &endptr);
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;
429     else if (*endptr == 'C') cached_quota_filecount = (int)n;
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
452 if (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"
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);
460   goto RECALCULATE;
461   }
462
463 /* Quota values agree; parse the rest of the data to get the sizes. At this
464 stage, *endptr points either to 0 or to '\n'.  */
465
466 DEBUG(D_transport)
467   debug_printf("computing maildir size from maildirsize data\n");
468
469 while (*endptr++ == '\n')
470   {
471   if (*endptr == 0) break;
472   linecount++;
473   ptr = endptr;
474   size += (off_t)Ustrtod(ptr, &endptr);
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
481 the size of the mailbox as cached in the file. The "rules" say that if this
482 value indicates that the mailbox is over quota, we must recalculate if there is
483 more than one entry in the file, or if the file is older than 15 minutes. Also,
484 just in case there are weird values in the file, recalculate if either of the
485 values is negative. */
486
487 if (*endptr == 0)
488   {
489   if (size < 0 || filecount < 0)
490     {
491     DEBUG(D_transport) debug_printf("negative value in maildirsize "
492       "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
493     goto RECALCULATE;
494     }
495
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       {
506       DEBUG(D_transport) debug_printf("over quota and maildirsize has "
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
525 else
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
550   if (fd >= 0) (void)close(fd);
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);
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);
559
560   fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
561   if (fd >= 0)
562     {
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);
565     len = Ustrlen(buffer);
566     if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
567       {
568       (void)close(fd);
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);
584     (void)close(fd);
585     fd = -2;
586     }
587   }
588
589 /* Return the sizes and the file descriptor, if any */
590
591 DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
592   " filecount=%d\n", size, filecount);
593 *returned_size = size;
594 *returned_filecount = filecount;
595 return fd;
596 }
597
598 /* End of tf_maildir.c */