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