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