mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2000,2010,2013 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 2016 Kevin J. McCarthy <kevin@8t8.us>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#if HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "mutt.h"
25#include "buffy.h"
26#include "mailbox.h"
27#include "mx.h"
28
29#include "mutt_curses.h"
30
31#ifdef USE_SIDEBAR
32#include "sidebar.h"
33#endif
34
35#ifdef USE_IMAP
36#include "imap.h"
37#endif
38
39#include <string.h>
40#include <sys/stat.h>
41#include <dirent.h>
42#include <utime.h>
43#include <ctype.h>
44#include <unistd.h>
45
46#include <stdio.h>
47
48static time_t BuffyTime = 0; /* last time we started checking for mail */
49static time_t BuffyStatsTime = 0; /* last time we check performed mail_check_stats */
50time_t BuffyDoneTime = 0; /* last time we knew for sure how much mail there was. */
51static short BuffyCount = 0; /* how many boxes with new mail */
52static short BuffyNotify = 0; /* # of unnotified new boxes */
53
54static BUFFY* buffy_get (const char *path);
55
56/* Find the last message in the file.
57 * upon success return 0. If no message found - return -1 */
58
59static int fseek_last_message (FILE * f)
60{
61 LOFF_T pos;
62 char buffer[BUFSIZ + 9]; /* 7 for "\n\nFrom " */
63 int bytes_read;
64 int i; /* Index into `buffer' for scanning. */
65
66 memset (buffer, 0, sizeof(buffer));
67 fseek (f, 0, SEEK_END);
68 pos = ftello (f);
69
70 /* Set `bytes_read' to the size of the last, probably partial, buffer; 0 <
71 * `bytes_read' <= `BUFSIZ'. */
72 bytes_read = pos % BUFSIZ;
73 if (bytes_read == 0)
74 bytes_read = BUFSIZ;
75 /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all
76 * reads will be on block boundaries, which might increase efficiency. */
77 while ((pos -= bytes_read) >= 0)
78 {
79 /* we save in the buffer at the end the first 7 chars from the last read */
80 strncpy (buffer + BUFSIZ, buffer, 5+2); /* 2 == 2 * mutt_strlen(CRLF) */
81 fseeko (f, pos, SEEK_SET);
82 bytes_read = fread (buffer, sizeof (char), bytes_read, f);
83 if (bytes_read == -1)
84 return -1;
85 for (i = bytes_read; --i >= 0;)
86 if (!mutt_strncmp (buffer + i, "\n\nFrom ", mutt_strlen ("\n\nFrom ")))
87 { /* found it - go to the beginning of the From */
88 fseeko (f, pos + i + 2, SEEK_SET);
89 return 0;
90 }
91 bytes_read = BUFSIZ;
92 }
93
94 /* here we are at the beginning of the file */
95 if (!mutt_strncmp ("From ", buffer, 5))
96 {
97 fseek (f, 0, 0);
98 return (0);
99 }
100
101 return (-1);
102}
103
104/* Return 1 if the last message is new */
105static int test_last_status_new (FILE * f)
106{
107 HEADER *hdr;
108 ENVELOPE* tmp_envelope;
109 int result = 0;
110
111 if (fseek_last_message (f) == -1)
112 return (0);
113
114 hdr = mutt_new_header ();
115 tmp_envelope = mutt_read_rfc822_header (f, hdr, 0, 0);
116 if (!(hdr->read || hdr->old))
117 result = 1;
118
119 mutt_free_envelope(&tmp_envelope);
120 mutt_free_header (&hdr);
121
122 return result;
123}
124
125static int test_new_folder (const char *path)
126{
127 FILE *f;
128 int rc = 0;
129 int typ;
130
131 typ = mx_get_magic (path);
132
133 if (typ != MUTT_MBOX && typ != MUTT_MMDF)
134 return 0;
135
136 if ((f = fopen (path, "rb")))
137 {
138 rc = test_last_status_new (f);
139 safe_fclose (&f);
140 }
141
142 return rc;
143}
144
145void mutt_buffy_cleanup (const char *buf, struct stat *st)
146{
147 struct utimbuf ut;
148 BUFFY *tmp;
149
150 if (option(OPTCHECKMBOXSIZE))
151 {
152 tmp = mutt_find_mailbox (buf);
153 if (tmp && !tmp->new)
154 mutt_update_mailbox (tmp);
155 }
156 else
157 {
158 /* fix up the times so buffy won't get confused */
159 if (st->st_mtime > st->st_atime)
160 {
161 ut.actime = st->st_atime;
162 ut.modtime = time (NULL);
163 utime (buf, &ut);
164 }
165 else
166 utime (buf, NULL);
167 }
168}
169
170BUFFY *mutt_find_mailbox (const char *path)
171{
172 BUFFY *tmp = NULL;
173 struct stat sb;
174 struct stat tmp_sb;
175
176 if (stat (path,&sb) != 0)
177 return NULL;
178
179 for (tmp = Incoming; tmp; tmp = tmp->next)
180 {
181 if (stat (tmp->path,&tmp_sb) ==0 &&
182 sb.st_dev == tmp_sb.st_dev && sb.st_ino == tmp_sb.st_ino)
183 break;
184 }
185 return tmp;
186}
187
188void mutt_update_mailbox (BUFFY * b)
189{
190 struct stat sb;
191
192 if (!b)
193 return;
194
195 if (stat (b->path, &sb) == 0)
196 b->size = (off_t) sb.st_size;
197 else
198 b->size = 0;
199 return;
200}
201
202static BUFFY *buffy_new (const char *path)
203{
204 BUFFY* buffy;
205 char rp[PATH_MAX] = "";
206 char *r = NULL;
207
208 buffy = (BUFFY *) safe_calloc (1, sizeof (BUFFY));
209 strfcpy (buffy->path, path, sizeof (buffy->path));
210 r = realpath (path, rp);
211 strfcpy (buffy->realpath, r ? rp : path, sizeof (buffy->realpath));
212 buffy->next = NULL;
213 buffy->magic = 0;
214
215 return buffy;
216}
217
218static void buffy_free (BUFFY **mailbox)
219{
220 FREE (mailbox); /* __FREE_CHECKED__ */
221}
222
223int mutt_parse_mailboxes (BUFFER *path, BUFFER *s, unsigned long data, BUFFER *err)
224{
225 BUFFY **tmp,*tmp1;
226 char buf[_POSIX_PATH_MAX];
227 struct stat sb;
228 char f1[PATH_MAX];
229 char *p;
230
231 while (MoreArgs (s))
232 {
233 mutt_extract_token (path, s, 0);
234 strfcpy (buf, path->data, sizeof (buf));
235
236 if(data == MUTT_UNMAILBOXES && mutt_strcmp(buf,"*") == 0)
237 {
238 for (tmp = &Incoming; *tmp;)
239 {
240 tmp1=(*tmp)->next;
241#ifdef USE_SIDEBAR
242 mutt_sb_notify_mailbox (*tmp, 0);
243#endif
244 buffy_free (tmp);
245 *tmp=tmp1;
246 }
247 return 0;
248 }
249
250 mutt_expand_path (buf, sizeof (buf));
251
252 /* Skip empty tokens. */
253 if(!*buf) continue;
254
255 /* avoid duplicates */
256 p = realpath (buf, f1);
257 for (tmp = &Incoming; *tmp; tmp = &((*tmp)->next))
258 {
259 if (mutt_strcmp (p ? p : buf, (*tmp)->realpath) == 0)
260 {
261 dprint(3,(debugfile,"mailbox '%s' already registered as '%s'\n", buf, (*tmp)->path));
262 break;
263 }
264 }
265
266 if(data == MUTT_UNMAILBOXES)
267 {
268 if(*tmp)
269 {
270 tmp1=(*tmp)->next;
271#ifdef USE_SIDEBAR
272 mutt_sb_notify_mailbox (*tmp, 0);
273#endif
274 buffy_free (tmp);
275 *tmp=tmp1;
276 }
277 continue;
278 }
279
280 if (!*tmp) {
281 *tmp = buffy_new (buf);
282#ifdef USE_SIDEBAR
283 mutt_sb_notify_mailbox (*tmp, 1);
284#endif
285 }
286
287 (*tmp)->new = 0;
288 (*tmp)->notified = 1;
289 (*tmp)->newly_created = 0;
290
291 /* for check_mbox_size, it is important that if the folder is new (tested by
292 * reading it), the size is set to 0 so that later when we check we see
293 * that it increased . without check_mbox_size we probably don't care.
294 */
295 if (option(OPTCHECKMBOXSIZE) &&
296 stat ((*tmp)->path, &sb) == 0 && !test_new_folder ((*tmp)->path))
297 {
298 /* some systems out there don't have an off_t type */
299 (*tmp)->size = (off_t) sb.st_size;
300 }
301 else
302 (*tmp)->size = 0;
303 }
304 return 0;
305}
306
307/* Checks the specified maildir subdir (cur or new) for new mail or mail counts.
308 * check_new: if true, check for new mail.
309 * check_stats: if true, count total, new, and flagged mesages.
310 * Returns 1 if the dir has new mail.
311 */
312static int buffy_maildir_check_dir (BUFFY* mailbox, const char *dir_name, int check_new,
313 int check_stats)
314{
315 char path[_POSIX_PATH_MAX];
316 char msgpath[_POSIX_PATH_MAX];
317 DIR *dirp;
318 struct dirent *de;
319 char *p;
320 int rc = 0;
321 struct stat sb;
322
323 snprintf (path, sizeof (path), "%s/%s", mailbox->path, dir_name);
324
325 /* when $mail_check_recent is set, if the new/ directory hasn't been modified since
326 * the user last exited the mailbox, then we know there is no recent mail.
327 */
328 if (check_new && option(OPTMAILCHECKRECENT))
329 {
330 if (stat(path, &sb) == 0 && sb.st_mtime < mailbox->last_visited)
331 {
332 rc = 0;
333 check_new = 0;
334 }
335 }
336
337 if (! (check_new || check_stats))
338 return rc;
339
340 if ((dirp = opendir (path)) == NULL)
341 {
342 mailbox->magic = 0;
343 return 0;
344 }
345
346 while ((de = readdir (dirp)) != NULL)
347 {
348 if (*de->d_name == '.')
349 continue;
350
351 p = strstr (de->d_name, ":2,");
352 if (p && strchr (p + 3, 'T'))
353 continue;
354
355 if (check_stats)
356 {
357 mailbox->msg_count++;
358 if (p && strchr (p + 3, 'F'))
359 mailbox->msg_flagged++;
360 }
361 if (!p || !strchr (p + 3, 'S'))
362 {
363 if (check_stats)
364 mailbox->msg_unread++;
365 if (check_new)
366 {
367 if (option(OPTMAILCHECKRECENT))
368 {
369 snprintf(msgpath, sizeof(msgpath), "%s/%s", path, de->d_name);
370 /* ensure this message was received since leaving this mailbox */
371 if (stat(msgpath, &sb) == 0 && (sb.st_ctime <= mailbox->last_visited))
372 continue;
373 }
374 mailbox->new = 1;
375 rc = 1;
376 check_new = 0;
377 if (!check_stats)
378 break;
379 }
380 }
381 }
382
383 closedir (dirp);
384
385 return rc;
386}
387
388/* Checks new mail for a maildir mailbox.
389 * check_stats: if true, also count total, new, and flagged mesages.
390 * Returns 1 if the mailbox has new mail.
391 */
392static int buffy_maildir_check (BUFFY* mailbox, int check_stats)
393{
394 int rc, check_new = 1;
395
396 if (check_stats)
397 {
398 mailbox->msg_count = 0;
399 mailbox->msg_unread = 0;
400 mailbox->msg_flagged = 0;
401 }
402
403 rc = buffy_maildir_check_dir (mailbox, "new", check_new, check_stats);
404
405 check_new = !rc && option (OPTMAILDIRCHECKCUR);
406 if (check_new || check_stats)
407 if (buffy_maildir_check_dir (mailbox, "cur", check_new, check_stats))
408 rc = 1;
409
410 return rc;
411}
412
413/* Checks new mail for an mbox mailbox
414 * check_stats: if true, also count total, new, and flagged mesages.
415 * Returns 1 if the mailbox has new mail.
416 */
417static int buffy_mbox_check (BUFFY* mailbox, struct stat *sb, int check_stats)
418{
419 int rc = 0;
420 int new_or_changed;
421 CONTEXT ctx;
422
423 if (option (OPTCHECKMBOXSIZE))
424 new_or_changed = sb->st_size > mailbox->size;
425 else
426 new_or_changed = sb->st_mtime > sb->st_atime
427 || (mailbox->newly_created && sb->st_ctime == sb->st_mtime && sb->st_ctime == sb->st_atime);
428
429 if (new_or_changed)
430 {
431 if (!option(OPTMAILCHECKRECENT) || sb->st_mtime > mailbox->last_visited)
432 {
433 rc = 1;
434 mailbox->new = 1;
435 }
436 }
437 else if (option(OPTCHECKMBOXSIZE))
438 {
439 /* some other program has deleted mail from the folder */
440 mailbox->size = (off_t) sb->st_size;
441 }
442
443 if (mailbox->newly_created &&
444 (sb->st_ctime != sb->st_mtime || sb->st_ctime != sb->st_atime))
445 mailbox->newly_created = 0;
446
447 if (check_stats &&
448 (mailbox->stats_last_checked < sb->st_mtime))
449 {
450 if (mx_open_mailbox (mailbox->path,
451 MUTT_READONLY | MUTT_QUIET | MUTT_NOSORT | MUTT_PEEK,
452 &ctx) != NULL)
453 {
454 mailbox->msg_count = ctx.msgcount;
455 mailbox->msg_unread = ctx.unread;
456 mailbox->msg_flagged = ctx.flagged;
457 mailbox->stats_last_checked = ctx.mtime;
458 mx_close_mailbox (&ctx, 0);
459 }
460 }
461
462 return rc;
463}
464
465/* Check all Incoming for new mail and total/new/flagged messages
466 * force: if true, ignore BuffyTimeout and check for new mail anyway
467 */
468int mutt_buffy_check (int force)
469{
470 BUFFY *tmp;
471 struct stat sb;
472 struct stat contex_sb;
473 time_t t;
474 int check_stats = 0;
475#ifdef USE_SIDEBAR
476 short orig_new;
477 int orig_count, orig_unread, orig_flagged;
478#endif
479
480 sb.st_size=0;
481 contex_sb.st_dev=0;
482 contex_sb.st_ino=0;
483
484#ifdef USE_IMAP
485 /* update postponed count as well, on force */
486 if (force)
487 mutt_update_num_postponed ();
488#endif
489
490 /* fastest return if there are no mailboxes */
491 if (!Incoming)
492 return 0;
493 t = time (NULL);
494 if (!force && (t - BuffyTime < BuffyTimeout))
495 return BuffyCount;
496
497 if (option (OPTMAILCHECKSTATS) &&
498 (t - BuffyStatsTime >= BuffyCheckStatsInterval))
499 {
500 check_stats = 1;
501 BuffyStatsTime = t;
502 }
503
504 BuffyTime = t;
505 BuffyCount = 0;
506 BuffyNotify = 0;
507
508#ifdef USE_IMAP
509 BuffyCount += imap_buffy_check (force, check_stats);
510#endif
511
512 /* check device ID and serial number instead of comparing paths */
513 if (!Context || Context->magic == MUTT_IMAP || Context->magic == MUTT_POP
514 || stat (Context->path, &contex_sb) != 0)
515 {
516 contex_sb.st_dev=0;
517 contex_sb.st_ino=0;
518 }
519
520 for (tmp = Incoming; tmp; tmp = tmp->next)
521 {
522#ifdef USE_SIDEBAR
523 orig_new = tmp->new;
524 orig_count = tmp->msg_count;
525 orig_unread = tmp->msg_unread;
526 orig_flagged = tmp->msg_flagged;
527#endif
528
529 if (tmp->magic != MUTT_IMAP)
530 {
531 tmp->new = 0;
532#ifdef USE_POP
533 if (mx_is_pop (tmp->path))
534 tmp->magic = MUTT_POP;
535 else
536#endif
537 if (stat (tmp->path, &sb) != 0 || (S_ISREG(sb.st_mode) && sb.st_size == 0) ||
538 (!tmp->magic && (tmp->magic = mx_get_magic (tmp->path)) <= 0))
539 {
540 /* if the mailbox still doesn't exist, set the newly created flag to
541 * be ready for when it does. */
542 tmp->newly_created = 1;
543 tmp->magic = 0;
544 tmp->size = 0;
545 continue;
546 }
547 }
548
549 /* check to see if the folder is the currently selected folder
550 * before polling */
551 if (!Context || !Context->path ||
552 (( tmp->magic == MUTT_IMAP || tmp->magic == MUTT_POP )
553 ? mutt_strcmp (tmp->path, Context->path) :
554 (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino)))
555 {
556 switch (tmp->magic)
557 {
558 case MUTT_MBOX:
559 case MUTT_MMDF:
560 if (buffy_mbox_check (tmp, &sb, check_stats) > 0)
561 BuffyCount++;
562 break;
563
564 case MUTT_MAILDIR:
565 if (buffy_maildir_check (tmp, check_stats) > 0)
566 BuffyCount++;
567 break;
568
569 case MUTT_MH:
570 if (mh_buffy (tmp, check_stats) > 0)
571 BuffyCount++;
572 break;
573 }
574 }
575 else if (option(OPTCHECKMBOXSIZE) && Context && Context->path)
576 tmp->size = (off_t) sb.st_size; /* update the size of current folder */
577
578#ifdef USE_SIDEBAR
579 if ((orig_new != tmp->new) ||
580 (orig_count != tmp->msg_count) ||
581 (orig_unread != tmp->msg_unread) ||
582 (orig_flagged != tmp->msg_flagged))
583 SidebarNeedsRedraw = 1;
584#endif
585
586 if (!tmp->new)
587 tmp->notified = 0;
588 else if (!tmp->notified)
589 BuffyNotify++;
590 }
591
592 BuffyDoneTime = BuffyTime;
593 return (BuffyCount);
594}
595
596int mutt_buffy_list (void)
597{
598 BUFFY *tmp;
599 char path[_POSIX_PATH_MAX];
600 char buffylist[2*STRING];
601 size_t pos = 0;
602 int first = 1;
603
604 int have_unnotified = BuffyNotify;
605
606 buffylist[0] = 0;
607 pos += strlen (strncat (buffylist, _("New mail in "), sizeof (buffylist) - 1 - pos)); /* __STRNCAT_CHECKED__ */
608 for (tmp = Incoming; tmp; tmp = tmp->next)
609 {
610 /* Is there new mail in this mailbox? */
611 if (!tmp->new || (have_unnotified && tmp->notified))
612 continue;
613
614 strfcpy (path, tmp->path, sizeof (path));
615 mutt_pretty_mailbox (path, sizeof (path));
616
617 if (!first && (MuttMessageWindow->cols >= 7) &&
618 (pos + strlen (path) >= (size_t)MuttMessageWindow->cols - 7))
619 break;
620
621 if (!first)
622 pos += strlen (strncat(buffylist + pos, ", ", sizeof(buffylist)-1-pos)); /* __STRNCAT_CHECKED__ */
623
624 /* Prepend an asterisk to mailboxes not already notified */
625 if (!tmp->notified)
626 {
627 /* pos += strlen (strncat(buffylist + pos, "*", sizeof(buffylist)-1-pos)); __STRNCAT_CHECKED__ */
628 tmp->notified = 1;
629 BuffyNotify--;
630 }
631 pos += strlen (strncat(buffylist + pos, path, sizeof(buffylist)-1-pos)); /* __STRNCAT_CHECKED__ */
632 first = 0;
633 }
634 if (!first && tmp)
635 {
636 strncat (buffylist + pos, ", ...", sizeof (buffylist) - 1 - pos); /* __STRNCAT_CHECKED__ */
637 }
638 if (!first)
639 {
640 mutt_message ("%s", buffylist);
641 return (1);
642 }
643 /* there were no mailboxes needing to be notified, so clean up since
644 * BuffyNotify has somehow gotten out of sync
645 */
646 BuffyNotify = 0;
647 return (0);
648}
649
650void mutt_buffy_setnotified (const char *path)
651{
652 BUFFY *buffy;
653
654 buffy = buffy_get(path);
655 if (!buffy)
656 return;
657
658 buffy->notified = 1;
659 time(&buffy->last_visited);
660}
661
662int mutt_buffy_notify (void)
663{
664 if (mutt_buffy_check (0) && BuffyNotify)
665 {
666 return (mutt_buffy_list ());
667 }
668 return (0);
669}
670
671/*
672 * mutt_buffy() -- incoming folders completion routine
673 *
674 * given a folder name, this routine gives the next incoming folder with new
675 * mail.
676 */
677void mutt_buffy (char *s, size_t slen)
678{
679 BUFFY *tmp = Incoming;
680 int pass, found = 0;
681
682 mutt_expand_path (s, slen);
683
684 if (mutt_buffy_check (0))
685 {
686 for (pass = 0; pass < 2; pass++)
687 for (tmp = Incoming; tmp; tmp = tmp->next)
688 {
689 mutt_expand_path (tmp->path, sizeof (tmp->path));
690 if ((found || pass) && tmp->new)
691 {
692 strfcpy (s, tmp->path, slen);
693 mutt_pretty_mailbox (s, slen);
694 return;
695 }
696 if (mutt_strcmp (s, tmp->path) == 0)
697 found = 1;
698 }
699
700 mutt_buffy_check (1); /* buffy was wrong - resync things */
701 }
702
703 /* no folders with new mail */
704 *s = '\0';
705}
706
707/* fetch buffy object for given path, if present */
708static BUFFY* buffy_get (const char *path)
709{
710 BUFFY *cur;
711 char *epath;
712
713 if (!path)
714 return NULL;
715
716 epath = safe_strdup(path);
717 mutt_expand_path(epath, mutt_strlen(epath));
718
719 for (cur = Incoming; cur; cur = cur->next)
720 {
721 /* must be done late because e.g. IMAP delimiter may change */
722 mutt_expand_path (cur->path, sizeof (cur->path));
723 if (!mutt_strcmp(cur->path, path))
724 {
725 FREE (&epath);
726 return cur;
727 }
728 }
729
730 FREE (&epath);
731 return NULL;
732}