1 /**
2  * @file
3  * Mbox local mailbox type
4  *
5  * @authors
6  * Copyright (C) 1996-2002,2010,2013 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
8  * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
9  *
10  * @copyright
11  * This program is free software: you can redistribute it and/or modify it under
12  * the terms of the GNU General Public License as published by the Free Software
13  * Foundation, either version 2 of the License, or (at your option) any later
14  * version.
15  *
16  * This program is distributed in the hope that it will be useful, but WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19  * details.
20  *
21  * You should have received a copy of the GNU General Public License along with
22  * this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24 
25 /**
26  * @page mbox_mbox Mbox local mailbox type
27  *
28  * Mbox local mailbox type
29  *
30  * This file contains code to parse 'mbox' and 'mmdf' style mailboxes.
31  *
32  * Implementation: #MxMboxOps
33  * Implementation: #MxMmdfOps
34  */
35 
36 #include "config.h"
37 #include <fcntl.h>
38 #include <inttypes.h> // IWYU pragma: keep
39 #include <limits.h>
40 #include <stdbool.h>
41 #include <stdint.h>
42 #include <stdio.h>
43 #include <string.h>
44 #include <sys/stat.h>
45 #include <time.h>
46 #include <unistd.h>
47 #include <utime.h>
48 #include "mutt/lib.h"
49 #include "address/lib.h"
50 #include "config/lib.h"
51 #include "email/lib.h"
52 #include "core/lib.h"
53 #include "mutt.h"
54 #include "lib.h"
55 #include "progress/lib.h"
56 #include "copy.h"
57 #include "mutt_globals.h"
58 #include "mutt_header.h"
59 #include "muttlib.h"
60 #include "mx.h"
61 #include "protos.h"
62 
63 /**
64  * struct MUpdate - Store of new offsets, used by mutt_sync_mailbox()
65  */
66 struct MUpdate
67 {
68   bool valid;
69   LOFF_T hdr;
70   LOFF_T body;
71   long lines;
72   LOFF_T length;
73 };
74 
75 /**
76  * mbox_adata_free - Free the private Account data - Implements Account::adata_free()
77  */
mbox_adata_free(void ** ptr)78 static void mbox_adata_free(void **ptr)
79 {
80   struct MboxAccountData *m = *ptr;
81 
82   mutt_file_fclose(&m->fp);
83   FREE(ptr);
84 }
85 
86 /**
87  * mbox_adata_new - Create a new MboxAccountData struct
88  * @retval ptr New MboxAccountData
89  */
mbox_adata_new(void)90 static struct MboxAccountData *mbox_adata_new(void)
91 {
92   return mutt_mem_calloc(1, sizeof(struct MboxAccountData));
93 }
94 
95 /**
96  * mbox_adata_get - Get the private data associated with a Mailbox
97  * @param m Mailbox
98  * @retval ptr Private data
99  */
mbox_adata_get(struct Mailbox * m)100 static struct MboxAccountData *mbox_adata_get(struct Mailbox *m)
101 {
102   if (!m)
103     return NULL;
104   if ((m->type != MUTT_MBOX) && (m->type != MUTT_MMDF))
105     return NULL;
106   struct Account *a = m->account;
107   if (!a)
108     return NULL;
109   return a->adata;
110 }
111 
112 /**
113  * init_mailbox - Add Mbox data to the Mailbox
114  * @param m Mailbox
115  * @retval  0 Success
116  * @retval -1 Error Bad format
117  */
init_mailbox(struct Mailbox * m)118 static int init_mailbox(struct Mailbox *m)
119 {
120   if (!m || !m->account)
121     return -1;
122   if ((m->type != MUTT_MBOX) && (m->type != MUTT_MMDF))
123     return -1;
124   if (m->account->adata)
125     return 0;
126 
127   m->account->adata = mbox_adata_new();
128   m->account->adata_free = mbox_adata_free;
129   return 0;
130 }
131 
132 /**
133  * mbox_lock_mailbox - Lock a mailbox
134  * @param m     Mailbox to lock
135  * @param excl  Exclusive lock?
136  * @param retry Should retry if unable to lock?
137  * @retval  0 Success
138  * @retval -1 Failure
139  */
mbox_lock_mailbox(struct Mailbox * m,bool excl,bool retry)140 static int mbox_lock_mailbox(struct Mailbox *m, bool excl, bool retry)
141 {
142   struct MboxAccountData *adata = mbox_adata_get(m);
143   if (!adata)
144     return -1;
145 
146   int rc = mutt_file_lock(fileno(adata->fp), excl, retry);
147   if (rc == 0)
148     adata->locked = true;
149   else if (retry && !excl)
150   {
151     m->readonly = true;
152     return 0;
153   }
154 
155   return rc;
156 }
157 
158 /**
159  * mbox_unlock_mailbox - Unlock a mailbox
160  * @param m Mailbox to unlock
161  */
mbox_unlock_mailbox(struct Mailbox * m)162 static void mbox_unlock_mailbox(struct Mailbox *m)
163 {
164   struct MboxAccountData *adata = mbox_adata_get(m);
165   if (!adata)
166     return;
167 
168   if (adata->locked)
169   {
170     fflush(adata->fp);
171 
172     mutt_file_unlock(fileno(adata->fp));
173     adata->locked = false;
174   }
175 }
176 
177 /**
178  * mmdf_parse_mailbox - Read a mailbox in MMDF format
179  * @param m Mailbox
180  * @retval enum #MxOpenReturns
181  */
mmdf_parse_mailbox(struct Mailbox * m)182 static enum MxOpenReturns mmdf_parse_mailbox(struct Mailbox *m)
183 {
184   if (!m)
185     return MX_OPEN_ERROR;
186 
187   struct MboxAccountData *adata = mbox_adata_get(m);
188   if (!adata)
189     return MX_OPEN_ERROR;
190 
191   char buf[8192];
192   char return_path[1024];
193   int count = 0;
194   int lines;
195   time_t t;
196   LOFF_T loc, tmploc;
197   struct Email *e = NULL;
198   struct stat st = { 0 };
199   struct Progress *progress = NULL;
200   enum MxOpenReturns rc = MX_OPEN_ERROR;
201 
202   if (stat(mailbox_path(m), &st) == -1)
203   {
204     mutt_perror(mailbox_path(m));
205     goto fail;
206   }
207   mutt_file_get_stat_timespec(&adata->atime, &st, MUTT_STAT_ATIME);
208   mutt_file_get_stat_timespec(&m->mtime, &st, MUTT_STAT_MTIME);
209   m->size = st.st_size;
210 
211   buf[sizeof(buf) - 1] = '\0';
212 
213   if (m->verbose)
214   {
215     char msg[PATH_MAX];
216     snprintf(msg, sizeof(msg), _("Reading %s..."), mailbox_path(m));
217     progress = progress_new(msg, MUTT_PROGRESS_READ, 0);
218   }
219 
220   while (true)
221   {
222     if (!fgets(buf, sizeof(buf) - 1, adata->fp))
223       break;
224 
225     if (SigInt)
226       break;
227 
228     if (mutt_str_equal(buf, MMDF_SEP))
229     {
230       loc = ftello(adata->fp);
231       if (loc < 0)
232         goto fail;
233 
234       count++;
235       if (m->verbose)
236         progress_update(progress, count, (int) (loc / (m->size / 100 + 1)));
237 
238       if (m->msg_count == m->email_max)
239         mx_alloc_memory(m);
240       e = email_new();
241       m->emails[m->msg_count] = e;
242       e->offset = loc;
243       e->index = m->msg_count;
244 
245       if (!fgets(buf, sizeof(buf) - 1, adata->fp))
246       {
247         /* TODO: memory leak??? */
248         mutt_debug(LL_DEBUG1, "unexpected EOF\n");
249         break;
250       }
251 
252       return_path[0] = '\0';
253 
254       if (!is_from(buf, return_path, sizeof(return_path), &t))
255       {
256         if (fseeko(adata->fp, loc, SEEK_SET) != 0)
257         {
258           mutt_debug(LL_DEBUG1, "#1 fseek() failed\n");
259           mutt_error(_("Mailbox is corrupt"));
260           goto fail;
261         }
262       }
263       else
264         e->received = t - mutt_date_local_tz(t);
265 
266       e->env = mutt_rfc822_read_header(adata->fp, e, false, false);
267 
268       loc = ftello(adata->fp);
269       if (loc < 0)
270         goto fail;
271 
272       if ((e->body->length > 0) && (e->lines > 0))
273       {
274         tmploc = loc + e->body->length;
275 
276         if ((tmploc > 0) && (tmploc < m->size))
277         {
278           if ((fseeko(adata->fp, tmploc, SEEK_SET) != 0) ||
279               !fgets(buf, sizeof(buf) - 1, adata->fp) || !mutt_str_equal(MMDF_SEP, buf))
280           {
281             if (fseeko(adata->fp, loc, SEEK_SET) != 0)
282               mutt_debug(LL_DEBUG1, "#2 fseek() failed\n");
283             e->body->length = -1;
284           }
285         }
286         else
287           e->body->length = -1;
288       }
289       else
290         e->body->length = -1;
291 
292       if (e->body->length < 0)
293       {
294         lines = -1;
295         do
296         {
297           loc = ftello(adata->fp);
298           if (loc < 0)
299             goto fail;
300           if (!fgets(buf, sizeof(buf) - 1, adata->fp))
301             break;
302           lines++;
303         } while (!mutt_str_equal(buf, MMDF_SEP));
304 
305         e->lines = lines;
306         e->body->length = loc - e->body->offset;
307       }
308 
309       if (TAILQ_EMPTY(&e->env->return_path) && return_path[0])
310         mutt_addrlist_parse(&e->env->return_path, return_path);
311 
312       if (TAILQ_EMPTY(&e->env->from))
313         mutt_addrlist_copy(&e->env->from, &e->env->return_path, false);
314 
315       m->msg_count++;
316     }
317     else
318     {
319       mutt_debug(LL_DEBUG1, "corrupt mailbox\n");
320       mutt_error(_("Mailbox is corrupt"));
321       goto fail;
322     }
323   }
324 
325   if (SigInt)
326   {
327     SigInt = false;
328     rc = MX_OPEN_ABORT; /* action aborted */
329     goto fail;
330   }
331 
332   rc = MX_OPEN_OK;
333 fail:
334   progress_free(&progress);
335   return rc;
336 }
337 
338 /**
339  * mbox_parse_mailbox - Read a mailbox from disk
340  * @param m Mailbox
341  * @retval enum #MxOpenReturns
342  *
343  * Note that this function is also called when new mail is appended to the
344  * currently open folder, and NOT just when the mailbox is initially read.
345  *
346  * @note It is assumed that the mailbox being read has been locked before this
347  *       routine gets called.  Strange things could happen if it's not!
348  */
mbox_parse_mailbox(struct Mailbox * m)349 static enum MxOpenReturns mbox_parse_mailbox(struct Mailbox *m)
350 {
351   if (!m)
352     return MX_OPEN_ERROR;
353 
354   struct MboxAccountData *adata = mbox_adata_get(m);
355   if (!adata)
356     return MX_OPEN_ERROR;
357 
358   struct stat st = { 0 };
359   char buf[8192], return_path[256];
360   struct Email *e_cur = NULL;
361   time_t t;
362   int count = 0, lines = 0;
363   LOFF_T loc;
364   struct Progress *progress = NULL;
365   enum MxOpenReturns rc = MX_OPEN_ERROR;
366 
367   /* Save information about the folder at the time we opened it. */
368   if (stat(mailbox_path(m), &st) == -1)
369   {
370     mutt_perror(mailbox_path(m));
371     goto fail;
372   }
373 
374   m->size = st.st_size;
375   mutt_file_get_stat_timespec(&m->mtime, &st, MUTT_STAT_MTIME);
376   mutt_file_get_stat_timespec(&adata->atime, &st, MUTT_STAT_ATIME);
377 
378   if (!m->readonly)
379     m->readonly = access(mailbox_path(m), W_OK) ? true : false;
380 
381   if (m->verbose)
382   {
383     char msg[PATH_MAX];
384     snprintf(msg, sizeof(msg), _("Reading %s..."), mailbox_path(m));
385     progress = progress_new(msg, MUTT_PROGRESS_READ, 0);
386   }
387 
388   loc = ftello(adata->fp);
389   while ((fgets(buf, sizeof(buf), adata->fp)) && !SigInt)
390   {
391     if (is_from(buf, return_path, sizeof(return_path), &t))
392     {
393       /* Save the Content-Length of the previous message */
394       if (count > 0)
395       {
396         struct Email *e = m->emails[m->msg_count - 1];
397         if (e->body->length < 0)
398         {
399           e->body->length = loc - e->body->offset - 1;
400           if (e->body->length < 0)
401             e->body->length = 0;
402         }
403         if (!e->lines)
404           e->lines = lines ? lines - 1 : 0;
405       }
406 
407       count++;
408 
409       if (m->verbose)
410       {
411         progress_update(progress, count, (int) (ftello(adata->fp) / (m->size / 100 + 1)));
412       }
413 
414       if (m->msg_count == m->email_max)
415         mx_alloc_memory(m);
416 
417       m->emails[m->msg_count] = email_new();
418       e_cur = m->emails[m->msg_count];
419       e_cur->received = t - mutt_date_local_tz(t);
420       e_cur->offset = loc;
421       e_cur->index = m->msg_count;
422 
423       e_cur->env = mutt_rfc822_read_header(adata->fp, e_cur, false, false);
424 
425       /* if we know how long this message is, either just skip over the body,
426        * or if we don't know how many lines there are, count them now (this will
427        * save time by not having to search for the next message marker).  */
428       if (e_cur->body->length > 0)
429       {
430         LOFF_T tmploc;
431 
432         loc = ftello(adata->fp);
433 
434         /* The test below avoids a potential integer overflow if the
435          * content-length is huge (thus necessarily invalid).  */
436         tmploc = (e_cur->body->length < m->size) ? (loc + e_cur->body->length + 1) : -1;
437 
438         if ((tmploc > 0) && (tmploc < m->size))
439         {
440           /* check to see if the content-length looks valid.  we expect to
441            * to see a valid message separator at this point in the stream */
442           if ((fseeko(adata->fp, tmploc, SEEK_SET) != 0) ||
443               !fgets(buf, sizeof(buf), adata->fp) || !mutt_str_startswith(buf, "From "))
444           {
445             mutt_debug(LL_DEBUG1, "bad content-length in message %d (cl=" OFF_T_FMT ")\n",
446                        e_cur->index, e_cur->body->length);
447             mutt_debug(LL_DEBUG1, "    LINE: %s", buf);
448             /* nope, return the previous position */
449             if ((loc < 0) || (fseeko(adata->fp, loc, SEEK_SET) != 0))
450             {
451               mutt_debug(LL_DEBUG1, "#1 fseek() failed\n");
452             }
453             e_cur->body->length = -1;
454           }
455         }
456         else if (tmploc != m->size)
457         {
458           /* content-length would put us past the end of the file, so it
459            * must be wrong */
460           e_cur->body->length = -1;
461         }
462 
463         if (e_cur->body->length != -1)
464         {
465           /* good content-length.  check to see if we know how many lines
466            * are in this message.  */
467           if (e_cur->lines == 0)
468           {
469             int cl = e_cur->body->length;
470 
471             /* count the number of lines in this message */
472             if ((loc < 0) || (fseeko(adata->fp, loc, SEEK_SET) != 0))
473               mutt_debug(LL_DEBUG1, "#2 fseek() failed\n");
474             while (cl-- > 0)
475             {
476               if (fgetc(adata->fp) == '\n')
477                 e_cur->lines++;
478             }
479           }
480 
481           /* return to the offset of the next message separator */
482           if (fseeko(adata->fp, tmploc, SEEK_SET) != 0)
483             mutt_debug(LL_DEBUG1, "#3 fseek() failed\n");
484         }
485       }
486 
487       m->msg_count++;
488 
489       if (TAILQ_EMPTY(&e_cur->env->return_path) && return_path[0])
490       {
491         mutt_addrlist_parse(&e_cur->env->return_path, return_path);
492       }
493 
494       if (TAILQ_EMPTY(&e_cur->env->from))
495         mutt_addrlist_copy(&e_cur->env->from, &e_cur->env->return_path, false);
496 
497       lines = 0;
498     }
499     else
500       lines++;
501 
502     loc = ftello(adata->fp);
503   }
504 
505   /* Only set the content-length of the previous message if we have read more
506    * than one message during _this_ invocation.  If this routine is called
507    * when new mail is received, we need to make sure not to clobber what
508    * previously was the last message since the headers may be sorted.  */
509   if (count > 0)
510   {
511     struct Email *e = m->emails[m->msg_count - 1];
512     if (e->body->length < 0)
513     {
514       e->body->length = ftello(adata->fp) - e->body->offset - 1;
515       if (e->body->length < 0)
516         e->body->length = 0;
517     }
518 
519     if (!e->lines)
520       e->lines = lines ? lines - 1 : 0;
521   }
522 
523   if (SigInt)
524   {
525     SigInt = false;
526     rc = MX_OPEN_ABORT;
527     goto fail; /* action aborted */
528   }
529 
530   rc = MX_OPEN_OK;
531 fail:
532   progress_free(&progress);
533   return rc;
534 }
535 
536 /**
537  * reopen_mailbox - Close and reopen a mailbox
538  * @param m          Mailbox
539  * @retval >0 Success, e.g. #MX_STATUS_REOPENED, #MX_STATUS_NEW_MAIL
540  * @retval -1 Error
541  */
reopen_mailbox(struct Mailbox * m)542 static int reopen_mailbox(struct Mailbox *m)
543 {
544   if (!m)
545     return -1;
546 
547   struct MboxAccountData *adata = mbox_adata_get(m);
548   if (!adata)
549     return -1;
550 
551   bool (*cmp_headers)(const struct Email *, const struct Email *) = NULL;
552   struct Email **e_old = NULL;
553   int old_msg_count;
554   bool msg_mod = false;
555   int rc = -1;
556 
557   /* silent operations */
558   m->verbose = false;
559 
560   /* our heuristics require the old mailbox to be unsorted */
561   const short c_sort = cs_subset_sort(NeoMutt->sub, "sort");
562   if (c_sort != SORT_ORDER)
563   {
564     cs_subset_str_native_set(NeoMutt->sub, "sort", SORT_ORDER, NULL);
565     mailbox_changed(m, NT_MAILBOX_RESORT);
566     cs_subset_str_native_set(NeoMutt->sub, "sort", c_sort, NULL);
567   }
568 
569   e_old = NULL;
570   old_msg_count = 0;
571 
572   /* simulate a close */
573   mutt_hash_free(&m->id_hash);
574   mutt_hash_free(&m->subj_hash);
575   mutt_hash_free(&m->label_hash);
576   FREE(&m->v2r);
577   if (m->readonly)
578   {
579     for (int i = 0; i < m->msg_count; i++)
580       email_free(&(m->emails[i])); /* nothing to do! */
581     FREE(&m->emails);
582   }
583   else
584   {
585     /* save the old headers */
586     old_msg_count = m->msg_count;
587     e_old = m->emails;
588     m->emails = NULL;
589   }
590 
591   m->email_max = 0; /* force allocation of new headers */
592   m->msg_count = 0;
593   m->vcount = 0;
594   m->msg_tagged = 0;
595   m->msg_deleted = 0;
596   m->msg_new = 0;
597   m->msg_unread = 0;
598   m->msg_flagged = 0;
599   m->changed = false;
600   m->id_hash = NULL;
601   m->subj_hash = NULL;
602   mutt_make_label_hash(m);
603 
604   switch (m->type)
605   {
606     case MUTT_MBOX:
607     case MUTT_MMDF:
608       cmp_headers = email_cmp_strict;
609       mutt_file_fclose(&adata->fp);
610       adata->fp = mutt_file_fopen(mailbox_path(m), "r");
611       if (!adata->fp)
612         rc = -1;
613       else if (m->type == MUTT_MBOX)
614         rc = mbox_parse_mailbox(m);
615       else
616         rc = mmdf_parse_mailbox(m);
617       break;
618 
619     default:
620       rc = -1;
621       break;
622   }
623 
624   if (rc == -1)
625   {
626     /* free the old headers */
627     for (int i = 0; i < old_msg_count; i++)
628       email_free(&(e_old[i]));
629     FREE(&e_old);
630 
631     m->verbose = true;
632     return -1;
633   }
634 
635   mutt_file_touch_atime(fileno(adata->fp));
636 
637   /* now try to recover the old flags */
638 
639   if (!m->readonly)
640   {
641     for (int i = 0; i < m->msg_count; i++)
642     {
643       bool found = false;
644 
645       /* some messages have been deleted, and new  messages have been
646        * appended at the end; the heuristic is that old messages have then
647        * "advanced" towards the beginning of the folder, so we begin the
648        * search at index "i" */
649       int j;
650       for (j = i; j < old_msg_count; j++)
651       {
652         if (!e_old[j])
653           continue;
654         if (cmp_headers(m->emails[i], e_old[j]))
655         {
656           found = true;
657           break;
658         }
659       }
660       if (!found)
661       {
662         for (j = 0; (j < i) && (j < old_msg_count); j++)
663         {
664           if (!e_old[j])
665             continue;
666           if (cmp_headers(m->emails[i], e_old[j]))
667           {
668             found = true;
669             break;
670           }
671         }
672       }
673 
674       if (found)
675       {
676         m->changed = true;
677         if (e_old[j]->changed)
678         {
679           /* Only update the flags if the old header was changed;
680            * otherwise, the header may have been modified externally,
681            * and we don't want to lose _those_ changes */
682           mutt_set_flag(m, m->emails[i], MUTT_FLAG, e_old[j]->flagged);
683           mutt_set_flag(m, m->emails[i], MUTT_REPLIED, e_old[j]->replied);
684           mutt_set_flag(m, m->emails[i], MUTT_OLD, e_old[j]->old);
685           mutt_set_flag(m, m->emails[i], MUTT_READ, e_old[j]->read);
686         }
687         mutt_set_flag(m, m->emails[i], MUTT_DELETE, e_old[j]->deleted);
688         mutt_set_flag(m, m->emails[i], MUTT_PURGE, e_old[j]->purge);
689         mutt_set_flag(m, m->emails[i], MUTT_TAG, e_old[j]->tagged);
690 
691         /* we don't need this header any more */
692         email_free(&(e_old[j]));
693       }
694     }
695 
696     /* free the remaining old headers */
697     for (int j = 0; j < old_msg_count; j++)
698     {
699       if (e_old[j])
700       {
701         email_free(&(e_old[j]));
702         msg_mod = true;
703       }
704     }
705     FREE(&e_old);
706   }
707 
708   mailbox_changed(m, NT_MAILBOX_UPDATE);
709   m->verbose = true;
710 
711   return (m->changed || msg_mod) ? MX_STATUS_REOPENED : MX_STATUS_NEW_MAIL;
712 }
713 
714 /**
715  * mbox_has_new - Does the mailbox have new mail
716  * @param m Mailbox
717  * @retval true The mailbox has at least 1 new messages (not old)
718  * @retval false otherwise
719  */
mbox_has_new(struct Mailbox * m)720 static bool mbox_has_new(struct Mailbox *m)
721 {
722   for (int i = 0; i < m->msg_count; i++)
723   {
724     struct Email *e = m->emails[i];
725     if (!e)
726       break;
727     if (!e->deleted && !e->read && !e->old)
728       return true;
729   }
730   return false;
731 }
732 
733 /**
734  * fseek_last_message - Find the last message in the file
735  * @param fp File to search
736  * @retval  0 Success
737  * @retval -1 No message found
738  */
fseek_last_message(FILE * fp)739 static int fseek_last_message(FILE *fp)
740 {
741   LOFF_T pos;
742   char buf[BUFSIZ + 7] = { 0 }; // 7 for "\n\nFrom "
743   size_t bytes_read;
744 
745   fseek(fp, 0, SEEK_END);
746   pos = ftello(fp);
747 
748   /* Set 'bytes_read' to the size of the last, probably partial, buf;
749    * 0 < 'bytes_read' <= 'BUFSIZ'.  */
750   bytes_read = pos % BUFSIZ;
751   if (bytes_read == 0)
752     bytes_read = BUFSIZ;
753   /* Make 'pos' a multiple of 'BUFSIZ' (0 if the file is short), so that all
754    * reads will be on block boundaries, which might increase efficiency.  */
755   while ((pos -= bytes_read) >= 0)
756   {
757     /* we save in the buf at the end the first 7 chars from the last read */
758     memcpy(buf + BUFSIZ, buf, 7);
759     fseeko(fp, pos, SEEK_SET);
760     bytes_read = fread(buf, sizeof(char), bytes_read, fp);
761     if (bytes_read == 0)
762       return -1;
763     /* 'i' is Index into 'buf' for scanning.  */
764     for (int i = bytes_read; i >= 0; i--)
765     {
766       if (mutt_str_startswith(buf + i, "\n\nFrom "))
767       { /* found it - go to the beginning of the From */
768         fseeko(fp, pos + i + 2, SEEK_SET);
769         return 0;
770       }
771     }
772     bytes_read = BUFSIZ;
773   }
774 
775   /* here we are at the beginning of the file */
776   if (mutt_str_startswith(buf, "From "))
777   {
778     fseek(fp, 0, SEEK_SET);
779     return 0;
780   }
781 
782   return -1;
783 }
784 
785 /**
786  * test_last_status_new - Is the last message new
787  * @param fp File to check
788  * @retval true The last message is new
789  */
test_last_status_new(FILE * fp)790 static bool test_last_status_new(FILE *fp)
791 {
792   struct Email *e = NULL;
793   struct Envelope *tmp_envelope = NULL;
794   bool rc = false;
795 
796   if (fseek_last_message(fp) == -1)
797     return false;
798 
799   e = email_new();
800   tmp_envelope = mutt_rfc822_read_header(fp, e, false, false);
801   if (!e->read && !e->old)
802     rc = true;
803 
804   mutt_env_free(&tmp_envelope);
805   email_free(&e);
806 
807   return rc;
808 }
809 
810 /**
811  * mbox_test_new_folder - Test if an mbox or mmdf mailbox has new mail
812  * @param path Path to the mailbox
813  * @retval true The folder contains new mail
814  */
mbox_test_new_folder(const char * path)815 bool mbox_test_new_folder(const char *path)
816 {
817   bool rc = false;
818 
819   enum MailboxType type = mx_path_probe(path);
820 
821   if ((type != MUTT_MBOX) && (type != MUTT_MMDF))
822     return false;
823 
824   FILE *fp = fopen(path, "rb");
825   if (fp)
826   {
827     rc = test_last_status_new(fp);
828     mutt_file_fclose(&fp);
829   }
830 
831   return rc;
832 }
833 
834 /**
835  * mbox_reset_atime - Reset the access time on the mailbox file
836  * @param m  Mailbox
837  * @param st Timestamp
838  *
839  * if mailbox has at least 1 new message, sets mtime > atime of mailbox so
840  * mailbox check reports new mail
841  */
mbox_reset_atime(struct Mailbox * m,struct stat * st)842 void mbox_reset_atime(struct Mailbox *m, struct stat *st)
843 {
844   struct utimbuf utimebuf;
845   struct stat st2 = { 0 };
846 
847   if (!st)
848   {
849     if (stat(mailbox_path(m), &st2) < 0)
850       return;
851     st = &st2;
852   }
853 
854   utimebuf.actime = st->st_atime;
855   utimebuf.modtime = st->st_mtime;
856 
857   /* When $mbox_check_recent is set, existing new mail is ignored, so do not
858    * reset the atime to mtime-1 to signal new mail.  */
859   const bool c_mail_check_recent =
860       cs_subset_bool(NeoMutt->sub, "mail_check_recent");
861   if (!c_mail_check_recent && (utimebuf.actime >= utimebuf.modtime) && mbox_has_new(m))
862   {
863     utimebuf.actime = utimebuf.modtime - 1;
864   }
865 
866   utime(mailbox_path(m), &utimebuf);
867 }
868 
869 /**
870  * mbox_ac_owns_path - Check whether an Account owns a Mailbox path - Implements MxOps::ac_owns_path() - @ingroup mx_ac_owns_path
871  */
mbox_ac_owns_path(struct Account * a,const char * path)872 static bool mbox_ac_owns_path(struct Account *a, const char *path)
873 {
874   if ((a->type != MUTT_MBOX) && (a->type != MUTT_MMDF))
875     return false;
876 
877   struct MailboxNode *np = STAILQ_FIRST(&a->mailboxes);
878   if (!np)
879     return false;
880 
881   return mutt_str_equal(mailbox_path(np->mailbox), path);
882 }
883 
884 /**
885  * mbox_ac_add - Add a Mailbox to an Account - Implements MxOps::ac_add() - @ingroup mx_ac_add
886  */
mbox_ac_add(struct Account * a,struct Mailbox * m)887 static bool mbox_ac_add(struct Account *a, struct Mailbox *m)
888 {
889   return true;
890 }
891 
892 /**
893  * mbox_open_readwrite - Open an mbox read-write
894  * @param m Mailbox
895  * @retval ptr FILE handle
896  *
897  * This function ensures that the FILE and readonly flag are changed atomically.
898  */
mbox_open_readwrite(struct Mailbox * m)899 static FILE *mbox_open_readwrite(struct Mailbox *m)
900 {
901   FILE *fp = fopen(mailbox_path(m), "r+");
902   if (fp)
903     m->readonly = false;
904   return fp;
905 }
906 
907 /**
908  * mbox_open_readonly - Open an mbox read-only
909  * @param m Mailbox
910  * @retval ptr FILE handle
911  *
912  * This function ensures that the FILE and readonly flag are changed atomically.
913  */
mbox_open_readonly(struct Mailbox * m)914 static FILE *mbox_open_readonly(struct Mailbox *m)
915 {
916   FILE *fp = fopen(mailbox_path(m), "r");
917   if (fp)
918     m->readonly = true;
919   return fp;
920 }
921 
922 /**
923  * mbox_mbox_open - Open a Mailbox - Implements MxOps::mbox_open() - @ingroup mx_mbox_open
924  */
mbox_mbox_open(struct Mailbox * m)925 static enum MxOpenReturns mbox_mbox_open(struct Mailbox *m)
926 {
927   if (init_mailbox(m) != 0)
928     return MX_OPEN_ERROR;
929 
930   struct MboxAccountData *adata = mbox_adata_get(m);
931   if (!adata)
932     return MX_OPEN_ERROR;
933 
934   adata->fp = mbox_open_readwrite(m);
935   if (!adata->fp)
936   {
937     adata->fp = mbox_open_readonly(m);
938   }
939   if (!adata->fp)
940   {
941     mutt_perror(mailbox_path(m));
942     return MX_OPEN_ERROR;
943   }
944 
945   mutt_sig_block();
946   if (mbox_lock_mailbox(m, false, true) == -1)
947   {
948     mutt_sig_unblock();
949     return MX_OPEN_ERROR;
950   }
951 
952   m->has_new = true;
953   enum MxOpenReturns rc = MX_OPEN_ERROR;
954   if (m->type == MUTT_MBOX)
955     rc = mbox_parse_mailbox(m);
956   else if (m->type == MUTT_MMDF)
957     rc = mmdf_parse_mailbox(m);
958   else
959     rc = MX_OPEN_ERROR;
960 
961   if (!mbox_has_new(m))
962     m->has_new = false;
963   clearerr(adata->fp); // Clear the EOF flag
964   mutt_file_touch_atime(fileno(adata->fp));
965 
966   mbox_unlock_mailbox(m);
967   mutt_sig_unblock();
968   return rc;
969 }
970 
971 /**
972  * mbox_mbox_open_append - Open a Mailbox for appending - Implements MxOps::mbox_open_append() - @ingroup mx_mbox_open_append
973  */
mbox_mbox_open_append(struct Mailbox * m,OpenMailboxFlags flags)974 static bool mbox_mbox_open_append(struct Mailbox *m, OpenMailboxFlags flags)
975 {
976   if (init_mailbox(m) != 0)
977     return false;
978 
979   struct MboxAccountData *adata = mbox_adata_get(m);
980   if (!adata)
981     return false;
982 
983   if (!adata->fp)
984   {
985     // create dir recursively
986     char *tmp_path = mutt_path_dirname(mailbox_path(m));
987     if (mutt_file_mkdir(tmp_path, S_IRWXU) == -1)
988     {
989       mutt_perror(mailbox_path(m));
990       FREE(&tmp_path);
991       return false;
992     }
993     FREE(&tmp_path);
994 
995     adata->fp =
996         mutt_file_fopen(mailbox_path(m), (flags & MUTT_NEWFOLDER) ? "w+" : "a+");
997     if (!adata->fp)
998     {
999       mutt_perror(mailbox_path(m));
1000       return false;
1001     }
1002 
1003     if (mbox_lock_mailbox(m, true, true) != false)
1004     {
1005       mutt_error(_("Couldn't lock %s"), mailbox_path(m));
1006       mutt_file_fclose(&adata->fp);
1007       return false;
1008     }
1009   }
1010 
1011   fseek(adata->fp, 0, SEEK_END);
1012 
1013   return true;
1014 }
1015 
1016 /**
1017  * mbox_mbox_check - Check for new mail - Implements MxOps::mbox_check() - @ingroup mx_mbox_check
1018  * @param[in]  m Mailbox
1019  * @retval #MX_STATUS_REOPENED  Mailbox has been reopened
1020  * @retval #MX_STATUS_NEW_MAIL  New mail has arrived
1021  * @retval #MX_STATUS_LOCKED    Couldn't lock the file
1022  */
mbox_mbox_check(struct Mailbox * m)1023 static enum MxStatus mbox_mbox_check(struct Mailbox *m)
1024 {
1025   struct MboxAccountData *adata = mbox_adata_get(m);
1026   if (!adata)
1027     return MX_STATUS_ERROR;
1028 
1029   if (!adata->fp)
1030   {
1031     if (mbox_mbox_open(m) != MX_OPEN_OK)
1032       return MX_STATUS_ERROR;
1033     mailbox_changed(m, NT_MAILBOX_INVALID);
1034   }
1035   if (!adata->fp)
1036     return MX_STATUS_ERROR;
1037 
1038   struct stat st = { 0 };
1039   bool unlock = false;
1040   bool modified = false;
1041 
1042   if (stat(mailbox_path(m), &st) == 0)
1043   {
1044     if ((mutt_file_stat_timespec_compare(&st, MUTT_STAT_MTIME, &m->mtime) == 0) &&
1045         (st.st_size == m->size))
1046     {
1047       return MX_STATUS_OK;
1048     }
1049 
1050     if (st.st_size == m->size)
1051     {
1052       /* the file was touched, but it is still the same length, so just exit */
1053       mutt_file_get_stat_timespec(&m->mtime, &st, MUTT_STAT_MTIME);
1054       return MX_STATUS_OK;
1055     }
1056 
1057     if (st.st_size > m->size)
1058     {
1059       /* lock the file if it isn't already */
1060       if (!adata->locked)
1061       {
1062         mutt_sig_block();
1063         if (mbox_lock_mailbox(m, false, false) == -1)
1064         {
1065           mutt_sig_unblock();
1066           /* we couldn't lock the mailbox, but nothing serious happened:
1067            * probably the new mail arrived: no reason to wait till we can
1068            * parse it: we'll get it on the next pass */
1069           return MX_STATUS_LOCKED;
1070         }
1071         unlock = 1;
1072       }
1073 
1074       /* Check to make sure that the only change to the mailbox is that
1075        * message(s) were appended to this file.  My heuristic is that we should
1076        * see the message separator at *exactly* what used to be the end of the
1077        * folder.  */
1078       char buf[1024];
1079       if (fseeko(adata->fp, m->size, SEEK_SET) != 0)
1080         mutt_debug(LL_DEBUG1, "#1 fseek() failed\n");
1081       if (fgets(buf, sizeof(buf), adata->fp))
1082       {
1083         if (((m->type == MUTT_MBOX) && mutt_str_startswith(buf, "From ")) ||
1084             ((m->type == MUTT_MMDF) && mutt_str_equal(buf, MMDF_SEP)))
1085         {
1086           if (fseeko(adata->fp, m->size, SEEK_SET) != 0)
1087             mutt_debug(LL_DEBUG1, "#2 fseek() failed\n");
1088 
1089           int old_msg_count = m->msg_count;
1090           if (m->type == MUTT_MBOX)
1091             mbox_parse_mailbox(m);
1092           else
1093             mmdf_parse_mailbox(m);
1094 
1095           if (m->msg_count > old_msg_count)
1096             mailbox_changed(m, NT_MAILBOX_INVALID);
1097 
1098           /* Only unlock the folder if it was locked inside of this routine.
1099            * It may have been locked elsewhere, like in
1100            * mutt_checkpoint_mailbox().  */
1101           if (unlock)
1102           {
1103             mbox_unlock_mailbox(m);
1104             mutt_sig_unblock();
1105           }
1106 
1107           return MX_STATUS_NEW_MAIL; /* signal that new mail arrived */
1108         }
1109         else
1110           modified = true;
1111       }
1112       else
1113       {
1114         mutt_debug(LL_DEBUG1, "fgets returned NULL\n");
1115         modified = true;
1116       }
1117     }
1118     else
1119       modified = true;
1120   }
1121 
1122   if (modified)
1123   {
1124     if (reopen_mailbox(m) != -1)
1125     {
1126       mailbox_changed(m, NT_MAILBOX_INVALID);
1127       if (unlock)
1128       {
1129         mbox_unlock_mailbox(m);
1130         mutt_sig_unblock();
1131       }
1132       return MX_STATUS_REOPENED;
1133     }
1134   }
1135 
1136   /* fatal error */
1137 
1138   mbox_unlock_mailbox(m);
1139   mx_fastclose_mailbox(m);
1140   mutt_sig_unblock();
1141   mutt_error(_("Mailbox was corrupted"));
1142   return MX_STATUS_ERROR;
1143 }
1144 
1145 /**
1146  * mbox_mbox_sync - Save changes to the Mailbox - Implements MxOps::mbox_sync() - @ingroup mx_mbox_sync
1147  */
mbox_mbox_sync(struct Mailbox * m)1148 static enum MxStatus mbox_mbox_sync(struct Mailbox *m)
1149 {
1150   struct MboxAccountData *adata = mbox_adata_get(m);
1151   if (!adata)
1152     return MX_STATUS_ERROR;
1153 
1154   struct Buffer *tempfile = NULL;
1155   char buf[32];
1156   int j;
1157   bool unlink_tempfile = false;
1158   int need_sort = 0; /* flag to resort mailbox if new mail arrives */
1159   int first = -1;    /* first message to be written */
1160   LOFF_T offset;     /* location in mailbox to write changed messages */
1161   struct stat st = { 0 };
1162   struct MUpdate *new_offset = NULL;
1163   struct MUpdate *old_offset = NULL;
1164   FILE *fp = NULL;
1165   struct Progress *progress = NULL;
1166   enum MxStatus rc = MX_STATUS_ERROR;
1167 
1168   /* sort message by their position in the mailbox on disk */
1169   const short c_sort = cs_subset_sort(NeoMutt->sub, "sort");
1170   if (c_sort != SORT_ORDER)
1171   {
1172     cs_subset_str_native_set(NeoMutt->sub, "sort", SORT_ORDER, NULL);
1173     mailbox_changed(m, NT_MAILBOX_RESORT);
1174     cs_subset_str_native_set(NeoMutt->sub, "sort", c_sort, NULL);
1175     need_sort = 1;
1176   }
1177 
1178   /* need to open the file for writing in such a way that it does not truncate
1179    * the file, so use read-write mode.  */
1180   adata->fp = freopen(mailbox_path(m), "r+", adata->fp);
1181   if (!adata->fp)
1182   {
1183     mx_fastclose_mailbox(m);
1184     mutt_error(_("Fatal error!  Could not reopen mailbox!"));
1185     goto fatal;
1186   }
1187 
1188   mutt_sig_block();
1189 
1190   if (mbox_lock_mailbox(m, true, true) == -1)
1191   {
1192     mutt_sig_unblock();
1193     mutt_error(_("Unable to lock mailbox"));
1194     goto bail;
1195   }
1196 
1197   /* Check to make sure that the file hasn't changed on disk */
1198   enum MxStatus check = mbox_mbox_check(m);
1199   if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED))
1200   {
1201     /* new mail arrived, or mailbox reopened */
1202     rc = check;
1203     goto bail;
1204   }
1205   else if (check < 0)
1206   {
1207     goto fatal;
1208   }
1209 
1210   /* Create a temporary file to write the new version of the mailbox in. */
1211   tempfile = mutt_buffer_pool_get();
1212   mutt_buffer_mktemp(tempfile);
1213   int fd = open(mutt_buffer_string(tempfile), O_WRONLY | O_EXCL | O_CREAT, 0600);
1214   if ((fd == -1) || !(fp = fdopen(fd, "w")))
1215   {
1216     if (fd != -1)
1217     {
1218       close(fd);
1219       unlink_tempfile = true;
1220     }
1221     mutt_error(_("Could not create temporary file"));
1222     goto bail;
1223   }
1224   unlink_tempfile = true;
1225 
1226   /* find the first deleted/changed message.  we save a lot of time by only
1227    * rewriting the mailbox from the point where it has actually changed.  */
1228   int i = 0;
1229   for (; (i < m->msg_count) && !m->emails[i]->deleted &&
1230          !m->emails[i]->changed && !m->emails[i]->attach_del;
1231        i++)
1232   {
1233   }
1234   if (i == m->msg_count)
1235   {
1236     /* this means ctx->changed or m->msg_deleted was set, but no
1237      * messages were found to be changed or deleted.  This should
1238      * never happen, is we presume it is a bug in neomutt.  */
1239     mutt_error(
1240         _("sync: mbox modified, but no modified messages (report this bug)"));
1241     mutt_debug(LL_DEBUG1, "no modified messages\n");
1242     goto bail;
1243   }
1244 
1245   /* save the index of the first changed/deleted message */
1246   first = i;
1247   /* where to start overwriting */
1248   offset = m->emails[i]->offset;
1249 
1250   /* the offset stored in the header does not include the MMDF_SEP, so make
1251    * sure we seek to the correct location */
1252   if (m->type == MUTT_MMDF)
1253     offset -= (sizeof(MMDF_SEP) - 1);
1254 
1255   /* allocate space for the new offsets */
1256   new_offset = mutt_mem_calloc(m->msg_count - first, sizeof(struct MUpdate));
1257   old_offset = mutt_mem_calloc(m->msg_count - first, sizeof(struct MUpdate));
1258 
1259   if (m->verbose)
1260   {
1261     char msg[PATH_MAX];
1262     snprintf(msg, sizeof(msg), _("Writing %s..."), mailbox_path(m));
1263     progress = progress_new(msg, MUTT_PROGRESS_WRITE, m->msg_count);
1264   }
1265 
1266   for (i = first, j = 0; i < m->msg_count; i++)
1267   {
1268     if (m->verbose)
1269       progress_update(progress, i, i / (m->msg_count / 100 + 1));
1270     /* back up some information which is needed to restore offsets when
1271      * something fails.  */
1272 
1273     old_offset[i - first].valid = true;
1274     old_offset[i - first].hdr = m->emails[i]->offset;
1275     old_offset[i - first].body = m->emails[i]->body->offset;
1276     old_offset[i - first].lines = m->emails[i]->lines;
1277     old_offset[i - first].length = m->emails[i]->body->length;
1278 
1279     if (!m->emails[i]->deleted)
1280     {
1281       j++;
1282 
1283       if (m->type == MUTT_MMDF)
1284       {
1285         if (fputs(MMDF_SEP, fp) == EOF)
1286         {
1287           mutt_perror(mutt_buffer_string(tempfile));
1288           goto bail;
1289         }
1290       }
1291 
1292       /* save the new offset for this message.  we add 'offset' because the
1293        * temporary file only contains saved message which are located after
1294        * 'offset' in the real mailbox */
1295       new_offset[i - first].hdr = ftello(fp) + offset;
1296 
1297       struct Message *msg = mx_msg_open(m, m->emails[i]->msgno);
1298       const int rc2 = mutt_copy_message(fp, m->emails[i], msg, MUTT_CM_UPDATE,
1299                                         CH_FROM | CH_UPDATE | CH_UPDATE_LEN, 0);
1300       mx_msg_close(m, &msg);
1301       if (rc2 != 0)
1302       {
1303         mutt_perror(mutt_buffer_string(tempfile));
1304         goto bail;
1305       }
1306 
1307       /* Since messages could have been deleted, the offsets stored in memory
1308        * will be wrong, so update what we can, which is the offset of this
1309        * message, and the offset of the body.  If this is a multipart message,
1310        * we just flush the in memory cache so that the message will be reparsed
1311        * if the user accesses it later.  */
1312       new_offset[i - first].body = ftello(fp) - m->emails[i]->body->length + offset;
1313       mutt_body_free(&m->emails[i]->body->parts);
1314 
1315       switch (m->type)
1316       {
1317         case MUTT_MMDF:
1318           if (fputs(MMDF_SEP, fp) == EOF)
1319           {
1320             mutt_perror(mutt_buffer_string(tempfile));
1321             goto bail;
1322           }
1323           break;
1324         default:
1325           if (fputs("\n", fp) == EOF)
1326           {
1327             mutt_perror(mutt_buffer_string(tempfile));
1328             goto bail;
1329           }
1330       }
1331     }
1332   }
1333 
1334   if (mutt_file_fclose(&fp) != 0)
1335   {
1336     mutt_debug(LL_DEBUG1, "mutt_file_fclose (&) returned non-zero\n");
1337     mutt_perror(mutt_buffer_string(tempfile));
1338     goto bail;
1339   }
1340 
1341   /* Save the state of this folder. */
1342   if (stat(mailbox_path(m), &st) == -1)
1343   {
1344     mutt_perror(mailbox_path(m));
1345     goto bail;
1346   }
1347 
1348   unlink_tempfile = false;
1349 
1350   fp = fopen(mutt_buffer_string(tempfile), "r");
1351   if (!fp)
1352   {
1353     mutt_sig_unblock();
1354     mx_fastclose_mailbox(m);
1355     mutt_debug(LL_DEBUG1, "unable to reopen temp copy of mailbox!\n");
1356     mutt_perror(mutt_buffer_string(tempfile));
1357     FREE(&new_offset);
1358     FREE(&old_offset);
1359     goto fatal;
1360   }
1361 
1362   if ((fseeko(adata->fp, offset, SEEK_SET) != 0) || /* seek the append location */
1363       /* do a sanity check to make sure the mailbox looks ok */
1364       !fgets(buf, sizeof(buf), adata->fp) ||
1365       ((m->type == MUTT_MBOX) && !mutt_str_startswith(buf, "From ")) ||
1366       ((m->type == MUTT_MMDF) && !mutt_str_equal(MMDF_SEP, buf)))
1367   {
1368     mutt_debug(LL_DEBUG1, "message not in expected position\n");
1369     mutt_debug(LL_DEBUG1, "    LINE: %s\n", buf);
1370     i = -1;
1371   }
1372   else
1373   {
1374     if (fseeko(adata->fp, offset, SEEK_SET) != 0) /* return to proper offset */
1375     {
1376       i = -1;
1377       mutt_debug(LL_DEBUG1, "fseek() failed\n");
1378     }
1379     else
1380     {
1381       /* copy the temp mailbox back into place starting at the first
1382        * change/deleted message */
1383       if (m->verbose)
1384         mutt_message(_("Committing changes..."));
1385       i = mutt_file_copy_stream(fp, adata->fp);
1386 
1387       if (ferror(adata->fp))
1388         i = -1;
1389     }
1390     if (i >= 0)
1391     {
1392       m->size = ftello(adata->fp); /* update the mailbox->size of the mailbox */
1393       if ((m->size < 0) || (ftruncate(fileno(adata->fp), m->size) != 0))
1394       {
1395         i = -1;
1396         mutt_debug(LL_DEBUG1, "ftruncate() failed\n");
1397       }
1398     }
1399   }
1400 
1401   mutt_file_fclose(&fp);
1402   fp = NULL;
1403   mbox_unlock_mailbox(m);
1404 
1405   if ((mutt_file_fclose(&adata->fp) != 0) || (i == -1))
1406   {
1407     /* error occurred while writing the mailbox back, so keep the temp copy around */
1408 
1409     struct Buffer *savefile = mutt_buffer_pool_get();
1410 
1411     const char *const c_tmpdir = cs_subset_path(NeoMutt->sub, "tmpdir");
1412     mutt_buffer_printf(savefile, "%s/neomutt.%s-%s-%u", NONULL(c_tmpdir), NONULL(Username),
1413                        NONULL(ShortHostname), (unsigned int) getpid());
1414     rename(mutt_buffer_string(tempfile), mutt_buffer_string(savefile));
1415     mutt_sig_unblock();
1416     mx_fastclose_mailbox(m);
1417     mutt_buffer_pretty_mailbox(savefile);
1418     mutt_error(_("Write failed!  Saved partial mailbox to %s"), mutt_buffer_string(savefile));
1419     mutt_buffer_pool_release(&savefile);
1420     FREE(&new_offset);
1421     FREE(&old_offset);
1422     goto fatal;
1423   }
1424 
1425   /* Restore the previous access/modification times */
1426   mbox_reset_atime(m, &st);
1427 
1428   /* reopen the mailbox in read-only mode */
1429   adata->fp = mbox_open_readwrite(m);
1430   if (!adata->fp)
1431   {
1432     adata->fp = mbox_open_readonly(m);
1433   }
1434   if (!adata->fp)
1435   {
1436     unlink(mutt_buffer_string(tempfile));
1437     mutt_sig_unblock();
1438     mx_fastclose_mailbox(m);
1439     mutt_error(_("Fatal error!  Could not reopen mailbox!"));
1440     FREE(&new_offset);
1441     FREE(&old_offset);
1442     goto fatal;
1443   }
1444 
1445   /* update the offsets of the rewritten messages */
1446   for (i = first, j = first; i < m->msg_count; i++)
1447   {
1448     if (!m->emails[i]->deleted)
1449     {
1450       m->emails[i]->offset = new_offset[i - first].hdr;
1451       m->emails[i]->body->hdr_offset = new_offset[i - first].hdr;
1452       m->emails[i]->body->offset = new_offset[i - first].body;
1453       m->emails[i]->index = j++;
1454     }
1455   }
1456   FREE(&new_offset);
1457   FREE(&old_offset);
1458   unlink(mutt_buffer_string(tempfile)); /* remove partial copy of the mailbox */
1459   mutt_buffer_pool_release(&tempfile);
1460   mutt_sig_unblock();
1461 
1462   const bool c_check_mbox_size =
1463       cs_subset_bool(NeoMutt->sub, "check_mbox_size");
1464   if (c_check_mbox_size)
1465   {
1466     struct Mailbox *m_tmp = mailbox_find(mailbox_path(m));
1467     if (m_tmp && !m_tmp->has_new)
1468       mailbox_update(m_tmp);
1469   }
1470 
1471   progress_free(&progress);
1472   return 0; /* signal success */
1473 
1474 bail: /* Come here in case of disaster */
1475 
1476   mutt_file_fclose(&fp);
1477 
1478   if (tempfile && unlink_tempfile)
1479     unlink(mutt_buffer_string(tempfile));
1480 
1481   /* restore offsets, as far as they are valid */
1482   if ((first >= 0) && old_offset)
1483   {
1484     for (i = first; (i < m->msg_count) && old_offset[i - first].valid; i++)
1485     {
1486       m->emails[i]->offset = old_offset[i - first].hdr;
1487       m->emails[i]->body->hdr_offset = old_offset[i - first].hdr;
1488       m->emails[i]->body->offset = old_offset[i - first].body;
1489       m->emails[i]->lines = old_offset[i - first].lines;
1490       m->emails[i]->body->length = old_offset[i - first].length;
1491     }
1492   }
1493 
1494   /* this is ok to call even if we haven't locked anything */
1495   mbox_unlock_mailbox(m);
1496 
1497   mutt_sig_unblock();
1498   FREE(&new_offset);
1499   FREE(&old_offset);
1500 
1501   adata->fp = freopen(mailbox_path(m), "r", adata->fp);
1502   if (!adata->fp)
1503   {
1504     mutt_error(_("Could not reopen mailbox"));
1505     mx_fastclose_mailbox(m);
1506     goto fatal;
1507   }
1508 
1509   mailbox_changed(m, NT_MAILBOX_UPDATE);
1510   if (need_sort)
1511   {
1512     /* if the mailbox was reopened, the thread tree will be invalid so make
1513      * sure to start threading from scratch.  */
1514     mailbox_changed(m, NT_MAILBOX_RESORT);
1515   }
1516 
1517 fatal:
1518   mutt_buffer_pool_release(&tempfile);
1519   progress_free(&progress);
1520   return rc;
1521 }
1522 
1523 /**
1524  * mbox_mbox_close - Close a Mailbox - Implements MxOps::mbox_close() - @ingroup mx_mbox_close
1525  */
mbox_mbox_close(struct Mailbox * m)1526 static enum MxStatus mbox_mbox_close(struct Mailbox *m)
1527 {
1528   struct MboxAccountData *adata = mbox_adata_get(m);
1529   if (!adata)
1530     return MX_STATUS_ERROR;
1531 
1532   if (!adata->fp)
1533     return MX_STATUS_OK;
1534 
1535   if (adata->append)
1536   {
1537     mutt_file_unlock(fileno(adata->fp));
1538     mutt_sig_unblock();
1539   }
1540 
1541   mutt_file_fclose(&adata->fp);
1542 
1543   /* fix up the times so mailbox won't get confused */
1544   if (m->peekonly && !mutt_buffer_is_empty(&m->pathbuf) &&
1545       (mutt_file_timespec_compare(&m->mtime, &adata->atime) > 0))
1546   {
1547 #ifdef HAVE_UTIMENSAT
1548     struct timespec ts[2];
1549     ts[0] = adata->atime;
1550     ts[1] = m->mtime;
1551     utimensat(AT_FDCWD, m->path, ts, 0);
1552 #else
1553     struct utimbuf ut;
1554     ut.actime = adata->atime.tv_sec;
1555     ut.modtime = m->mtime.tv_sec;
1556     utime(mailbox_path(m), &ut);
1557 #endif
1558   }
1559 
1560   return MX_STATUS_OK;
1561 }
1562 
1563 /**
1564  * mbox_msg_open - Open an email message in a Mailbox - Implements MxOps::msg_open() - @ingroup mx_msg_open
1565  */
mbox_msg_open(struct Mailbox * m,struct Message * msg,int msgno)1566 static bool mbox_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
1567 {
1568   struct MboxAccountData *adata = mbox_adata_get(m);
1569   if (!adata)
1570     return false;
1571 
1572   msg->fp = mutt_file_fopen(mailbox_path(m), "r");
1573   if (!msg->fp)
1574     return false;
1575 
1576   return true;
1577 }
1578 
1579 /**
1580  * mbox_msg_open_new - Open a new message in a Mailbox - Implements MxOps::msg_open_new() - @ingroup mx_msg_open_new
1581  */
mbox_msg_open_new(struct Mailbox * m,struct Message * msg,const struct Email * e)1582 static bool mbox_msg_open_new(struct Mailbox *m, struct Message *msg, const struct Email *e)
1583 {
1584   struct MboxAccountData *adata = mbox_adata_get(m);
1585   if (!adata)
1586     return false;
1587 
1588   msg->fp = adata->fp;
1589   return true;
1590 }
1591 
1592 /**
1593  * mbox_msg_commit - Save changes to an email - Implements MxOps::msg_commit() - @ingroup mx_msg_commit
1594  */
mbox_msg_commit(struct Mailbox * m,struct Message * msg)1595 static int mbox_msg_commit(struct Mailbox *m, struct Message *msg)
1596 {
1597   if (fputc('\n', msg->fp) == EOF)
1598     return -1;
1599 
1600   if ((fflush(msg->fp) == EOF) || (fsync(fileno(msg->fp)) == -1))
1601   {
1602     mutt_perror(_("Can't write message"));
1603     return -1;
1604   }
1605 
1606   return 0;
1607 }
1608 
1609 /**
1610  * mbox_msg_close - Close an email - Implements MxOps::msg_close() - @ingroup mx_msg_close
1611  */
mbox_msg_close(struct Mailbox * m,struct Message * msg)1612 static int mbox_msg_close(struct Mailbox *m, struct Message *msg)
1613 {
1614   if (msg->write)
1615     msg->fp = NULL;
1616   else
1617     mutt_file_fclose(&msg->fp);
1618 
1619   return 0;
1620 }
1621 
1622 /**
1623  * mbox_msg_padding_size - Bytes of padding between messages - Implements MxOps::msg_padding_size() - @ingroup mx_msg_padding_size
1624  * @param m Mailbox
1625  * @retval 1 Always
1626  */
mbox_msg_padding_size(struct Mailbox * m)1627 static int mbox_msg_padding_size(struct Mailbox *m)
1628 {
1629   return 1;
1630 }
1631 
1632 /**
1633  * mbox_path_probe - Is this an mbox Mailbox? - Implements MxOps::path_probe() - @ingroup mx_path_probe
1634  */
mbox_path_probe(const char * path,const struct stat * st)1635 enum MailboxType mbox_path_probe(const char *path, const struct stat *st)
1636 {
1637   if (!st)
1638     return MUTT_UNKNOWN;
1639 
1640   if (S_ISDIR(st->st_mode))
1641     return MUTT_UNKNOWN;
1642 
1643   if (st->st_size == 0)
1644     return MUTT_MBOX;
1645 
1646   FILE *fp = fopen(path, "r");
1647   if (!fp)
1648     return MUTT_UNKNOWN;
1649 
1650   int ch;
1651   while ((ch = fgetc(fp)) != EOF)
1652   {
1653     /* Some mailbox creation tools erroneously append a blank line to
1654      * a file before appending a mail message.  This allows neomutt to
1655      * detect type for and thus open those files. */
1656     if ((ch != '\n') && (ch != '\r'))
1657     {
1658       ungetc(ch, fp);
1659       break;
1660     }
1661   }
1662 
1663   enum MailboxType type = MUTT_UNKNOWN;
1664   char tmp[256];
1665   if (fgets(tmp, sizeof(tmp), fp))
1666   {
1667     if (mutt_str_startswith(tmp, "From "))
1668       type = MUTT_MBOX;
1669     else if (mutt_str_equal(tmp, MMDF_SEP))
1670       type = MUTT_MMDF;
1671   }
1672   mutt_file_fclose(&fp);
1673 
1674   const bool c_check_mbox_size =
1675       cs_subset_bool(NeoMutt->sub, "check_mbox_size");
1676   if (!c_check_mbox_size)
1677   {
1678     /* need to restore the times here, the file was not really accessed,
1679      * only the type was accessed.  This is important, because detection
1680      * of "new mail" depends on those times set correctly.  */
1681 #ifdef HAVE_UTIMENSAT
1682     struct timespec ts[2];
1683     mutt_file_get_stat_timespec(&ts[0], &st, MUTT_STAT_ATIME);
1684     mutt_file_get_stat_timespec(&ts[1], &st, MUTT_STAT_MTIME);
1685     utimensat(AT_FDCWD, path, ts, 0);
1686 #else
1687     struct utimbuf times;
1688     times.actime = st->st_atime;
1689     times.modtime = st->st_mtime;
1690     utime(path, &times);
1691 #endif
1692   }
1693 
1694   return type;
1695 }
1696 
1697 /**
1698  * mbox_path_canon - Canonicalise a Mailbox path - Implements MxOps::path_canon() - @ingroup mx_path_canon
1699  */
mbox_path_canon(char * buf,size_t buflen)1700 static int mbox_path_canon(char *buf, size_t buflen)
1701 {
1702   mutt_path_canon(buf, buflen, HomeDir, false);
1703   return 0;
1704 }
1705 
1706 /**
1707  * mbox_path_pretty - Abbreviate a Mailbox path - Implements MxOps::path_pretty() - @ingroup mx_path_pretty
1708  */
mbox_path_pretty(char * buf,size_t buflen,const char * folder)1709 static int mbox_path_pretty(char *buf, size_t buflen, const char *folder)
1710 {
1711   if (mutt_path_abbr_folder(buf, folder))
1712     return 0;
1713 
1714   if (mutt_path_pretty(buf, buflen, HomeDir, false))
1715     return 0;
1716 
1717   return -1;
1718 }
1719 
1720 /**
1721  * mbox_path_parent - Find the parent of a Mailbox path - Implements MxOps::path_parent() - @ingroup mx_path_parent
1722  */
mbox_path_parent(char * buf,size_t buflen)1723 static int mbox_path_parent(char *buf, size_t buflen)
1724 {
1725   if (mutt_path_parent(buf))
1726     return 0;
1727 
1728   if (buf[0] == '~')
1729     mutt_path_canon(buf, buflen, HomeDir, false);
1730 
1731   if (mutt_path_parent(buf))
1732     return 0;
1733 
1734   return -1;
1735 }
1736 
1737 /**
1738  * mbox_path_is_empty - Is the mailbox empty - Implements MxOps::path_is_empty() - @ingroup mx_path_is_empty
1739  */
mbox_path_is_empty(const char * path)1740 static int mbox_path_is_empty(const char *path)
1741 {
1742   return mutt_file_check_empty(path);
1743 }
1744 
1745 /**
1746  * mmdf_msg_commit - Save changes to an email - Implements MxOps::msg_commit() - @ingroup mx_msg_commit
1747  */
mmdf_msg_commit(struct Mailbox * m,struct Message * msg)1748 static int mmdf_msg_commit(struct Mailbox *m, struct Message *msg)
1749 {
1750   if (fputs(MMDF_SEP, msg->fp) == EOF)
1751     return -1;
1752 
1753   if ((fflush(msg->fp) == EOF) || (fsync(fileno(msg->fp)) == -1))
1754   {
1755     mutt_perror(_("Can't write message"));
1756     return -1;
1757   }
1758 
1759   return 0;
1760 }
1761 
1762 /**
1763  * mmdf_msg_padding_size - Bytes of padding between messages - Implements MxOps::msg_padding_size() - @ingroup mx_msg_padding_size
1764  * @param m Mailbox
1765  * @retval 10 Always
1766  */
mmdf_msg_padding_size(struct Mailbox * m)1767 static int mmdf_msg_padding_size(struct Mailbox *m)
1768 {
1769   return 10;
1770 }
1771 
1772 /**
1773  * mbox_mbox_check_stats - Check the Mailbox statistics - Implements MxOps::mbox_check_stats() - @ingroup mx_mbox_check_stats
1774  */
mbox_mbox_check_stats(struct Mailbox * m,uint8_t flags)1775 static enum MxStatus mbox_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1776 {
1777   struct stat st = { 0 };
1778   if (stat(mailbox_path(m), &st) != 0)
1779     return MX_STATUS_ERROR;
1780 
1781   bool new_or_changed;
1782 
1783   const bool c_check_mbox_size =
1784       cs_subset_bool(NeoMutt->sub, "check_mbox_size");
1785   if (c_check_mbox_size)
1786     new_or_changed = (st.st_size > m->size);
1787   else
1788   {
1789     new_or_changed =
1790         (mutt_file_stat_compare(&st, MUTT_STAT_MTIME, &st, MUTT_STAT_ATIME) > 0) ||
1791         (m->newly_created &&
1792          (mutt_file_stat_compare(&st, MUTT_STAT_CTIME, &st, MUTT_STAT_MTIME) == 0) &&
1793          (mutt_file_stat_compare(&st, MUTT_STAT_CTIME, &st, MUTT_STAT_ATIME) == 0));
1794   }
1795 
1796   if (new_or_changed)
1797   {
1798     const bool c_mail_check_recent =
1799         cs_subset_bool(NeoMutt->sub, "mail_check_recent");
1800     if (!c_mail_check_recent ||
1801         (mutt_file_stat_timespec_compare(&st, MUTT_STAT_MTIME, &m->last_visited) > 0))
1802     {
1803       m->has_new = true;
1804     }
1805   }
1806   else if (c_check_mbox_size)
1807   {
1808     /* some other program has deleted mail from the folder */
1809     m->size = (off_t) st.st_size;
1810   }
1811 
1812   if (m->newly_created && ((st.st_ctime != st.st_mtime) || (st.st_ctime != st.st_atime)))
1813     m->newly_created = false;
1814 
1815   if ((flags != 0) && mutt_file_stat_timespec_compare(&st, MUTT_STAT_MTIME,
1816                                                       &m->stats_last_checked) > 0)
1817   {
1818     bool old_peek = m->peekonly;
1819     mx_mbox_open(m, MUTT_QUIET | MUTT_NOSORT | MUTT_PEEK);
1820     mx_mbox_close(m);
1821     m->peekonly = old_peek;
1822   }
1823 
1824   if (m->has_new || m->msg_new)
1825     return MX_STATUS_NEW_MAIL;
1826   return MX_STATUS_OK;
1827 }
1828 
1829 /**
1830  * MxMboxOps - Mbox Mailbox - Implements ::MxOps - @ingroup mx_api
1831  */
1832 struct MxOps MxMboxOps = {
1833   // clang-format off
1834   .type            = MUTT_MBOX,
1835   .name             = "mbox",
1836   .is_local         = true,
1837   .ac_owns_path     = mbox_ac_owns_path,
1838   .ac_add           = mbox_ac_add,
1839   .mbox_open        = mbox_mbox_open,
1840   .mbox_open_append = mbox_mbox_open_append,
1841   .mbox_check       = mbox_mbox_check,
1842   .mbox_check_stats = mbox_mbox_check_stats,
1843   .mbox_sync        = mbox_mbox_sync,
1844   .mbox_close       = mbox_mbox_close,
1845   .msg_open         = mbox_msg_open,
1846   .msg_open_new     = mbox_msg_open_new,
1847   .msg_commit       = mbox_msg_commit,
1848   .msg_close        = mbox_msg_close,
1849   .msg_padding_size = mbox_msg_padding_size,
1850   .msg_save_hcache  = NULL,
1851   .tags_edit        = NULL,
1852   .tags_commit      = NULL,
1853   .path_probe       = mbox_path_probe,
1854   .path_canon       = mbox_path_canon,
1855   .path_pretty      = mbox_path_pretty,
1856   .path_parent      = mbox_path_parent,
1857   .path_is_empty    = mbox_path_is_empty,
1858   // clang-format on
1859 };
1860 
1861 /**
1862  * MxMmdfOps - MMDF Mailbox - Implements ::MxOps - @ingroup mx_api
1863  */
1864 struct MxOps MxMmdfOps = {
1865   // clang-format off
1866   .type            = MUTT_MMDF,
1867   .name             = "mmdf",
1868   .is_local         = true,
1869   .ac_owns_path     = mbox_ac_owns_path,
1870   .ac_add           = mbox_ac_add,
1871   .mbox_open        = mbox_mbox_open,
1872   .mbox_open_append = mbox_mbox_open_append,
1873   .mbox_check       = mbox_mbox_check,
1874   .mbox_check_stats = mbox_mbox_check_stats,
1875   .mbox_sync        = mbox_mbox_sync,
1876   .mbox_close       = mbox_mbox_close,
1877   .msg_open         = mbox_msg_open,
1878   .msg_open_new     = mbox_msg_open_new,
1879   .msg_commit       = mmdf_msg_commit,
1880   .msg_close        = mbox_msg_close,
1881   .msg_padding_size = mmdf_msg_padding_size,
1882   .msg_save_hcache  = NULL,
1883   .tags_edit        = NULL,
1884   .tags_commit      = NULL,
1885   .path_probe       = mbox_path_probe,
1886   .path_canon       = mbox_path_canon,
1887   .path_pretty      = mbox_path_pretty,
1888   .path_parent      = mbox_path_parent,
1889   .path_is_empty    = mbox_path_is_empty,
1890   // clang-format on
1891 };
1892