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, ×);
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