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