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