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