move the last crypt-* things into lib-crypt, adapt configure.ac
[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 #include <lib-lib/file.h>
20 #include <lib-lib/mapping.h>
21
22 #include <lib-mime/mime.h>
23
24 #include <lib-ui/enter.h>
25
26 #include "mutt.h"
27 #include "handler.h"
28 #include "mutt_menu.h"
29 #include "rfc1524.h"
30 #include "sort.h"
31 #include "thread.h"
32 #include "mx.h"
33 #include <lib-crypt/crypt.h>
34
35 #include <imap/imap.h>
36 #include <imap/mx_imap.h>
37
38 #include "lib/debug.h"
39
40 #include <ctype.h>
41 #include <unistd.h>
42 #include <string.h>
43 #include <sys/stat.h>
44
45 static struct mapping_t PostponeHelp[] = {
46   {N_("Exit"),  OP_EXIT},
47   {N_("Del"),   OP_DELETE},
48   {N_("Undel"), OP_UNDELETE},
49   {N_("Help"),  OP_HELP},
50   {NULL,        OP_NULL}
51 };
52
53
54
55 static short PostCount = 0;
56 static CONTEXT *PostContext = NULL;
57 static short UpdateNumPostponed = 0;
58
59 /* Return the number of postponed messages.
60  * if force is 0, use a cached value if it is costly to get a fresh
61  * count (IMAP) - else check.
62  */
63 int mutt_num_postponed (int force)
64 {
65   struct stat st;
66   CONTEXT ctx;
67
68   static time_t LastModify = 0;
69   static char *OldPostponed = NULL;
70
71   if (UpdateNumPostponed) {
72     UpdateNumPostponed = 0;
73     force = 1;
74   }
75
76   if (Postponed != OldPostponed) {
77     OldPostponed = Postponed;
78     LastModify = 0;
79     force = 1;
80   }
81
82   if (!Postponed)
83     return 0;
84
85   /* LastModify is useless for IMAP */
86   if (imap_is_magic (Postponed, NULL) == M_IMAP) {
87     if (force) {
88       short newpc;
89
90       newpc = imap_mailbox_check (Postponed, 0);
91       if (newpc >= 0) {
92         PostCount = newpc;
93         debug_print (2, ("%d postponed IMAP messages found.\n", PostCount));
94       }
95       else
96         debug_print (2, ("using old IMAP postponed count.\n"));
97     }
98     return PostCount;
99   }
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, ssize_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 = vskipspaces(tmp->data + 18);
301         if (!ctx->id_hash)
302           ctx->id_hash = mutt_make_id_hash (ctx);
303         *cur = hash_find (ctx->id_hash, p);
304       }
305
306       /* Remove the X-Mutt-References: header field. */
307       next = tmp->next;
308       if (last)
309         last->next = tmp->next;
310       else
311         hdr->env->userhdrs = tmp->next;
312       tmp->next = NULL;
313       mutt_free_list (&tmp);
314       tmp = next;
315       if (*cur)
316         code |= SENDREPLY;
317     }
318     else if (ascii_strncasecmp ("X-Mutt-Fcc:", tmp->data, 11) == 0) {
319       p = vskipspaces(tmp->data + 11);
320       m_strcpy(fcc, fcclen, p);
321       mutt_pretty_mailbox (fcc);
322
323       /* remove the X-Mutt-Fcc: header field */
324       next = tmp->next;
325       if (last)
326         last->next = tmp->next;
327       else
328         hdr->env->userhdrs = tmp->next;
329       tmp->next = NULL;
330       mutt_free_list (&tmp);
331       tmp = next;
332     }
333     else if ((m_strncmp("Pgp:", tmp->data, 4) == 0       /* this is generated
334                                                           * by old mutt versions
335                                                           */
336                  || m_strncmp("X-Mutt-PGP:", tmp->data, 11) == 0)) {
337       hdr->security = mutt_parse_crypt_hdr (strchr (tmp->data, ':') + 1, 1);
338       hdr->security |= APPLICATION_PGP;
339
340       /* remove the pgp field */
341       next = tmp->next;
342       if (last)
343         last->next = tmp->next;
344       else
345         hdr->env->userhdrs = tmp->next;
346       tmp->next = NULL;
347       mutt_free_list (&tmp);
348       tmp = next;
349     }
350     else if (m_strncmp("X-Mutt-SMIME:", tmp->data, 13) == 0) {
351       hdr->security = mutt_parse_crypt_hdr (strchr (tmp->data, ':') + 1, 1);
352       hdr->security |= APPLICATION_SMIME;
353
354       /* remove the smime field */
355       next = tmp->next;
356       if (last)
357         last->next = tmp->next;
358       else
359         hdr->env->userhdrs = tmp->next;
360       tmp->next = NULL;
361       mutt_free_list (&tmp);
362       tmp = next;
363     }
364
365 #ifdef MIXMASTER
366     else if (m_strncmp("X-Mutt-Mix:", tmp->data, 11) == 0) {
367       char *t;
368
369       mutt_free_list (&hdr->chain);
370
371       t = strtok (tmp->data + 11, " \t\n");
372       while (t) {
373         hdr->chain = mutt_add_list (hdr->chain, t);
374         t = strtok (NULL, " \t\n");
375       }
376
377       next = tmp->next;
378       if (last)
379         last->next = tmp->next;
380       else
381         hdr->env->userhdrs = tmp->next;
382       tmp->next = NULL;
383       mutt_free_list (&tmp);
384       tmp = next;
385     }
386 #endif
387
388     else {
389       last = tmp;
390       tmp = tmp->next;
391     }
392   }
393   return (code);
394 }
395
396
397
398 int mutt_parse_crypt_hdr (char *p, int set_signas)
399 {
400   int pgp = 0;
401   char pgp_sign_as[LONG_STRING] = "\0", *q;
402   char smime_cryptalg[LONG_STRING] = "\0";
403
404   for (p = vskipspaces(p); *p; p++) {
405     switch (*p) {
406     case 'e':
407     case 'E':
408       pgp |= ENCRYPT;
409       break;
410
411     case 's':
412     case 'S':
413       pgp |= SIGN;
414       q = pgp_sign_as;
415
416       if (*(p + 1) == '<') {
417         for (p += 2;
418              *p && *p != '>' && q < pgp_sign_as + sizeof (pgp_sign_as) - 1;
419              *q++ = *p++);
420
421         if (*p != '>') {
422           mutt_error _("Illegal PGP header");
423
424           return 0;
425         }
426       }
427
428       *q = '\0';
429       break;
430
431       /* This used to be the micalg parameter.
432        * 
433        * It's no longer needed, so we just skip the parameter in order
434        * to be able to recall old messages.
435        */
436     case 'm':
437     case 'M':
438       if (*(p + 1) == '<') {
439         for (p += 2; *p && *p != '>'; p++);
440         if (*p != '>') {
441           mutt_error _("Illegal PGP header");
442
443           return 0;
444         }
445       }
446
447       break;
448
449
450     case 'c':
451     case 'C':
452       q = smime_cryptalg;
453
454       if (*(p + 1) == '<') {
455         for (p += 2;
456              *p && *p != '>'
457              && q < smime_cryptalg + sizeof (smime_cryptalg) - 1;
458              *q++ = *p++);
459
460         if (*p != '>') {
461           mutt_error _("Illegal S/MIME header");
462
463           return 0;
464         }
465       }
466
467       *q = '\0';
468       break;
469
470     case 'i':
471     case 'I':
472       pgp |= INLINE;
473       break;
474
475     default:
476       mutt_error _("Illegal PGP header");
477       return 0;
478     }
479
480   }
481
482   /* the cryptalg field must not be empty */
483   if (*smime_cryptalg)
484     m_strreplace(&SmimeCryptAlg, smime_cryptalg);
485
486   if (set_signas || *pgp_sign_as)
487     m_strreplace(&PgpSignAs, pgp_sign_as);
488
489   return pgp;
490 }
491
492
493
494 int mutt_prepare_template (FILE * fp, CONTEXT * ctx, HEADER * newhdr,
495                            HEADER * hdr, short weed)
496 {
497   MESSAGE *msg = NULL;
498   char file[_POSIX_PATH_MAX];
499   BODY *b;
500   FILE *bfp;
501
502   int rv = -1;
503   STATE s;
504
505   p_clear(&s, 1);
506
507   if (!fp && (msg = mx_open_message (ctx, hdr->msgno)) == NULL)
508     return (-1);
509
510   if (!fp)
511     fp = msg->fp;
512
513   bfp = fp;
514
515   /* parse the message header and MIME structure */
516
517   fseeko (fp, hdr->offset, 0);
518   newhdr->offset = hdr->offset;
519   newhdr->env = mutt_read_rfc822_header (fp, newhdr, 1, weed);
520   newhdr->content->length = hdr->content->length;
521   mutt_parse_part (fp, newhdr->content);
522
523   p_delete(&newhdr->env->message_id);
524   p_delete(&newhdr->env->mail_followup_to);        /* really? */
525
526   /* decrypt pgp/mime encoded messages */
527
528   if ((APPLICATION_PGP | APPLICATION_SMIME) & hdr->security
529       && mutt_is_multipart_encrypted (newhdr->content))
530   {
531     int ccap = (APPLICATION_PGP | APPLICATION_SMIME) & hdr->security;
532     newhdr->security |= ENCRYPT | ccap;
533     if (!crypt_valid_passphrase (ccap))
534       goto err;
535
536     mutt_message _("Decrypting message...");
537
538     if (((ccap & APPLICATION_PGP)
539          && crypt_pgp_decrypt_mime (fp, &bfp, newhdr->content, &b) == -1)
540         || ((ccap & APPLICATION_SMIME)
541             && crypt_smime_decrypt_mime (fp, &bfp, newhdr->content, &b) == -1)
542         || b == NULL) {
543     err:
544       mx_close_message (&msg);
545       envelope_delete(&newhdr->env);
546       mutt_free_body (&newhdr->content);
547       mutt_error _("Decryption failed.");
548
549       return -1;
550     }
551
552     mutt_free_body (&newhdr->content);
553     newhdr->content = b;
554
555     mutt_clear_error ();
556   }
557
558   /* 
559    * remove a potential multipart/signed layer - useful when
560    * resending messages 
561    */
562
563   if (mutt_is_multipart_signed (newhdr->content)) {
564     newhdr->security |= SIGN;
565     if (ascii_strcasecmp (mutt_get_parameter
566                           ("protocol", newhdr->content->parameter),
567                           "application/pgp-signature") == 0)
568       newhdr->security |= APPLICATION_PGP;
569     else
570       newhdr->security |= APPLICATION_SMIME;
571
572     /* destroy the signature */
573     mutt_free_body (&newhdr->content->parts->next);
574     newhdr->content = mutt_remove_multipart (newhdr->content);
575   }
576
577
578   /* 
579    * We don't need no primary multipart.
580    * Note: We _do_ preserve messages!
581    * 
582    * XXX - we don't handle multipart/alternative in any 
583    * smart way when sending messages.  However, one may
584    * consider this a feature.
585    * 
586    */
587
588   if (newhdr->content->type == TYPEMULTIPART)
589     newhdr->content = mutt_remove_multipart (newhdr->content);
590
591   s.fpin = bfp;
592
593   /* create temporary files for all attachments */
594   for (b = newhdr->content; b; b = b->next) {
595
596     /* what follows is roughly a receive-mode variant of
597      * mutt_get_tmp_attachment () from muttlib.c
598      */
599
600     file[0] = '\0';
601     if (b->filename) {
602       m_strcpy(file, sizeof(file), b->filename);
603       b->d_filename = m_strdup(b->filename);
604     }
605     else {
606       /* avoid Content-Disposition: header with temporary filename */
607       b->use_disp = 0;
608     }
609
610     /* set up state flags */
611
612     s.flags = 0;
613
614     if (b->type == TYPETEXT) {
615       if (!ascii_strcasecmp
616           ("yes", mutt_get_parameter ("x-mutt-noconv", b->parameter)))
617         b->noconv = 1;
618       else {
619         s.flags |= M_CHARCONV;
620         b->noconv = 0;
621       }
622
623       mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
624     }
625
626     mutt_adv_mktemp (NULL, file, sizeof (file));
627     if ((s.fpout = safe_fopen (file, "w")) == NULL)
628       goto bail;
629
630
631     if (mutt_is_application_pgp (b) & (ENCRYPT | SIGN)) {
632
633       mutt_body_handler (b, &s);
634
635       newhdr->security |= mutt_is_application_pgp (newhdr->content);
636
637       b->type = TYPETEXT;
638       m_strreplace(&b->subtype, "plain");
639       mutt_delete_parameter ("x-action", &b->parameter);
640     }
641     else
642       mutt_decode_attachment (b, &s);
643
644     if (safe_fclose (&s.fpout) != 0)
645       goto bail;
646
647     m_strreplace(&b->filename, file);
648     b->unlink = 1;
649
650     mutt_stamp_attachment (b);
651
652     mutt_free_body (&b->parts);
653     if (b->hdr)
654       b->hdr->content = NULL;   /* avoid dangling pointer */
655   }
656
657   /* Fix encryption flags. */
658
659   /* No inline if multipart. */
660   if ((newhdr->security & INLINE) && newhdr->content->next)
661     newhdr->security &= ~INLINE;
662
663   /* Theoretically, both could be set. Take the one the user wants to set by default. */
664   if ((newhdr->security & APPLICATION_PGP)
665       && (newhdr->security & APPLICATION_SMIME)) {
666     if (option (OPTSMIMEISDEFAULT))
667       newhdr->security &= ~APPLICATION_PGP;
668     else
669       newhdr->security &= ~APPLICATION_SMIME;
670   }
671
672   rv = 0;
673
674 bail:
675
676   /* that's it. */
677   if (bfp != fp)
678     fclose (bfp);
679   if (msg)
680     mx_close_message (&msg);
681
682   if (rv == -1) {
683     envelope_delete(&newhdr->env);
684     mutt_free_body (&newhdr->content);
685   }
686
687   return rv;
688 }