936fc25ec96eb6acfafe7a161ffe7425df13d797
[apps/madmutt.git] / mx.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 #if HAVE_CONFIG_H
12 # include "config.h"
13 #endif
14
15 #include <dirent.h>
16 #include <fcntl.h>
17 #include <sys/file.h>
18 #include <sys/stat.h>
19 #include <errno.h>
20 #include <unistd.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <ctype.h>
24 #include <utime.h>
25
26 #include <lib-lib/mem.h>
27 #include <lib-lib/ascii.h>
28 #include <lib-lib/str.h>
29 #include <lib-lib/macros.h>
30 #include <lib-lib/file.h>
31 #include <lib-lib/url.h>
32
33 #include <lib-sys/unix.h>
34
35 #include <lib-mime/mime.h>
36
37 #include <lib-ui/sidebar.h>
38
39 #include "mutt.h"
40 #include "buffy.h"
41 #include "mx.h"
42 #include "mbox.h"
43 #include "mh.h"
44 #include "sort.h"
45 #include "thread.h"
46 #include "copy.h"
47 #include "keymap.h"
48 #include "compress.h"
49
50 #include <imap/imap.h>
51 #include <imap/mx_imap.h>
52
53 #include <pop/pop.h>
54 #include <pop/mx_pop.h>
55
56 #ifdef USE_NNTP
57 #include "nntp/nntp.h"
58 #include "nntp/mx_nntp.h"
59 #endif
60
61 #ifdef USE_DOTLOCK
62 #include "dotlock.h"
63 #endif
64
65 #include <lib-crypt/crypt.h>
66
67 #include "lib/list.h"
68
69 static list2_t* MailboxFormats = NULL;
70 #define MX_COMMAND(idx,cmd) ((mx_t*) MailboxFormats->data[idx])->cmd
71 #define MX_IDX(idx) (idx >= 0 && idx < MailboxFormats->length)
72
73 #define mutt_is_spool(s)  (m_strcmp(Spoolfile, s) == 0)
74
75 #ifdef USE_DOTLOCK
76 /* parameters: 
77  * path - file to lock
78  * retry - should retry if unable to lock?
79  */
80
81 static int invoke_dotlock (const char *path, int dummy, int flags, int retry)
82 {
83   char cmd[LONG_STRING + _POSIX_PATH_MAX];
84   char f[SHORT_STRING + _POSIX_PATH_MAX];
85   char r[SHORT_STRING];
86
87   if (flags & DL_FL_RETRY)
88     snprintf (r, sizeof (r), "-r %d ", retry ? MAXLOCKATTEMPT : 0);
89
90   mutt_quote_filename (f, sizeof (f), path);
91
92   snprintf (cmd, sizeof (cmd),
93             "%s %s%s%s%s%s%s%s",
94             NONULL (MuttDotlock),
95             flags & DL_FL_TRY ? "-t " : "",
96             flags & DL_FL_UNLOCK ? "-u " : "",
97             flags & DL_FL_USEPRIV ? "-p " : "",
98             flags & DL_FL_FORCE ? "-f " : "",
99             flags & DL_FL_UNLINK ? "-d " : "",
100             flags & DL_FL_RETRY ? r : "", f);
101
102   return mutt_system (cmd);
103 }
104
105 static int dotlock_file (const char *path, int fd, int retry)
106 {
107   int r;
108   int flags = DL_FL_USEPRIV | DL_FL_RETRY;
109
110   if (retry)
111     retry = 1;
112
113 retry_lock:
114   if ((r = invoke_dotlock (path, fd, flags, retry)) == DL_EX_EXIST) {
115     if (!option (OPTNOCURSES)) {
116       char msg[LONG_STRING];
117
118       snprintf (msg, sizeof (msg),
119                 _("Lock count exceeded, remove lock for %s?"), path);
120       if (retry && mutt_yesorno (msg, M_YES) == M_YES) {
121         flags |= DL_FL_FORCE;
122         retry--;
123         mutt_clear_error ();
124         goto retry_lock;
125       }
126     }
127     else {
128       mutt_error (_("Can't dotlock %s.\n"), path);
129     }
130   }
131   return (r == DL_EX_OK ? 0 : -1);
132 }
133
134 static int undotlock_file (const char *path, int fd)
135 {
136   return (invoke_dotlock (path, fd, DL_FL_USEPRIV | DL_FL_UNLOCK, 0) ==
137           DL_EX_OK ? 0 : -1);
138 }
139
140 #endif /* USE_DOTLOCK */
141
142 /* looks up index of type for path in MailboxFormats */
143 static int mx_get_idx (const char* path) {
144   int i = 0, t = 0;
145   struct stat st;
146
147   /* first, test all non-local folders to avoid stat() call */
148   for (i = 0; i < MailboxFormats->length; i++) {
149     if (!MX_COMMAND(i,local))
150       t = MX_COMMAND(i,mx_is_magic)(path, NULL);
151     if (t >= 1)
152       return (t-1);
153   }
154   if (stat (path, &st) == 0) {
155     /* if stat() succeeded, keep testing until success and
156      * pass stat() info so that we only need to do it once */
157     for (i = 0; i < MailboxFormats->length; i++) {
158       if (MX_COMMAND(i,local))
159         t = MX_COMMAND(i,mx_is_magic)(path, &st);
160       if (t >= 1)
161         return (t-1);
162     }
163   }
164   return (-1);
165 }
166
167 /* Args:
168  *      excl            if excl != 0, request an exclusive lock
169  *      dot             if dot != 0, try to dotlock the file
170  *      time_out        should retry locking?
171  */
172 int mx_lock_file (const char *path, int fd, int excl, int dot, int time_out)
173 {
174 #if defined (USE_FCNTL) || defined (USE_FLOCK)
175   int count;
176   int attempt;
177   struct stat prev_sb;
178 #endif
179   int r = 0;
180
181 #ifdef USE_FCNTL
182   struct flock lck;
183
184
185   p_clear(&lck, 1);
186   lck.l_type = excl ? F_WRLCK : F_RDLCK;
187   lck.l_whence = SEEK_SET;
188
189   count = 0;
190   attempt = 0;
191   prev_sb.st_size = 0;
192   while (fcntl (fd, F_SETLK, &lck) == -1) {
193     struct stat sb;
194
195     if (errno != EAGAIN && errno != EACCES) {
196       mutt_perror ("fcntl");
197       return (-1);
198     }
199
200     if (fstat (fd, &sb) != 0)
201       sb.st_size = 0;
202
203     if (count == 0)
204       prev_sb = sb;
205
206     /* only unlock file if it is unchanged */
207     if (prev_sb.st_size == sb.st_size
208         && ++count >= (time_out ? MAXLOCKATTEMPT : 0)) {
209       if (time_out)
210         mutt_error _("Timeout exceeded while attempting fcntl lock!");
211
212       return (-1);
213     }
214
215     prev_sb = sb;
216
217     mutt_message (_("Waiting for fcntl lock... %d"), ++attempt);
218     sleep (1);
219   }
220 #endif /* USE_FCNTL */
221
222 #ifdef USE_FLOCK
223   count = 0;
224   attempt = 0;
225   while (flock (fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) {
226     struct stat sb;
227
228     if (errno != EWOULDBLOCK) {
229       mutt_perror ("flock");
230       r = -1;
231       break;
232     }
233
234     if (fstat (fd, &sb) != 0)
235       sb.st_size = 0;
236
237     if (count == 0)
238       prev_sb = sb;
239
240     /* only unlock file if it is unchanged */
241     if (prev_sb.st_size == sb.st_size
242         && ++count >= (time_out ? MAXLOCKATTEMPT : 0)) {
243       if (time_out)
244         mutt_error _("Timeout exceeded while attempting flock lock!");
245
246       r = -1;
247       break;
248     }
249
250     prev_sb = sb;
251
252     mutt_message (_("Waiting for flock attempt... %d"), ++attempt);
253     sleep (1);
254   }
255 #endif /* USE_FLOCK */
256
257 #ifdef USE_DOTLOCK
258   if (r == 0 && dot)
259     r = dotlock_file (path, fd, time_out);
260 #endif /* USE_DOTLOCK */
261
262   if (r == -1) {
263     /* release any other locks obtained in this routine */
264
265 #ifdef USE_FCNTL
266     lck.l_type = F_UNLCK;
267     fcntl (fd, F_SETLK, &lck);
268 #endif /* USE_FCNTL */
269
270 #ifdef USE_FLOCK
271     flock (fd, LOCK_UN);
272 #endif /* USE_FLOCK */
273
274     return (-1);
275   }
276
277   return 0;
278 }
279
280 int mx_unlock_file (const char *path, int fd, int dot)
281 {
282 #ifdef USE_FCNTL
283   struct flock unlockit;
284
285   p_clear(&unlockit, 1);
286   unlockit.l_type = F_UNLCK;
287   unlockit.l_whence = SEEK_SET;
288   fcntl (fd, F_SETLK, &unlockit);
289 #endif
290
291 #ifdef USE_FLOCK
292   flock (fd, LOCK_UN);
293 #endif
294
295 #ifdef USE_DOTLOCK
296   if (dot)
297     undotlock_file (path, fd);
298 #endif
299
300   return 0;
301 }
302
303 void mx_unlink_empty (const char *path)
304 {
305   int fd;
306
307 #ifndef USE_DOTLOCK
308   struct stat sb;
309 #endif
310
311   if ((fd = open (path, O_RDWR)) == -1)
312     return;
313
314   if (mx_lock_file (path, fd, 1, 0, 1) == -1) {
315     close (fd);
316     return;
317   }
318
319 #ifdef USE_DOTLOCK
320   invoke_dotlock (path, fd, DL_FL_UNLINK, 1);
321 #else
322   if (fstat (fd, &sb) == 0 && sb.st_size == 0)
323     unlink (path);
324 #endif
325
326   mx_unlock_file (path, fd, 0);
327   close (fd);
328 }
329
330 /* try to figure out what type of mailbox ``path'' is */
331 int mx_get_magic (const char *path) {
332   int i = 0;
333
334   if (m_strlen(path) == 0)
335     return (-1);
336   if ((i = mx_get_idx (path)) >= 0)
337     return (MX_COMMAND(i,type));
338   return (-1);
339 }
340
341 int mx_is_local (int m) {
342   if (!MX_IDX(m))
343     return (0);
344   return (MX_COMMAND(m,local));
345 }
346
347 /*
348  * set DefaultMagic to the given value
349  */
350 int mx_set_magic (const char *s)
351 {
352   if (ascii_strcasecmp (s, "mbox") == 0)
353     DefaultMagic = M_MBOX;
354   else if (ascii_strcasecmp (s, "mmdf") == 0)
355     DefaultMagic = M_MMDF;
356   else if (ascii_strcasecmp (s, "mh") == 0)
357     DefaultMagic = M_MH;
358   else if (ascii_strcasecmp (s, "maildir") == 0)
359     DefaultMagic = M_MAILDIR;
360   else
361     return (-1);
362
363   return 0;
364 }
365
366 /* mx_access: Wrapper for access, checks permissions on a given mailbox.
367  *   We may be interested in using ACL-style flags at some point, currently
368  *   we use the normal access() flags. */
369 int mx_access (const char *path, int flags)
370 {
371   int i = 0;
372
373   if ((i = mx_get_idx (path)) >= 0 && MX_COMMAND(i,mx_access))
374     return (MX_COMMAND(i,mx_access)(path,flags));
375   return (0);
376 }
377
378 static int mx_open_mailbox_append (CONTEXT * ctx, int flags)
379 {
380   struct stat sb;
381
382   /* special case for appending to compressed folders -
383    * even if we can not open them for reading */
384   if (mutt_can_append_compressed (ctx->path))
385     mutt_open_append_compressed (ctx);
386
387   ctx->append = 1;
388
389   if (mx_get_magic (ctx->path) == M_IMAP)
390     return imap_open_mailbox_append (ctx);
391
392   if (stat (ctx->path, &sb) == 0) {
393     ctx->magic = mx_get_magic (ctx->path);
394
395     switch (ctx->magic) {
396     case 0:
397       mutt_error (_("%s is not a mailbox."), ctx->path);
398       /* fall through */
399     case -1:
400       return (-1);
401     }
402   }
403   else if (errno == ENOENT) {
404     ctx->magic = DefaultMagic;
405
406     if (ctx->magic == M_MH || ctx->magic == M_MAILDIR) {
407       char tmp[_POSIX_PATH_MAX];
408
409       if (mkdir (ctx->path, S_IRWXU)) {
410         mutt_perror (ctx->path);
411         return (-1);
412       }
413
414       if (ctx->magic == M_MAILDIR) {
415         snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
416         if (mkdir (tmp, S_IRWXU)) {
417           mutt_perror (tmp);
418           rmdir (ctx->path);
419           return (-1);
420         }
421
422         snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
423         if (mkdir (tmp, S_IRWXU)) {
424           mutt_perror (tmp);
425           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
426           rmdir (tmp);
427           rmdir (ctx->path);
428           return (-1);
429         }
430         snprintf (tmp, sizeof (tmp), "%s/tmp", ctx->path);
431         if (mkdir (tmp, S_IRWXU)) {
432           mutt_perror (tmp);
433           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
434           rmdir (tmp);
435           snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
436           rmdir (tmp);
437           rmdir (ctx->path);
438           return (-1);
439         }
440       }
441       else {
442         int i;
443
444         snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", ctx->path);
445         if ((i = creat (tmp, S_IRWXU)) == -1) {
446           mutt_perror (tmp);
447           rmdir (ctx->path);
448           return (-1);
449         }
450         close (i);
451       }
452     }
453   }
454   else {
455     mutt_perror (ctx->path);
456     return (-1);
457   }
458
459   switch (ctx->magic) {
460   case M_MBOX:
461   case M_MMDF:
462     if ((ctx->fp =
463          safe_fopen (ctx->path, flags & M_NEWFOLDER ? "w" : "a")) == NULL
464         || mbox_lock_mailbox (ctx, 1, 1) != 0) {
465       if (!ctx->fp)
466         mutt_perror (ctx->path);
467       else {
468         mutt_error (_("Couldn't lock %s\n"), ctx->path);
469         safe_fclose (&ctx->fp);
470       }
471       return (-1);
472     }
473     fseeko (ctx->fp, 0, 2);
474     break;
475
476   case M_MH:
477   case M_MAILDIR:
478     /* nothing to do */
479     break;
480
481   default:
482     return (-1);
483   }
484
485   return 0;
486 }
487
488 /*
489  * open a mailbox and parse it
490  *
491  * Args:
492  *      flags   M_NOSORT        do not sort mailbox
493  *              M_APPEND        open mailbox for appending
494  *              M_READONLY      open mailbox in read-only mode
495  *              M_QUIET         only print error messages
496  *      ctx     if non-null, context struct to use
497  */
498 CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT * pctx)
499 {
500   CONTEXT *ctx = pctx;
501   int rc;
502
503   if (!ctx)
504     ctx = p_new(CONTEXT, 1);
505   p_clear(ctx, 1);
506   ctx->path = m_strdup(path);
507
508   ctx->msgnotreadyet = -1;
509   ctx->collapsed = 0;
510
511   if (flags & M_QUIET)
512     ctx->quiet = 1;
513   if (flags & M_READONLY)
514     ctx->readonly = 1;
515   if (flags & M_COUNT)
516     ctx->counting = 1;
517
518   if (flags & (M_APPEND | M_NEWFOLDER)) {
519     if (mx_open_mailbox_append (ctx, flags) != 0) {
520       mx_fastclose_mailbox (ctx);
521       if (!pctx)
522         p_delete(&ctx);
523       return NULL;
524     }
525     return ctx;
526   }
527
528   if (!MX_IDX(ctx->magic-1))
529     ctx->magic = mx_get_magic (path);
530
531   if (ctx->magic == M_COMPRESSED)
532     mutt_open_read_compressed (ctx);
533
534   if (ctx->magic == 0)
535     mutt_error (_("%s is not a mailbox."), path);
536
537   if (ctx->magic == -1)
538     mutt_perror (path);
539
540   if (ctx->magic <= 0) {
541     mx_fastclose_mailbox (ctx);
542     if (!pctx)
543       p_delete(&ctx);
544     return (NULL);
545   }
546
547   /* if the user has a `push' command in their .muttrc, or in a folder-hook,
548    * it will cause the progress messages not to be displayed because
549    * mutt_refresh() will think we are in the middle of a macro.  so set a
550    * flag to indicate that we should really refresh the screen.
551    */
552   set_option (OPTFORCEREFRESH);
553
554   if (!ctx->quiet)
555     mutt_message (_("Reading %s..."), ctx->path);
556
557   rc = MX_COMMAND(ctx->magic-1,mx_open_mailbox)(ctx);
558
559   if (rc == 0) {
560     if ((flags & M_NOSORT) == 0) {
561       /* avoid unnecessary work since the mailbox is completely unthreaded
562          to begin with */
563       unset_option (OPTSORTSUBTHREADS);
564       unset_option (OPTNEEDRESCORE);
565       mutt_sort_headers (ctx, 1);
566     }
567     if (!ctx->quiet)
568       mutt_clear_error ();
569   }
570   else {
571     mx_fastclose_mailbox (ctx);
572     if (!pctx)
573       p_delete(&ctx);
574   }
575
576   unset_option (OPTFORCEREFRESH);
577   return (ctx);
578 }
579
580 /* free up memory associated with the mailbox context */
581 void mx_fastclose_mailbox (CONTEXT * ctx)
582 {
583   int i;
584
585   if (!ctx)
586     return;
587
588   if (MX_IDX(ctx->magic-1) && MX_COMMAND(ctx->magic-1,mx_fastclose_mailbox))
589     MX_COMMAND(ctx->magic-1,mx_fastclose_mailbox(ctx));
590   if (ctx->subj_hash)
591     hash_destroy (&ctx->subj_hash, NULL);
592   if (ctx->id_hash)
593     hash_destroy (&ctx->id_hash, NULL);
594   mutt_clear_threads (ctx);
595   for (i = 0; i < ctx->msgcount; i++)
596     header_delete(&ctx->hdrs[i]);
597   p_delete(&ctx->hdrs);
598   p_delete(&ctx->v2r);
599
600   if (ctx->compressinfo)
601     mutt_fast_close_compressed (ctx);
602
603   p_delete(&ctx->path);
604   p_delete(&ctx->pattern);
605   if (ctx->limit_pattern)
606     mutt_pattern_free (&ctx->limit_pattern);
607   safe_fclose (&ctx->fp);
608   p_clear(ctx, 1);
609 }
610
611 /* save changes to disk */
612 static int sync_mailbox (CONTEXT * ctx, int *index_hint)
613 {
614   int rc = -1;
615
616   if (!ctx->quiet)
617     mutt_message (_("Writing %s..."), ctx->path);
618
619   if (MX_IDX(ctx->magic-1))
620     /* the 1 is only of interest for IMAP and means EXPUNGE */
621     rc = MX_COMMAND(ctx->magic-1,mx_sync_mailbox(ctx,1,index_hint));
622
623   if (rc == 0 && ctx->compressinfo)
624     return mutt_sync_compressed (ctx);
625
626   return rc;
627 }
628
629 /* move deleted mails to the trash folder */
630 static int trash_append (CONTEXT * ctx)
631 {
632   CONTEXT *ctx_trash;
633   int i = 0;
634   struct stat st, stc;
635
636   if (!TrashPath || !ctx->deleted ||
637       (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH)))
638     return 0;
639
640   for (; i < ctx->msgcount && (!ctx->hdrs[i]->deleted ||
641                                ctx->hdrs[i]->appended); i++);
642   if (i == ctx->msgcount)
643     return 0;                   /* nothing to be done */
644
645   if (mutt_save_confirm (TrashPath, &st) != 0) {
646     mutt_error _("message(s) not deleted");
647
648     return -1;
649   }
650
651   if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino
652       && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
653     return 0;                   /* we are in the trash folder: simple sync */
654
655   if ((ctx_trash = mx_open_mailbox (TrashPath, M_APPEND, NULL)) != NULL) {
656     for (i = 0; i < ctx->msgcount; i++)
657       if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->appended
658           && !ctx->hdrs[i]->purged
659           && mutt_append_message (ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1) {
660         mx_close_mailbox (ctx_trash, NULL);
661         return -1;
662       }
663
664     mx_close_mailbox (ctx_trash, NULL);
665   }
666   else {
667     mutt_error _("Can't open trash folder");
668
669     return -1;
670   }
671
672   return 0;
673 }
674
675 /* save changes and close mailbox */
676 static int _mx_close_mailbox (CONTEXT * ctx, int *index_hint)
677 {
678   int i, move_messages = 0, purge = 1, read_msgs = 0;
679   int check;
680   int isSpool = 0;
681   CONTEXT f;
682   char mbox[_POSIX_PATH_MAX];
683   char buf[SHORT_STRING];
684
685   if (!ctx)
686     return 0;
687
688   ctx->closing = 1;
689
690 #ifdef USE_NNTP
691   if (ctx->magic == M_NNTP) {
692     int ret;
693
694     ret = nntp_close_mailbox (ctx);
695     mx_fastclose_mailbox (ctx);
696     return ret;
697   }
698 #endif
699   if (ctx->readonly || ctx->dontwrite) {
700     /* mailbox is readonly or we don't want to write */
701     mx_fastclose_mailbox (ctx);
702     return 0;
703   }
704
705   if (ctx->append) {
706     /* mailbox was opened in write-mode */
707     if (ctx->magic == M_MBOX || ctx->magic == M_MMDF)
708       mbox_close_mailbox (ctx);
709     else
710       mx_fastclose_mailbox (ctx);
711     return 0;
712   }
713
714   for (i = 0; i < ctx->msgcount; i++) {
715     if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read
716         && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
717       read_msgs++;
718   }
719
720   if (read_msgs && quadoption (OPT_MOVE) != M_NO) {
721     char *p;
722
723     if ((p = mutt_find_hook (M_MBOXHOOK, ctx->path))) {
724       isSpool = 1;
725       m_strcpy(mbox, sizeof(mbox), p);
726     }
727     else {
728       m_strcpy(mbox, sizeof(mbox), NONULL(Inbox));
729       isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox);
730     }
731     mutt_expand_path (mbox, sizeof (mbox));
732
733     if (isSpool) {
734       snprintf (buf, sizeof (buf), _("Move read messages to %s?"), mbox);
735       if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1) {
736         ctx->closing = 0;
737         return (-1);
738       }
739     }
740   }
741
742   /* 
743    * There is no point in asking whether or not to purge if we are
744    * just marking messages as "trash".
745    */
746   if (ctx->deleted && !(ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
747     snprintf (buf, sizeof (buf), ctx->deleted == 1
748               ? _("Purge %d deleted message?") :
749               _("Purge %d deleted messages?"), ctx->deleted);
750     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0) {
751       ctx->closing = 0;
752       return (-1);
753     }
754   }
755
756   /* IMAP servers manage the OLD flag themselves */
757   if (ctx->magic != M_IMAP)
758     if (option (OPTMARKOLD)) {
759       for (i = 0; i < ctx->msgcount; i++) {
760         if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old)
761           mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, 1);
762       }
763     }
764
765   if (move_messages) {
766     mutt_message (_("Moving read messages to %s..."), mbox);
767
768     /* try to use server-side copy first */
769     i = 1;
770
771     if (ctx->magic == M_IMAP && imap_is_magic (mbox, NULL) == M_IMAP) {
772       /* tag messages for moving, and clear old tags, if any */
773       for (i = 0; i < ctx->msgcount; i++)
774         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
775             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
776           ctx->hdrs[i]->tagged = 1;
777         else
778           ctx->hdrs[i]->tagged = 0;
779
780       i = imap_copy_messages (ctx, NULL, mbox, 1);
781     }
782
783     if (i == 0)                 /* success */
784       mutt_clear_error ();
785     else if (i == -1) {         /* horrible error, bail */
786       ctx->closing = 0;
787       return -1;
788     }
789     else                        /* use regular append-copy mode */
790     {
791       if (mx_open_mailbox (mbox, M_APPEND, &f) == NULL) {
792         ctx->closing = 0;
793         return -1;
794       }
795
796       for (i = 0; i < ctx->msgcount; i++) {
797         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
798             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED))) {
799           if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) ==
800               0) {
801             mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, 1);
802             mutt_set_flag (ctx, ctx->hdrs[i], M_APPENDED, 1);
803           }
804           else {
805             mx_close_mailbox (&f, NULL);
806             ctx->closing = 0;
807             return -1;
808           }
809         }
810       }
811
812       mx_close_mailbox (&f, NULL);
813     }
814
815   }
816   else if (!ctx->changed && ctx->deleted == 0) {
817     mutt_message _("Mailbox is unchanged.");
818
819     mx_fastclose_mailbox (ctx);
820     return 0;
821   }
822
823   /* copy mails to the trash before expunging */
824   if (purge && ctx->deleted)
825     if (trash_append (ctx) != 0) {
826       ctx->closing = 0;
827       return -1;
828     }
829
830   /* allow IMAP to preserve the deleted flag across sessions */
831   if (ctx->magic == M_IMAP) {
832     if ((check = imap_sync_mailbox (ctx, purge, index_hint)) != 0) {
833       ctx->closing = 0;
834       return check;
835     }
836   }
837   else
838   {
839     if (!purge) {
840       for (i = 0; i < ctx->msgcount; i++)
841         ctx->hdrs[i]->deleted = 0;
842       ctx->deleted = 0;
843     }
844
845     if (ctx->changed || ctx->deleted) {
846       if ((check = sync_mailbox (ctx, index_hint)) != 0) {
847         ctx->closing = 0;
848         return check;
849       }
850     }
851   }
852
853   if (move_messages)
854     mutt_message (_("%d kept, %d moved, %d deleted."),
855                   ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted);
856   else
857     mutt_message (_("%d kept, %d deleted."),
858                   ctx->msgcount - ctx->deleted, ctx->deleted);
859
860   if (ctx->msgcount == ctx->deleted &&
861       (ctx->magic == M_MMDF || ctx->magic == M_MBOX) &&
862       !mutt_is_spool (ctx->path) && !option (OPTSAVEEMPTY))
863     mx_unlink_empty (ctx->path);
864
865   if (ctx->compressinfo && mutt_slow_close_compressed (ctx))
866     return (-1);
867
868   mx_fastclose_mailbox (ctx);
869
870   return 0;
871 }
872
873 int mx_close_mailbox (CONTEXT * ctx, int *index_hint) {
874   int ret = 0;
875   if (!ctx)
876     return (0);
877   ret = _mx_close_mailbox (ctx, index_hint);
878   sidebar_set_buffystats (ctx);
879   return (ret);
880 }
881
882 /* update a Context structure's internal tables. */
883
884 void mx_update_tables (CONTEXT * ctx, int committing)
885 {
886   int i, j;
887
888   /* update memory to reflect the new state of the mailbox */
889   ctx->vcount = 0;
890   ctx->vsize = 0;
891   ctx->tagged = 0;
892   ctx->deleted = 0;
893   ctx->new = 0;
894   ctx->unread = 0;
895   ctx->changed = 0;
896   ctx->flagged = 0;
897 #define this_body ctx->hdrs[j]->content
898   for (i = 0, j = 0; i < ctx->msgcount; i++) {
899     if ((committing && (!ctx->hdrs[i]->deleted ||
900                         (ctx->magic == M_MAILDIR
901                          && option (OPTMAILDIRTRASH)))) || (!committing
902                                                             && ctx->hdrs[i]->
903                                                             active)) {
904       if (i != j) {
905         ctx->hdrs[j] = ctx->hdrs[i];
906         ctx->hdrs[i] = NULL;
907       }
908       ctx->hdrs[j]->msgno = j;
909       if (ctx->hdrs[j]->virtual != -1) {
910         ctx->v2r[ctx->vcount] = j;
911         ctx->hdrs[j]->virtual = ctx->vcount++;
912         ctx->vsize += this_body->length + this_body->offset -
913           this_body->hdr_offset;
914       }
915
916       if (committing)
917         ctx->hdrs[j]->changed = 0;
918       else if (ctx->hdrs[j]->changed)
919         ctx->changed++;
920
921       if (!committing
922           || (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
923         if (ctx->hdrs[j]->deleted)
924           ctx->deleted++;
925       }
926
927       if (ctx->hdrs[j]->tagged)
928         ctx->tagged++;
929       if (ctx->hdrs[j]->flagged)
930         ctx->flagged++;
931       if (!ctx->hdrs[j]->read) {
932         ctx->unread++;
933         if (!ctx->hdrs[j]->old)
934           ctx->new++;
935       }
936
937       j++;
938     }
939     else {
940       if (ctx->magic == M_MH || ctx->magic == M_MAILDIR)
941         ctx->size -= (ctx->hdrs[i]->content->length +
942                       ctx->hdrs[i]->content->offset -
943                       ctx->hdrs[i]->content->hdr_offset);
944       /* remove message from the hash tables */
945       if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
946         hash_delete (ctx->subj_hash, ctx->hdrs[i]->env->real_subj,
947                      ctx->hdrs[i], NULL);
948       if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
949         hash_delete (ctx->id_hash, ctx->hdrs[i]->env->message_id,
950                      ctx->hdrs[i], NULL);
951       header_delete(&ctx->hdrs[i]);
952     }
953   }
954 #undef this_body
955   ctx->msgcount = j;
956
957   /* update sidebar count */
958   sidebar_set_buffystats (ctx);
959 }
960
961
962 /* save changes to mailbox
963  *
964  * return values:
965  *      0               success
966  *      -1              error
967  */
968 static int _mx_sync_mailbox (CONTEXT * ctx, int *index_hint)
969 {
970   int rc, i;
971   int purge = 1;
972   int msgcount, deleted;
973
974   if (ctx->dontwrite) {
975     char buf[STRING], tmp[STRING];
976
977     if (km_expand_key (buf, sizeof (buf),
978                        km_find_func (MENU_MAIN, OP_TOGGLE_WRITE)))
979       snprintf (tmp, sizeof (tmp), _(" Press '%s' to toggle write"), buf);
980     else
981       m_strcpy(tmp, sizeof(tmp), _("Use 'toggle-write' to re-enable write!"));
982
983     mutt_error (_("Mailbox is marked unwritable. %s"), tmp);
984     return -1;
985   }
986   else if (ctx->readonly) {
987     mutt_error _("Mailbox is read-only.");
988
989     return -1;
990   }
991
992   if (!ctx->changed && !ctx->deleted) {
993     mutt_message _("Mailbox is unchanged.");
994
995     return (0);
996   }
997
998   if (ctx->deleted) {
999     char buf[SHORT_STRING];
1000
1001     snprintf (buf, sizeof (buf), ctx->deleted == 1
1002               ? _("Purge %d deleted message?") :
1003               _("Purge %d deleted messages?"), ctx->deleted);
1004     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
1005       return (-1);
1006     else if (purge == M_NO) {
1007       if (!ctx->changed)
1008         return 0;               /* nothing to do! */
1009       /* let IMAP servers hold on to D flags */
1010       if (ctx->magic != M_IMAP)
1011       {
1012         for (i = 0; i < ctx->msgcount; i++)
1013           ctx->hdrs[i]->deleted = 0;
1014         ctx->deleted = 0;
1015       }
1016     }
1017     else if (ctx->last_tag && ctx->last_tag->deleted)
1018       ctx->last_tag = NULL;     /* reset last tagged msg now useless */
1019   }
1020
1021   /* really only for IMAP - imap_sync_mailbox results in a call to
1022    * mx_update_tables, so ctx->deleted is 0 when it comes back */
1023   msgcount = ctx->msgcount;
1024   deleted = ctx->deleted;
1025
1026   if (purge && ctx->deleted) {
1027     if (trash_append (ctx) == -1)
1028       return -1;
1029   }
1030
1031   if (ctx->magic == M_IMAP)
1032     rc = imap_sync_mailbox (ctx, purge, index_hint);
1033   else
1034     rc = sync_mailbox (ctx, index_hint);
1035   if (rc == 0) {
1036     if (ctx->magic == M_IMAP && !purge)
1037       mutt_message (_("Mailbox checkpointed."));
1038
1039     else
1040       mutt_message (_("%d kept, %d deleted."), msgcount - deleted, deleted);
1041
1042     mutt_sleep (0);
1043
1044     if (ctx->msgcount == ctx->deleted &&
1045         (ctx->magic == M_MBOX || ctx->magic == M_MMDF) &&
1046         !mutt_is_spool (ctx->path) && !option (OPTSAVEEMPTY)) {
1047       unlink (ctx->path);
1048       mx_fastclose_mailbox (ctx);
1049       return 0;
1050     }
1051
1052     /* if we haven't deleted any messages, we don't need to resort */
1053     /* ... except for certain folder formats which need "unsorted" 
1054      * sort order in order to synchronize folders.
1055      * 
1056      * MH and maildir are safe.  mbox-style seems to need re-sorting,
1057      * at least with the new threading code.
1058      */
1059     if (purge || (ctx->magic != M_MAILDIR && ctx->magic != M_MH)) {
1060       /* IMAP does this automatically after handling EXPUNGE */
1061       if (ctx->magic != M_IMAP)
1062       {
1063         mx_update_tables (ctx, 1);
1064         mutt_sort_headers (ctx, 1);     /* rethread from scratch */
1065       }
1066     }
1067   }
1068
1069   return (rc);
1070 }
1071
1072 int mx_sync_mailbox (CONTEXT* ctx, int* index_hint) {
1073   int ret = _mx_sync_mailbox (ctx, index_hint);
1074   sidebar_set_buffystats (ctx);
1075   return (ret);
1076 }
1077
1078 /* args:
1079  *      dest    destintation mailbox
1080  *      hdr     message being copied (required for maildir support, because
1081  *              the filename depends on the message flags)
1082  */
1083 MESSAGE *mx_open_new_message (CONTEXT * dest, HEADER * hdr, int flags)
1084 {
1085   MESSAGE *msg;
1086   address_t *p = NULL;
1087
1088   if (!MX_IDX(dest->magic-1)) {
1089     return (NULL);
1090   }
1091
1092   msg = p_new(MESSAGE, 1);
1093   msg->magic = dest->magic;
1094   msg->write = 1;
1095
1096   if (hdr) {
1097     msg->flags.flagged = hdr->flagged;
1098     msg->flags.replied = hdr->replied;
1099     msg->flags.read = hdr->read;
1100     msg->received = hdr->received;
1101   }
1102
1103   if (msg->received == 0)
1104     time (&msg->received);
1105
1106   if (MX_COMMAND(dest->magic-1,mx_open_new_message)(msg, dest, hdr) == 0) {
1107     if (dest->magic == M_MMDF)
1108       fputs (MMDF_SEP, msg->fp);
1109
1110     if ((msg->magic == M_MBOX || msg->magic == M_MMDF) && flags & M_ADD_FROM) {
1111       if (hdr) {
1112         if (hdr->env->return_path)
1113           p = hdr->env->return_path;
1114         else if (hdr->env->sender)
1115           p = hdr->env->sender;
1116         else
1117           p = hdr->env->from;
1118       }
1119
1120       fprintf (msg->fp, "From %s %s", p ? p->mailbox : NONULL (Username),
1121                ctime (&msg->received));
1122     }
1123   }
1124   else
1125     p_delete(&msg);
1126
1127   return msg;
1128 }
1129
1130 /* check for new mail */
1131 int mx_check_mailbox (CONTEXT * ctx, int *index_hint, int lock) {
1132   if (ctx->compressinfo)
1133     return mutt_check_mailbox_compressed (ctx);
1134
1135   if (ctx) {
1136     if (ctx->locked)
1137       lock = 0;
1138     if (MX_IDX(ctx->magic-1) && MX_COMMAND(ctx->magic-1,mx_check_mailbox))
1139       return (MX_COMMAND(ctx->magic-1,mx_check_mailbox)(ctx, index_hint, lock));
1140   }
1141
1142   return (-1);
1143 }
1144
1145 /* return a stream pointer for a message */
1146 MESSAGE *mx_open_message (CONTEXT * ctx, int msgno)
1147 {
1148   MESSAGE *msg;
1149
1150   msg = p_new(MESSAGE, 1);
1151   switch (msg->magic = ctx->magic) {
1152   case M_MBOX:
1153   case M_MMDF:
1154     msg->fp = ctx->fp;
1155     break;
1156
1157   case M_MH:
1158   case M_MAILDIR:
1159     {
1160       HEADER *cur = ctx->hdrs[msgno];
1161       char path[_POSIX_PATH_MAX];
1162
1163       snprintf (path, sizeof (path), "%s/%s", ctx->path, cur->path);
1164
1165       if ((msg->fp = fopen (path, "r")) == NULL && errno == ENOENT &&
1166           ctx->magic == M_MAILDIR)
1167         msg->fp = maildir_open_find_message (ctx->path, cur->path);
1168
1169       if (msg->fp == NULL) {
1170         mutt_perror (path);
1171         p_delete(&msg);
1172       }
1173     }
1174     break;
1175
1176   case M_IMAP:
1177     {
1178       if (imap_fetch_message (msg, ctx, msgno) != 0)
1179         p_delete(&msg);
1180       break;
1181     }
1182
1183   case M_POP:
1184     {
1185       if (pop_fetch_message (msg, ctx, msgno) != 0)
1186         p_delete(&msg);
1187       break;
1188     }
1189
1190 #ifdef USE_NNTP
1191   case M_NNTP:
1192     {
1193       if (nntp_fetch_message (msg, ctx, msgno) != 0)
1194         p_delete(&msg);
1195       break;
1196     }
1197 #endif /* USE_NNTP */
1198
1199   default:
1200     p_delete(&msg);
1201     break;
1202   }
1203   return (msg);
1204 }
1205
1206 /* commit a message to a folder */
1207
1208 int mx_commit_message (MESSAGE * msg, CONTEXT * ctx) {
1209   if (!(msg->write && ctx->append)) {
1210     return -1;
1211   }
1212   if (!ctx || !MX_IDX(ctx->magic-1) || !MX_COMMAND(ctx->magic-1,mx_commit_message))
1213     return (-1);
1214   return (MX_COMMAND(ctx->magic-1,mx_commit_message) (msg, ctx));
1215 }
1216
1217 /* close a pointer to a message */
1218 int mx_close_message (MESSAGE ** msg)
1219 {
1220   int r = 0;
1221
1222   if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR
1223       || (*msg)->magic == M_IMAP
1224       || (*msg)->magic == M_POP
1225 #ifdef USE_NNTP
1226       || (*msg)->magic == M_NNTP
1227 #endif
1228     ) {
1229     r = safe_fclose (&(*msg)->fp);
1230   }
1231   else
1232     (*msg)->fp = NULL;
1233
1234   if ((*msg)->path) {
1235     unlink ((*msg)->path);
1236     p_delete(&(*msg)->path);
1237   }
1238
1239   p_delete(msg);
1240   return (r);
1241 }
1242
1243 void mx_alloc_memory (CONTEXT * ctx)
1244 {
1245   int i;
1246   size_t s = MAX (sizeof (HEADER *), sizeof (int));
1247
1248   if ((ctx->hdrmax + 25) * s < ctx->hdrmax * s) {
1249     mutt_error _("Integer overflow -- can't allocate memory.");
1250
1251     sleep (1);
1252     mutt_exit (1);
1253   }
1254
1255   if (ctx->hdrs) {
1256     p_realloc(&ctx->hdrs, ctx->hdrmax += 25);
1257     p_realloc(&ctx->v2r, ctx->hdrmax);
1258   }
1259   else {
1260     ctx->hdrs = p_new(HEADER *, (ctx->hdrmax += 25));
1261     ctx->v2r = p_new(int, ctx->hdrmax);
1262   }
1263   for (i = ctx->msgcount; i < ctx->hdrmax; i++) {
1264     ctx->hdrs[i] = NULL;
1265     ctx->v2r[i] = -1;
1266   }
1267 }
1268
1269 /* this routine is called to update the counts in the context structure for
1270  * the last message header parsed.
1271  */
1272 void mx_update_context (CONTEXT * ctx, int new_messages)
1273 {
1274   HEADER *h;
1275   int msgno;
1276
1277   for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++) {
1278     h = ctx->hdrs[msgno];
1279
1280     /* NOTE: this _must_ be done before the check for mailcap! */
1281     h->security = crypt_query (h->content);
1282
1283     if (!ctx->pattern) {
1284       ctx->v2r[ctx->vcount] = msgno;
1285       h->virtual = ctx->vcount++;
1286     }
1287     else
1288       h->virtual = -1;
1289     h->msgno = msgno;
1290
1291     if (h->env->supersedes) {
1292       HEADER *h2;
1293
1294       if (!ctx->id_hash)
1295         ctx->id_hash = mutt_make_id_hash (ctx);
1296
1297       h2 = hash_find (ctx->id_hash, h->env->supersedes);
1298
1299       /* p_delete(&h->env->supersedes); should I ? */
1300       if (h2) {
1301         h2->superseded = 1;
1302         if (!ctx->counting && option (OPTSCORE))
1303           mutt_score_message (ctx, h2, 1);
1304       }
1305     }
1306
1307     /* add this message to the hash tables */
1308     if (ctx->id_hash && h->env->message_id)
1309       hash_insert (ctx->id_hash, h->env->message_id, h, 0);
1310     if (!ctx->counting) {
1311       if (ctx->subj_hash && h->env->real_subj)
1312         hash_insert (ctx->subj_hash, h->env->real_subj, h, 1);
1313
1314       if (option (OPTSCORE))
1315         mutt_score_message (ctx, h, 0);
1316     }
1317
1318     if (h->changed)
1319       ctx->changed = 1;
1320     if (h->flagged)
1321       ctx->flagged++;
1322     if (h->deleted)
1323       ctx->deleted++;
1324     if (!h->read) {
1325       ctx->unread++;
1326       if (!h->old)
1327         ctx->new++;
1328     }
1329   }
1330   /* update sidebar count */
1331   sidebar_set_buffystats (ctx);
1332 }
1333
1334 /*
1335  * Return:
1336  * 1 if the specified mailbox contains 0 messages.
1337  * 0 if the mailbox contains messages
1338  * -1 on error
1339  */
1340 int mx_check_empty (const char *path)
1341 {
1342   int i = 0;
1343   if ((i = mx_get_idx (path)) >= 0 && MX_COMMAND(i,mx_check_empty))
1344     return (MX_COMMAND(i,mx_check_empty)(path));
1345   errno = EINVAL;
1346   return (-1);
1347 }
1348
1349 int mx_acl_check (CONTEXT* ctx, int flag) {
1350   if (!ctx || !MX_IDX(ctx->magic-1))
1351     return (0);
1352   /* if no acl_check defined for module, assume permission is granted */
1353   if (!MX_COMMAND(ctx->magic-1,mx_acl_check))
1354     return (1);
1355   return (MX_COMMAND(ctx->magic-1,mx_acl_check)(ctx,flag));
1356 }
1357
1358 void mx_init (void) {
1359   list_push_back (&MailboxFormats, (void*) mbox_reg_mx ());
1360   list_push_back (&MailboxFormats, (void*) mmdf_reg_mx ());
1361   list_push_back (&MailboxFormats, (void*) mh_reg_mx ());
1362   list_push_back (&MailboxFormats, (void*) maildir_reg_mx ());
1363   list_push_back (&MailboxFormats, (void*) imap_reg_mx ());
1364   list_push_back (&MailboxFormats, (void*) pop_reg_mx ());
1365 #ifdef USE_NNTP
1366   list_push_back (&MailboxFormats, (void*) nntp_reg_mx ());
1367 #endif
1368   list_push_back (&MailboxFormats, (void*) compress_reg_mx ());
1369 }
1370
1371 int mx_rebuild_cache (void) {
1372 #ifndef USE_HCACHE
1373   mutt_error (_("Support for header caching was not build in."));
1374   return (1);
1375 #else
1376   int i = 0, magic = 0;
1377   CONTEXT* ctx = NULL;
1378   BUFFY* b = NULL;
1379
1380   if (list_empty(Incoming)) {
1381     mutt_error (_("No mailboxes defined."));
1382     return (1);
1383   }
1384
1385   for (i = 0; i < Incoming->length; i++) {
1386     b = (BUFFY*) Incoming->data[i];
1387     magic = mx_get_magic (b->path);
1388     if (magic != M_MAILDIR && magic != M_MH && magic != M_IMAP)
1389       continue;
1390     sidebar_set_current (b->path);
1391     sidebar_draw (CurrentMenu);
1392     if ((ctx = mx_open_mailbox (b->path,
1393                                 M_READONLY | M_NOSORT | M_COUNT,
1394                                 NULL)) != NULL)
1395       mx_close_mailbox (ctx, 0);
1396   }
1397   mutt_clear_error ();
1398
1399   if (Context && Context->path)
1400     sidebar_set_current (Context->path);
1401   sidebar_draw (CurrentMenu);
1402
1403   return (0);
1404 #endif
1405 }
1406
1407 void mutt_parse_mime_message (CONTEXT * ctx, HEADER * cur)
1408 {
1409   MESSAGE *msg;
1410   int flags = 0;
1411
1412   do {
1413     if (cur->content->type != TYPEMESSAGE
1414         && cur->content->type != TYPEMULTIPART)
1415       break;                     /* nothing to do */
1416
1417     if (cur->content->parts)
1418       break;                     /* The message was parsed earlier. */
1419
1420     if ((msg = mx_open_message (ctx, cur->msgno))) {
1421       mutt_parse_part (msg->fp, cur->content);
1422
1423       cur->security = crypt_query (cur->content);
1424
1425       mx_close_message (&msg);
1426     }
1427   } while (0);
1428   mutt_count_body_parts (cur, flags | M_PARTS_RECOUNT);
1429 }