1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2019-2021 Free Software Foundation, Inc.
3 
4    This library is free software; you can redistribute it and/or modify
5    it under the terms of the GNU Lesser General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public License
15    along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include <config.h>
18 #include <stdlib.h>
19 #include <unistd.h>
20 #ifdef WITH_PTHREAD
21 # include <pthread.h>
22 #endif
23 #include <sys/stat.h>
24 #include <signal.h>
25 #include <mailutils/sys/mboxrd.h>
26 #include <mailutils/sys/mailbox.h>
27 #include <mailutils/sys/message.h>
28 #include <mailutils/diag.h>
29 #include <mailutils/errno.h>
30 #include <mailutils/url.h>
31 #include <mailutils/property.h>
32 #include <mailutils/io.h>
33 #include <mailutils/observer.h>
34 #include <mailutils/filter.h>
35 #include <mailutils/stream.h>
36 #include <mailutils/locker.h>
37 #include <mailutils/nls.h>
38 #include <mailutils/header.h>
39 #include <mailutils/attribute.h>
40 #include <mailutils/envelope.h>
41 #include <mailutils/util.h>
42 #include <mailutils/cctype.h>
43 #include <mailutils/sys/folder.h>
44 #include <mailutils/sys/registrar.h>
45 
46 static void
mboxrd_destroy(mu_mailbox_t mailbox)47 mboxrd_destroy (mu_mailbox_t mailbox)
48 {
49   size_t i;
50   struct mu_mboxrd_mailbox *dmp = mailbox->data;
51 
52   if (!dmp)
53     return;
54 
55   mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE1,
56 	    ("%s (%s)", __func__, dmp->name));
57   mu_monitor_wrlock (mailbox->monitor);
58   for (i = 0; i < dmp->mesg_count; i++)
59     {
60       mu_mboxrd_message_free (dmp->mesg[i]);
61     }
62   free (dmp->mesg);
63   free (dmp->name);
64   free (dmp);
65   mailbox->data = NULL;
66   mu_monitor_unlock (mailbox->monitor);
67 }
68 
69 static int
mboxrd_mailbox_init_stream(struct mu_mboxrd_mailbox * dmp)70 mboxrd_mailbox_init_stream (struct mu_mboxrd_mailbox *dmp)
71 {
72   int rc;
73   mu_mailbox_t mailbox = dmp->mailbox;
74 
75   /*
76    * Initialize stream flags.  If append mode is requested, convert it to
77    * read-write, so that mboxrd_flush_unlocked be able to update the
78    * X-IMAPbase header in the first message, if necessary.
79    */
80   dmp->stream_flags = mailbox->flags;
81   if (dmp->stream_flags & MU_STREAM_APPEND)
82     dmp->stream_flags = (dmp->stream_flags & ~MU_STREAM_APPEND) | MU_STREAM_RDWR;
83   else if (dmp->stream_flags & MU_STREAM_WRITE)
84     dmp->stream_flags |= MU_STREAM_READ;
85 
86   rc = mu_mapfile_stream_create (&mailbox->stream, dmp->name, dmp->stream_flags);
87   if (rc)
88     {
89       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
90 		("%s:%s (%s): %s",
91 		 __func__, "mu_mapfile_stream_create", dmp->name,
92 		 mu_strerror (rc)));
93 
94       /* Fallback to regular file stream */
95       rc = mu_file_stream_create (&mailbox->stream, dmp->name, dmp->stream_flags);
96       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
97 		("%s:%s (%s): %s",
98 		 __func__, "mu_file_stream_create", dmp->name,
99 		 mu_strerror (rc)));
100 
101       if (rc)
102 	return rc;
103     }
104 
105   mu_stream_set_buffer (mailbox->stream, mu_buffer_full, 0);
106   return 0;
107 }
108 
109 static int
mboxrd_open(mu_mailbox_t mailbox,int flags)110 mboxrd_open (mu_mailbox_t mailbox, int flags)
111 {
112   struct mu_mboxrd_mailbox *dmp = mailbox->data;
113   int rc;
114 
115   if (!dmp)
116     return EINVAL;
117 
118   mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE1,
119 	    ("%s(%s, 0x%x)", __func__, dmp->name, mailbox->flags));
120 
121   mailbox->flags = flags;
122 
123   rc = mboxrd_mailbox_init_stream (dmp);
124 
125   if (rc == 0
126       && mailbox->locker == NULL
127       && (flags & (MU_STREAM_WRITE | MU_STREAM_APPEND | MU_STREAM_CREAT)))
128     {
129       rc = mu_locker_create_ext (&mailbox->locker, dmp->name, NULL);
130       if (rc)
131 	mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
132 		  ("%s:%s (%s): %s",
133 		   __func__, "mu_locker_create_ext", dmp->name,
134 		   mu_strerror (rc)));
135     }
136 
137   return rc;
138 }
139 
140 enum
141   {
142     FLUSH_SYNC,
143     FLUSH_EXPUNGE, /* implies SYNC */
144     FLUSH_UIDVALIDITY
145   };
146 
147 static int mboxrd_flush (struct mu_mboxrd_mailbox *dmp, int flag);
148 
149 static int
mboxrd_close(mu_mailbox_t mailbox)150 mboxrd_close (mu_mailbox_t mailbox)
151 {
152   struct mu_mboxrd_mailbox *dmp = mailbox->data;
153   size_t i;
154 
155   if (!dmp)
156     return EINVAL;
157 
158   mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE1,
159 	    ("%s (%s)", __func__, dmp->name));
160 
161   if (dmp->uidvalidity_changed && (dmp->stream_flags & MU_STREAM_WRITE))
162     mboxrd_flush (dmp, FLUSH_UIDVALIDITY);
163 
164   mu_locker_unlock (mailbox->locker);
165   mu_monitor_wrlock (mailbox->monitor);
166   for (i = 0; i < dmp->mesg_count; i++)
167     {
168       mu_mboxrd_message_free (dmp->mesg[i]);
169     }
170   free (dmp->mesg);
171   dmp->mesg = NULL;
172   dmp->mesg_count = dmp->mesg_max = 0;
173   dmp->size = 0;
174   dmp->uidvalidity = 0;
175   dmp->uidnext = 1;
176   mu_monitor_unlock (mailbox->monitor);
177   mu_stream_destroy (&mailbox->stream);
178   return 0;
179 }
180 
181 static int
mboxrd_remove(mu_mailbox_t mailbox)182 mboxrd_remove (mu_mailbox_t mailbox)
183 {
184   struct mu_mboxrd_mailbox *dmp = mailbox->data;
185 
186   if (!dmp)
187     return EINVAL;
188   mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE1,
189 	    ("%s (%s)", __func__, dmp->name));
190   if (unlink (dmp->name))
191     return errno;
192   return 0;
193 }
194 
195 static int
mboxrd_is_updated(mu_mailbox_t mailbox)196 mboxrd_is_updated (mu_mailbox_t mailbox)
197 {
198   struct mu_mboxrd_mailbox *dmp = mailbox->data;
199   mu_off_t size = 0;
200 
201   if (!dmp)
202     return 0;
203 
204   if (mu_stream_size (mailbox->stream, &size) != 0)
205     return 1;
206   if (size < dmp->size)
207     {
208       mu_observable_notify (mailbox->observable, MU_EVT_MAILBOX_CORRUPT,
209 			    mailbox);
210       mu_diag_output (MU_DIAG_EMERG, _("mailbox corrupted, shrank in size"));
211       return 0;
212     }
213   return (dmp->size == size);
214 }
215 
216 #ifdef WITH_PTHREAD
217 void
mboxrd_cleanup(void * arg)218 mboxrd_cleanup (void *arg)
219 {
220   mu_mailbox_t mailbox = arg;
221   mu_monitor_unlock (mailbox->monitor);
222   mu_locker_unlock (mailbox->locker);
223 }
224 #endif
225 
226 static int
mboxrd_alloc_message(struct mu_mboxrd_mailbox * dmp,struct mu_mboxrd_message ** dmsg_ptr)227 mboxrd_alloc_message (struct mu_mboxrd_mailbox *dmp,
228 		       struct mu_mboxrd_message **dmsg_ptr)
229 {
230   struct mu_mboxrd_message *dmsg;
231 
232   if (dmp->mesg_count == dmp->mesg_max)
233     {
234       size_t n = dmp->mesg_max;
235       void *p;
236 
237       if (n == 0)
238 	n = 64;
239       else
240 	{
241 	  if ((size_t) -1 / 3 * 2 / sizeof (dmp->mesg[0]) <= n)
242 	    return ENOMEM;
243 	  n += (n + 1) / 2;
244 	}
245       p = realloc (dmp->mesg, n * sizeof (dmp->mesg[0]));
246       if (!p)
247 	return ENOMEM;
248       dmp->mesg = p;
249       dmp->mesg_max = n;
250     }
251   dmsg = calloc (1, sizeof (*dmsg));
252   if (!dmsg)
253     return ENOMEM;
254   dmsg->mbox = dmp;
255   dmsg->num = dmp->mesg_count;
256   dmp->mesg[dmp->mesg_count++] = dmsg;
257   *dmsg_ptr = dmsg;
258   return 0;
259 }
260 
261 static int
mboxrd_dispatch(mu_mailbox_t mailbox,int evt,void * data)262 mboxrd_dispatch (mu_mailbox_t mailbox, int evt, void *data)
263 {
264   if (!mailbox->observable)
265     return 0;
266 
267   mu_monitor_unlock (mailbox->monitor);
268   if (mu_observable_notify (mailbox->observable, evt, data))
269     {
270       if (mailbox->locker)
271 	mu_locker_unlock (mailbox->locker);
272       return EINTR;
273     }
274   mu_monitor_wrlock (mailbox->monitor);
275   return 0;
276 }
277 
278 /* Notes on the UID subsystem
279 
280    1. The values of uidvalidity and uidnext are stored in the
281       X-IMAPbase header in the first message.
282    2. Message UID is stored in the X-UID header in that message.
283    3. To minimize unwanted modifications to the mailbox, the
284       UID subsystem is initialized only in the following cases:
285 
286       3a. Upon mailbox scanning, if the first message contains a
287 	  valid X-IMAPbase header. In this case, the
288 	  mboxrd_rescan_unlocked function initializes each
289 	  message's uid value from the X-UID header. The first
290 	  message that lacks X-UID or with an X-UID that cannot
291 	  be parsed, gets assigned new UID. The subsequent
292 	  messages are assigned new UIDs no matter whether they
293 	  have X-UID headers. In this case, the uidvalidity value
294 	  is reset to the current timestamp, to indicate that all
295 	  UIDs might have changed.
296 
297       3b. When any of the following functions are called for
298 	  the first time: mboxrd_uidvalidity, mboxrd_uidnext,
299 	  mboxrd_message_uid. This means that the caller used
300 	  mu_mailbox_uidvalidity, mu_mailbox_uidnext, or
301 	  mu_message_get_uid.
302 	  In this case, each message is assigned a UID equal to
303 	  its ordinal number (1-based) in the mailbox.
304 	  This is done by the mu_mboxrd_mailbox_uid_setup function.
305 
306    4. When a message is appended to the mailbox, any existing
307       X-IMAPbase and X-UID headers are removed from it. If the
308       UID subsystem is initialized, the message is assigned a new
309       UID.
310    5. Assigning new UID to a message does not change its attributes.
311       Instead, its uid_modified flag is set.
312 */
313 
314 /* Allocate next available UID for the mailbox.
315    The caller must ensure that the UID subsystem is initialized.
316 */
317 static unsigned long
mboxrd_alloc_next_uid(struct mu_mboxrd_mailbox * mbox)318 mboxrd_alloc_next_uid (struct mu_mboxrd_mailbox *mbox)
319 {
320   mbox->uidvalidity_changed = 1;
321   return mbox->uidnext++;
322 }
323 
324 static void
mboxrd_message_alloc_uid(struct mu_mboxrd_message * dmsg)325 mboxrd_message_alloc_uid (struct mu_mboxrd_message *dmsg)
326 {
327   dmsg->uid = mboxrd_alloc_next_uid (dmsg->mbox);
328   dmsg->uid_modified = 1;
329 }
330 
331 /* Width of the decimal representation of the maximum value of the unsigned
332  * type t.  146/485 is the closest approximation of log10(2):
333  *
334  *  log10(2) = .301030
335  *  146/485  = .301031
336 */
337 #define UINT_STRWIDTH(t) ((int)((sizeof(t) * 8 * 146 + 484) / 485))
338 
339 /*
340  * The format for the X-IMAPbase header is:
341  *
342  *    X-IMAPbase: <V> <N>
343  *
344  * where <V> and <N> are current values of the uidvalidity and uidnext
345  * parameters, correspondingly.
346  *
347  * The header is stored in the first message.  To avoid rewriting entire
348  * mailbox when one of the parameters chages, the values of <V> and <N>
349  * are left-padded with spaces to the maximum width of their data types.
350  *
351  * Offset of the header in the mailbox and its length (without the
352  * trailing newline) are stored in x_imapbase_off and x_imapbase_len
353  * members of struct mu_mboxrd_mailbox.
354  *
355  * The X_IMAPBASE_MAX macro returns maximum size of the buffer necessary
356  * for formatting the X-IMAPbase header.  In fact, it is 2 bytes wider
357  * than necessary (due to the two '0' in the sample string below).
358  */
359 #define X_IMAPBASE_MAX(d)		   \
360   (sizeof (MU_HEADER_X_IMAPBASE ": 0 0") + \
361    UINT_STRWIDTH ((d)->uidvalidity) +	   \
362    UINT_STRWIDTH ((d)->uidnext))
363 
364 /*
365  * A modified version of Marc Crispin VALID macro.
366  *
367  * This function handles all existing flavors of the From_ line, most
368  * of which are antiquated and fallen out of use:
369  *
370  *              From user Wed Dec  2 05:53 1992
371  * BSD          From user Wed Dec  2 05:53:22 1992
372  * SysV         From user Wed Dec  2 05:53 PST 1992
373  * rn           From user Wed Dec  2 05:53:22 PST 1992
374  *              From user Wed Dec  2 05:53 -0700 1992
375  *              From user Wed Dec  2 05:53:22 -0700 1992
376  *              From user Wed Dec  2 05:53 1992 PST
377  *              From user Wed Dec  2 05:53:22 1992 PST
378  *              From user Wed Dec  2 05:53 1992 -0700
379  * Solaris      From user Wed Dec  2 05:53:22 1992 -0700
380  *
381  * (plus all of them followed by " remote from xxx").  Moreover, the
382  * user part is allowed to have whitespaces in it (although mailutils
383  * email address functions won't tolerate it).
384  *
385  * Input: S - line read from the mailbox (with \n terminator).
386  * Output: ZP - points to the space character preceding the time zone
387  * information in S.  If there is no TZ, points to the terminating \n.
388  *
389  * Return value: If S is a valid From_ line, a pointer to the time
390  * information in S.  Otherwise, NULL.
391  */
392 static inline char *
parse_from_line(char const * s,char ** zp)393 parse_from_line (char const *s, char **zp)
394 {
395   int ti = 0;
396   int zn;
397   // Validate if the string begins with 'From '
398   if ((*s == 'F') && (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') &&
399       (s[4] == ' '))
400     {
401       char *x = strchr (s, '\n');
402       if (x)
403 	{
404 	  if (x - s >= 41)
405 	    {
406 	      static char suf[] = " remote from ";
407 #             define suflen (sizeof(suf)-1)
408 
409 	      for (zn = -1; x + zn > s && x[zn] != ' '; (zn)--);
410 	      if (memcmp (x + zn - suflen + 1, suf, suflen) == 0)
411 		x += zn - suflen + 1;
412 	    }
413 	  if (x - s >= 27)
414 	    {
415 	      if (x[-5] == ' ')
416 		{
417 		  if (x[-8] == ':')
418 		    {
419 		      zn = 0;
420 		      ti = -5;
421 		    }
422 		  else if (x[-9] == ' ')
423 		    ti = zn = -9;
424 		  else if ((x[-11] == ' ')
425 			   && ((x[-10] == '+') || (x[-10] == '-')))
426 		    ti = zn = -11;
427 		}
428 	      else if (x[-4] == ' ')
429 		{
430 		  if (x[-9] == ' ')
431 		    {
432 		      zn = -4;
433 		      ti = -9;
434 		    }
435 		}
436 	      else if (x[-6] == ' ')
437 		{
438 		  if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-')))
439 		    {
440 		      zn = -6;
441 		      ti = -11;
442 		    }
443 		}
444 	      if (ti && !((x[ti - 3] == ':') &&
445 			  (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') &&
446 			  (x[ti - 3] == ' ') && (x[ti - 7] == ' ') &&
447 			  (x[ti - 11] == ' ')))
448 		ti = 0;
449 	    }
450 	}
451       if (ti)
452 	{
453 	  *zp = x + zn;
454 	  return (char*) x + ti;
455 	}
456     }
457   return NULL;
458 }
459 
460 /* Finalize current message */
461 static inline int
scan_message_finalize(struct mu_mboxrd_mailbox * dmp,struct mu_mboxrd_message * dmsg,mu_stream_t stream,size_t n,int * force_init_uids)462 scan_message_finalize (struct mu_mboxrd_mailbox *dmp,
463 		       struct mu_mboxrd_message *dmsg, mu_stream_t stream,
464 		       size_t n, int *force_init_uids)
465 {
466   int rc;
467   size_t count;
468 
469   rc = mu_stream_seek (stream, 0, MU_SEEK_CUR, &dmsg->message_end);
470   if (rc)
471     {
472       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
473 		("%s:%s (%s): %s",
474 		 __func__, "mu_stream_seek", dmp->name,
475 		 mu_strerror (rc)));
476       return -1;
477     }
478   dmsg->message_end -= n + 1;
479   if (dmsg->uid == 0)
480     *force_init_uids = 1;
481   if (*force_init_uids)
482     mboxrd_message_alloc_uid (dmsg);
483 
484   /* Every 100 mesgs update the lock, it should be every minute.  */
485   if (dmp->mailbox->locker && (dmp->mesg_count % 100) == 0)
486     mu_locker_touchlock (dmp->mailbox->locker);
487 
488   count = dmp->mesg_count;
489   mboxrd_dispatch (dmp->mailbox, MU_EVT_MESSAGE_ADD, &count);
490   return 0;
491 }
492 
493 static inline struct mu_mboxrd_message *
scan_message_begin(struct mu_mboxrd_mailbox * dmp,mu_stream_t stream,char * buf,size_t n,char * ti,char * zn)494 scan_message_begin (struct mu_mboxrd_mailbox *dmp, mu_stream_t stream,
495 		    char *buf, size_t n, char *ti, char *zn)
496 {
497   int rc;
498   struct mu_mboxrd_message *dmsg;
499 
500   /* Create new message */
501   rc = mboxrd_alloc_message (dmp, &dmsg);
502   if (rc)
503     {
504       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
505 		("%s:%s (%s): %s",
506 		 __func__, "mboxrd_alloc_message", dmp->name,
507 		 mu_strerror (rc)));
508       return NULL;
509     }
510   rc = mu_stream_seek (stream, 0, MU_SEEK_CUR, &dmsg->message_start);
511   if (rc)
512     {
513       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
514 		("%s:%s (%s): %s",
515 		 __func__, "mu_stream_seek", dmp->name,
516 		 mu_strerror (rc)));
517       return NULL;
518     }
519   dmsg->message_start -= n;
520   dmsg->from_length = n;
521   dmsg->env_sender_len = ti - buf - 10;
522   while (dmsg->env_sender_len > 6 && buf[dmsg->env_sender_len-1] == ' ')
523     dmsg->env_sender_len--;
524   dmsg->env_sender_len -= 5;
525 
526   if (zn[0] != '\n')
527     {
528       /*
529        * Ideally, From_ line should not contain time zone info.  If it does,
530        * zn points to the first space before the TZ.  The latter can be an
531        * abbreviated time zone or a numeric offset from UTC (see the comment
532        * to parse_from_line above).  Parse the date line and convert it to
533        * a normalized form (ctime(3)).
534        */
535       struct tm tm;
536       static char const fmt[] = "%a %b %e %H:%M%[:%S%] %(%Z %Y%|%Y%[ %Z%]%)";
537       struct mu_timezone tz;
538       char *te;
539       time_t t;
540 
541       if (mu_scan_datetime (ti - 10, fmt, &tm, &tz, &te) == 0)
542 	t = mu_datetime_to_utc (&tm, &tz);
543       else
544 	t = time (NULL);
545       gmtime_r (&t, &tm);
546       mu_strftime (dmsg->date, sizeof (dmsg->date), MU_DATETIME_FROM, &tm);
547     }
548   else
549     {
550       /*
551        * No zone information in the timestamp.  Copy the timestamp to
552        * the date buffer.  The timestamp may or may not contain seconds.
553        * In the latter case, assume '0' seconds.
554        */
555       if (ti[6] == ':')
556 	memcpy (dmsg->date, ti - 10, MU_DATETIME_FROM_LENGTH);
557       else
558 	{
559 	  memcpy (dmsg->date, ti - 10, 16);
560 	  memcpy (dmsg->date + 16, ":00", 3);
561 	  dmsg->date[19] = ' ';
562 	  memcpy (dmsg->date + 20, ti + 7, 4);
563 	}
564       dmsg->date[24] = 0;
565     }
566 
567   return dmsg;
568 }
569 
570 /* Scan the mailbox starting from the given offset.
571  *
572  * Notes on the mailbox format:
573  *
574  *  1. A mailbox consists of a series of messages.
575  *
576  *  2. Each message is preceded by a From_ line and followed by a blank line.
577  *     A From_ line is a line that begins with the five characters 'F', 'r',
578  *     'o', 'm', and ' ', followed by sender email and delivery date.  The
579  *     From_ line parser is able to handle various From_ line formats
580  *     (differing mainly in date/time format), most of which are encountered
581  *     only in ancient mailboxes.  Nevertheless, this makes escaping of the
582  *     From_ lines less crucial and ensures optimal robustness in handling
583  *     different mailbox formats (mboxo, mboxrd and mboxcl (mboxcl2) are
584  *     all handled properly.
585  *
586  *  3. The From_ lines and the blank lines bracket messages, fore and aft.
587  *     They do not comprise a message divider.  This means, in particular,
588  *     that both bracketing lines should be included in the message octet
589  *     and line counts.  However, counting the From_ line goes against
590  *     the mailutils approach of logically dividing envelope and the rest
591  *     of the message and would create useless differences compared to
592  *     another mailbox formats.  For this reason, the From_ line is not
593  *     reflected in returns from the mu_message_size and mu_message_lines
594  *     functions.  The terminating blank line, on the contrary, is assumed
595  *     to be part of the message body and is counted in body and message
596  *     size and line count computations.
597  *
598  *  4. A mailbox that contains zero messages contains no lines.
599  *
600  *  5. The first message in the mailbox is not preceded by a blank line.
601  *     The last message in the mailbox is not followed by a From_ line.
602  *
603  *  6. If a non-empty file does not have valid From_ construct in its
604  *     first physical line, it will be rejected by the parser.
605  *
606  *  7. A message may contain blank lines.  It should not, however, contain
607  *     lines beginning with the sequence 'F', 'r', 'o', 'm', ' '.
608  *
609  *  8. When incorporating a message into the mailbox, any line in the message
610  *     body that begins with zero or more '>' characters immediately followed
611  *     by the sequence 'F', 'r', 'o', 'm', ' ', is escaped by prepending it
612  *     with a '>' character.  Thus, "From " becomes ">From ", ">From "
613  *     becomes ">>From ", and so on.  When reading the message body, a
614  *     reverse operation is performed.
615  *
616  *  9. Last message in the mailbox is allowed to end with a partial last line
617  *     (i.e. the one whose final characters are not two newlines).  The message
618  *     will be handled as usual.  When a new message is incorporated to the
619  *     mailbox, the missing newlines will be added to the end of the last
620  *     message.
621  */
622 static int
mboxrd_rescan_unlocked(mu_mailbox_t mailbox,mu_off_t offset)623 mboxrd_rescan_unlocked (mu_mailbox_t mailbox, mu_off_t offset)
624 {
625   struct mu_mboxrd_mailbox *dmp = mailbox->data;
626   int rc;
627   mu_stream_t stream;
628   char *buf = NULL;
629   size_t bufsize = 0;
630   size_t n;
631   enum mboxrd_scan_state
632   {
633     mboxrd_scan_init,  /* At the beginning of the file */
634     mboxrd_scan_header,/* Scanning message header */
635     mboxrd_scan_body,  /* Scanning message body */
636     mboxrd_scan_empty_line /* At the empty line within or at the end of body */
637   } state = mboxrd_scan_init;
638   struct mu_mboxrd_message *dmsg = NULL;
639   char *zn, *ti;
640   int force_init_uids = 0;
641   size_t numlines = 0;
642 
643 # define IS_HEADER(h,b,n)			\
644   ((n) > sizeof (h) - 1				\
645    && strncasecmp (b, h, sizeof (h) - 1) == 0	\
646    && b[sizeof (h) - 1] == ':')
647 
648 
649   rc = mu_stream_size (mailbox->stream, &dmp->size);
650   if (rc)
651     return rc;
652   if (offset == dmp->size)
653     return 0;
654   if (!(dmp->stream_flags & MU_STREAM_READ))
655     return 0;
656 
657   rc = mu_streamref_create (&stream, mailbox->stream);
658   if (rc)
659     {
660       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
661 		("%s:%s (%s): %s",
662 		 __func__, "mu_streamref_create", dmp->name,
663 		 mu_strerror (rc)));
664       return rc;
665     }
666 
667   rc = mu_stream_seek (stream, offset, MU_SEEK_SET, NULL);
668   if (rc)
669     {
670       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
671 		("%s:%s (%s): %s",
672 		 __func__, "mu_stream_seek", dmp->name,
673 		 mu_strerror (rc)));
674       return rc;
675     }
676 
677   while ((rc = mu_stream_getline (stream, &buf, &bufsize, &n)) == 0
678 	 && n > 0)
679     {
680       switch (state)
681 	{
682 	case mboxrd_scan_init:
683 	  if ((ti = parse_from_line (buf, &zn)) == 0)
684 	    {
685 	      mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
686 			("%s does not start with a valid From_ line",
687 			 dmp->name));
688 	      rc = MU_ERR_PARSE;
689 	      goto err;
690 	    }
691 	  if ((dmsg = scan_message_begin (dmp, stream, buf, n, ti, zn)) == NULL)
692 	    goto err;
693 	  state = mboxrd_scan_header;
694 	  break;
695 
696 	case mboxrd_scan_header:
697 	  if (n == 1 && buf[0] == '\n')
698 	    {
699 	      rc = mu_stream_seek (stream, 0, MU_SEEK_CUR, &dmsg->body_start);
700 	      if (rc)
701 		{
702 		  mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
703 			    ("%s:%s (%s): %s",
704 			     __func__, "mu_stream_seek", dmp->name,
705 			     mu_strerror (rc)));
706 		  goto err;
707 		}
708 	      state = mboxrd_scan_body;
709 	    }
710 	  else if (mu_isspace (buf[0]))
711 	    continue;
712 	  else if (!dmp->uidvalidity_scanned
713 		   && IS_HEADER (MU_HEADER_X_IMAPBASE, buf, n))
714 	    {
715 	      if (sscanf (buf + sizeof (MU_HEADER_X_IMAPBASE),
716 			  "%lu %lu",
717 			  &dmp->uidvalidity, &dmp->uidnext) == 2)
718 		{
719 		  mu_off_t off;
720 
721 		  rc = mu_stream_seek (stream, 0, MU_SEEK_CUR, &off);
722 		  if (rc)
723 		    {
724 		      mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
725 				("%s:%s (%s): %s",
726 				 __func__, "mu_stream_seek", dmp->name,
727 				 mu_strerror (rc)));
728 		      goto err;
729 		    }
730 		  dmp->x_imapbase_len = n - 1;
731 		  dmp->x_imapbase_off = off - n;
732 		  dmp->uidvalidity_scanned = 1;
733 		}
734 	    }
735 	  else if (!force_init_uids
736 		   && dmsg->uid == 0
737 		   && IS_HEADER (MU_HEADER_X_UID, buf, n))
738 	    {
739 	      if (!(sscanf (buf + sizeof (MU_HEADER_X_UID), "%lu", &dmsg->uid) == 1
740 		    && dmsg->uid < dmp->uidnext
741 		    && (dmsg->num == 0 || dmsg->uid > dmp->mesg[dmsg->num - 1]->uid)))
742 		{
743 		  force_init_uids = 1;
744 		}
745 	    }
746 	  else if (IS_HEADER (MU_HEADER_STATUS, buf, n))
747 	    {
748 	      mu_attribute_string_to_flags (buf + sizeof (MU_HEADER_STATUS),
749 					    &dmsg->attr_flags);
750 	    }
751 	  break;
752 
753 	case mboxrd_scan_body:
754 	  if (n == 1 && buf[0] == '\n')
755 	    {
756 	      state = mboxrd_scan_empty_line;
757 	    }
758 	  break;
759 
760 	case mboxrd_scan_empty_line:
761 	  if ((ti = parse_from_line (buf, &zn)) != 0)
762 	    {
763 	      if (scan_message_finalize (dmp, dmsg, stream, n, &force_init_uids))
764 		goto err;
765 	      if ((dmsg = scan_message_begin (dmp, stream, buf, n, ti, zn)) == NULL)
766 		goto err;
767 	      state = mboxrd_scan_header;
768 	    }
769 	  else if (n == 1 && buf[0] == '\n')
770 	    state = mboxrd_scan_empty_line;
771 	  else
772 	    state = mboxrd_scan_body;
773 	}
774       if (++numlines % 1000 == 0)
775 	mboxrd_dispatch (mailbox, MU_EVT_MAILBOX_PROGRESS, NULL);
776     }
777 
778   if (dmsg)
779     {
780       if (scan_message_finalize (dmp, dmsg, stream, 0, &force_init_uids))
781 	goto err;
782     }
783 
784  err:
785   if (rc)
786     {
787       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
788 		("%s:%s (%s): %s",
789 		 __func__, "mu_stream_read", dmp->name,
790 		 mu_strerror (rc)));
791     }
792   mu_stream_unref (stream);
793   free (buf);
794 
795   if (force_init_uids)
796     {
797       dmp->uidvalidity = (unsigned long) time (NULL);
798       dmp->uidvalidity_changed = 1;
799       dmp->uidvalidity_scanned = 1;
800     }
801 
802   return rc;
803 }
804 
805 /* Scan the mailbox starting from the given offset */
806 static int
mboxrd_rescan(mu_mailbox_t mailbox,mu_off_t offset)807 mboxrd_rescan (mu_mailbox_t mailbox, mu_off_t offset)
808 {
809   struct mu_mboxrd_mailbox *dmp = mailbox->data;
810   int rc;
811 
812   if (!dmp)
813     return EINVAL;
814 
815   if (!(dmp->stream_flags & MU_STREAM_READ))
816     return 0;
817 
818   mu_monitor_wrlock (mailbox->monitor);
819 #ifdef WITH_PTHREAD
820   pthread_cleanup_push (mboxrd_cleanup, (void *)mailbox);
821 #endif
822 
823   if (mailbox->locker && (rc = mu_locker_lock (mailbox->locker)))
824     {
825       mu_monitor_unlock (mailbox->monitor);
826       return rc;
827     }
828 
829   rc = mboxrd_rescan_unlocked (mailbox, offset);
830 
831   if (mailbox->locker)
832     mu_locker_unlock (mailbox->locker);
833   mu_monitor_unlock (mailbox->monitor);
834 
835 #ifdef WITH_PTHREAD
836   pthread_cleanup_pop (0);
837 #endif
838 
839   return rc;
840 }
841 
842 static int
mboxrd_refresh(mu_mailbox_t mailbox)843 mboxrd_refresh (mu_mailbox_t mailbox)
844 {
845   struct mu_mboxrd_mailbox *dmp = mailbox->data;
846 
847   if (mboxrd_is_updated (mailbox))
848     return 0;
849   return mboxrd_rescan (mailbox,
850 			 dmp->mesg_count == 0
851 			   ? 0
852 			   : dmp->mesg[dmp->mesg_count - 1]->message_end + 1);
853 }
854 
855 static int
mboxrd_scan(mu_mailbox_t mailbox,size_t i,size_t * pcount)856 mboxrd_scan (mu_mailbox_t mailbox, size_t i, size_t *pcount)
857 {
858   struct mu_mboxrd_mailbox *dmp = mailbox->data;
859 
860   if (!dmp)
861     return EINVAL;
862 
863   mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE1,
864 	    ("%s (%s)", __func__, dmp->name));
865 
866   if (i == 0 || (dmp->mesg_count && i > dmp->mesg_count))
867     return EINVAL;
868 
869   if (!mboxrd_is_updated (mailbox))
870     {
871       int rc;
872 
873       while (i < dmp->mesg_count)
874 	mu_mboxrd_message_free (dmp->mesg[dmp->mesg_count--]);
875 
876       rc = mboxrd_refresh (mailbox);
877       if (rc)
878 	return rc;
879     }
880   else if (mailbox->observable)
881     {
882       for (; i <= dmp->mesg_count; i++)
883 	{
884 	  size_t tmp = i;
885 	  if (mu_observable_notify (mailbox->observable, MU_EVT_MESSAGE_ADD,
886 				    &tmp) != 0)
887 	    break;
888 	  /* FIXME: Hardcoded value! Must be configurable */
889 	  if (((i + 1) % 50) == 0)
890 	    mu_observable_notify (mailbox->observable, MU_EVT_MAILBOX_PROGRESS,
891 				  NULL);
892 	}
893     }
894   if (pcount)
895     *pcount = dmp->mesg_count;
896   return 0;
897 }
898 
899 static int
mboxrd_messages_recent(mu_mailbox_t mailbox,size_t * pcount)900 mboxrd_messages_recent (mu_mailbox_t mailbox, size_t *pcount)
901 {
902   size_t i;
903   size_t count = 0;
904   struct mu_mboxrd_mailbox *dmp = mailbox->data;
905 
906   int rc = mboxrd_refresh (mailbox);
907   if (rc)
908     return rc;
909 
910   for (i = 0; i < dmp->mesg_count; i++)
911     {
912       if (MU_ATTRIBUTE_IS_UNSEEN (dmp->mesg[i]->attr_flags))
913 	++count;
914     }
915 
916   *pcount = count;
917 
918   return 0;
919 }
920 
921 static int
mboxrd_message_unseen(mu_mailbox_t mailbox,size_t * pmsgno)922 mboxrd_message_unseen (mu_mailbox_t mailbox, size_t *pmsgno)
923 {
924   size_t i;
925   struct mu_mboxrd_mailbox *dmp = mailbox->data;
926 
927   int rc = mboxrd_refresh (mailbox);
928   if (rc)
929     return rc;
930 
931   for (i = 0; i < dmp->mesg_count; i++)
932     {
933       if (MU_ATTRIBUTE_IS_UNREAD (dmp->mesg[i]->attr_flags))
934 	{
935 	  *pmsgno = i + 1;
936 	  return 0;
937 	}
938     }
939 
940   *pmsgno = 0;
941   return 0;
942 }
943 
944 /* Initialize the mailbox UID subsystem. See the Notes above. */
945 int
mu_mboxrd_mailbox_uid_setup(struct mu_mboxrd_mailbox * dmp)946 mu_mboxrd_mailbox_uid_setup (struct mu_mboxrd_mailbox *dmp)
947 {
948   if (!dmp->uidvalidity_scanned)
949     {
950       size_t i;
951       int rc = mboxrd_refresh (dmp->mailbox);
952       if (rc || dmp->uidvalidity_scanned)
953 	return rc;
954 
955       dmp->uidvalidity = (unsigned long)time (NULL);
956       dmp->uidnext = 1;
957       dmp->uidvalidity_scanned = 1;
958       dmp->uidvalidity_changed = 1;
959 
960       for (i = 0; i < dmp->mesg_count; i++)
961 	mboxrd_message_alloc_uid (dmp->mesg[i]);
962     }
963   return 0;
964 }
965 
966 static int
mboxrd_get_uidvalidity(mu_mailbox_t mailbox,unsigned long * puidvalidity)967 mboxrd_get_uidvalidity (mu_mailbox_t mailbox, unsigned long *puidvalidity)
968 {
969   struct mu_mboxrd_mailbox *dmp = mailbox->data;
970   int rc = mu_mboxrd_mailbox_uid_setup (dmp);
971   if (rc == 0)
972     *puidvalidity = dmp->uidvalidity;
973   return rc;
974 }
975 
976 static int
mboxrd_set_uidvalidity(mu_mailbox_t mailbox,unsigned long uidvalidity)977 mboxrd_set_uidvalidity (mu_mailbox_t mailbox, unsigned long uidvalidity)
978 {
979   struct mu_mboxrd_mailbox *dmp = mailbox->data;
980   int rc = mu_mboxrd_mailbox_uid_setup (dmp);
981   if (rc == 0)
982     dmp->uidvalidity = uidvalidity;
983   return rc;
984 }
985 
986 static int
mboxrd_uidnext(mu_mailbox_t mailbox,size_t * puidnext)987 mboxrd_uidnext (mu_mailbox_t mailbox, size_t *puidnext)
988 {
989   struct mu_mboxrd_mailbox *dmp = mailbox->data;
990   int rc = mu_mboxrd_mailbox_uid_setup (dmp);
991   if (rc == 0)
992     *puidnext = dmp->uidnext;
993   return rc;
994 }
995 
996 static int
mboxrd_get_message(mu_mailbox_t mailbox,size_t msgno,mu_message_t * pmsg)997 mboxrd_get_message (mu_mailbox_t mailbox, size_t msgno, mu_message_t *pmsg)
998 {
999   struct mu_mboxrd_mailbox *dmp = mailbox->data;
1000   int rc;
1001 
1002   if (!dmp || msgno < 1)
1003     return EINVAL;
1004   if (pmsg == NULL)
1005     return MU_ERR_OUT_PTR_NULL;
1006 
1007   if (dmp->mesg_count == 0)
1008     {
1009       rc = mboxrd_scan (mailbox, 1, NULL);
1010       if (rc)
1011 	return rc;
1012     }
1013 
1014   if (msgno > dmp->mesg_count)
1015     return MU_ERR_NOENT;
1016 
1017   return mu_mboxrd_message_get (dmp->mesg[msgno-1], pmsg);
1018 }
1019 
1020 static int
qid2off(mu_message_qid_t qid,mu_off_t * pret)1021 qid2off (mu_message_qid_t qid, mu_off_t *pret)
1022 {
1023   mu_off_t ret = 0;
1024   for (;*qid; qid++)
1025     {
1026       if (!('0' <= *qid && *qid <= '9'))
1027 	return 1;
1028       ret = ret * 10 + *qid - '0';
1029     }
1030   *pret = ret;
1031   return 0;
1032 }
1033 
1034 static int
mboxrd_quick_get_message(mu_mailbox_t mailbox,mu_message_qid_t qid,mu_message_t * pmsg)1035 mboxrd_quick_get_message (mu_mailbox_t mailbox, mu_message_qid_t qid,
1036 			   mu_message_t *pmsg)
1037 {
1038   int rc;
1039   struct mu_mboxrd_mailbox *dmp = mailbox->data;
1040   struct mu_mboxrd_message *dmsg;
1041   mu_off_t offset;
1042 
1043   if (mailbox == NULL || qid2off (qid, &offset)
1044       || !(mailbox->flags & MU_STREAM_QACCESS))
1045     return EINVAL;
1046 
1047   if (dmp->mesg_count == 0)
1048     {
1049       rc = mboxrd_rescan (mailbox, offset);
1050       if (rc)
1051 	return rc;
1052       if (dmp->mesg_count == 0)
1053 	return MU_ERR_NOENT;
1054     }
1055 
1056   dmsg = dmp->mesg[0];
1057   if (dmsg->message_start != offset)
1058     return MU_ERR_EXISTS;
1059   if (dmsg->message)
1060     {
1061       if (pmsg)
1062 	*pmsg = dmsg->message;
1063       return 0;
1064     }
1065   return mu_mboxrd_message_get (dmsg, pmsg);
1066 }
1067 
1068 static int
mailbox_append_message(mu_mailbox_t mailbox,mu_message_t msg)1069 mailbox_append_message (mu_mailbox_t mailbox, mu_message_t msg)
1070 {
1071   int rc;
1072   mu_off_t size;
1073   mu_stream_t istr, flt;
1074   static char *exclude_headers[] = {
1075     MU_HEADER_X_IMAPBASE,
1076     MU_HEADER_X_UID,
1077     NULL
1078   };
1079   struct mu_mboxrd_mailbox *dmp = mailbox->data;
1080 
1081   if (dmp->mesg_count)
1082     {
1083       char nl[2];
1084       static char pad[] = { '\n', '\n' };
1085       int n;
1086 
1087       size = dmp->mesg[dmp->mesg_count-1]->message_end - 1;
1088       rc = mu_stream_seek (mailbox->stream, size, MU_SEEK_SET, NULL);
1089       if (rc)
1090 	return rc;
1091       rc = mu_stream_read (mailbox->stream, nl, 2, NULL);
1092       if (rc)
1093 	return rc;
1094 
1095       if (nl[1] != '\n')
1096 	n = 2;
1097       else if (nl[0] != '\n')
1098 	n = 1;
1099       else
1100 	n = 0;
1101 
1102       if (n)
1103 	{
1104 	  mu_stream_write (mailbox->stream, pad, n, NULL);
1105 	}
1106       size += n + 2;
1107     }
1108   else
1109     {
1110       size = 0;
1111       rc = mu_stream_seek (mailbox->stream, size, MU_SEEK_SET, NULL);
1112     }
1113 
1114   if (rc)
1115     return rc;
1116 
1117   rc = mu_message_get_streamref (msg, &istr);
1118   if (rc)
1119     return rc;
1120 
1121   do
1122     {
1123       mu_envelope_t env;
1124       char *date = NULL;
1125       char *sender = NULL;
1126 
1127       rc = mu_message_get_envelope (msg, &env);
1128       if (rc)
1129 	break;
1130 
1131       rc = mu_envelope_aget_sender (env, &sender);
1132       if (rc == 0)
1133 	{
1134 	  rc = mu_envelope_aget_date (env, &date);
1135 	  if (rc)
1136 	    {
1137 	      rc = mu_message_reconstruct_envelope (msg, &env);
1138 	      if (rc == 0)
1139 		{
1140 		  rc = mu_envelope_aget_sender (env, &sender);
1141 		  if (rc == 0)
1142 		    {
1143 		      rc = mu_envelope_aget_date (env, &date);
1144 		      if (rc == MU_ERR_NOENT)
1145 			{
1146 			  date = strdup ("Thu Jan  1 00:00:00 1970");
1147 			  if (date)
1148 			    rc = 0;
1149 			  else
1150 			    rc = ENOMEM;
1151 			}
1152 		    }
1153 
1154 		  mu_envelope_destroy (&env, msg);
1155 		}
1156 	    }
1157 
1158 	  if (rc)
1159 	    {
1160 	      free (sender);
1161 	      break;
1162 	    }
1163 
1164 	  rc = mu_stream_printf (mailbox->stream, "From %s %s\n",
1165 				 sender, date);
1166 	  free (sender);
1167 	  free (date);
1168 	}
1169 
1170       if (rc)
1171 	break;
1172 
1173       rc = mu_stream_header_copy (mailbox->stream, istr, exclude_headers);
1174       if (rc)
1175 	break;
1176 
1177       /* Write UID-related data */
1178       if (dmp->uidvalidity_scanned)
1179 	{
1180 	  if (dmp->mesg_count == 0)
1181 	    mu_stream_printf (mailbox->stream, "%s: %*lu %*lu\n",
1182 			      MU_HEADER_X_IMAPBASE,
1183 			      UINT_STRWIDTH (dmp->uidvalidity),
1184 			      dmp->uidvalidity,
1185 			      UINT_STRWIDTH (dmp->uidnext),
1186 			      dmp->uidnext);
1187 	  mu_stream_printf (mailbox->stream, "%s: %lu\n",
1188 			    MU_HEADER_X_UID,
1189 			    mboxrd_alloc_next_uid (dmp));
1190 	}
1191 
1192       rc = mu_stream_write (mailbox->stream, "\n", 1, NULL);
1193       if (rc)
1194 	break;
1195 
1196       rc = mu_filter_create (&flt, istr, "FROMRD",
1197 			     MU_FILTER_ENCODE, MU_STREAM_READ);
1198       mu_stream_destroy (&istr);
1199       rc = mu_stream_copy_nl (mailbox->stream, flt, 0, NULL);
1200       mu_stream_unref (flt);
1201     }
1202   while (0);
1203 
1204   if (rc)
1205     {
1206       int rc1;
1207       mu_stream_destroy (&istr);
1208       rc1 = mu_stream_truncate (mailbox->stream, size);
1209       if (rc1)
1210 	mu_error (_("cannot truncate stream after failed append: %s"),
1211 		  mu_stream_strerror (mailbox->stream, rc1));
1212       return rc;
1213     }
1214 
1215   /* Rescan the message */
1216   rc = mboxrd_rescan_unlocked (mailbox, size);
1217   if (rc)
1218     return rc;
1219 
1220   if (mailbox->observable)
1221     {
1222       char *buf = NULL;
1223       mu_asprintf (&buf, "%lu", (unsigned long) size);
1224       mu_observable_notify (mailbox->observable,
1225 			    MU_EVT_MAILBOX_MESSAGE_APPEND, buf);
1226       free (buf);
1227     }
1228 
1229   return 0;
1230 }
1231 
1232 static int
mboxrd_append_message(mu_mailbox_t mailbox,mu_message_t msg)1233 mboxrd_append_message (mu_mailbox_t mailbox, mu_message_t msg)
1234 {
1235   struct mu_mboxrd_mailbox *dmp = mailbox->data;
1236   int rc;
1237 
1238   rc = mboxrd_refresh (mailbox);
1239   if (rc)
1240     return rc;
1241 
1242   mu_monitor_wrlock (mailbox->monitor);
1243   if (mailbox->locker && (rc = mu_locker_lock (mailbox->locker)) != 0)
1244     {
1245       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
1246 		("%s(%s):%s: %s",
1247 		 __func__, dmp->name, "mu_locker_lock",
1248 		 mu_strerror (rc)));
1249     }
1250   else
1251     {
1252       rc = mailbox_append_message (mailbox, msg);
1253 
1254       if (mailbox->locker)
1255 	mu_locker_unlock (mailbox->locker);
1256     }
1257   mu_monitor_unlock (mailbox->monitor);
1258   return rc;
1259 }
1260 
1261 static int
mboxrd_messages_count(mu_mailbox_t mailbox,size_t * pcount)1262 mboxrd_messages_count (mu_mailbox_t mailbox, size_t *pcount)
1263 {
1264   struct mu_mboxrd_mailbox *dmp = mailbox->data;
1265   int rc;
1266 
1267   if (!dmp)
1268     return EINVAL;
1269 
1270   rc = mboxrd_refresh (mailbox);
1271   if (rc)
1272     return rc;
1273 
1274   if (pcount)
1275     *pcount = dmp->mesg_count;
1276 
1277   return 0;
1278 }
1279 
1280 static int
mboxrd_get_size(mu_mailbox_t mailbox,mu_off_t * psize)1281 mboxrd_get_size (mu_mailbox_t mailbox, mu_off_t *psize)
1282 {
1283   mu_off_t size;
1284   int rc;
1285 
1286   rc  = mu_stream_size (mailbox->stream, &size);
1287   if (rc != 0)
1288     return rc;
1289   if (psize)
1290     *psize = size;
1291   return 0;
1292 }
1293 
1294 static int
mboxrd_stat(mu_mailbox_t mailbox,struct stat * st)1295 mboxrd_stat (mu_mailbox_t mailbox, struct stat *st)
1296 {
1297   int rc;
1298   mu_transport_t trans[2];
1299 
1300   rc = mu_stream_ioctl (mailbox->stream, MU_IOCTL_TRANSPORT,
1301 			MU_IOCTL_OP_GET, trans);
1302   if (rc == 0)
1303     {
1304       if (fstat ((int) (intptr_t) trans[0], st))
1305 	rc = errno;
1306     }
1307   return rc;
1308 }
1309 
1310 static int
mboxrd_set_priv(struct mu_mboxrd_mailbox * dmp,struct stat * st)1311 mboxrd_set_priv (struct mu_mboxrd_mailbox *dmp, struct stat *st)
1312 {
1313   int rc;
1314   mu_transport_t trans[2];
1315 
1316   rc = mu_stream_ioctl (dmp->mailbox->stream, MU_IOCTL_TRANSPORT,
1317 			MU_IOCTL_OP_GET, trans);
1318   if (rc == 0)
1319     {
1320       int fd = (intptr_t) trans[0];
1321       if (fchmod (fd, st->st_mode))
1322 	{
1323 	  mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
1324 		    ("%s:%s: chmod failed: %s",
1325 		     __func__, dmp->name, strerror (errno)));
1326 	  rc = errno;
1327 	}
1328       else if (fchown (fd, st->st_uid, st->st_gid))
1329 	{
1330 	  mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
1331 		    ("%s:%s: chown failed: %s",
1332 		     __func__, dmp->name, strerror (errno)));
1333 	  rc = errno;
1334 	}
1335     }
1336   return rc;
1337 }
1338 
1339 static int
mboxrd_get_atime(mu_mailbox_t mailbox,time_t * return_time)1340 mboxrd_get_atime (mu_mailbox_t mailbox, time_t *return_time)
1341 {
1342   struct mu_mboxrd_mailbox *dmp = mailbox->data;
1343   struct stat st;
1344   int rc;
1345 
1346   if (dmp == NULL)
1347     return EINVAL;
1348   if ((rc = mboxrd_stat (mailbox, &st)) == 0)
1349     *return_time = st.st_atime;
1350   return rc;
1351 }
1352 
1353 struct mu_mboxrd_flush_tracker
1354 {
1355   struct mu_mboxrd_mailbox *dmp;
1356   size_t *ref;
1357   size_t mesg_count;
1358 };
1359 
1360 static int
tracker_init(struct mu_mboxrd_flush_tracker * trk,struct mu_mboxrd_mailbox * dmp)1361 tracker_init (struct mu_mboxrd_flush_tracker *trk,
1362 	      struct mu_mboxrd_mailbox *dmp)
1363 {
1364   trk->ref = calloc (dmp->mesg_count, sizeof (trk->ref[0]));
1365   if (!trk->ref)
1366     return ENOMEM;
1367   trk->dmp = dmp;
1368   trk->mesg_count = 0;
1369   return 0;
1370 }
1371 
1372 static void
tracker_free(struct mu_mboxrd_flush_tracker * trk)1373 tracker_free (struct mu_mboxrd_flush_tracker *trk)
1374 {
1375   free (trk->ref);
1376 }
1377 
1378 static struct mu_mboxrd_message *
tracker_next_ref(struct mu_mboxrd_flush_tracker * trk,size_t orig_num)1379 tracker_next_ref (struct mu_mboxrd_flush_tracker *trk, size_t orig_num)
1380 {
1381   trk->ref[trk->mesg_count++] = orig_num;
1382   return trk->dmp->mesg[orig_num];
1383 }
1384 
1385 static void
mboxrd_tracker_sync(struct mu_mboxrd_flush_tracker * trk)1386 mboxrd_tracker_sync (struct mu_mboxrd_flush_tracker *trk)
1387 {
1388   struct mu_mboxrd_mailbox *dmp = trk->dmp;
1389   size_t i;
1390 
1391   if (trk->mesg_count == 0)
1392     {
1393       for (i = 0; i < dmp->mesg_count; i++)
1394 	mu_mboxrd_message_free (dmp->mesg[i]);
1395       dmp->size = 0;
1396       dmp->uidvalidity_scanned = 0;
1397     }
1398   else
1399     {
1400       /* Mark */
1401       for (i = 0; i < trk->mesg_count; i++)
1402 	dmp->mesg[trk->ref[i]]->mark = 1;
1403       /* Sweep */
1404       for (i = 0; i < dmp->mesg_count; i++)
1405 	if (!dmp->mesg[i]->mark)
1406 	  mu_mboxrd_message_free (dmp->mesg[i]);
1407       /* Reorder */
1408       for (i = 0; i < trk->mesg_count; i++)
1409 	{
1410 	  dmp->mesg[i] = dmp->mesg[trk->ref[i]];
1411 	  dmp->mesg[i]->mark = 0;
1412 	}
1413       dmp->mesg_count = trk->mesg_count;
1414       dmp->size = dmp->mesg[dmp->mesg_count - 1]->message_end + 1;
1415     }
1416   dmp->mesg_count = trk->mesg_count;
1417   /* FIXME: Check uidvalidity values?? */
1418 }
1419 
1420 /* Write to the output stream DEST messages in the range [from,to).
1421    Update TRK accordingly.
1422 */
1423 static int
mboxrd_mailbox_copy_unchanged(struct mu_mboxrd_flush_tracker * trk,size_t from,size_t to,mu_stream_t dest)1424 mboxrd_mailbox_copy_unchanged (struct mu_mboxrd_flush_tracker *trk,
1425 				size_t from, size_t to,
1426 				mu_stream_t dest)
1427 {
1428   if (to > from)
1429     {
1430       struct mu_mboxrd_mailbox *dmp = trk->dmp;
1431       mu_off_t start;
1432       mu_off_t stop;
1433       size_t i;
1434       mu_off_t off;
1435       int rc;
1436 
1437       start = dmp->mesg[from]->message_start;
1438       if (to == dmp->mesg_count)
1439 	stop = dmp->mesg[to-1]->message_end + 1;
1440       else
1441 	stop = dmp->mesg[to]->message_start;
1442 
1443       rc = mu_stream_seek (dest, 0, MU_SEEK_CUR, &off);
1444       if (rc)
1445 	return rc;
1446       off -= start;
1447       /* Fixup offsets */
1448       for (i = from; i < to; i++)
1449 	{
1450 	  struct mu_mboxrd_message *ref = tracker_next_ref (trk, i);
1451 	  ref->message_start += off;
1452 	  ref->body_start += off;
1453 	  ref->message_end += off;
1454 	}
1455 
1456       /* Copy data */
1457       rc = mu_stream_seek (dmp->mailbox->stream, start, MU_SEEK_SET, NULL);
1458       if (rc)
1459 	return rc;
1460       return mu_stream_copy (dest, dmp->mailbox->stream, stop - start, NULL);
1461     }
1462   return 0;
1463 }
1464 
1465 /* Flush the mailbox described by the tracker TRK to the stream TEMPSTR.
1466    First modified message is I (0-based). EXPUNGE is 1 if the
1467    MU_ATTRIBUTE_DELETED attribute is to be honored.
1468 */
1469 static int
mboxrd_flush_temp(struct mu_mboxrd_flush_tracker * trk,size_t i,mu_stream_t tempstr,int expunge)1470 mboxrd_flush_temp (struct mu_mboxrd_flush_tracker *trk,
1471 		    size_t i,
1472 		    mu_stream_t tempstr, int expunge)
1473 {
1474   struct mu_mboxrd_mailbox *dmp = trk->dmp;
1475   size_t start = 0;
1476   size_t save_imapbase = 0;
1477   size_t expcount = 0;
1478   int rc;
1479 
1480   rc = mu_stream_seek (trk->dmp->mailbox->stream, 0, MU_SEEK_SET, NULL);
1481   if (rc)
1482     return rc;
1483   while (i < dmp->mesg_count)
1484     {
1485       struct mu_mboxrd_message *dmsg = dmp->mesg[i];
1486 
1487       if (expunge && (dmsg->attr_flags & MU_ATTRIBUTE_DELETED))
1488 	{
1489 	  size_t expevt[2] = { i + 1, expcount };
1490 
1491 	  rc = mboxrd_mailbox_copy_unchanged (trk, start, i, tempstr);
1492 	  if (rc)
1493 	    return rc;
1494 	  mu_observable_notify (dmp->mailbox->observable,
1495 				MU_EVT_MAILBOX_MESSAGE_EXPUNGE,
1496 				expevt);
1497 	  expcount++;
1498 	  mu_message_destroy (&dmsg->message, dmsg);
1499 
1500 	  /* Make sure uidvalidity and next uid are preserved even if
1501 	     the first message (where they are saved) is deleted */
1502 	  if (i == save_imapbase)
1503 	    {
1504 	      save_imapbase = i + 1;
1505 	      if (save_imapbase < dmp->mesg_count)
1506 		dmp->mesg[save_imapbase]->attr_flags |= MU_ATTRIBUTE_MODIFIED;
1507 	    }
1508 	  i++;
1509 	  start = i;
1510 	  continue;
1511 	}
1512 
1513       if (dmsg->uid_modified
1514 	  || (dmsg->attr_flags & MU_ATTRIBUTE_MODIFIED)
1515 	  || mu_message_is_modified (dmsg->message))
1516 	{
1517 	  char *x_imapbase = NULL;
1518 
1519 	  rc = mboxrd_mailbox_copy_unchanged (trk, start, i, tempstr);
1520 	  if (rc)
1521 	    return rc;
1522 	  if (save_imapbase == i)
1523 	    {
1524 	      mu_asprintf (&x_imapbase, "%*lu %*lu",
1525 			   UINT_STRWIDTH (dmp->uidvalidity),
1526 			   dmp->uidvalidity,
1527 			   UINT_STRWIDTH (dmp->uidnext),
1528 			   dmp->uidnext);
1529 	    }
1530 	  rc = mu_mboxrd_message_reconstruct (tempstr, dmsg,
1531 					      tracker_next_ref (trk, i),
1532 					      x_imapbase);
1533 	  free (x_imapbase);
1534 	  if (rc)
1535 	    return rc;
1536 	  i++;
1537 	  start = i;
1538 	  continue;
1539 	}
1540 
1541       i++;
1542     }
1543   rc = mboxrd_mailbox_copy_unchanged (trk, start, i, tempstr);
1544   if (rc)
1545     return rc;
1546   if (trk->mesg_count)
1547     {
1548       rc = mu_stream_truncate (tempstr,
1549 			       trk->dmp->mesg[trk->ref[trk->mesg_count - 1]]->message_end + 1);
1550     }
1551 
1552   return mu_stream_flush (tempstr);
1553 }
1554 
1555 /*
1556  * Copy the temporary mailbox stream TEMPSTR to the mailbox referred to by
1557  * the tracker TRK.
1558  */
1559 static inline int
mboxrd_copyback(struct mu_mboxrd_flush_tracker * trk,mu_stream_t tempstr)1560 mboxrd_copyback (struct mu_mboxrd_flush_tracker *trk, mu_stream_t tempstr)
1561 {
1562   int rc;
1563   mu_stream_t mbx_stream = trk->dmp->mailbox->stream;
1564   mu_off_t size;
1565 
1566   rc = mu_stream_seek (tempstr, 0, MU_SEEK_SET, NULL);
1567   if (rc)
1568     {
1569       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
1570 		("%s: can't rewind temporary file: %s",
1571 		 __func__, mu_strerror (rc)));
1572       return rc;
1573     }
1574 
1575   rc = mu_stream_seek (mbx_stream, 0, MU_SEEK_SET, NULL);
1576   if (rc)
1577     {
1578       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
1579 		("%s: can't rewind mailbox %s: %s",
1580 		 __func__, trk->dmp->name, mu_strerror (rc)));
1581       return rc;
1582     }
1583 
1584   rc = mu_stream_copy (mbx_stream, tempstr, 0, &size);
1585   if (rc)
1586     {
1587       mu_error (_("copying back to mailbox %s failed: %s"),
1588 		trk->dmp->name, mu_strerror (rc));
1589       return rc;
1590     }
1591   rc = mu_stream_truncate (mbx_stream, size);
1592   if (rc)
1593     {
1594       mu_error (_("cannot truncate mailbox stream: %s"),
1595 		mu_stream_strerror (mbx_stream, rc));
1596       return rc;
1597     }
1598 
1599   mboxrd_tracker_sync (trk);
1600   return 0;
1601 }
1602 
1603 /* Flush the mailbox described by the tracker TRK to the stream TEMPSTR.
1604    EXPUNGE is 1 if the MU_ATTRIBUTE_DELETED attribute is to be honored.
1605    Assumes that simultaneous access to the mailbox has been blocked.
1606 */
1607 static int
mboxrd_flush_unlocked(struct mu_mboxrd_flush_tracker * trk,int mode)1608 mboxrd_flush_unlocked (struct mu_mboxrd_flush_tracker *trk, int mode)
1609 {
1610   struct mu_mboxrd_mailbox *dmp = trk->dmp;
1611   int rc;
1612   size_t dirty;
1613   mu_stream_t tempstr;
1614   struct mu_tempfile_hints hints;
1615   int tempfd;
1616   char *tempname;
1617   char *p;
1618 
1619   mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE1,
1620 	    ("%s (%s)", __func__, dmp->name));
1621   if (dmp->mesg_count == 0)
1622     return 0;
1623   if (mode == FLUSH_UIDVALIDITY && !dmp->uidvalidity_changed)
1624     return 0;
1625 
1626   rc = mboxrd_refresh (dmp->mailbox);
1627   if (rc)
1628     return rc;
1629 
1630   if (dmp->uidvalidity_changed)
1631     {
1632       size_t i;
1633       char buf[X_IMAPBASE_MAX (dmp)];
1634       int n;
1635       mu_stream_t stream = dmp->mailbox->stream;
1636 
1637       /*
1638        * Format the X-IMAPbase header and check if it will fit in place
1639        * of the existing one (if any).  If so, write it at once and return.
1640        */
1641       n = snprintf (buf, sizeof (buf), "%s: %*lu %*lu",
1642 		    MU_HEADER_X_IMAPBASE,
1643 		    UINT_STRWIDTH (dmp->uidvalidity),
1644 		    dmp->uidvalidity,
1645 		    UINT_STRWIDTH (dmp->uidnext),
1646 		    dmp->uidnext);
1647 
1648       if (dmp->x_imapbase_len && dmp->x_imapbase_len >= n)
1649 	{
1650 	  rc = mu_stream_seek (stream, dmp->x_imapbase_off, MU_SEEK_SET, NULL);
1651 	  if (rc)
1652 	    {
1653 	      mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
1654 			("%s:%s (%s): %s",
1655 			 __func__, "mu_stream_seek", dmp->name,
1656 			 mu_strerror (rc)));
1657 	      return rc;
1658 	    }
1659 	  rc = mu_stream_printf (stream, "%-*s",
1660 				 (int) dmp->x_imapbase_len,
1661 				 buf);
1662 	  if (rc)
1663 	    {
1664 	      mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
1665 			("%s:%s (%s): %s",
1666 			 __func__, "mu_stream_printf", dmp->name,
1667 			 mu_strerror (rc)));
1668 	      return rc;
1669 	    }
1670 	}
1671       else
1672 	{
1673 	  /*
1674 	   * There is no X-IMAPbase header yet or it is not wide enough to
1675 	   * accept the current value.  Fall back to reformatting entire
1676 	   * mailbox.  Clear any other changes that might have been done
1677 	   * to its messages.
1678 	   */
1679 	  dmp->mesg[0]->uid_modified = 1;
1680 
1681 	  if (mode == FLUSH_UIDVALIDITY)
1682 	    {
1683 	      for (i = 1; i < dmp->mesg_count; i++)
1684 		{
1685 		  struct mu_mboxrd_message *dmsg = dmp->mesg[i];
1686 		  dmsg->attr_flags &= ~(MU_ATTRIBUTE_MODIFIED|MU_ATTRIBUTE_DELETED);
1687 		}
1688 	    }
1689 	}
1690     }
1691 
1692   for (dirty = 0; dirty < dmp->mesg_count; dirty++)
1693     {
1694       struct mu_mboxrd_message *dmsg = dmp->mesg[dirty];
1695       if (dmsg->uid_modified)
1696 	break;
1697       if ((dmsg->attr_flags & MU_ATTRIBUTE_MODIFIED)
1698 	  || (dmsg->attr_flags & MU_ATTRIBUTE_DELETED)
1699 	  || (dmsg->message && mu_message_is_modified (dmsg->message)))
1700 	break;
1701     }
1702 
1703   rc = 0;
1704   if (dirty < dmp->mesg_count)
1705     {
1706       p = strrchr (dmp->name, '/');
1707       if (p)
1708 	{
1709 	  size_t l = p - dmp->name;
1710 	  hints.tmpdir = malloc (l + 1);
1711 	  if (!hints.tmpdir)
1712 	    return ENOMEM;
1713 	  memcpy (hints.tmpdir, dmp->name, l);
1714 	  hints.tmpdir[l] = 0;
1715 	}
1716       else
1717 	{
1718 	  hints.tmpdir = mu_getcwd ();
1719 	  if (!hints.tmpdir)
1720 	    return ENOMEM;
1721 	}
1722       rc = mu_tempfile (&hints, MU_TEMPFILE_TMPDIR, &tempfd, &tempname);
1723       if (rc == 0)
1724 	{
1725 	  rc = mu_fd_stream_create (&tempstr, tempname, tempfd,
1726 				    MU_STREAM_RDWR|MU_STREAM_SEEK);
1727 	}
1728       else if (rc == EACCES)
1729 	{
1730 	  /*
1731 	   * Mail spool directory is not writable for the user. Fall
1732 	   * back to using temporary stream located elsewhere. When
1733 	   * ready, it will be copied back to the mailbox.
1734 	   *
1735 	   * Reset the tempname to NULL to instruct the code below
1736 	   * which approach to take.
1737 	   */
1738 	  tempname = NULL;
1739 
1740 	  rc = mu_temp_file_stream_create (&tempstr, NULL, 0);
1741 	}
1742 
1743       if (rc)
1744 	{
1745 	  free (hints.tmpdir);
1746 	  close (tempfd);
1747 	  free (tempname);
1748 	  return rc;
1749 	}
1750 
1751       rc = mboxrd_flush_temp (trk, dirty, tempstr, mode == FLUSH_EXPUNGE);
1752       if (rc == 0)
1753 	{
1754 	  if (tempname)
1755 	    {
1756 	      /* Mail spool is writable. Rename the temporary copy back
1757 		 to mailbox */
1758 	      char *backup;
1759 	      struct stat st;
1760 
1761 	      if ((rc = mboxrd_stat (dmp->mailbox, &st)) != 0)
1762 		{
1763 		  mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
1764 			    ("%s:%s: stat failed: %s",
1765 			     __func__, dmp->name, strerror (errno)));
1766 		}
1767 	      else
1768 		{
1769 		  mu_stream_flush (tempstr);
1770 		  backup = mu_tempname (hints.tmpdir);
1771 		  if (rename (dmp->name, backup))
1772 		    {
1773 		      rc = errno;
1774 		      mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
1775 				("%s:%s: failed to rename to backup file %s: %s",
1776 				 __func__, dmp->name, tempname,
1777 				 mu_strerror (rc)));
1778 		      unlink (backup);
1779 		    }
1780 		  else
1781 		    {
1782 		      rc = rename (tempname, dmp->name);
1783 		      if (rc == 0)
1784 			{
1785 			  /* Success. Synchronize internal data with the
1786 			     counter. */
1787 			  mboxrd_tracker_sync (trk);
1788 			  mu_stream_destroy (&dmp->mailbox->stream);
1789 			  rc = mboxrd_mailbox_init_stream (dmp);
1790 			  if (rc == 0)
1791 			    mboxrd_set_priv (dmp, &st);
1792 			}
1793 		      else
1794 			{
1795 			  int rc1;
1796 			  mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
1797 				    ("%s: failed to rename temporary file %s %s: %s",
1798 				     __func__, tempname, dmp->name,
1799 				     mu_strerror (rc)));
1800 			  rc1 = rename (backup, dmp->name);
1801 			  if (rc1)
1802 			    {
1803 			      mu_error (_("failed to restore %s from backup %s: %s"),
1804 					dmp->name, backup, mu_strerror (rc1));
1805 			      mu_error (_("backup left in %s"), backup);
1806 			      free (backup);
1807 			      backup = NULL;
1808 			    }
1809 			}
1810 		    }
1811 
1812 		  if (backup)
1813 		    {
1814 		      unlink (backup);
1815 		      free (backup);
1816 		    }
1817 		  unlink (tempname);
1818 		}
1819 	    }
1820 	  else
1821 	    {
1822 	      /* Mail spool not writable.  Copy the tempstr back to mailbox. */
1823 	      rc = mboxrd_copyback (trk, tempstr);
1824 	    }
1825 	}
1826       free (tempname);
1827       free (hints.tmpdir);
1828       mu_stream_unref (tempstr);
1829     }
1830 
1831   dmp->uidvalidity_changed = 0;
1832 
1833   return rc;
1834 }
1835 
1836 /* Flush the changes in the mailbox DMP to disk storage.
1837    EXPUNGE is 1 if the MU_ATTRIBUTE_DELETED attribute is to be honored.
1838    Block simultaneous access for the duration of the process.
1839 
1840    This is done by creating a temporary mailbox on the same device as
1841    DMP and by transferring all messages (whether changed or not) to
1842    it. If the process succeeds, old mailbox is removed and the temporary
1843    one is renamed to it. In case of failure, the temporary is removed and
1844    the original mailbox remains unchanged.
1845 */
1846 static int
mboxrd_flush(struct mu_mboxrd_mailbox * dmp,int mode)1847 mboxrd_flush (struct mu_mboxrd_mailbox *dmp, int mode)
1848 {
1849   int rc;
1850   sigset_t signalset;
1851 #ifdef WITH_PTHREAD
1852   int state;
1853 #endif
1854   struct mu_mboxrd_flush_tracker trk;
1855 
1856   /* Lock the mailbox */
1857   if (dmp->mailbox->locker
1858       && (rc = mu_locker_lock (dmp->mailbox->locker)) != 0)
1859     return rc;
1860 
1861 #ifdef WITH_PTHREAD
1862   pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &state);
1863 #endif
1864   sigemptyset (&signalset);
1865   sigaddset (&signalset, SIGTERM);
1866   sigaddset (&signalset, SIGHUP);
1867   sigaddset (&signalset, SIGTSTP);
1868   sigaddset (&signalset, SIGINT);
1869   sigaddset (&signalset, SIGWINCH);
1870   sigprocmask (SIG_BLOCK, &signalset, 0);
1871 
1872   rc = tracker_init (&trk, dmp);
1873   if (rc == 0)
1874     {
1875       rc = mboxrd_flush_unlocked (&trk, mode);
1876       tracker_free (&trk);
1877     }
1878 
1879 #ifdef WITH_PTHREAD
1880   pthread_setcancelstate (state, &state);
1881 #endif
1882   sigprocmask (SIG_UNBLOCK, &signalset, 0);
1883 
1884   if (dmp->mailbox->locker)
1885     mu_locker_unlock (dmp->mailbox->locker);
1886   return rc;
1887 }
1888 
1889 static int
mboxrd_expunge(mu_mailbox_t mailbox)1890 mboxrd_expunge (mu_mailbox_t mailbox)
1891 {
1892   struct mu_mboxrd_mailbox *dmp = mailbox->data;
1893   return mboxrd_flush (dmp, FLUSH_EXPUNGE);
1894 }
1895 
1896 static int
mboxrd_sync(mu_mailbox_t mailbox)1897 mboxrd_sync (mu_mailbox_t mailbox)
1898 {
1899   struct mu_mboxrd_mailbox *dmp = mailbox->data;
1900   return mboxrd_flush (dmp, FLUSH_SYNC);
1901 }
1902 
1903 int
mu_mboxrd_mailbox_init(mu_mailbox_t mailbox)1904 mu_mboxrd_mailbox_init (mu_mailbox_t mailbox)
1905 {
1906   int status;
1907   struct mu_mboxrd_mailbox *dmp;
1908   mu_property_t property = NULL;
1909 
1910   if (mailbox == NULL)
1911     return EINVAL;
1912 
1913   /* Allocate specific mbox data.  */
1914   dmp = calloc (1, sizeof (*dmp));
1915   if (dmp == NULL)
1916     return ENOMEM;
1917 
1918   /* Back pointer.  */
1919   dmp->mailbox = mailbox;
1920   dmp->uidnext = 1;
1921 
1922   status = mu_url_aget_path (mailbox->url, &dmp->name);
1923   if (status)
1924     {
1925       free (dmp);
1926       return status;
1927     }
1928 
1929   mailbox->data = dmp;
1930 
1931   /* Overloading the defaults.  */
1932   mailbox->_destroy = mboxrd_destroy;
1933   mailbox->_open = mboxrd_open;
1934   mailbox->_close = mboxrd_close;
1935   mailbox->_remove = mboxrd_remove;
1936   mailbox->_scan = mboxrd_scan;
1937   mailbox->_is_updated = mboxrd_is_updated;
1938 
1939   mailbox->_get_message = mboxrd_get_message;
1940   mailbox->_quick_get_message = mboxrd_quick_get_message;
1941   mailbox->_messages_count = mboxrd_messages_count;
1942   mailbox->_messages_recent = mboxrd_messages_recent;
1943   mailbox->_message_unseen = mboxrd_message_unseen;
1944 
1945   mailbox->_append_message = mboxrd_append_message;
1946 
1947   mailbox->_expunge = mboxrd_expunge;
1948   mailbox->_sync = mboxrd_sync;
1949 
1950   mailbox->_get_uidvalidity = mboxrd_get_uidvalidity;
1951   mailbox->_set_uidvalidity = mboxrd_set_uidvalidity;
1952   mailbox->_uidnext = mboxrd_uidnext;
1953   mailbox->_get_size = mboxrd_get_size;
1954   mailbox->_get_atime = mboxrd_get_atime;
1955 
1956   mu_mailbox_get_property (mailbox, &property);
1957   mu_property_set_value (property, "TYPE", "MBOX", 1);
1958 
1959   mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE1,
1960 	    ("%s (%s)", __func__, dmp->name));
1961   return 0;
1962 }
1963 
1964 /* Folder support */
1965 
1966 /* Return MU_FOLDER_ATTRIBUTE_FILE if NAME looks like a mboxrd
1967    mailbox.
1968 
1969    If MU_AUTODETECT_ACCURACY is 0 (i.e. autodetection is disabled),
1970    always returns MU_FOLDER_ATTRIBUTE_FILE.
1971 
1972    Otherwise, the function analyzes first 128 bytes from file. If they
1973    look like a message header start, i.e. match "^[A-Za-z_][A-Za-z0-9_-]*:",
1974    then the file is considered a mboxrd mailbox.
1975 
1976    Additionally, if MU_AUTODETECT_ACCURACY is greater than 1, the last
1977    3 characters of the file are considered. For valid mboxrd they must
1978    be "\n.\n".
1979 */
1980 static int
mboxrd_detect(char const * name)1981 mboxrd_detect (char const *name)
1982 {
1983   int res = 0;
1984 
1985   if (mu_autodetect_accuracy () == 0)
1986     res = MU_FOLDER_ATTRIBUTE_FILE;
1987   else
1988     {
1989       int rc;
1990       mu_stream_t str = NULL;
1991 
1992       rc = mu_file_stream_create (&str, name, MU_STREAM_READ);
1993       if (rc == 0)
1994 	{
1995 	  char *buf = NULL;
1996 	  size_t size = 0;
1997 	  size_t n;
1998 
1999 	  rc = mu_stream_getline (str, &buf, &size, &n);
2000 	  if (rc == 0)
2001 	    {
2002 	      char *zn;
2003 	      if (parse_from_line (buf, &zn))
2004 		{
2005 		  res = MU_FOLDER_ATTRIBUTE_FILE;
2006 		}
2007 	    }
2008 	  free (buf);
2009 	  mu_stream_destroy (&str);
2010 	}
2011     }
2012   return res;
2013 }
2014 
2015 static int
mboxrd_is_scheme(mu_record_t record,mu_url_t url,int flags)2016 mboxrd_is_scheme (mu_record_t record, mu_url_t url, int flags)
2017 {
2018   int rc = 0;
2019   int scheme_matched = mu_url_is_scheme (url, record->scheme);
2020   if (scheme_matched || mu_scheme_autodetect_p (url))
2021     {
2022       struct stat st;
2023       const char *path;
2024 
2025       mu_url_sget_path (url, &path);
2026       if (stat (path, &st) < 0)
2027 	{
2028 	  if (errno == ENOENT)
2029 	    {
2030 	      if (scheme_matched)
2031 		return MU_FOLDER_ATTRIBUTE_FILE & flags;
2032 	    }
2033 	  return 0;
2034 	}
2035 
2036       if (S_ISREG (st.st_mode) || S_ISCHR (st.st_mode))
2037 	{
2038 	  if (st.st_size == 0)
2039 	    {
2040 	      rc |= MU_FOLDER_ATTRIBUTE_FILE;
2041 	    }
2042 	  else if (flags & MU_FOLDER_ATTRIBUTE_FILE)
2043 	    {
2044 	      rc |= mboxrd_detect (path);
2045 	    }
2046 	}
2047 
2048       if ((flags & MU_FOLDER_ATTRIBUTE_DIRECTORY)
2049 	  && S_ISDIR (st.st_mode))
2050 	rc |= MU_FOLDER_ATTRIBUTE_DIRECTORY;
2051     }
2052   return rc;
2053 }
2054 
2055 static struct _mu_record _mboxrd_record =
2056 {
2057   MU_MBOX_PRIO,
2058   "mbox",
2059   MU_RECORD_LOCAL,
2060   MU_URL_SCHEME | MU_URL_PATH | MU_URL_PARAM,
2061   MU_URL_PATH,
2062   mu_url_expand_path, /* URL init. */
2063   mu_mboxrd_mailbox_init, /* Mailbox init.  */
2064   NULL, /* Mailer init.  */
2065   _mu_fsfolder_init, /* Folder init.  */
2066   NULL, /* No need for back pointer.  */
2067   mboxrd_is_scheme, /* _is_scheme method.  */
2068   NULL, /* _get_url method.  */
2069   NULL, /* _get_mailbox method.  */
2070   NULL, /* _get_mailer method.  */
2071   NULL  /* _get_folder method.  */
2072 };
2073 mu_record_t mu_mbox_record = &_mboxrd_record;
2074