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