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