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