Taint enforce: directory open backstops, single-key search filename
[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 /* 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   if (!(regex = pcre_compile(CS maildirfolder_create_regex, PCRE_COPT,
151     CCSS &error, &offset, NULL)))
152     {
153     addr->message = string_sprintf("appendfile: regular expression "
154       "error: %s at offset %d while compiling %s", error, offset,
155       maildirfolder_create_regex);
156     return FALSE;
157     }
158
159   if (pcre_exec(regex, NULL, CS path, Ustrlen(path), 0, 0, NULL, 0) >= 0)
160     {
161     uschar *fname = string_sprintf("%s/maildirfolder", path);
162     if (Ustat(fname, &statbuf) == 0)
163       {
164       DEBUG(D_transport) debug_printf("maildirfolder already exists\n");
165       }
166     else
167       {
168       int fd = Uopen(fname, O_WRONLY|O_APPEND|O_CREAT, 0600);
169       if (fd < 0)
170         {
171         addr->message = string_sprintf("appendfile: failed to create "
172           "maildirfolder file in %s directory: %s", path, strerror(errno));
173         return FALSE;
174         }
175       (void)close(fd);
176       DEBUG(D_transport) debug_printf("created maildirfolder file\n");
177       }
178     }
179   else
180     {
181     DEBUG(D_transport) debug_printf("maildirfolder file not required\n");
182     }
183   }
184
185 return TRUE;   /* Everything exists that should exist */
186 }
187
188
189
190
191 /*************************************************
192 *       Update maildirsizefile for new file      *
193 *************************************************/
194
195 /* This function is called to add a new line to the file, recording the length
196 of the newly added message. There isn't much we can do on failure...
197
198 Arguments:
199   fd           the open file descriptor
200   size         the size of the message
201
202 Returns:       nothing
203 */
204
205 void
206 maildir_record_length(int fd, int size)
207 {
208 int len;
209 uschar buffer[256];
210 sprintf(CS buffer, "%d 1\n", size);
211 len = Ustrlen(buffer);
212 if (lseek(fd, 0, SEEK_END) >= 0)
213   {
214   len = write(fd, buffer, len);
215   DEBUG(D_transport)
216     debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
217   }
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
257 if (!(dir = exim_opendir(path)))
258   return 0;
259
260 for (struct dirent *ent; ent = readdir(dir); )
261   {
262   uschar * s, * name = US ent->d_name;
263   struct stat statbuf;
264
265   if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
266
267   /* We are normally supplied with a regex for choosing which directories to
268   scan. We do the regex match first, because that avoids a stat() for names
269   we aren't interested in. */
270
271   if (dir_regex != NULL &&
272       pcre_exec(dir_regex, NULL, CS name, Ustrlen(name), 0, 0, NULL, 0) < 0)
273     {
274     DEBUG(D_transport)
275       debug_printf("skipping %s/%s: dir_regex does not match\n", path, name);
276     continue;
277     }
278
279   /* The name is OK; stat it. */
280
281   s = string_sprintf("%s/%s", path, name);
282   if (Ustat(s, &statbuf) < 0)
283     {
284     DEBUG(D_transport)
285       debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
286         s, strerror(errno));
287     continue;
288     }
289
290   if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
291     {
292     DEBUG(D_transport)
293       debug_printf("skipping %s/%s: not a directory\n", s, name);
294     continue;
295     }
296
297   /* Keep the latest timestamp encountered */
298
299   if (statbuf.st_mtime > *latest) *latest = statbuf.st_mtime;
300
301   /* If this is a maildir folder, call this function recursively. */
302
303   if (name[0] == '.')
304     sum += maildir_compute_size(s, filecount, latest, regex, dir_regex,
305       timestamp_only);
306
307   /* Otherwise it must be a folder that contains messages (e.g. new or cur), so
308   we need to get its size, unless all we are interested in is the timestamp. */
309
310   else if (!timestamp_only)
311     sum += check_dir_size(s, filecount, regex);
312   }
313
314 closedir(dir);
315 DEBUG(D_transport)
316   {
317   if (timestamp_only)
318     debug_printf("maildir_compute_size (timestamp_only): %ld\n",
319     (long int) *latest);
320   else
321     debug_printf("maildir_compute_size: path=%s\n  sum=" OFF_T_FMT
322       " filecount=%d timestamp=%ld\n",
323       path, sum, *filecount, (long int) *latest);
324   }
325 return sum;
326 }
327
328
329
330 /*************************************************
331 *        Create or update maildirsizefile        *
332 *************************************************/
333
334 /* This function is called before a delivery if the option to use
335 maildirsizefile is enabled. Its function is to create the file if it does not
336 exist, or to update it if that is necessary.
337
338 The logic in this function follows the rules that are described in
339
340   http://www.inter7.com/courierimap/README.maildirquota.html
341
342 Or, at least, it is supposed to!
343
344 Arguments:
345   path             the path to the maildir directory; this is already backed-up
346                      to the parent if the delivery directory is a maildirfolder
347   ob               the appendfile options block
348   regex            a compiled regex for getting a file's size from its name
349   dir_regex        a compiled regex for selecting maildir directories
350   returned_size    where to return the current size of the maildir, even if
351                      the maildirsizefile is removed because of a race
352
353 Returns:           >=0  a file descriptor for an open maildirsize file
354                    -1   there was an error opening or accessing the file
355                    -2   the file was removed because of a race
356 */
357
358 int
359 maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
360   const pcre *regex, const pcre *dir_regex, off_t *returned_size,
361   int *returned_filecount)
362 {
363 int count, fd;
364 off_t cached_quota = 0;
365 int cached_quota_filecount = 0;
366 int filecount = 0;
367 int linecount = 0;
368 off_t size = 0;
369 uschar *filename;
370 uschar buffer[MAX_FILE_SIZE];
371 uschar *ptr = buffer;
372 uschar *endptr;
373
374 /* Try a few times to open or create the file, in case another process is doing
375 the same thing. */
376
377 filename = string_sprintf("%s/maildirsize", path);
378
379 DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
380 if ((fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600)) < 0)
381   {
382   if (errno != ENOENT) return -1;
383   DEBUG(D_transport)
384     debug_printf("%s does not exist: recalculating\n", filename);
385   goto RECALCULATE;
386   }
387
388 /* The file has been successfully opened. Check that the cached quota value is
389 still correct, and that the size of the file is still small enough. If so,
390 compute the maildir size from the file. */
391
392 if ((count = read(fd, buffer, sizeof(buffer))) >= sizeof(buffer))
393   {
394   DEBUG(D_transport)
395     debug_printf("maildirsize file too big (%d): recalculating\n", count);
396   goto RECALCULATE;
397   }
398 buffer[count] = 0;   /* Ensure string terminated */
399
400 /* Read the quota parameters from the first line of the data. */
401
402 DEBUG(D_transport)
403   debug_printf("reading quota parameters from maildirsize data\n");
404
405 for (;;)
406   {
407   off_t n = (off_t)Ustrtod(ptr, &endptr);
408
409   /* Only two data items are currently defined; ignore any others that
410   may be present. The spec is for a number followed by a letter. Anything
411   else we reject and recalculate. */
412
413   if (*endptr == 'S') cached_quota = n;
414     else if (*endptr == 'C') cached_quota_filecount = (int)n;
415   if (!isalpha(*endptr++))
416     {
417     DEBUG(D_transport)
418       debug_printf("quota parameter number not followed by letter in "
419         "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
420         buffer);
421     goto RECALCULATE;
422     }
423   if (*endptr == '\n' || *endptr == 0) break;
424   if (*endptr++ != ',')
425     {
426     DEBUG(D_transport)
427       debug_printf("quota parameter not followed by comma in "
428         "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
429         buffer);
430     goto RECALCULATE;
431     }
432   ptr = endptr;
433   }
434
435 /* Check the cached values against the current settings */
436
437 if (cached_quota != ob->quota_value ||
438     cached_quota_filecount != ob->quota_filecount_value)
439   {
440   DEBUG(D_transport)
441     debug_printf("cached quota is out of date: recalculating\n"
442       "  quota=" OFF_T_FMT " cached_quota=" OFF_T_FMT " filecount_quota=%d "
443       "cached_quota_filecount=%d\n", ob->quota_value,
444       cached_quota, ob->quota_filecount_value, cached_quota_filecount);
445   goto RECALCULATE;
446   }
447
448 /* Quota values agree; parse the rest of the data to get the sizes. At this
449 stage, *endptr points either to 0 or to '\n'.  */
450
451 DEBUG(D_transport)
452   debug_printf("computing maildir size from maildirsize data\n");
453
454 while (*endptr++ == '\n')
455   {
456   if (*endptr == 0) break;
457   linecount++;
458   ptr = endptr;
459   size += (off_t)Ustrtod(ptr, &endptr);
460   if (*endptr != ' ') break;
461   ptr = endptr + 1;
462   filecount += Ustrtol(ptr, &endptr, 10);
463   }
464
465 /* If *endptr is zero, we have successfully parsed the file, and we now have
466 the size of the mailbox as cached in the file. The "rules" say that if this
467 value indicates that the mailbox is over quota, we must recalculate if there is
468 more than one entry in the file, or if the file is older than 15 minutes. Also,
469 just in case there are weird values in the file, recalculate if either of the
470 values is negative. */
471
472 if (*endptr == 0)
473   {
474   if (size < 0 || filecount < 0)
475     {
476     DEBUG(D_transport) debug_printf("negative value in maildirsize "
477       "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
478     goto RECALCULATE;
479     }
480
481   if (ob->quota_value > 0 &&
482       (size + (ob->quota_is_inclusive? message_size : 0) > ob->quota_value ||
483         (ob->quota_filecount_value > 0 &&
484           filecount + (ob->quota_is_inclusive ? 1:0) >
485             ob->quota_filecount_value)
486       ))
487     {
488     struct stat statbuf;
489     if (linecount > 1)
490       {
491       DEBUG(D_transport) debug_printf("over quota and maildirsize has "
492         "more than 1 entry: recalculating\n");
493       goto RECALCULATE;
494       }
495
496     if (fstat(fd, &statbuf) < 0) goto RECALCULATE;  /* Should never occur */
497
498     if (time(NULL) - statbuf.st_mtime > 15*60)
499       {
500       DEBUG(D_transport) debug_printf("over quota and maildirsize is older "
501         "than 15 minutes: recalculating\n");
502       goto RECALCULATE;
503       }
504     }
505   }
506
507
508 /* If *endptr is not zero, there was a syntax error in the file. */
509
510 else
511   {
512   int len;
513   time_t old_latest, new_latest;
514   uschar *tempname;
515   struct timeval tv;
516
517   DEBUG(D_transport)
518     {
519     uschar *p = endptr;
520     while (p > buffer && p[-1] != '\n') p--;
521     endptr[1] = 0;
522
523     debug_printf("error in maildirsizefile: unexpected character %d in "
524       "line %d (starting '%s'): recalculating\n",
525       *endptr, linecount + 1, string_printing(p));
526     }
527
528   /* Either there is no file, or the quota value has changed, or the file has
529   got too big, or there was some format error in the file. Recalculate the size
530   and write new contents to a temporary file; then rename it. After any
531   error, just return -1 as the file descriptor. */
532
533   RECALCULATE:
534
535   if (fd >= 0) (void)close(fd);
536   old_latest = 0;
537   filecount = 0;
538   size = maildir_compute_size(path, &filecount, &old_latest, regex, dir_regex,
539     FALSE);
540
541   (void)gettimeofday(&tv, NULL);
542   tempname = string_sprintf("%s/tmp/" TIME_T_FMT ".H%luP%lu.%s",
543     path, tv.tv_sec, tv.tv_usec, (long unsigned) getpid(), primary_hostname);
544
545   fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
546   if (fd >= 0)
547     {
548     (void)sprintf(CS buffer, OFF_T_FMT "S,%dC\n" OFF_T_FMT " %d\n",
549       ob->quota_value, ob->quota_filecount_value, size, filecount);
550     len = Ustrlen(buffer);
551     if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
552       {
553       (void)close(fd);
554       fd = -1;
555       }
556     }
557
558   /* If any of the directories have been modified since the last timestamp we
559   saw, we have to junk this maildirsize file. */
560
561   DEBUG(D_transport) debug_printf("checking subdirectory timestamps\n");
562   new_latest = 0;
563   (void)maildir_compute_size(path, NULL, &new_latest , NULL, dir_regex, TRUE);
564   if (new_latest > old_latest)
565     {
566     DEBUG(D_transport) debug_printf("abandoning maildirsize because of "
567       "a later subdirectory modification\n");
568     (void)Uunlink(filename);
569     (void)close(fd);
570     fd = -2;
571     }
572   }
573
574 /* Return the sizes and the file descriptor, if any */
575
576 DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
577   " filecount=%d\n", size, filecount);
578 *returned_size = size;
579 *returned_filecount = filecount;
580 return fd;
581 }
582
583 /* End of tf_maildir.c */