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