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