move buffer.[hc] into lib-lib/
[apps/madmutt.git] / postpone.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 <lib-lib/mem.h>
16 #include <lib-lib/str.h>
17 #include <lib-lib/ascii.h>
18 #include <lib-lib/macros.h>
19
20 #include "mutt.h"
21 #include "enter.h"
22 #include "handler.h"
23 #include "mutt_menu.h"
24 #include "rfc1524.h"
25 #include "mime.h"
26 #include "mapping.h"
27 #include "sort.h"
28 #include "thread.h"
29 #include "mx.h"
30 #ifdef USE_IMAP
31 #include "imap.h"
32 #include "imap/mx_imap.h"
33 #endif
34 #include "mutt_crypt.h"
35
36 #include "lib/debug.h"
37
38 #include <ctype.h>
39 #include <unistd.h>
40 #include <string.h>
41 #include <sys/stat.h>
42
43 static struct mapping_t PostponeHelp[] = {
44   {N_("Exit"),  OP_EXIT},
45   {N_("Del"),   OP_DELETE},
46   {N_("Undel"), OP_UNDELETE},
47   {N_("Help"),  OP_HELP},
48   {NULL,        OP_NULL}
49 };
50
51
52
53 static short PostCount = 0;
54 static CONTEXT *PostContext = NULL;
55 static short UpdateNumPostponed = 0;
56
57 /* Return the number of postponed messages.
58  * if force is 0, use a cached value if it is costly to get a fresh
59  * count (IMAP) - else check.
60  */
61 int mutt_num_postponed (int force)
62 {
63   struct stat st;
64   CONTEXT ctx;
65
66   static time_t LastModify = 0;
67   static char *OldPostponed = NULL;
68
69   if (UpdateNumPostponed) {
70     UpdateNumPostponed = 0;
71     force = 1;
72   }
73
74   if (Postponed != OldPostponed) {
75     OldPostponed = Postponed;
76     LastModify = 0;
77     force = 1;
78   }
79
80   if (!Postponed)
81     return 0;
82
83 #ifdef USE_IMAP
84   /* LastModify is useless for IMAP */
85   if (imap_is_magic (Postponed, NULL) == M_IMAP) {
86     if (force) {
87       short newpc;
88
89       newpc = imap_mailbox_check (Postponed, 0);
90       if (newpc >= 0) {
91         PostCount = newpc;
92         debug_print (2, ("%d postponed IMAP messages found.\n", PostCount));
93       }
94       else
95         debug_print (2, ("using old IMAP postponed count.\n"));
96     }
97     return PostCount;
98   }
99 #endif
100
101   if (stat (Postponed, &st) == -1) {
102     PostCount = 0;
103     LastModify = 0;
104     return (0);
105   }
106
107   if (S_ISDIR (st.st_mode)) {
108     /* if we have a maildir mailbox, we need to stat the "new" dir */
109
110     char buf[_POSIX_PATH_MAX];
111
112     snprintf (buf, sizeof (buf), "%s/new", Postponed);
113     if (access (buf, F_OK) == 0 && stat (buf, &st) == -1) {
114       PostCount = 0;
115       LastModify = 0;
116       return 0;
117     }
118   }
119
120   if (LastModify < st.st_mtime) {
121 #ifdef USE_NNTP
122     int optnews = option (OPTNEWS);
123 #endif
124     LastModify = st.st_mtime;
125
126     if (access (Postponed, R_OK | F_OK) != 0)
127       return (PostCount = 0);
128 #ifdef USE_NNTP
129     if (optnews)
130       unset_option (OPTNEWS);
131 #endif
132     if (mx_open_mailbox (Postponed, M_NOSORT | M_QUIET, &ctx) == NULL)
133       PostCount = 0;
134     else
135       PostCount = ctx.msgcount;
136     mx_fastclose_mailbox (&ctx);
137 #ifdef USE_NNTP
138     if (optnews)
139       set_option (OPTNEWS);
140 #endif
141   }
142
143   return (PostCount);
144 }
145
146 void mutt_update_num_postponed (void)
147 {
148   UpdateNumPostponed = 1;
149 }
150
151 static void post_entry (char *s, size_t slen, MUTTMENU * menu, int entry)
152 {
153   CONTEXT *ctx = (CONTEXT *) menu->data;
154
155   _mutt_make_string (s, slen, NONULL (HdrFmt), ctx, ctx->hdrs[entry],
156                      M_FORMAT_ARROWCURSOR);
157 }
158
159 static HEADER *select_msg (void)
160 {
161   MUTTMENU *menu;
162   int i, done = 0, r = -1;
163   char helpstr[SHORT_STRING];
164   short orig_sort;
165
166   menu = mutt_new_menu ();
167   menu->make_entry = post_entry;
168   menu->menu = MENU_POST;
169   menu->max = PostContext->msgcount;
170   menu->title = _("Postponed Messages");
171   menu->data = PostContext;
172   menu->help =
173     mutt_compile_help (helpstr, sizeof (helpstr), MENU_POST, PostponeHelp);
174
175   /* The postponed mailbox is setup to have sorting disabled, but the global
176    * Sort variable may indicate something different.   Sorting has to be
177    * disabled while the postpone menu is being displayed. */
178   orig_sort = Sort;
179   Sort = SORT_ORDER;
180
181   while (!done) {
182     switch (i = mutt_menuLoop (menu)) {
183     case OP_DELETE:
184     case OP_UNDELETE:
185       mutt_set_flag (PostContext, PostContext->hdrs[menu->current], M_DELETE,
186                      (i == OP_DELETE) ? 1 : 0);
187       PostCount = PostContext->msgcount - PostContext->deleted;
188       if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
189         menu->oldcurrent = menu->current;
190         menu->current++;
191         if (menu->current >= menu->top + menu->pagelen) {
192           menu->top = menu->current;
193           menu->redraw = REDRAW_INDEX | REDRAW_STATUS;
194         }
195         else
196           menu->redraw |= REDRAW_MOTION_RESYNCH;
197       }
198       else
199         menu->redraw = REDRAW_CURRENT;
200       break;
201
202     case OP_GENERIC_SELECT_ENTRY:
203       r = menu->current;
204       done = 1;
205       break;
206
207     case OP_EXIT:
208       done = 1;
209       break;
210     }
211   }
212
213   Sort = orig_sort;
214   mutt_menuDestroy (&menu);
215   return (r > -1 ? PostContext->hdrs[r] : NULL);
216 }
217
218 /* args:
219  *      ctx     Context info, used when recalling a message to which
220  *              we reply.
221  *      hdr     envelope/attachment info for recalled message
222  *      cur     if message was a reply, `cur' is set to the message which
223  *              `hdr' is in reply to
224  *      fcc     fcc for the recalled message
225  *      fcclen  max length of fcc
226  *
227  * return vals:
228  *      -1              error/no messages
229  *      0               normal exit
230  *      SENDREPLY       recalled message is a reply
231  */
232 int mutt_get_postponed (CONTEXT * ctx, HEADER * hdr, HEADER ** cur, char *fcc,
233                         size_t fcclen)
234 {
235   HEADER *h;
236   int code = SENDPOSTPONED;
237   LIST *tmp;
238   LIST *last = NULL;
239   LIST *next;
240   char *p;
241   int opt_delete;
242
243   if (!Postponed)
244     return (-1);
245
246   if ((PostContext = mx_open_mailbox (Postponed, M_NOSORT, NULL)) == NULL) {
247     PostCount = 0;
248     mutt_error _("No postponed messages.");
249
250     return (-1);
251   }
252
253   if (!PostContext->msgcount) {
254     PostCount = 0;
255     mx_close_mailbox (PostContext, NULL);
256     p_delete(&PostContext);
257     mutt_error _("No postponed messages.");
258
259     return (-1);
260   }
261
262   if (PostContext->msgcount == 1) {
263     /* only one message, so just use that one. */
264     h = PostContext->hdrs[0];
265   }
266   else if ((h = select_msg ()) == NULL) {
267     mx_close_mailbox (PostContext, NULL);
268     p_delete(&PostContext);
269     return (-1);
270   }
271
272   if (mutt_prepare_template (NULL, PostContext, hdr, h, 0) < 0) {
273     mx_fastclose_mailbox (PostContext);
274     p_delete(&PostContext);
275     return (-1);
276   }
277
278   /* finished with this message, so delete it. */
279   mutt_set_flag (PostContext, h, M_DELETE, 1);
280
281   /* and consider it saved, so that it won't be moved to the trash folder */
282   mutt_set_flag (PostContext, h, M_APPENDED, 1);
283
284   /* update the count for the status display */
285   PostCount = PostContext->msgcount - PostContext->deleted;
286
287   /* avoid the "purge deleted messages" prompt */
288   opt_delete = quadoption (OPT_DELETE);
289   set_quadoption (OPT_DELETE, M_YES);
290   mx_close_mailbox (PostContext, NULL);
291   set_quadoption (OPT_DELETE, opt_delete);
292
293   p_delete(&PostContext);
294
295   for (tmp = hdr->env->userhdrs; tmp;) {
296     if (ascii_strncasecmp ("X-Mutt-References:", tmp->data, 18) == 0) {
297       if (ctx) {
298         /* if a mailbox is currently open, look to see if the orignal message
299            the user attempted to reply to is in this mailbox */
300         p = tmp->data + 18;
301         SKIPWS (p);
302         if (!ctx->id_hash)
303           ctx->id_hash = mutt_make_id_hash (ctx);
304         *cur = hash_find (ctx->id_hash, p);
305       }
306
307       /* Remove the X-Mutt-References: header field. */
308       next = tmp->next;
309       if (last)
310         last->next = tmp->next;
311       else
312         hdr->env->userhdrs = tmp->next;
313       tmp->next = NULL;
314       mutt_free_list (&tmp);
315       tmp = next;
316       if (*cur)
317         code |= SENDREPLY;
318     }
319     else if (ascii_strncasecmp ("X-Mutt-Fcc:", tmp->data, 11) == 0) {
320       p = tmp->data + 11;
321       SKIPWS (p);
322       strfcpy (fcc, p, fcclen);
323       mutt_pretty_mailbox (fcc);
324
325       /* remove the X-Mutt-Fcc: header field */
326       next = tmp->next;
327       if (last)
328         last->next = tmp->next;
329       else
330         hdr->env->userhdrs = tmp->next;
331       tmp->next = NULL;
332       mutt_free_list (&tmp);
333       tmp = next;
334     }
335     else if ((WithCrypto & APPLICATION_PGP)
336              && (str_ncmp ("Pgp:", tmp->data, 4) == 0       /* this is generated
337                                                                  * by old mutt versions
338                                                                  */
339                  || str_ncmp ("X-Mutt-PGP:", tmp->data, 11) == 0)) {
340       hdr->security = mutt_parse_crypt_hdr (strchr (tmp->data, ':') + 1, 1);
341       hdr->security |= APPLICATION_PGP;
342
343       /* remove the pgp field */
344       next = tmp->next;
345       if (last)
346         last->next = tmp->next;
347       else
348         hdr->env->userhdrs = tmp->next;
349       tmp->next = NULL;
350       mutt_free_list (&tmp);
351       tmp = next;
352     }
353     else if ((WithCrypto & APPLICATION_SMIME)
354              && str_ncmp ("X-Mutt-SMIME:", tmp->data, 13) == 0) {
355       hdr->security = mutt_parse_crypt_hdr (strchr (tmp->data, ':') + 1, 1);
356       hdr->security |= APPLICATION_SMIME;
357
358       /* remove the smime field */
359       next = tmp->next;
360       if (last)
361         last->next = tmp->next;
362       else
363         hdr->env->userhdrs = tmp->next;
364       tmp->next = NULL;
365       mutt_free_list (&tmp);
366       tmp = next;
367     }
368
369 #ifdef MIXMASTER
370     else if (str_ncmp ("X-Mutt-Mix:", tmp->data, 11) == 0) {
371       char *t;
372
373       mutt_free_list (&hdr->chain);
374
375       t = strtok (tmp->data + 11, " \t\n");
376       while (t) {
377         hdr->chain = mutt_add_list (hdr->chain, t);
378         t = strtok (NULL, " \t\n");
379       }
380
381       next = tmp->next;
382       if (last)
383         last->next = tmp->next;
384       else
385         hdr->env->userhdrs = tmp->next;
386       tmp->next = NULL;
387       mutt_free_list (&tmp);
388       tmp = next;
389     }
390 #endif
391
392     else {
393       last = tmp;
394       tmp = tmp->next;
395     }
396   }
397   return (code);
398 }
399
400
401
402 int mutt_parse_crypt_hdr (char *p, int set_signas)
403 {
404   int pgp = 0;
405   char pgp_sign_as[LONG_STRING] = "\0", *q;
406   char smime_cryptalg[LONG_STRING] = "\0";
407
408   if (!WithCrypto)
409     return 0;
410
411   SKIPWS (p);
412   for (; *p; p++) {
413
414     switch (*p) {
415     case 'e':
416     case 'E':
417       pgp |= ENCRYPT;
418       break;
419
420     case 's':
421     case 'S':
422       pgp |= SIGN;
423       q = pgp_sign_as;
424
425       if (*(p + 1) == '<') {
426         for (p += 2;
427              *p && *p != '>' && q < pgp_sign_as + sizeof (pgp_sign_as) - 1;
428              *q++ = *p++);
429
430         if (*p != '>') {
431           mutt_error _("Illegal PGP header");
432
433           return 0;
434         }
435       }
436
437       *q = '\0';
438       break;
439
440       /* This used to be the micalg parameter.
441        * 
442        * It's no longer needed, so we just skip the parameter in order
443        * to be able to recall old messages.
444        */
445     case 'm':
446     case 'M':
447       if (*(p + 1) == '<') {
448         for (p += 2; *p && *p != '>'; p++);
449         if (*p != '>') {
450           mutt_error _("Illegal PGP header");
451
452           return 0;
453         }
454       }
455
456       break;
457
458
459     case 'c':
460     case 'C':
461       q = smime_cryptalg;
462
463       if (*(p + 1) == '<') {
464         for (p += 2;
465              *p && *p != '>'
466              && q < smime_cryptalg + sizeof (smime_cryptalg) - 1;
467              *q++ = *p++);
468
469         if (*p != '>') {
470           mutt_error _("Illegal S/MIME header");
471
472           return 0;
473         }
474       }
475
476       *q = '\0';
477       break;
478
479     case 'i':
480     case 'I':
481       pgp |= INLINE;
482       break;
483
484     default:
485       mutt_error _("Illegal PGP header");
486       return 0;
487     }
488
489   }
490
491   /* the cryptalg field must not be empty */
492   if ((WithCrypto & APPLICATION_SMIME) && *smime_cryptalg)
493     str_replace (&SmimeCryptAlg, smime_cryptalg);
494
495   if ((WithCrypto & APPLICATION_PGP) && (set_signas || *pgp_sign_as))
496     str_replace (&PgpSignAs, pgp_sign_as);
497
498   return pgp;
499 }
500
501
502
503 int mutt_prepare_template (FILE * fp, CONTEXT * ctx, HEADER * newhdr,
504                            HEADER * hdr, short weed)
505 {
506   MESSAGE *msg = NULL;
507   char file[_POSIX_PATH_MAX];
508   BODY *b;
509   FILE *bfp;
510
511   int rv = -1;
512   STATE s;
513
514   p_clear(&s, 1);
515
516   if (!fp && (msg = mx_open_message (ctx, hdr->msgno)) == NULL)
517     return (-1);
518
519   if (!fp)
520     fp = msg->fp;
521
522   bfp = fp;
523
524   /* parse the message header and MIME structure */
525
526   fseeko (fp, hdr->offset, 0);
527   newhdr->offset = hdr->offset;
528   newhdr->env = mutt_read_rfc822_header (fp, newhdr, 1, weed);
529   newhdr->content->length = hdr->content->length;
530   mutt_parse_part (fp, newhdr->content);
531
532   p_delete(&newhdr->env->message_id);
533   p_delete(&newhdr->env->mail_followup_to);        /* really? */
534
535   /* decrypt pgp/mime encoded messages */
536
537   if ((WithCrypto & (APPLICATION_PGP | APPLICATION_SMIME) & hdr->security)
538       && mutt_is_multipart_encrypted (newhdr->content)) {
539     int ccap =
540       WithCrypto & (APPLICATION_PGP | APPLICATION_SMIME) & hdr->security;
541     newhdr->security |= ENCRYPT | ccap;
542     if (!crypt_valid_passphrase (ccap))
543       goto err;
544
545     mutt_message _("Decrypting message...");
546
547     if (((ccap & APPLICATION_PGP)
548          && crypt_pgp_decrypt_mime (fp, &bfp, newhdr->content, &b) == -1)
549         || ((ccap & APPLICATION_SMIME)
550             && crypt_smime_decrypt_mime (fp, &bfp, newhdr->content, &b) == -1)
551         || b == NULL) {
552     err:
553       mx_close_message (&msg);
554       mutt_free_envelope (&newhdr->env);
555       mutt_free_body (&newhdr->content);
556       mutt_error _("Decryption failed.");
557
558       return -1;
559     }
560
561     mutt_free_body (&newhdr->content);
562     newhdr->content = b;
563
564     mutt_clear_error ();
565   }
566
567   /* 
568    * remove a potential multipart/signed layer - useful when
569    * resending messages 
570    */
571
572   if (WithCrypto && mutt_is_multipart_signed (newhdr->content)) {
573     newhdr->security |= SIGN;
574     if ((WithCrypto & APPLICATION_PGP)
575         &&
576         ascii_strcasecmp (mutt_get_parameter
577                           ("protocol", newhdr->content->parameter),
578                           "application/pgp-signature") == 0)
579       newhdr->security |= APPLICATION_PGP;
580     else if ((WithCrypto & APPLICATION_SMIME))
581       newhdr->security |= APPLICATION_SMIME;
582
583     /* destroy the signature */
584     mutt_free_body (&newhdr->content->parts->next);
585     newhdr->content = mutt_remove_multipart (newhdr->content);
586   }
587
588
589   /* 
590    * We don't need no primary multipart.
591    * Note: We _do_ preserve messages!
592    * 
593    * XXX - we don't handle multipart/alternative in any 
594    * smart way when sending messages.  However, one may
595    * consider this a feature.
596    * 
597    */
598
599   if (newhdr->content->type == TYPEMULTIPART)
600     newhdr->content = mutt_remove_multipart (newhdr->content);
601
602   s.fpin = bfp;
603
604   /* create temporary files for all attachments */
605   for (b = newhdr->content; b; b = b->next) {
606
607     /* what follows is roughly a receive-mode variant of
608      * mutt_get_tmp_attachment () from muttlib.c
609      */
610
611     file[0] = '\0';
612     if (b->filename) {
613       strfcpy (file, b->filename, sizeof (file));
614       b->d_filename = m_strdup(b->filename);
615     }
616     else {
617       /* avoid Content-Disposition: header with temporary filename */
618       b->use_disp = 0;
619     }
620
621     /* set up state flags */
622
623     s.flags = 0;
624
625     if (b->type == TYPETEXT) {
626       if (!ascii_strcasecmp
627           ("yes", mutt_get_parameter ("x-mutt-noconv", b->parameter)))
628         b->noconv = 1;
629       else {
630         s.flags |= M_CHARCONV;
631         b->noconv = 0;
632       }
633
634       mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
635     }
636
637     mutt_adv_mktemp (NULL, file, sizeof (file));
638     if ((s.fpout = safe_fopen (file, "w")) == NULL)
639       goto bail;
640
641
642     if ((WithCrypto & APPLICATION_PGP)
643         && (mutt_is_application_pgp (b) & (ENCRYPT | SIGN))) {
644
645       mutt_body_handler (b, &s);
646
647       newhdr->security |= mutt_is_application_pgp (newhdr->content);
648
649       b->type = TYPETEXT;
650       str_replace (&b->subtype, "plain");
651       mutt_delete_parameter ("x-action", &b->parameter);
652     }
653     else
654       mutt_decode_attachment (b, &s);
655
656     if (safe_fclose (&s.fpout) != 0)
657       goto bail;
658
659     str_replace (&b->filename, file);
660     b->unlink = 1;
661
662     mutt_stamp_attachment (b);
663
664     mutt_free_body (&b->parts);
665     if (b->hdr)
666       b->hdr->content = NULL;   /* avoid dangling pointer */
667   }
668
669   /* Fix encryption flags. */
670
671   /* No inline if multipart. */
672   if (WithCrypto && (newhdr->security & INLINE) && newhdr->content->next)
673     newhdr->security &= ~INLINE;
674
675   /* Do we even support multiple mechanisms? */
676   newhdr->security &= WithCrypto | ~(APPLICATION_PGP | APPLICATION_SMIME);
677
678   /* Theoretically, both could be set. Take the one the user wants to set by default. */
679   if ((newhdr->security & APPLICATION_PGP)
680       && (newhdr->security & APPLICATION_SMIME)) {
681     if (option (OPTSMIMEISDEFAULT))
682       newhdr->security &= ~APPLICATION_PGP;
683     else
684       newhdr->security &= ~APPLICATION_SMIME;
685   }
686
687   rv = 0;
688
689 bail:
690
691   /* that's it. */
692   if (bfp != fp)
693     fclose (bfp);
694   if (msg)
695     mx_close_message (&msg);
696
697   if (rv == -1) {
698     mutt_free_envelope (&newhdr->env);
699     mutt_free_body (&newhdr->content);
700   }
701
702   return rv;
703 }