]> git.llucax.com Git - software/mutt-debian.git/blob - init.c
refreshed sidebar patch, readded it to mutt-patched, re-enabled the build of mutt...
[software/mutt-debian.git] / init.c
1 /*
2  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
3  * 
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  * 
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  * 
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */ 
18
19 #if HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 #include "mutt.h"
24 #include "mapping.h"
25 #include "mutt_curses.h"
26 #include "mutt_menu.h"
27 #include "mutt_regex.h"
28 #include "history.h"
29 #include "keymap.h"
30 #include "mbyte.h"
31 #include "charset.h"
32 #include "mutt_crypt.h"
33 #include "mutt_idna.h"
34
35 #if defined(USE_SSL)
36 #include "mutt_ssl.h"
37 #endif
38
39
40
41 #include "mx.h"
42 #include "init.h"
43 #include "mailbox.h"
44
45 #include <ctype.h>
46 #include <stdlib.h>
47 #include <unistd.h>
48 #include <string.h>
49 #include <sys/utsname.h>
50 #include <errno.h>
51 #include <sys/wait.h>
52
53 #define CHECK_PAGER \
54   if ((CurrentMenu == MENU_PAGER) && (idx >= 0) &&      \
55             (MuttVars[idx].flags & R_RESORT)) \
56         { \
57           snprintf (err->data, err->dsize, \
58             _("Not available in this menu.")); \
59           return (-1); \
60         }
61
62 typedef struct myvar
63 {
64   char *name;
65   char *value;
66   struct myvar* next;
67 } myvar_t;
68
69 static myvar_t* MyVars;
70
71 static int var_to_string (int idx, char* val, size_t len);
72
73 static void myvar_set (const char* var, const char* val);
74 static const char* myvar_get (const char* var);
75 static void myvar_del (const char* var);
76
77 static void toggle_quadoption (int opt)
78 {
79   int n = opt/4;
80   int b = (opt % 4) * 2;
81
82   QuadOptions[n] ^= (1 << b);
83 }
84
85 void set_quadoption (int opt, int flag)
86 {
87   int n = opt/4;
88   int b = (opt % 4) * 2;
89
90   QuadOptions[n] &= ~(0x3 << b);
91   QuadOptions[n] |= (flag & 0x3) << b;
92 }
93
94 int quadoption (int opt)
95 {
96   int n = opt/4;
97   int b = (opt % 4) * 2;
98
99   return (QuadOptions[n] >> b) & 0x3;
100 }
101
102 int query_quadoption (int opt, const char *prompt)
103 {
104   int v = quadoption (opt);
105
106   switch (v)
107   {
108     case M_YES:
109     case M_NO:
110       return (v);
111
112     default:
113       v = mutt_yesorno (prompt, (v == M_ASKYES));
114       CLEARLINE (LINES - 1);
115       return (v);
116   }
117
118   /* not reached */
119 }
120
121 /* given the variable ``s'', return the index into the rc_vars array which
122    matches, or -1 if the variable is not found.  */
123 static int mutt_option_index (char *s)
124 {
125   int i;
126
127   for (i = 0; MuttVars[i].option; i++)
128     if (mutt_strcmp (s, MuttVars[i].option) == 0)
129       return (MuttVars[i].type == DT_SYN ?  mutt_option_index ((char *) MuttVars[i].data) : i);
130   return (-1);
131 }
132
133 int mutt_extract_token (BUFFER *dest, BUFFER *tok, int flags)
134 {
135   char          ch;
136   char          qc = 0; /* quote char */
137   char          *pc;
138
139   /* reset the destination pointer to the beginning of the buffer */
140   dest->dptr = dest->data;
141
142   SKIPWS (tok->dptr);
143   while ((ch = *tok->dptr))
144   {
145     if (!qc)
146     {
147       if ((ISSPACE (ch) && !(flags & M_TOKEN_SPACE)) ||
148           (ch == '#' && !(flags & M_TOKEN_COMMENT)) ||
149           (ch == '=' && (flags & M_TOKEN_EQUAL)) ||
150           (ch == ';' && !(flags & M_TOKEN_SEMICOLON)) ||
151           ((flags & M_TOKEN_PATTERN) && strchr ("~%=!|", ch)))
152         break;
153     }
154
155     tok->dptr++;
156
157     if (ch == qc)
158       qc = 0; /* end of quote */
159     else if (!qc && (ch == '\'' || ch == '"') && !(flags & M_TOKEN_QUOTE))
160       qc = ch;
161     else if (ch == '\\' && qc != '\'')
162     {
163         if (!*tok->dptr)
164             return -1; /* premature end of token */
165       switch (ch = *tok->dptr++)
166       {
167         case 'c':
168         case 'C':
169             if (!*tok->dptr)
170                 return -1; /* premature end of token */
171           mutt_buffer_addch (dest, (toupper ((unsigned char) *tok->dptr)
172                                     - '@') & 0x7f);
173           tok->dptr++;
174           break;
175         case 'r':
176           mutt_buffer_addch (dest, '\r');
177           break;
178         case 'n':
179           mutt_buffer_addch (dest, '\n');
180           break;
181         case 't':
182           mutt_buffer_addch (dest, '\t');
183           break;
184         case 'f':
185           mutt_buffer_addch (dest, '\f');
186           break;
187         case 'e':
188           mutt_buffer_addch (dest, '\033');
189           break;
190         default:
191           if (isdigit ((unsigned char) ch) &&
192               isdigit ((unsigned char) *tok->dptr) &&
193               isdigit ((unsigned char) *(tok->dptr + 1)))
194           {
195
196             mutt_buffer_addch (dest, (ch << 6) + (*tok->dptr << 3) + *(tok->dptr + 1) - 3504);
197             tok->dptr += 2;
198           }
199           else
200             mutt_buffer_addch (dest, ch);
201       }
202     }
203     else if (ch == '^' && (flags & M_TOKEN_CONDENSE))
204     {
205         if (!*tok->dptr)
206             return -1; /* premature end of token */
207       ch = *tok->dptr++;
208       if (ch == '^')
209         mutt_buffer_addch (dest, ch);
210       else if (ch == '[')
211         mutt_buffer_addch (dest, '\033');
212       else if (isalpha ((unsigned char) ch))
213         mutt_buffer_addch (dest, toupper ((unsigned char) ch) - '@');
214       else
215       {
216         mutt_buffer_addch (dest, '^');
217         mutt_buffer_addch (dest, ch);
218       }
219     }
220     else if (ch == '`' && (!qc || qc == '"'))
221     {
222       FILE      *fp;
223       pid_t     pid;
224       char      *cmd, *ptr;
225       size_t    expnlen;
226       BUFFER    expn;
227       int       line = 0;
228
229       pc = tok->dptr;
230       do {
231         if ((pc = strpbrk (pc, "\\`")))
232         {
233           /* skip any quoted chars */
234           if (*pc == '\\')
235             pc += 2;
236         }
237       } while (pc && *pc != '`');
238       if (!pc)
239       {
240         dprint (1, (debugfile, "mutt_get_token: mismatched backticks\n"));
241         return (-1);
242       }
243       cmd = mutt_substrdup (tok->dptr, pc);
244       if ((pid = mutt_create_filter (cmd, NULL, &fp, NULL)) < 0)
245       {
246         dprint (1, (debugfile, "mutt_get_token: unable to fork command: %s", cmd));
247         FREE (&cmd);
248         return (-1);
249       }
250       FREE (&cmd);
251
252       tok->dptr = pc + 1;
253
254       /* read line */
255       memset (&expn, 0, sizeof (expn));
256       expn.data = mutt_read_line (NULL, &expn.dsize, fp, &line);
257       fclose (fp);
258       mutt_wait_filter (pid);
259
260       /* if we got output, make a new string consiting of the shell ouptput
261          plus whatever else was left on the original line */
262       /* BUT: If this is inside a quoted string, directly add output to 
263        * the token */
264       if (expn.data && qc)
265       {
266         mutt_buffer_addstr (dest, expn.data);
267         FREE (&expn.data);
268       }
269       else if (expn.data)
270       {
271         expnlen = mutt_strlen (expn.data);
272         tok->dsize = expnlen + mutt_strlen (tok->dptr) + 1;
273         ptr = safe_malloc (tok->dsize);
274         memcpy (ptr, expn.data, expnlen);
275         strcpy (ptr + expnlen, tok->dptr);      /* __STRCPY_CHECKED__ */
276         if (tok->destroy)
277           FREE (&tok->data);
278         tok->data = ptr;
279         tok->dptr = ptr;
280         tok->destroy = 1; /* mark that the caller should destroy this data */
281         ptr = NULL;
282         FREE (&expn.data);
283       }
284     }
285     else if (ch == '$' && (!qc || qc == '"') && (*tok->dptr == '{' || isalpha ((unsigned char) *tok->dptr)))
286     {
287       const char *env = NULL;
288       char *var = NULL;
289       int idx;
290
291       if (*tok->dptr == '{')
292       {
293         tok->dptr++;
294         if ((pc = strchr (tok->dptr, '}')))
295         {
296           var = mutt_substrdup (tok->dptr, pc);
297           tok->dptr = pc + 1;
298         }
299       }
300       else
301       {
302         for (pc = tok->dptr; isalnum ((unsigned char) *pc) || *pc == '_'; pc++)
303           ;
304         var = mutt_substrdup (tok->dptr, pc);
305         tok->dptr = pc;
306       }
307       if (var)
308       {
309         if ((env = getenv (var)) || (env = myvar_get (var)))
310           mutt_buffer_addstr (dest, env);
311         else if ((idx = mutt_option_index (var)) != -1)
312         {
313           /* expand settable mutt variables */
314           char val[LONG_STRING];
315
316           if (var_to_string (idx, val, sizeof (val)))
317             mutt_buffer_addstr (dest, val);
318         }
319         FREE (&var);
320       }
321     }
322     else
323       mutt_buffer_addch (dest, ch);
324   }
325   mutt_buffer_addch (dest, 0); /* terminate the string */
326   SKIPWS (tok->dptr);
327   return 0;
328 }
329
330 static void mutt_free_opt (struct option_t* p)
331 {
332   REGEXP* pp;
333
334   switch (p->type & DT_MASK)
335   {
336   case DT_ADDR:
337     rfc822_free_address ((ADDRESS**)p->data);
338     break;
339   case DT_RX:
340     pp = (REGEXP*)p->data;
341     FREE (&pp->pattern);
342     if (pp->rx)
343     {
344       regfree (pp->rx);
345       FREE (&pp->rx);
346     }
347     break;
348   case DT_PATH:
349   case DT_STR:
350     FREE ((char**)p->data);             /* __FREE_CHECKED__ */
351     break;
352   }
353 }
354
355 /* clean up before quitting */
356 void mutt_free_opts (void)
357 {
358   int i;
359
360   for (i = 0; MuttVars[i].option; i++)
361     mutt_free_opt (MuttVars + i);
362
363   mutt_free_rx_list (&Alternates);
364   mutt_free_rx_list (&UnAlternates);
365   mutt_free_rx_list (&MailLists);
366   mutt_free_rx_list (&UnMailLists);
367   mutt_free_rx_list (&SubscribedLists);
368   mutt_free_rx_list (&UnSubscribedLists);
369   mutt_free_rx_list (&NoSpamList);
370 }
371
372 static void add_to_list (LIST **list, const char *str)
373 {
374   LIST *t, *last = NULL;
375
376   /* don't add a NULL or empty string to the list */
377   if (!str || *str == '\0')
378     return;
379
380   /* check to make sure the item is not already on this list */
381   for (last = *list; last; last = last->next)
382   {
383     if (ascii_strcasecmp (str, last->data) == 0)
384     {
385       /* already on the list, so just ignore it */
386       last = NULL;
387       break;
388     }
389     if (!last->next)
390       break;
391   }
392
393   if (!*list || last)
394   {
395     t = (LIST *) safe_calloc (1, sizeof (LIST));
396     t->data = safe_strdup (str);
397     if (last)
398     {
399       last->next = t;
400       last = last->next;
401     }
402     else
403       *list = last = t;
404   }
405 }
406
407 int mutt_add_to_rx_list (RX_LIST **list, const char *s, int flags, BUFFER *err)
408 {
409   RX_LIST *t, *last = NULL;
410   REGEXP *rx;
411
412   if (!s || !*s)
413     return 0;
414
415   if (!(rx = mutt_compile_regexp (s, flags)))
416   {
417     snprintf (err->data, err->dsize, "Bad regexp: %s\n", s);
418     return -1;
419   }
420
421   /* check to make sure the item is not already on this list */
422   for (last = *list; last; last = last->next)
423   {
424     if (ascii_strcasecmp (rx->pattern, last->rx->pattern) == 0)
425     {
426       /* already on the list, so just ignore it */
427       last = NULL;
428       break;
429     }
430     if (!last->next)
431       break;
432   }
433
434   if (!*list || last)
435   {
436     t = mutt_new_rx_list();
437     t->rx = rx;
438     if (last)
439     {
440       last->next = t;
441       last = last->next;
442     }
443     else
444       *list = last = t;
445   }
446   else /* duplicate */
447     mutt_free_regexp (&rx);
448
449   return 0;
450 }
451
452 static int remove_from_spam_list (SPAM_LIST **list, const char *pat);
453
454 static int add_to_spam_list (SPAM_LIST **list, const char *pat, const char *templ, BUFFER *err)
455 {
456   SPAM_LIST *t = NULL, *last = NULL;
457   REGEXP *rx;
458   int n;
459   const char *p;
460
461   if (!pat || !*pat || !templ)
462     return 0;
463
464   if (!(rx = mutt_compile_regexp (pat, REG_ICASE)))
465   {
466     snprintf (err->data, err->dsize, _("Bad regexp: %s"), pat);
467     return -1;
468   }
469
470   /* check to make sure the item is not already on this list */
471   for (last = *list; last; last = last->next)
472   {
473     if (ascii_strcasecmp (rx->pattern, last->rx->pattern) == 0)
474     {
475       /* Already on the list. Formerly we just skipped this case, but
476        * now we're supporting removals, which means we're supporting
477        * re-adds conceptually. So we probably want this to imply a
478        * removal, then do an add. We can achieve the removal by freeing
479        * the template, and leaving t pointed at the current item.
480        */
481       t = last;
482       FREE(&t->template);
483       break;
484     }
485     if (!last->next)
486       break;
487   }
488
489   /* If t is set, it's pointing into an extant SPAM_LIST* that we want to
490    * update. Otherwise we want to make a new one to link at the list's end.
491    */
492   if (!t)
493   {
494     t = mutt_new_spam_list();
495     t->rx = rx;
496     if (last)
497       last->next = t;
498     else
499       *list = t;
500   }
501
502   /* Now t is the SPAM_LIST* that we want to modify. It is prepared. */
503   t->template = safe_strdup(templ);
504
505   /* Find highest match number in template string */
506   t->nmatch = 0;
507   for (p = templ; *p;)
508   {
509     if (*p == '%')
510     {
511         n = atoi(++p);
512         if (n > t->nmatch)
513           t->nmatch = n;
514         while (*p && isdigit((int)*p))
515           ++p;
516     }
517     else
518         ++p;
519   }
520
521   if (t->nmatch > t->rx->rx->re_nsub)
522   {
523     snprintf (err->data, err->dsize, _("Not enough subexpressions for spam "
524                                        "template"));
525     remove_from_spam_list(list, pat);
526     return -1;
527   }
528
529   t->nmatch++;         /* match 0 is always the whole expr */
530
531   return 0;
532 }
533
534 static int remove_from_spam_list (SPAM_LIST **list, const char *pat)
535 {
536   SPAM_LIST *spam, *prev;
537   int nremoved = 0;
538
539   /* Being first is a special case. */
540   spam = *list;
541   if (!spam)
542     return 0;
543   if (spam->rx && !mutt_strcmp(spam->rx->pattern, pat))
544   {
545     *list = spam->next;
546     mutt_free_regexp(&spam->rx);
547     FREE(&spam->template);
548     FREE(&spam);
549     return 1;
550   }
551
552   prev = spam;
553   for (spam = prev->next; spam;)
554   {
555     if (!mutt_strcmp(spam->rx->pattern, pat))
556     {
557       prev->next = spam->next;
558       mutt_free_regexp(&spam->rx);
559       FREE(&spam->template);
560       FREE(&spam);
561       spam = prev->next;
562       ++nremoved;
563     }
564     else
565       spam = spam->next;
566   }
567
568   return nremoved;
569 }
570
571
572 static void remove_from_list (LIST **l, const char *str)
573 {
574   LIST *p, *last = NULL;
575
576   if (mutt_strcmp ("*", str) == 0)
577     mutt_free_list (l);    /* ``unCMD *'' means delete all current entries */
578   else
579   {
580     p = *l;
581     last = NULL;
582     while (p)
583     {
584       if (ascii_strcasecmp (str, p->data) == 0)
585       {
586         FREE (&p->data);
587         if (last)
588           last->next = p->next;
589         else
590           (*l) = p->next;
591         FREE (&p);
592       }
593       else
594       {
595         last = p;
596         p = p->next;
597       }
598     }
599   }
600 }
601
602 static int remove_from_rx_list (RX_LIST **l, const char *str)
603 {
604   RX_LIST *p, *last = NULL;
605   int rv = -1;
606
607   if (mutt_strcmp ("*", str) == 0)
608   {
609     mutt_free_rx_list (l);    /* ``unCMD *'' means delete all current entries */
610     rv = 0;
611   }
612   else
613   {
614     p = *l;
615     last = NULL;
616     while (p)
617     {
618       if (ascii_strcasecmp (str, p->rx->pattern) == 0)
619       {
620         mutt_free_regexp (&p->rx);
621         if (last)
622           last->next = p->next;
623         else
624           (*l) = p->next;
625         FREE (&p);
626         rv = 0;
627       }
628       else
629       {
630         last = p;
631         p = p->next;
632       }
633     }
634   }
635   return (rv);
636 }
637
638 static int parse_unignore (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
639 {
640   do
641   {
642     mutt_extract_token (buf, s, 0);
643
644     /* don't add "*" to the unignore list */
645     if (strcmp (buf->data, "*")) 
646       add_to_list (&UnIgnore, buf->data);
647
648     remove_from_list (&Ignore, buf->data);
649   }
650   while (MoreArgs (s));
651
652   return 0;
653 }
654
655 static int parse_ignore (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
656 {
657   do
658   {
659     mutt_extract_token (buf, s, 0);
660     remove_from_list (&UnIgnore, buf->data);
661     add_to_list (&Ignore, buf->data);
662   }
663   while (MoreArgs (s));
664
665   return 0;
666 }
667
668 static int parse_list (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
669 {
670   do
671   {
672     mutt_extract_token (buf, s, 0);
673     add_to_list ((LIST **) data, buf->data);
674   }
675   while (MoreArgs (s));
676
677   return 0;
678 }
679
680 static void _alternates_clean (void)
681 {
682   int i;
683   if (Context && Context->msgcount) 
684   {
685     for (i = 0; i < Context->msgcount; i++)
686       Context->hdrs[i]->recip_valid = 0;
687   }
688 }
689
690 static int parse_alternates (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
691 {
692   group_context_t *gc = NULL;
693   
694   _alternates_clean();
695
696   do
697   {
698     mutt_extract_token (buf, s, 0);
699
700     if (parse_group_context (&gc, buf, s, data, err) == -1)
701       goto bail;
702
703     remove_from_rx_list (&UnAlternates, buf->data);
704
705     if (mutt_add_to_rx_list (&Alternates, buf->data, REG_ICASE, err) != 0)
706       goto bail;
707
708     if (mutt_group_context_add_rx (gc, buf->data, REG_ICASE, err) != 0)
709       goto bail;
710   }
711   while (MoreArgs (s));
712   
713   mutt_group_context_destroy (&gc);
714   return 0;
715   
716  bail:
717   mutt_group_context_destroy (&gc);
718   return -1;
719 }
720
721 static int parse_unalternates (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
722 {
723   _alternates_clean();
724   do
725   {
726     mutt_extract_token (buf, s, 0);
727     remove_from_rx_list (&Alternates, buf->data);
728
729     if (mutt_strcmp (buf->data, "*") &&
730         mutt_add_to_rx_list (&UnAlternates, buf->data, REG_ICASE, err) != 0)
731       return -1;
732
733   }
734   while (MoreArgs (s));
735
736   return 0;
737 }
738
739 static int parse_spam_list (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
740 {
741   BUFFER templ;
742
743   memset(&templ, 0, sizeof(templ));
744
745   /* Insist on at least one parameter */
746   if (!MoreArgs(s))
747   {
748     if (data == M_SPAM)
749       strfcpy(err->data, _("spam: no matching pattern"), err->dsize);
750     else
751       strfcpy(err->data, _("nospam: no matching pattern"), err->dsize);
752     return -1;
753   }
754
755   /* Extract the first token, a regexp */
756   mutt_extract_token (buf, s, 0);
757
758   /* data should be either M_SPAM or M_NOSPAM. M_SPAM is for spam commands. */
759   if (data == M_SPAM)
760   {
761     /* If there's a second parameter, it's a template for the spam tag. */
762     if (MoreArgs(s))
763     {
764       mutt_extract_token (&templ, s, 0);
765
766       /* Add to the spam list. */
767       if (add_to_spam_list (&SpamList, buf->data, templ.data, err) != 0) {
768           FREE(&templ.data);
769           return -1;
770       }
771       FREE(&templ.data);
772     }
773
774     /* If not, try to remove from the nospam list. */
775     else
776     {
777       remove_from_rx_list(&NoSpamList, buf->data);
778     }
779
780     return 0;
781   }
782
783   /* M_NOSPAM is for nospam commands. */
784   else if (data == M_NOSPAM)
785   {
786     /* nospam only ever has one parameter. */
787
788     /* "*" is a special case. */
789     if (!mutt_strcmp(buf->data, "*"))
790     {
791       mutt_free_spam_list (&SpamList);
792       mutt_free_rx_list (&NoSpamList);
793       return 0;
794     }
795
796     /* If it's on the spam list, just remove it. */
797     if (remove_from_spam_list(&SpamList, buf->data) != 0)
798       return 0;
799
800     /* Otherwise, add it to the nospam list. */
801     if (mutt_add_to_rx_list (&NoSpamList, buf->data, REG_ICASE, err) != 0)
802       return -1;
803
804     return 0;
805   }
806
807   /* This should not happen. */
808   strfcpy(err->data, "This is no good at all.", err->dsize);
809   return -1;
810 }
811
812
813 static int parse_unlist (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
814 {
815   do
816   {
817     mutt_extract_token (buf, s, 0);
818     /*
819      * Check for deletion of entire list
820      */
821     if (mutt_strcmp (buf->data, "*") == 0)
822     {
823       mutt_free_list ((LIST **) data);
824       break;
825     }
826     remove_from_list ((LIST **) data, buf->data);
827   }
828   while (MoreArgs (s));
829
830   return 0;
831 }
832
833 static int parse_lists (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
834 {
835   group_context_t *gc = NULL;
836
837   do
838   {
839     mutt_extract_token (buf, s, 0);
840     
841     if (parse_group_context (&gc, buf, s, data, err) == -1)
842       goto bail;
843     
844     remove_from_rx_list (&UnMailLists, buf->data);
845     
846     if (mutt_add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
847       goto bail;
848     
849     if (mutt_group_context_add_rx (gc, buf->data, REG_ICASE, err) != 0)
850       goto bail;
851   }
852   while (MoreArgs (s));
853
854   mutt_group_context_destroy (&gc);
855   return 0;
856   
857  bail:
858   mutt_group_context_destroy (&gc);
859   return -1;
860 }
861
862 typedef enum group_state_t {
863   NONE, RX, ADDR
864 } group_state_t;
865
866 static int parse_group (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
867 {
868   group_context_t *gc = NULL;
869   group_state_t state = NONE;
870   ADDRESS *addr = NULL;
871   char *estr = NULL;
872   
873   do 
874   {
875     mutt_extract_token (buf, s, 0);
876     if (parse_group_context (&gc, buf, s, data, err) == -1)
877       goto bail;
878     
879     if (!mutt_strcasecmp (buf->data, "-rx"))
880       state = RX;
881     else if (!mutt_strcasecmp (buf->data, "-addr"))
882       state = ADDR;
883     else 
884     {
885       switch (state) 
886       {
887         case NONE:
888           strfcpy (err->data, _("Missing -rx or -addr."), err->dsize);
889           goto bail;
890         
891         case RX:
892           if (mutt_group_context_add_rx (gc, buf->data, REG_ICASE, err) != 0)
893             goto bail;
894           break;
895         
896         case ADDR:
897           if ((addr = mutt_parse_adrlist (NULL, buf->data)) == NULL)
898             goto bail;
899           if (mutt_addrlist_to_idna (addr, &estr)) 
900           {
901             snprintf (err->data, err->dsize, _("Warning: Bad IDN '%s'.\n"),
902                       estr);
903             goto bail;
904           }
905           mutt_group_context_add_adrlist (gc, addr);
906           rfc822_free_address (&addr);
907           break;
908       }
909     }
910   } while (MoreArgs (s));
911
912   mutt_group_context_destroy (&gc);
913   return 0;
914
915   bail:
916   mutt_group_context_destroy (&gc);
917   return -1;
918 }
919
920 static int parse_ungroup (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
921 {
922   strfcpy (err->data, "not implemented", err->dsize);
923   return -1;
924 }
925
926 /* always wise to do what someone else did before */
927 static void _attachments_clean (void)
928 {
929   int i;
930   if (Context && Context->msgcount) 
931   {
932     for (i = 0; i < Context->msgcount; i++)
933       Context->hdrs[i]->attach_valid = 0;
934   }
935 }
936
937 static int parse_attach_list (BUFFER *buf, BUFFER *s, LIST **ldata, BUFFER *err)
938 {
939   ATTACH_MATCH *a;
940   LIST *listp, *lastp;
941   char *p;
942   char *tmpminor;
943   int len;
944
945   /* Find the last item in the list that data points to. */
946   lastp = NULL;
947   dprint(5, (debugfile, "parse_attach_list: ldata = %p, *ldata = %p\n",
948               (void *)ldata, (void *)*ldata));
949   for (listp = *ldata; listp; listp = listp->next)
950   {
951     a = (ATTACH_MATCH *)listp->data;
952     dprint(5, (debugfile, "parse_attach_list: skipping %s/%s\n",
953                 a->major, a->minor));
954     lastp = listp;
955   }
956
957   do
958   {
959     mutt_extract_token (buf, s, 0);
960
961     if (!buf->data || *buf->data == '\0')
962       continue;
963    
964     a = safe_malloc(sizeof(ATTACH_MATCH));
965
966     /* some cheap hacks that I expect to remove */
967     if (!ascii_strcasecmp(buf->data, "any"))
968       a->major = safe_strdup("*/.*");
969     else if (!ascii_strcasecmp(buf->data, "none"))
970       a->major = safe_strdup("cheap_hack/this_should_never_match");
971     else
972       a->major = safe_strdup(buf->data);
973
974     if ((p = strchr(a->major, '/')))
975     {
976       *p = '\0';
977       ++p;
978       a->minor = p;
979     }
980     else
981     {
982       a->minor = "unknown";
983     }
984
985     len = strlen(a->minor);
986     tmpminor = safe_malloc(len+3);
987     strcpy(&tmpminor[1], a->minor); /* __STRCPY_CHECKED__ */
988     tmpminor[0] = '^';
989     tmpminor[len+1] = '$';
990     tmpminor[len+2] = '\0';
991
992     a->major_int = mutt_check_mime_type(a->major);
993     regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
994
995     FREE(&tmpminor);
996
997     dprint(5, (debugfile, "parse_attach_list: added %s/%s [%d]\n",
998                 a->major, a->minor, a->major_int));
999
1000     listp = safe_malloc(sizeof(LIST));
1001     listp->data = (char *)a;
1002     listp->next = NULL;
1003     if (lastp)
1004     {
1005       lastp->next = listp;
1006     }
1007     else
1008     {
1009       *ldata = listp;
1010     }
1011     lastp = listp;
1012   }
1013   while (MoreArgs (s));
1014    
1015   _attachments_clean();
1016   return 0;
1017 }
1018
1019 static int parse_unattach_list (BUFFER *buf, BUFFER *s, LIST **ldata, BUFFER *err)
1020 {
1021   ATTACH_MATCH *a;
1022   LIST *lp, *lastp, *newlp;
1023   char *tmp;
1024   int major;
1025   char *minor;
1026
1027   do
1028   {
1029     mutt_extract_token (buf, s, 0);
1030
1031     if (!ascii_strcasecmp(buf->data, "any"))
1032       tmp = safe_strdup("*/.*");
1033     else if (!ascii_strcasecmp(buf->data, "none"))
1034       tmp = safe_strdup("cheap_hack/this_should_never_match");
1035     else
1036       tmp = safe_strdup(buf->data);
1037
1038     if ((minor = strchr(tmp, '/')))
1039     {
1040       *minor = '\0';
1041       ++minor;
1042     }
1043     else
1044     {
1045       minor = "unknown";
1046     }
1047     major = mutt_check_mime_type(tmp);
1048
1049     /* We must do our own walk here because remove_from_list() will only
1050      * remove the LIST->data, not anything pointed to by the LIST->data. */
1051     lastp = NULL;
1052     for(lp = *ldata; lp; )
1053     {
1054       a = (ATTACH_MATCH *)lp->data;
1055       dprint(5, (debugfile, "parse_unattach_list: check %s/%s [%d] : %s/%s [%d]\n",
1056                   a->major, a->minor, a->major_int, tmp, minor, major));
1057       if (a->major_int == major && !mutt_strcasecmp(minor, a->minor))
1058       {
1059         dprint(5, (debugfile, "parse_unattach_list: removed %s/%s [%d]\n",
1060                     a->major, a->minor, a->major_int));
1061         regfree(&a->minor_rx);
1062         FREE(&a->major);
1063
1064         /* Relink backward */
1065         if (lastp)
1066           lastp->next = lp->next;
1067         else
1068           *ldata = lp->next;
1069
1070         newlp = lp->next;
1071         FREE(&lp->data);        /* same as a */
1072         FREE(&lp);
1073         lp = newlp;
1074         continue;
1075       }
1076
1077       lastp = lp;
1078       lp = lp->next;
1079     }
1080
1081   }
1082   while (MoreArgs (s));
1083    
1084   FREE(&tmp);
1085   _attachments_clean();
1086   return 0;
1087 }
1088
1089 static int print_attach_list (LIST *lp, char op, char *name)
1090 {
1091   while (lp) {
1092     printf("attachments %c%s %s/%s\n", op, name,
1093            ((ATTACH_MATCH *)lp->data)->major,
1094            ((ATTACH_MATCH *)lp->data)->minor);
1095     lp = lp->next;
1096   }
1097
1098   return 0;
1099 }
1100
1101
1102 static int parse_attachments (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
1103 {
1104   char op, *category;
1105   LIST **listp;
1106
1107   mutt_extract_token(buf, s, 0);
1108   if (!buf->data || *buf->data == '\0') {
1109     strfcpy(err->data, _("attachments: no disposition"), err->dsize);
1110     return -1;
1111   }
1112
1113   category = buf->data;
1114   op = *category++;
1115
1116   if (op == '?') {
1117     mutt_endwin (NULL);
1118     fflush (stdout);
1119     printf("\nCurrent attachments settings:\n\n");
1120     print_attach_list(AttachAllow,   '+', "A");
1121     print_attach_list(AttachExclude, '-', "A");
1122     print_attach_list(InlineAllow,   '+', "I");
1123     print_attach_list(InlineExclude, '-', "I");
1124     set_option (OPTFORCEREDRAWINDEX);
1125     set_option (OPTFORCEREDRAWPAGER);
1126     mutt_any_key_to_continue (NULL);
1127     return 0;
1128   }
1129
1130   if (op != '+' && op != '-') {
1131     op = '+';
1132     category--;
1133   }
1134   if (!ascii_strncasecmp(category, "attachment", strlen(category))) {
1135     if (op == '+')
1136       listp = &AttachAllow;
1137     else
1138       listp = &AttachExclude;
1139   }
1140   else if (!ascii_strncasecmp(category, "inline", strlen(category))) {
1141     if (op == '+')
1142       listp = &InlineAllow;
1143     else
1144       listp = &InlineExclude;
1145   }
1146   else {
1147     strfcpy(err->data, _("attachments: invalid disposition"), err->dsize);
1148     return -1;
1149   }
1150
1151   return parse_attach_list(buf, s, listp, err);
1152 }
1153
1154 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
1155 {
1156   char op, *p;
1157   LIST **listp;
1158
1159   mutt_extract_token(buf, s, 0);
1160   if (!buf->data || *buf->data == '\0') {
1161     strfcpy(err->data, _("unattachments: no disposition"), err->dsize);
1162     return -1;
1163   }
1164
1165   p = buf->data;
1166   op = *p++;
1167   if (op != '+' && op != '-') {
1168     op = '+';
1169     p--;
1170   }
1171   if (!ascii_strncasecmp(p, "attachment", strlen(p))) {
1172     if (op == '+')
1173       listp = &AttachAllow;
1174     else
1175       listp = &AttachExclude;
1176   }
1177   else if (!ascii_strncasecmp(p, "inline", strlen(p))) {
1178     if (op == '+')
1179       listp = &InlineAllow;
1180     else
1181       listp = &InlineExclude;
1182   }
1183   else {
1184     strfcpy(err->data, _("unattachments: invalid disposition"), err->dsize);
1185     return -1;
1186   }
1187
1188   return parse_unattach_list(buf, s, listp, err);
1189 }
1190
1191 static int parse_unlists (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
1192 {
1193   do
1194   {
1195     mutt_extract_token (buf, s, 0);
1196     remove_from_rx_list (&SubscribedLists, buf->data);
1197     remove_from_rx_list (&MailLists, buf->data);
1198     
1199     if (mutt_strcmp (buf->data, "*") && 
1200         mutt_add_to_rx_list (&UnMailLists, buf->data, REG_ICASE, err) != 0)
1201       return -1;
1202   }
1203   while (MoreArgs (s));
1204
1205   return 0;
1206 }
1207
1208 static int parse_subscribe (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
1209 {
1210   group_context_t *gc = NULL;
1211   
1212   do
1213   {
1214     mutt_extract_token (buf, s, 0);
1215
1216     if (parse_group_context (&gc, buf, s, data, err) == -1)
1217       goto bail;
1218     
1219     remove_from_rx_list (&UnMailLists, buf->data);
1220     remove_from_rx_list (&UnSubscribedLists, buf->data);
1221
1222     if (mutt_add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
1223       goto bail;
1224     if (mutt_add_to_rx_list (&SubscribedLists, buf->data, REG_ICASE, err) != 0)
1225       goto bail;
1226     if (mutt_group_context_add_rx (gc, buf->data, REG_ICASE, err) != 0)
1227       goto bail;
1228   }
1229   while (MoreArgs (s));
1230   
1231   mutt_group_context_destroy (&gc);
1232   return 0;
1233   
1234  bail:
1235   mutt_group_context_destroy (&gc);
1236   return -1;
1237 }
1238
1239 static int parse_unsubscribe (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
1240 {
1241   do
1242   {
1243     mutt_extract_token (buf, s, 0);
1244     remove_from_rx_list (&SubscribedLists, buf->data);
1245     
1246     if (mutt_strcmp (buf->data, "*") &&
1247         mutt_add_to_rx_list (&UnSubscribedLists, buf->data, REG_ICASE, err) != 0)
1248       return -1;
1249   }
1250   while (MoreArgs (s));
1251
1252   return 0;
1253 }
1254   
1255 static int parse_unalias (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
1256 {
1257   ALIAS *tmp, *last = NULL;
1258
1259   do
1260   {
1261     mutt_extract_token (buf, s, 0);
1262
1263     if (mutt_strcmp ("*", buf->data) == 0)
1264     {
1265       if (CurrentMenu == MENU_ALIAS)
1266       {
1267         for (tmp = Aliases; tmp ; tmp = tmp->next) 
1268           tmp->del = 1;
1269         set_option (OPTFORCEREDRAWINDEX);
1270       }
1271       else
1272         mutt_free_alias (&Aliases);
1273       break;
1274     }
1275     else
1276       for (tmp = Aliases; tmp; tmp = tmp->next)
1277       {
1278         if (mutt_strcasecmp (buf->data, tmp->name) == 0)
1279         {
1280           if (CurrentMenu == MENU_ALIAS)
1281           {
1282             tmp->del = 1;
1283             set_option (OPTFORCEREDRAWINDEX);
1284             break;
1285           }
1286
1287           if (last)
1288             last->next = tmp->next;
1289           else
1290             Aliases = tmp->next;
1291           tmp->next = NULL;
1292           mutt_free_alias (&tmp);
1293           break;
1294         }
1295         last = tmp;
1296       }
1297   }
1298   while (MoreArgs (s));
1299   return 0;
1300 }
1301
1302 static int parse_alias (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
1303 {
1304   ALIAS *tmp = Aliases;
1305   ALIAS *last = NULL;
1306   char *estr = NULL;
1307   group_context_t *gc = NULL;
1308   
1309   if (!MoreArgs (s))
1310   {
1311     strfcpy (err->data, _("alias: no address"), err->dsize);
1312     return (-1);
1313   }
1314
1315   mutt_extract_token (buf, s, 0);
1316
1317   if (parse_group_context (&gc, buf, s, data, err) == -1)
1318     return -1;
1319   
1320   /* check to see if an alias with this name already exists */
1321   for (; tmp; tmp = tmp->next)
1322   {
1323     if (!mutt_strcasecmp (tmp->name, buf->data))
1324       break;
1325     last = tmp;
1326   }
1327
1328   if (!tmp)
1329   {
1330     /* create a new alias */
1331     tmp = (ALIAS *) safe_calloc (1, sizeof (ALIAS));
1332     tmp->self = tmp;
1333     tmp->name = safe_strdup (buf->data);
1334     /* give the main addressbook code a chance */
1335     if (CurrentMenu == MENU_ALIAS)
1336       set_option (OPTMENUCALLER);
1337   }
1338   else
1339   {
1340     mutt_alias_delete_reverse (tmp);
1341     /* override the previous value */
1342     rfc822_free_address (&tmp->addr);
1343     if (CurrentMenu == MENU_ALIAS)
1344       set_option (OPTFORCEREDRAWINDEX);
1345   }
1346
1347   mutt_extract_token (buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE | M_TOKEN_SEMICOLON);
1348   dprint (3, (debugfile, "parse_alias: Second token is '%s'.\n",
1349               buf->data));
1350
1351   tmp->addr = mutt_parse_adrlist (tmp->addr, buf->data);
1352
1353   if (last)
1354     last->next = tmp;
1355   else
1356     Aliases = tmp;
1357   if (mutt_addrlist_to_idna (tmp->addr, &estr))
1358   {
1359     snprintf (err->data, err->dsize, _("Warning: Bad IDN '%s' in alias '%s'.\n"),
1360               estr, tmp->name);
1361     goto bail;
1362   }
1363
1364   mutt_group_context_add_adrlist (gc, tmp->addr);
1365   mutt_alias_add_reverse (tmp);
1366
1367 #ifdef DEBUG
1368   if (debuglevel >= 2) 
1369   {
1370     ADDRESS *a;
1371     /* A group is terminated with an empty address, so check a->mailbox */
1372     for (a = tmp->addr; a && a->mailbox; a = a->next)
1373     {
1374       if (!a->group)
1375         dprint (3, (debugfile, "parse_alias:   %s\n",
1376                     a->mailbox));
1377       else
1378         dprint (3, (debugfile, "parse_alias:   Group %s\n",
1379                     a->mailbox));
1380     }
1381   }
1382 #endif
1383   mutt_group_context_destroy (&gc);
1384   return 0;
1385   
1386   bail:
1387   mutt_group_context_destroy (&gc);
1388   return -1;
1389 }
1390
1391 static int
1392 parse_unmy_hdr (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
1393 {
1394   LIST *last = NULL;
1395   LIST *tmp = UserHeader;
1396   LIST *ptr;
1397   size_t l;
1398
1399   do
1400   {
1401     mutt_extract_token (buf, s, 0);
1402     if (mutt_strcmp ("*", buf->data) == 0)
1403       mutt_free_list (&UserHeader);
1404     else
1405     {
1406       tmp = UserHeader;
1407       last = NULL;
1408
1409       l = mutt_strlen (buf->data);
1410       if (buf->data[l - 1] == ':')
1411         l--;
1412
1413       while (tmp)
1414       {
1415         if (ascii_strncasecmp (buf->data, tmp->data, l) == 0 && tmp->data[l] == ':')
1416         {
1417           ptr = tmp;
1418           if (last)
1419             last->next = tmp->next;
1420           else
1421             UserHeader = tmp->next;
1422           tmp = tmp->next;
1423           ptr->next = NULL;
1424           mutt_free_list (&ptr);
1425         }
1426         else
1427         {
1428           last = tmp;
1429           tmp = tmp->next;
1430         }
1431       }
1432     }
1433   }
1434   while (MoreArgs (s));
1435   return 0;
1436 }
1437
1438 static int parse_my_hdr (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
1439 {
1440   LIST *tmp;
1441   size_t keylen;
1442   char *p;
1443
1444   mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
1445   if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':')
1446   {
1447     strfcpy (err->data, _("invalid header field"), err->dsize);
1448     return (-1);
1449   }
1450   keylen = p - buf->data + 1;
1451
1452   if (UserHeader)
1453   {
1454     for (tmp = UserHeader; ; tmp = tmp->next)
1455     {
1456       /* see if there is already a field by this name */
1457       if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0)
1458       {
1459         /* replace the old value */
1460         FREE (&tmp->data);
1461         tmp->data = buf->data;
1462         memset (buf, 0, sizeof (BUFFER));
1463         return 0;
1464       }
1465       if (!tmp->next)
1466         break;
1467     }
1468     tmp->next = mutt_new_list ();
1469     tmp = tmp->next;
1470   }
1471   else
1472   {
1473     tmp = mutt_new_list ();
1474     UserHeader = tmp;
1475   }
1476   tmp->data = buf->data;
1477   memset (buf, 0, sizeof (BUFFER));
1478   return 0;
1479 }
1480
1481 static int
1482 parse_sort (short *val, const char *s, const struct mapping_t *map, BUFFER *err)
1483 {
1484   int i, flags = 0;
1485
1486   if (mutt_strncmp ("reverse-", s, 8) == 0)
1487   {
1488     s += 8;
1489     flags = SORT_REVERSE;
1490   }
1491   
1492   if (mutt_strncmp ("last-", s, 5) == 0)
1493   {
1494     s += 5;
1495     flags |= SORT_LAST;
1496   }
1497
1498   if ((i = mutt_getvaluebyname (s, map)) == -1)
1499   {
1500     snprintf (err->data, err->dsize, _("%s: unknown sorting method"), s);
1501     return (-1);
1502   }
1503
1504   *val = i | flags;
1505
1506   return 0;
1507 }
1508
1509 static void mutt_set_default (struct option_t *p)
1510 {
1511   switch (p->type & DT_MASK)
1512   {
1513     case DT_STR:
1514       if (!p->init && *((char **) p->data))
1515         p->init = (unsigned long) safe_strdup (* ((char **) p->data));
1516       break;
1517     case DT_PATH:
1518       if (!p->init && *((char **) p->data))
1519       {
1520         char *cp = safe_strdup (*((char **) p->data));
1521         /* mutt_pretty_mailbox (cp); */
1522         p->init = (unsigned long) cp;
1523       }
1524       break;
1525     case DT_ADDR:
1526       if (!p->init && *((ADDRESS **) p->data))
1527       {
1528         char tmp[HUGE_STRING];
1529         *tmp = '\0';
1530         rfc822_write_address (tmp, sizeof (tmp), *((ADDRESS **) p->data), 0);
1531         p->init = (unsigned long) safe_strdup (tmp);
1532       }
1533       break;
1534     case DT_RX:
1535     {
1536       REGEXP *pp = (REGEXP *) p->data;
1537       if (!p->init && pp->pattern)
1538         p->init = (unsigned long) safe_strdup (pp->pattern);
1539       break;
1540     }
1541   }
1542 }
1543
1544 static void mutt_restore_default (struct option_t *p)
1545 {
1546   switch (p->type & DT_MASK)
1547   {
1548     case DT_STR:
1549       if (p->init)
1550         mutt_str_replace ((char **) p->data, (char *) p->init); 
1551       break;
1552     case DT_PATH:
1553       if (p->init)
1554       {
1555         char path[_POSIX_PATH_MAX];
1556
1557         strfcpy (path, (char *) p->init, sizeof (path));
1558         mutt_expand_path (path, sizeof (path));
1559         mutt_str_replace ((char **) p->data, path);
1560       }
1561       break;
1562     case DT_ADDR:
1563       if (p->init)
1564       {
1565         rfc822_free_address ((ADDRESS **) p->data);
1566         *((ADDRESS **) p->data) = rfc822_parse_adrlist (NULL, (char *) p->init);
1567       }
1568       break;
1569     case DT_BOOL:
1570       if (p->init)
1571         set_option (p->data);
1572       else
1573         unset_option (p->data);
1574       break;
1575     case DT_QUAD:
1576       set_quadoption (p->data, p->init);
1577       break;
1578     case DT_NUM:
1579     case DT_SORT:
1580     case DT_MAGIC:
1581       *((short *) p->data) = p->init;
1582       break;
1583     case DT_RX:
1584       {
1585         REGEXP *pp = (REGEXP *) p->data;
1586         int flags = 0;
1587
1588         FREE (&pp->pattern);
1589         if (pp->rx)
1590         {
1591           regfree (pp->rx);
1592           FREE (&pp->rx);
1593         }
1594
1595         if (p->init)
1596         {
1597           char *s = (char *) p->init;
1598
1599           pp->rx = safe_calloc (1, sizeof (regex_t));
1600           pp->pattern = safe_strdup ((char *) p->init);
1601           if (mutt_strcmp (p->option, "mask") != 0)
1602             flags |= mutt_which_case ((const char *) p->init);
1603           if (mutt_strcmp (p->option, "mask") == 0 && *s == '!')
1604           {
1605             s++;
1606             pp->not = 1;
1607           }
1608           if (REGCOMP (pp->rx, s, flags) != 0)
1609           {
1610             fprintf (stderr, _("mutt_restore_default(%s): error in regexp: %s\n"),
1611                      p->option, pp->pattern);
1612             FREE (&pp->pattern);
1613             regfree (pp->rx);
1614             FREE (&pp->rx);
1615           }
1616         }
1617       }
1618       break;
1619   }
1620
1621   if (p->flags & R_INDEX)
1622     set_option (OPTFORCEREDRAWINDEX);
1623   if (p->flags & R_PAGER)
1624     set_option (OPTFORCEREDRAWPAGER);
1625   if (p->flags & R_RESORT_SUB)
1626     set_option (OPTSORTSUBTHREADS);
1627   if (p->flags & R_RESORT)
1628     set_option (OPTNEEDRESORT);
1629   if (p->flags & R_RESORT_INIT)
1630     set_option (OPTRESORTINIT);
1631   if (p->flags & R_TREE)
1632     set_option (OPTREDRAWTREE);
1633 }
1634
1635 static size_t escape_string (char *dst, size_t len, const char* src)
1636 {
1637   char* p = dst;
1638
1639   if (!len)
1640     return 0;
1641   len--; /* save room for \0 */
1642 #define ESC_CHAR(C)     do { *p++ = '\\'; if (p - dst < len) *p++ = C; } while(0)
1643   while (p - dst < len && src && *src)
1644   {
1645     switch (*src)
1646     {
1647     case '\n':
1648       ESC_CHAR('n');
1649       break;
1650     case '\r':
1651       ESC_CHAR('r');
1652       break;
1653     case '\t':
1654       ESC_CHAR('t');
1655       break;
1656     default:
1657       if ((*src == '\\' || *src == '"') && p - dst < len - 1)
1658         *p++ = '\\';
1659       *p++ = *src;
1660     }
1661     src++;
1662   }
1663 #undef ESC_CHAR
1664   *p = '\0';
1665   return p - dst;
1666 }
1667
1668 static void pretty_var (char *dst, size_t len, const char *option, const char *val)
1669 {
1670   char *p;
1671
1672   if (!len)
1673     return;
1674
1675   strfcpy (dst, option, len);
1676   len--; /* save room for \0 */
1677   p = dst + mutt_strlen (dst);
1678
1679   if (p - dst < len)
1680     *p++ = '=';
1681   if (p - dst < len)
1682     *p++ = '"';
1683   p += escape_string (p, len - (p - dst) + 1, val);     /* \0 terminate it */
1684   if (p - dst < len)
1685     *p++ = '"';
1686   *p = 0;
1687 }
1688
1689 static int check_charset (struct option_t *opt, const char *val)
1690 {
1691   char *p, *q = NULL, *s = safe_strdup (val);
1692   int rc = 0, strict = strcmp (opt->option, "send_charset") == 0;
1693
1694   for (p = strtok_r (s, ":", &q); p; p = strtok_r (NULL, ":", &q))
1695   {
1696     if (!*p)
1697       continue;
1698     if (mutt_check_charset (p, strict) < 0)
1699     {
1700       rc = -1;
1701       break;
1702     }
1703   }
1704
1705   FREE(&s);
1706   return rc;
1707 }
1708
1709 static int parse_set (BUFFER *tmp, BUFFER *s, unsigned long data, BUFFER *err)
1710 {
1711   int query, unset, inv, reset, r = 0;
1712   int idx = -1;
1713   char *p, scratch[_POSIX_PATH_MAX];
1714   char* myvar;
1715
1716   while (MoreArgs (s))
1717   {
1718     /* reset state variables */
1719     query = 0;
1720     unset = data & M_SET_UNSET;
1721     inv = data & M_SET_INV;
1722     reset = data & M_SET_RESET;
1723     myvar = NULL;
1724
1725     if (*s->dptr == '?')
1726     {
1727       query = 1;
1728       s->dptr++;
1729     }
1730     else if (mutt_strncmp ("no", s->dptr, 2) == 0)
1731     {
1732       s->dptr += 2;
1733       unset = !unset;
1734     }
1735     else if (mutt_strncmp ("inv", s->dptr, 3) == 0)
1736     {
1737       s->dptr += 3;
1738       inv = !inv;
1739     }
1740     else if (*s->dptr == '&')
1741     {
1742       reset = 1;
1743       s->dptr++;
1744     }
1745
1746     /* get the variable name */
1747     mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1748
1749     if (!mutt_strncmp ("my_", tmp->data, 3))
1750       myvar = tmp->data;
1751     else if ((idx = mutt_option_index (tmp->data)) == -1 &&
1752         !(reset && !mutt_strcmp ("all", tmp->data)))
1753     {
1754       snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1755       return (-1);
1756     }
1757     SKIPWS (s->dptr);
1758
1759     if (reset)
1760     {
1761       if (query || unset || inv)
1762       {
1763         snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1764         return (-1);
1765       }
1766
1767       if (s && *s->dptr == '=')
1768       {
1769         snprintf (err->data, err->dsize, _("value is illegal with reset"));
1770         return (-1);
1771       }
1772      
1773       if (!mutt_strcmp ("all", tmp->data))
1774       {
1775         if (CurrentMenu == MENU_PAGER)
1776         {
1777           snprintf (err->data, err->dsize, _("Not available in this menu."));
1778           return (-1);
1779         }
1780         for (idx = 0; MuttVars[idx].option; idx++)
1781           mutt_restore_default (&MuttVars[idx]);
1782         set_option (OPTFORCEREDRAWINDEX);
1783         set_option (OPTFORCEREDRAWPAGER);
1784         set_option (OPTSORTSUBTHREADS);
1785         set_option (OPTNEEDRESORT);
1786         set_option (OPTRESORTINIT);
1787         set_option (OPTREDRAWTREE);
1788         return 0;
1789       }
1790       else
1791       {
1792         CHECK_PAGER;
1793         if (myvar)
1794           myvar_del (myvar);
1795         else
1796           mutt_restore_default (&MuttVars[idx]);
1797       }
1798     } 
1799     else if (!myvar && DTYPE (MuttVars[idx].type) == DT_BOOL)
1800     { 
1801       if (s && *s->dptr == '=')
1802       {
1803         if (unset || inv || query)
1804         {
1805           snprintf (err->data, err->dsize, _("Usage: set variable=yes|no"));
1806           return (-1);
1807         }
1808
1809         s->dptr++;
1810         mutt_extract_token (tmp, s, 0);
1811         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1812           unset = inv = 0;
1813         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1814           unset = 1;
1815         else
1816         {
1817           snprintf (err->data, err->dsize, _("Usage: set variable=yes|no"));
1818           return (-1);
1819         }
1820       }
1821
1822       if (query)
1823       {
1824         snprintf (err->data, err->dsize, option (MuttVars[idx].data)
1825                         ? _("%s is set") : _("%s is unset"), tmp->data);
1826         return 0;
1827       }
1828
1829       CHECK_PAGER;
1830       if (unset)
1831         unset_option (MuttVars[idx].data);
1832       else if (inv)
1833         toggle_option (MuttVars[idx].data);
1834       else
1835         set_option (MuttVars[idx].data);
1836     }
1837     else if (myvar || DTYPE (MuttVars[idx].type) == DT_STR ||
1838              DTYPE (MuttVars[idx].type) == DT_PATH ||
1839              DTYPE (MuttVars[idx].type) == DT_ADDR)
1840     {
1841       if (unset)
1842       {
1843         CHECK_PAGER;
1844         if (myvar)
1845           myvar_del (myvar);
1846         else if (DTYPE (MuttVars[idx].type) == DT_ADDR)
1847           rfc822_free_address ((ADDRESS **) MuttVars[idx].data);
1848         else
1849           /* MuttVars[idx].data is already 'char**' (or some 'void**') or... 
1850            * so cast to 'void*' is okay */
1851           FREE ((void *) MuttVars[idx].data);           /* __FREE_CHECKED__ */
1852       }
1853       else if (query || *s->dptr != '=')
1854       {
1855         char _tmp[LONG_STRING];
1856         const char *val = NULL;
1857
1858         if (myvar)
1859         {
1860           if ((val = myvar_get (myvar)))
1861           {
1862             pretty_var (err->data, err->dsize, myvar, val);
1863             break;
1864           }
1865           else
1866           {
1867             snprintf (err->data, err->dsize, _("%s: unknown variable"), myvar);
1868             return (-1);
1869           }
1870         }
1871         else if (DTYPE (MuttVars[idx].type) == DT_ADDR)
1872         {
1873           _tmp[0] = '\0';
1874           rfc822_write_address (_tmp, sizeof (_tmp), *((ADDRESS **) MuttVars[idx].data), 0);
1875           val = _tmp;
1876         }
1877         else if (DTYPE (MuttVars[idx].type) == DT_PATH)
1878         {
1879           _tmp[0] = '\0';
1880           strfcpy (_tmp, NONULL(*((char **) MuttVars[idx].data)), sizeof (_tmp));
1881           mutt_pretty_mailbox (_tmp, sizeof (_tmp));
1882           val = _tmp;
1883         }
1884         else
1885           val = *((char **) MuttVars[idx].data);
1886         
1887         /* user requested the value of this variable */
1888         pretty_var (err->data, err->dsize, MuttVars[idx].option, NONULL(val));
1889         break;
1890       }
1891       else
1892       {
1893         CHECK_PAGER;
1894         s->dptr++;
1895
1896         if (myvar)
1897         {
1898           /* myvar is a pointer to tmp and will be lost on extract_token */
1899           myvar = safe_strdup (myvar);
1900           myvar_del (myvar);
1901         }
1902
1903         mutt_extract_token (tmp, s, 0);
1904
1905         if (myvar)
1906         {
1907           myvar_set (myvar, tmp->data);
1908           FREE (&myvar);
1909           myvar="don't resort";
1910         }
1911         else if (DTYPE (MuttVars[idx].type) == DT_PATH)
1912         {
1913           /* MuttVars[idx].data is already 'char**' (or some 'void**') or... 
1914            * so cast to 'void*' is okay */
1915           FREE ((void *) MuttVars[idx].data);           /* __FREE_CHECKED__ */
1916
1917           strfcpy (scratch, tmp->data, sizeof (scratch));
1918           mutt_expand_path (scratch, sizeof (scratch));
1919           *((char **) MuttVars[idx].data) = safe_strdup (scratch);
1920         }
1921         else if (DTYPE (MuttVars[idx].type) == DT_STR)
1922         {
1923           if (strstr (MuttVars[idx].option, "charset") &&
1924               check_charset (&MuttVars[idx], tmp->data) < 0)
1925           {
1926             snprintf (err->data, err->dsize, _("Invalid value for option %s: \"%s\""),
1927                       MuttVars[idx].option, tmp->data);
1928             return (-1);
1929           }
1930
1931           FREE ((void *) MuttVars[idx].data);           /* __FREE_CHECKED__ */
1932           *((char **) MuttVars[idx].data) = safe_strdup (tmp->data);
1933           if (mutt_strcmp (MuttVars[idx].option, "charset") == 0)
1934             mutt_set_charset (Charset);
1935         }
1936         else
1937         {
1938           rfc822_free_address ((ADDRESS **) MuttVars[idx].data);
1939           *((ADDRESS **) MuttVars[idx].data) = rfc822_parse_adrlist (NULL, tmp->data);
1940         }
1941       }
1942     }
1943     else if (DTYPE(MuttVars[idx].type) == DT_RX)
1944     {
1945       REGEXP *ptr = (REGEXP *) MuttVars[idx].data;
1946       regex_t *rx;
1947       int e, flags = 0;
1948
1949       if (query || *s->dptr != '=')
1950       {
1951         /* user requested the value of this variable */
1952         pretty_var (err->data, err->dsize, MuttVars[idx].option, NONULL(ptr->pattern));
1953         break;
1954       }
1955
1956       if (option(OPTATTACHMSG) && !mutt_strcmp(MuttVars[idx].option, "reply_regexp"))
1957       {
1958         snprintf (err->data, err->dsize, "Operation not permitted when in attach-message mode.");
1959         r = -1;
1960         break;
1961       }
1962       
1963       CHECK_PAGER;
1964       s->dptr++;
1965
1966       /* copy the value of the string */
1967       mutt_extract_token (tmp, s, 0);
1968
1969       if (!ptr->pattern || mutt_strcmp (ptr->pattern, tmp->data) != 0)
1970       {
1971         int not = 0;
1972
1973         /* $mask is case-sensitive */
1974         if (mutt_strcmp (MuttVars[idx].option, "mask") != 0)
1975           flags |= mutt_which_case (tmp->data);
1976
1977         p = tmp->data;
1978         if (mutt_strcmp (MuttVars[idx].option, "mask") == 0)
1979         {
1980           if (*p == '!')
1981           {
1982             not = 1;
1983             p++;
1984           }
1985         }
1986           
1987         rx = (regex_t *) safe_malloc (sizeof (regex_t));
1988         if ((e = REGCOMP (rx, p, flags)) != 0)
1989         {
1990           regerror (e, rx, err->data, err->dsize);
1991           regfree (rx);
1992           FREE (&rx);
1993           break;
1994         }
1995
1996         /* get here only if everything went smootly */
1997         if (ptr->pattern)
1998         {
1999           FREE (&ptr->pattern);
2000           regfree ((regex_t *) ptr->rx);
2001           FREE (&ptr->rx);
2002         }
2003
2004         ptr->pattern = safe_strdup (tmp->data);
2005         ptr->rx = rx;
2006         ptr->not = not;
2007
2008         /* $reply_regexp and $alterantes require special treatment */
2009         
2010         if (Context && Context->msgcount &&
2011             mutt_strcmp (MuttVars[idx].option, "reply_regexp") == 0)
2012         {
2013           regmatch_t pmatch[1];
2014           int i;
2015           
2016 #define CUR_ENV Context->hdrs[i]->env
2017           for (i = 0; i < Context->msgcount; i++)
2018           {
2019             if (CUR_ENV && CUR_ENV->subject)
2020             {
2021               CUR_ENV->real_subj = (regexec (ReplyRegexp.rx,
2022                                     CUR_ENV->subject, 1, pmatch, 0)) ?
2023                                     CUR_ENV->subject : 
2024                                     CUR_ENV->subject + pmatch[0].rm_eo;
2025             }
2026           }
2027 #undef CUR_ENV
2028         }
2029       }
2030     }
2031     else if (DTYPE(MuttVars[idx].type) == DT_MAGIC)
2032     {
2033       if (query || *s->dptr != '=')
2034       {
2035         switch (DefaultMagic)
2036         {
2037           case M_MBOX:
2038             p = "mbox";
2039             break;
2040           case M_MMDF:
2041             p = "MMDF";
2042             break;
2043           case M_MH:
2044             p = "MH";
2045             break;
2046           case M_MAILDIR:
2047             p = "Maildir";
2048             break;
2049           default:
2050             p = "unknown";
2051             break;
2052         }
2053         snprintf (err->data, err->dsize, "%s=%s", MuttVars[idx].option, p);
2054         break;
2055       }
2056
2057       CHECK_PAGER;
2058       s->dptr++;
2059
2060       /* copy the value of the string */
2061       mutt_extract_token (tmp, s, 0);
2062       if (mx_set_magic (tmp->data))
2063       {
2064         snprintf (err->data, err->dsize, _("%s: invalid mailbox type"), tmp->data);
2065         r = -1;
2066         break;
2067       }
2068     }
2069     else if (DTYPE(MuttVars[idx].type) == DT_NUM)
2070     {
2071       short *ptr = (short *) MuttVars[idx].data;
2072       int val;
2073       char *t;
2074
2075       if (query || *s->dptr != '=')
2076       {
2077         val = *ptr;
2078         /* compatibility alias */
2079         if (mutt_strcmp (MuttVars[idx].option, "wrapmargin") == 0)
2080           val = *ptr < 0 ? -*ptr : 0;
2081
2082         /* user requested the value of this variable */
2083         snprintf (err->data, err->dsize, "%s=%d", MuttVars[idx].option, val);
2084         break;
2085       }
2086
2087       CHECK_PAGER;
2088       s->dptr++;
2089
2090       mutt_extract_token (tmp, s, 0);
2091       val = strtol (tmp->data, &t, 0);
2092
2093       if (!*tmp->data || *t || (short) val != val)
2094       {
2095         snprintf (err->data, err->dsize, _("%s: invalid value"), tmp->data);
2096         r = -1;
2097         break;
2098       }
2099       else
2100         *ptr = (short) val;
2101
2102       /* these ones need a sanity check */
2103       if (mutt_strcmp (MuttVars[idx].option, "history") == 0)
2104       {
2105         if (*ptr < 0)
2106           *ptr = 0;
2107         mutt_init_history ();
2108       }
2109       else if (mutt_strcmp (MuttVars[idx].option, "pager_index_lines") == 0)
2110       {
2111         if (*ptr < 0)
2112           *ptr = 0;
2113       }
2114       else if (mutt_strcmp (MuttVars[idx].option, "wrapmargin") == 0)
2115       {
2116         if (*ptr < 0)
2117           *ptr = 0;
2118         else
2119           *ptr = -*ptr;
2120       }
2121 #ifdef USE_IMAP
2122       else if (mutt_strcmp (MuttVars[idx].option, "imap_pipeline_depth") == 0)
2123       {
2124         if (*ptr < 0)
2125           *ptr = 0;
2126       }
2127 #endif
2128     }
2129     else if (DTYPE (MuttVars[idx].type) == DT_QUAD)
2130     {
2131       if (query)
2132       {
2133         char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
2134
2135         snprintf (err->data, err->dsize, "%s=%s", MuttVars[idx].option,
2136                   vals [ quadoption (MuttVars[idx].data) ]);
2137         break;
2138       }
2139
2140       CHECK_PAGER;
2141       if (*s->dptr == '=')
2142       {
2143         s->dptr++;
2144         mutt_extract_token (tmp, s, 0);
2145         if (ascii_strcasecmp ("yes", tmp->data) == 0)
2146           set_quadoption (MuttVars[idx].data, M_YES);
2147         else if (ascii_strcasecmp ("no", tmp->data) == 0)
2148           set_quadoption (MuttVars[idx].data, M_NO);
2149         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
2150           set_quadoption (MuttVars[idx].data, M_ASKYES);
2151         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
2152           set_quadoption (MuttVars[idx].data, M_ASKNO);
2153         else
2154         {
2155           snprintf (err->data, err->dsize, _("%s: invalid value"), tmp->data);
2156           r = -1;
2157           break;
2158         }
2159       }
2160       else
2161       {
2162         if (inv)
2163           toggle_quadoption (MuttVars[idx].data);
2164         else if (unset)
2165           set_quadoption (MuttVars[idx].data, M_NO);
2166         else
2167           set_quadoption (MuttVars[idx].data, M_YES);
2168       }
2169     }
2170     else if (DTYPE (MuttVars[idx].type) == DT_SORT)
2171     {
2172       const struct mapping_t *map = NULL;
2173
2174       switch (MuttVars[idx].type & DT_SUBTYPE_MASK)
2175       {
2176         case DT_SORT_ALIAS:
2177           map = SortAliasMethods;
2178           break;
2179         case DT_SORT_BROWSER:
2180           map = SortBrowserMethods;
2181           break;
2182         case DT_SORT_KEYS:
2183           if ((WithCrypto & APPLICATION_PGP))
2184             map = SortKeyMethods;
2185           break;
2186         case DT_SORT_AUX:
2187           map = SortAuxMethods;
2188           break;
2189         default:
2190           map = SortMethods;
2191           break;
2192       }
2193
2194       if (!map)
2195       {
2196         snprintf (err->data, err->dsize, _("%s: Unknown type."), MuttVars[idx].option);
2197         r = -1;
2198         break;
2199       }
2200       
2201       if (query || *s->dptr != '=')
2202       {
2203         p = mutt_getnamebyvalue (*((short *) MuttVars[idx].data) & SORT_MASK, map);
2204
2205         snprintf (err->data, err->dsize, "%s=%s%s%s", MuttVars[idx].option,
2206                   (*((short *) MuttVars[idx].data) & SORT_REVERSE) ? "reverse-" : "",
2207                   (*((short *) MuttVars[idx].data) & SORT_LAST) ? "last-" : "",
2208                   p);
2209         return 0;
2210       }
2211       CHECK_PAGER;
2212       s->dptr++;
2213       mutt_extract_token (tmp, s , 0);
2214
2215       if (parse_sort ((short *) MuttVars[idx].data, tmp->data, map, err) == -1)
2216       {
2217         r = -1;
2218         break;
2219       }
2220     }
2221     else
2222     {
2223       snprintf (err->data, err->dsize, _("%s: unknown type"), MuttVars[idx].option);
2224       r = -1;
2225       break;
2226     }
2227
2228     if (!myvar)
2229     {
2230       if (MuttVars[idx].flags & R_INDEX)
2231         set_option (OPTFORCEREDRAWINDEX);
2232       if (MuttVars[idx].flags & R_PAGER)
2233         set_option (OPTFORCEREDRAWPAGER);
2234       if (MuttVars[idx].flags & R_RESORT_SUB)
2235         set_option (OPTSORTSUBTHREADS);
2236       if (MuttVars[idx].flags & R_RESORT)
2237         set_option (OPTNEEDRESORT);
2238       if (MuttVars[idx].flags & R_RESORT_INIT)
2239         set_option (OPTRESORTINIT);
2240       if (MuttVars[idx].flags & R_TREE)
2241         set_option (OPTREDRAWTREE);
2242     }
2243   }
2244   return (r);
2245 }
2246
2247 #define MAXERRS 128
2248
2249 /* reads the specified initialization file.  returns -1 if errors were found
2250    so that we can pause to let the user know...  */
2251 static int source_rc (const char *rcfile, BUFFER *err)
2252 {
2253   FILE *f;
2254   int line = 0, rc = 0, conv = 0;
2255   BUFFER token;
2256   char *linebuf = NULL;
2257   char *currentline = NULL;
2258   size_t buflen;
2259   pid_t pid;
2260
2261   dprint (2, (debugfile, "Reading configuration file '%s'.\n",
2262           rcfile));
2263   
2264   if ((f = mutt_open_read (rcfile, &pid)) == NULL)
2265   {
2266     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
2267     return (-1);
2268   }
2269
2270   memset (&token, 0, sizeof (token));
2271   while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line)) != NULL)
2272   {
2273     conv=ConfigCharset && (*ConfigCharset) && Charset;
2274     if (conv) 
2275     {
2276       currentline=safe_strdup(linebuf);
2277       if (!currentline) continue;
2278       mutt_convert_string(&currentline, ConfigCharset, Charset, 0);
2279     } 
2280     else 
2281       currentline=linebuf;
2282
2283     if (mutt_parse_rc_line (currentline, &token, err) == -1)
2284     {
2285       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
2286       if (--rc < -MAXERRS) 
2287       {
2288         if (conv) FREE(&currentline);
2289         break;
2290       }
2291     }
2292     else
2293     {
2294       if (rc < 0)
2295         rc = -1;
2296     }
2297     if (conv) 
2298       FREE(&currentline);
2299   }
2300   FREE (&token.data);
2301   FREE (&linebuf);
2302   fclose (f);
2303   if (pid != -1)
2304     mutt_wait_filter (pid);
2305   if (rc)
2306   {
2307     /* the muttrc source keyword */
2308     snprintf (err->data, err->dsize, rc >= -MAXERRS ? _("source: errors in %s")
2309       : _("source: reading aborted due too many errors in %s"), rcfile);
2310     rc = -1;
2311   }
2312   return (rc);
2313 }
2314
2315 #undef MAXERRS
2316
2317 static int parse_source (BUFFER *tmp, BUFFER *s, unsigned long data, BUFFER *err)
2318 {
2319   char path[_POSIX_PATH_MAX];
2320
2321   if (mutt_extract_token (tmp, s, 0) != 0)
2322   {
2323     snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
2324     return (-1);
2325   }
2326   if (MoreArgs (s))
2327   {
2328     strfcpy (err->data, _("source: too many arguments"), err->dsize);
2329     return (-1);
2330   }
2331   strfcpy (path, tmp->data, sizeof (path));
2332   mutt_expand_path (path, sizeof (path));
2333   return (source_rc (path, err));
2334 }
2335
2336 /* line         command to execute
2337
2338    token        scratch buffer to be used by parser.  caller should free
2339                 token->data when finished.  the reason for this variable is
2340                 to avoid having to allocate and deallocate a lot of memory
2341                 if we are parsing many lines.  the caller can pass in the
2342                 memory to use, which avoids having to create new space for
2343                 every call to this function.
2344
2345    err          where to write error messages */
2346 int mutt_parse_rc_line (/* const */ char *line, BUFFER *token, BUFFER *err)
2347 {
2348   int i, r = -1;
2349   BUFFER expn;
2350
2351   if (!line || !*line)
2352     return 0;
2353
2354   memset (&expn, 0, sizeof (expn));
2355   expn.data = expn.dptr = line;
2356   expn.dsize = mutt_strlen (line);
2357
2358   *err->data = 0;
2359
2360   SKIPWS (expn.dptr);
2361   while (*expn.dptr)
2362   {
2363     if (*expn.dptr == '#')
2364       break; /* rest of line is a comment */
2365     if (*expn.dptr == ';')
2366     {
2367       expn.dptr++;
2368       continue;
2369     }
2370     mutt_extract_token (token, &expn, 0);
2371     for (i = 0; Commands[i].name; i++)
2372     {
2373       if (!mutt_strcmp (token->data, Commands[i].name))
2374       {
2375         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
2376           goto finish;
2377         break;
2378       }
2379     }
2380     if (!Commands[i].name)
2381     {
2382       snprintf (err->data, err->dsize, _("%s: unknown command"), NONULL (token->data));
2383       goto finish;
2384     }
2385   }
2386   r = 0;
2387 finish:
2388   if (expn.destroy)
2389     FREE (&expn.data);
2390   return (r);
2391 }
2392
2393
2394 #define NUMVARS (sizeof (MuttVars)/sizeof (MuttVars[0]))
2395 #define NUMCOMMANDS (sizeof (Commands)/sizeof (Commands[0]))
2396 /* initial string that starts completion. No telling how much crap 
2397  * the user has typed so far. Allocate LONG_STRING just to be sure! */
2398 static char User_typed [LONG_STRING] = {0}; 
2399
2400 static int  Num_matched = 0; /* Number of matches for completion */
2401 static char Completed [STRING] = {0}; /* completed string (command or variable) */
2402 static const char **Matches;
2403 /* this is a lie until mutt_init runs: */
2404 static int  Matches_listsize = MAX(NUMVARS,NUMCOMMANDS) + 10;
2405
2406 static void matches_ensure_morespace(int current)
2407 {
2408   int base_space, extra_space, space;
2409
2410   if (current > Matches_listsize - 2)
2411   {
2412     base_space = MAX(NUMVARS,NUMCOMMANDS) + 1; 
2413     extra_space = Matches_listsize - base_space;
2414     extra_space *= 2;
2415     space = base_space + extra_space;
2416     safe_realloc (&Matches, space * sizeof (char *));
2417     memset (&Matches[current + 1], 0, space - current);
2418     Matches_listsize = space;
2419   }
2420 }
2421
2422 /* helper function for completion.  Changes the dest buffer if
2423    necessary/possible to aid completion.
2424         dest == completion result gets here.
2425         src == candidate for completion.
2426         try == user entered data for completion.
2427         len == length of dest buffer.
2428 */
2429 static void candidate (char *dest, char *try, const char *src, int len)
2430 {
2431   int l;
2432
2433   if (strstr (src, try) == src)
2434   {
2435     matches_ensure_morespace (Num_matched);
2436     Matches[Num_matched++] = src;
2437     if (dest[0] == 0)
2438       strfcpy (dest, src, len);
2439     else
2440     {
2441       for (l = 0; src[l] && src[l] == dest[l]; l++);
2442       dest[l] = 0;
2443     }
2444   }
2445 }
2446
2447 int mutt_command_complete (char *buffer, size_t len, int pos, int numtabs)
2448 {
2449   char *pt = buffer;
2450   int num;
2451   int spaces; /* keep track of the number of leading spaces on the line */
2452   myvar_t *myv;
2453
2454   SKIPWS (buffer);
2455   spaces = buffer - pt;
2456
2457   pt = buffer + pos - spaces;
2458   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2459     pt--;
2460
2461   if (pt == buffer) /* complete cmd */
2462   {
2463     /* first TAB. Collect all the matches */
2464     if (numtabs == 1)
2465     {
2466       Num_matched = 0;
2467       strfcpy (User_typed, pt, sizeof (User_typed));
2468       memset (Matches, 0, Matches_listsize);
2469       memset (Completed, 0, sizeof (Completed));
2470       for (num = 0; Commands[num].name; num++)
2471         candidate (Completed, User_typed, Commands[num].name, sizeof (Completed));
2472       matches_ensure_morespace (Num_matched);
2473       Matches[Num_matched++] = User_typed;
2474
2475       /* All matches are stored. Longest non-ambiguous string is ""
2476        * i.e. dont change 'buffer'. Fake successful return this time */
2477       if (User_typed[0] == 0)
2478         return 1;
2479     }
2480
2481     if (Completed[0] == 0 && User_typed[0])
2482       return 0;
2483
2484      /* Num_matched will _always_ be atleast 1 since the initial
2485       * user-typed string is always stored */
2486     if (numtabs == 1 && Num_matched == 2)
2487       snprintf(Completed, sizeof(Completed),"%s", Matches[0]);
2488     else if (numtabs > 1 && Num_matched > 2)
2489       /* cycle thru all the matches */
2490       snprintf(Completed, sizeof(Completed), "%s", 
2491                Matches[(numtabs - 2) % Num_matched]);
2492
2493     /* return the completed command */
2494     strncpy (buffer, Completed, len - spaces);
2495   }
2496   else if (!mutt_strncmp (buffer, "set", 3)
2497            || !mutt_strncmp (buffer, "unset", 5)
2498            || !mutt_strncmp (buffer, "reset", 5)
2499            || !mutt_strncmp (buffer, "toggle", 6))
2500   {             /* complete variables */
2501     char *prefixes[] = { "no", "inv", "?", "&", 0 };
2502     
2503     pt++;
2504     /* loop through all the possible prefixes (no, inv, ...) */
2505     if (!mutt_strncmp (buffer, "set", 3))
2506     {
2507       for (num = 0; prefixes[num]; num++)
2508       {
2509         if (!mutt_strncmp (pt, prefixes[num], mutt_strlen (prefixes[num])))
2510         {
2511           pt += mutt_strlen (prefixes[num]);
2512           break;
2513         }
2514       }
2515     }
2516     
2517     /* first TAB. Collect all the matches */
2518     if (numtabs == 1)
2519     {
2520       Num_matched = 0;
2521       strfcpy (User_typed, pt, sizeof (User_typed));
2522       memset (Matches, 0, Matches_listsize);
2523       memset (Completed, 0, sizeof (Completed));
2524       for (num = 0; MuttVars[num].option; num++)
2525         candidate (Completed, User_typed, MuttVars[num].option, sizeof (Completed));
2526       for (myv = MyVars; myv; myv = myv->next)
2527         candidate (Completed, User_typed, myv->name, sizeof (Completed));
2528       matches_ensure_morespace (Num_matched);
2529       Matches[Num_matched++] = User_typed;
2530
2531       /* All matches are stored. Longest non-ambiguous string is ""
2532        * i.e. dont change 'buffer'. Fake successful return this time */
2533       if (User_typed[0] == 0)
2534         return 1;
2535     }
2536
2537     if (Completed[0] == 0 && User_typed[0])
2538       return 0;
2539
2540     /* Num_matched will _always_ be atleast 1 since the initial
2541      * user-typed string is always stored */
2542     if (numtabs == 1 && Num_matched == 2)
2543       snprintf(Completed, sizeof(Completed),"%s", Matches[0]);
2544     else if (numtabs > 1 && Num_matched > 2)
2545     /* cycle thru all the matches */
2546       snprintf(Completed, sizeof(Completed), "%s", 
2547                Matches[(numtabs - 2) % Num_matched]);
2548
2549     strncpy (pt, Completed, buffer + len - pt - spaces);
2550   }
2551   else if (!mutt_strncmp (buffer, "exec", 4))
2552   {
2553     struct binding_t *menu = km_get_table (CurrentMenu);
2554
2555     if (!menu && CurrentMenu != MENU_PAGER)
2556       menu = OpGeneric;
2557     
2558     pt++;
2559     /* first TAB. Collect all the matches */
2560     if (numtabs == 1)
2561     {
2562       Num_matched = 0;
2563       strfcpy (User_typed, pt, sizeof (User_typed));
2564       memset (Matches, 0, Matches_listsize);
2565       memset (Completed, 0, sizeof (Completed));
2566       for (num = 0; menu[num].name; num++)
2567         candidate (Completed, User_typed, menu[num].name, sizeof (Completed));
2568       /* try the generic menu */
2569       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) 
2570       {
2571         menu = OpGeneric;
2572         for (num = 0; menu[num].name; num++)
2573           candidate (Completed, User_typed, menu[num].name, sizeof (Completed));
2574       }
2575       matches_ensure_morespace (Num_matched);
2576       Matches[Num_matched++] = User_typed;
2577
2578       /* All matches are stored. Longest non-ambiguous string is ""
2579        * i.e. dont change 'buffer'. Fake successful return this time */
2580       if (User_typed[0] == 0)
2581         return 1;
2582     }
2583
2584     if (Completed[0] == 0 && User_typed[0])
2585       return 0;
2586
2587     /* Num_matched will _always_ be atleast 1 since the initial
2588      * user-typed string is always stored */
2589     if (numtabs == 1 && Num_matched == 2)
2590       snprintf(Completed, sizeof(Completed),"%s", Matches[0]);
2591     else if (numtabs > 1 && Num_matched > 2)
2592     /* cycle thru all the matches */
2593       snprintf(Completed, sizeof(Completed), "%s", 
2594                Matches[(numtabs - 2) % Num_matched]);
2595
2596     strncpy (pt, Completed, buffer + len - pt - spaces);
2597   }
2598   else
2599     return 0;
2600
2601   return 1;
2602 }
2603
2604 int mutt_var_value_complete (char *buffer, size_t len, int pos)
2605 {
2606   char var[STRING], *pt = buffer;
2607   int spaces;
2608   
2609   if (buffer[0] == 0)
2610     return 0;
2611
2612   SKIPWS (buffer);
2613   spaces = buffer - pt;
2614
2615   pt = buffer + pos - spaces;
2616   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2617     pt--;
2618   pt++; /* move past the space */
2619   if (*pt == '=') /* abort if no var before the '=' */
2620     return 0;
2621
2622   if (mutt_strncmp (buffer, "set", 3) == 0)
2623   {
2624     int idx;
2625     char val[LONG_STRING];
2626     const char *myvarval;
2627
2628     strfcpy (var, pt, sizeof (var));
2629     /* ignore the trailing '=' when comparing */
2630     var[mutt_strlen (var) - 1] = 0;
2631     if ((idx = mutt_option_index (var)) == -1)
2632     {
2633       if ((myvarval = myvar_get(var)) != NULL)
2634       {
2635         pretty_var (pt, len - (pt - buffer), var, myvarval);
2636         return 1;
2637       }
2638       return 0; /* no such variable. */
2639     }
2640     else if (var_to_string (idx, val, sizeof (val)))
2641     {
2642       snprintf (pt, len - (pt - buffer), "%s=\"%s\"", var, val);
2643       return 1;
2644     }
2645   }
2646   return 0;
2647 }
2648
2649 static int var_to_string (int idx, char* val, size_t len)
2650 {
2651   char tmp[LONG_STRING];
2652   char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
2653
2654   tmp[0] = '\0';
2655
2656   if ((DTYPE(MuttVars[idx].type) == DT_STR) ||
2657       (DTYPE(MuttVars[idx].type) == DT_PATH) ||
2658       (DTYPE(MuttVars[idx].type) == DT_RX))
2659   {
2660     strfcpy (tmp, NONULL (*((char **) MuttVars[idx].data)), sizeof (tmp));
2661     if (DTYPE (MuttVars[idx].type) == DT_PATH)
2662       mutt_pretty_mailbox (tmp, sizeof (tmp));
2663   }
2664   else if (DTYPE (MuttVars[idx].type) == DT_ADDR)
2665   {
2666     rfc822_write_address (tmp, sizeof (tmp), *((ADDRESS **) MuttVars[idx].data), 0);
2667   }
2668   else if (DTYPE (MuttVars[idx].type) == DT_QUAD)
2669     strfcpy (tmp, vals[quadoption (MuttVars[idx].data)], sizeof (tmp));
2670   else if (DTYPE (MuttVars[idx].type) == DT_NUM)
2671   {
2672     short sval = *((short *) MuttVars[idx].data);
2673
2674     /* avert your eyes, gentle reader */
2675     if (mutt_strcmp (MuttVars[idx].option, "wrapmargin") == 0)
2676       sval = sval > 0 ? 0 : -sval;
2677
2678     snprintf (tmp, sizeof (tmp), "%d", sval);
2679   }
2680   else if (DTYPE (MuttVars[idx].type) == DT_SORT)
2681   {
2682     const struct mapping_t *map;
2683     char *p;
2684
2685     switch (MuttVars[idx].type & DT_SUBTYPE_MASK)
2686     {
2687       case DT_SORT_ALIAS:
2688         map = SortAliasMethods;
2689         break;
2690       case DT_SORT_BROWSER:
2691         map = SortBrowserMethods;
2692         break;
2693       case DT_SORT_KEYS:
2694         if ((WithCrypto & APPLICATION_PGP))
2695           map = SortKeyMethods;
2696         else
2697           map = SortMethods;
2698         break;
2699       default:
2700         map = SortMethods;
2701         break;
2702     }
2703     p = mutt_getnamebyvalue (*((short *) MuttVars[idx].data) & SORT_MASK, map);
2704     snprintf (tmp, sizeof (tmp), "%s%s%s",
2705               (*((short *) MuttVars[idx].data) & SORT_REVERSE) ? "reverse-" : "",
2706               (*((short *) MuttVars[idx].data) & SORT_LAST) ? "last-" : "",
2707               p);
2708   }
2709   else if (DTYPE (MuttVars[idx].type) == DT_MAGIC)
2710   {
2711     char *p;
2712
2713     switch (DefaultMagic)
2714     {
2715       case M_MBOX:
2716         p = "mbox";
2717         break;
2718       case M_MMDF:
2719         p = "MMDF";
2720         break;
2721       case M_MH:
2722         p = "MH";
2723         break;
2724       case M_MAILDIR:
2725         p = "Maildir";
2726         break;
2727       default:
2728         p = "unknown";
2729     }
2730     strfcpy (tmp, p, sizeof (tmp));
2731   }
2732   else if (DTYPE (MuttVars[idx].type) == DT_BOOL)
2733     strfcpy (tmp, option (MuttVars[idx].data) ? "yes" : "no", sizeof (tmp));
2734   else
2735     return 0;
2736
2737   escape_string (val, len - 1, tmp);
2738
2739   return 1;
2740 }
2741
2742 /* Implement the -Q command line flag */
2743 int mutt_query_variables (LIST *queries)
2744 {
2745   LIST *p;
2746   
2747   char errbuff[LONG_STRING];
2748   char command[STRING];
2749   
2750   BUFFER err, token;
2751   
2752   memset (&err, 0, sizeof (err));
2753   memset (&token, 0, sizeof (token));
2754   
2755   err.data = errbuff;
2756   err.dsize = sizeof (errbuff);
2757   
2758   for (p = queries; p; p = p->next)
2759   {
2760     snprintf (command, sizeof (command), "set ?%s\n", p->data);
2761     if (mutt_parse_rc_line (command, &token, &err) == -1)
2762     {
2763       fprintf (stderr, "%s\n", err.data);
2764       FREE (&token.data);
2765       return 1;
2766     }
2767     printf ("%s\n", err.data);
2768   }
2769   
2770   FREE (&token.data);
2771   return 0;
2772 }
2773
2774 /* dump out the value of all the variables we have */
2775 int mutt_dump_variables (void)
2776 {
2777   int i;
2778   
2779   char errbuff[LONG_STRING];
2780   char command[STRING];
2781   
2782   BUFFER err, token;
2783   
2784   memset (&err, 0, sizeof (err));
2785   memset (&token, 0, sizeof (token));
2786   
2787   err.data = errbuff;
2788   err.dsize = sizeof (errbuff);
2789   
2790   for (i = 0; MuttVars[i].option; i++)
2791   {
2792     if (MuttVars[i].type == DT_SYN)
2793       continue;
2794
2795     snprintf (command, sizeof (command), "set ?%s\n", MuttVars[i].option);
2796     if (mutt_parse_rc_line (command, &token, &err) == -1)
2797     {
2798       fprintf (stderr, "%s\n", err.data);
2799       FREE (&token.data);
2800       return 1;
2801     }
2802     printf("%s\n", err.data);
2803   }
2804   
2805   FREE (&token.data);
2806   return 0;
2807 }
2808
2809 char *mutt_getnamebyvalue (int val, const struct mapping_t *map)
2810 {
2811   int i;
2812
2813   for (i=0; map[i].name; i++)
2814     if (map[i].value == val)
2815       return (map[i].name);
2816   return NULL;
2817 }
2818
2819 int mutt_getvaluebyname (const char *name, const struct mapping_t *map)
2820 {
2821   int i;
2822
2823   for (i = 0; map[i].name; i++)
2824     if (ascii_strcasecmp (map[i].name, name) == 0)
2825       return (map[i].value);
2826   return (-1);
2827 }
2828
2829 #ifdef DEBUG
2830 static void start_debug (void)
2831 {
2832   time_t t;
2833   int i;
2834   char buf[_POSIX_PATH_MAX];
2835   char buf2[_POSIX_PATH_MAX];
2836
2837   /* rotate the old debug logs */
2838   for (i=3; i>=0; i--)
2839   {
2840     snprintf (buf, sizeof(buf), "%s/.muttdebug%d", NONULL(Homedir), i);
2841     snprintf (buf2, sizeof(buf2), "%s/.muttdebug%d", NONULL(Homedir), i+1);
2842     rename (buf, buf2);
2843   }
2844   if ((debugfile = safe_fopen(buf, "w")) != NULL)
2845   {
2846     t = time (0);
2847     setbuf (debugfile, NULL); /* don't buffer the debugging output! */
2848     fprintf (debugfile, "Mutt %s started at %s.\nDebugging at level %d.\n\n",
2849              MUTT_VERSION, asctime (localtime (&t)), debuglevel);
2850   }
2851 }
2852 #endif
2853
2854 static int mutt_execute_commands (LIST *p)
2855 {
2856   BUFFER err, token;
2857   char errstr[SHORT_STRING];
2858
2859   memset (&err, 0, sizeof (err));
2860   err.data = errstr;
2861   err.dsize = sizeof (errstr);
2862   memset (&token, 0, sizeof (token));
2863   for (; p; p = p->next)
2864   {
2865     if (mutt_parse_rc_line (p->data, &token, &err) != 0)
2866     {
2867       fprintf (stderr, _("Error in command line: %s\n"), err.data);
2868       FREE (&token.data);
2869       return (-1);
2870     }
2871   }
2872   FREE (&token.data);
2873   return 0;
2874 }
2875
2876 void mutt_init (int skip_sys_rc, LIST *commands)
2877 {
2878   struct passwd *pw;
2879   struct utsname utsname;
2880   char *p, buffer[STRING], error[STRING];
2881   int i, default_rc = 0, need_pause = 0;
2882   BUFFER err;
2883
2884   memset (&err, 0, sizeof (err));
2885   err.data = error;
2886   err.dsize = sizeof (error);
2887
2888   Groups = hash_create (1031);
2889   ReverseAlias = hash_create (1031);
2890   
2891   mutt_menu_init ();
2892
2893   /* 
2894    * XXX - use something even more difficult to predict?
2895    */
2896   snprintf (AttachmentMarker, sizeof (AttachmentMarker),
2897             "\033]9;%ld\a", (long) time (NULL));
2898   
2899   /* on one of the systems I use, getcwd() does not return the same prefix
2900      as is listed in the passwd file */
2901   if ((p = getenv ("HOME")))
2902     Homedir = safe_strdup (p);
2903
2904   /* Get some information about the user */
2905   if ((pw = getpwuid (getuid ())))
2906   {
2907     char rnbuf[STRING];
2908
2909     Username = safe_strdup (pw->pw_name);
2910     if (!Homedir)
2911       Homedir = safe_strdup (pw->pw_dir);
2912
2913     Realname = safe_strdup (mutt_gecos_name (rnbuf, sizeof (rnbuf), pw));
2914     Shell = safe_strdup (pw->pw_shell);
2915     endpwent ();
2916   }
2917   else 
2918   {
2919     if (!Homedir)
2920     {
2921       mutt_endwin (NULL);
2922       fputs (_("unable to determine home directory"), stderr);
2923       exit (1);
2924     }
2925     if ((p = getenv ("USER")))
2926       Username = safe_strdup (p);
2927     else
2928     {
2929       mutt_endwin (NULL);
2930       fputs (_("unable to determine username"), stderr);
2931       exit (1);
2932     }
2933     Shell = safe_strdup ((p = getenv ("SHELL")) ? p : "/bin/sh");
2934   }
2935
2936 #ifdef DEBUG
2937   /* Start up debugging mode if requested */
2938   if (debuglevel > 0)
2939     start_debug ();
2940 #endif
2941
2942   /* And about the host... */
2943   uname (&utsname);
2944   /* some systems report the FQDN instead of just the hostname */
2945   if ((p = strchr (utsname.nodename, '.')))
2946   {
2947     Hostname = mutt_substrdup (utsname.nodename, p);
2948     p++;
2949     strfcpy (buffer, p, sizeof (buffer)); /* save the domain for below */
2950   }
2951   else
2952     Hostname = safe_strdup (utsname.nodename);
2953
2954 #ifndef DOMAIN
2955 #define DOMAIN buffer
2956   if (!p && getdnsdomainname (buffer, sizeof (buffer)) == -1)
2957     Fqdn = safe_strdup ("@");
2958   else
2959 #endif /* DOMAIN */
2960     if (*DOMAIN != '@')
2961   {
2962     Fqdn = safe_malloc (mutt_strlen (DOMAIN) + mutt_strlen (Hostname) + 2);
2963     sprintf (Fqdn, "%s.%s", NONULL(Hostname), DOMAIN);  /* __SPRINTF_CHECKED__ */
2964   }
2965   else
2966     Fqdn = safe_strdup(NONULL(Hostname));
2967
2968   if ((p = getenv ("MAIL")))
2969     Spoolfile = safe_strdup (p);
2970   else if ((p = getenv ("MAILDIR")))
2971     Spoolfile = safe_strdup (p);
2972   else
2973   {
2974 #ifdef HOMESPOOL
2975     mutt_concat_path (buffer, NONULL (Homedir), MAILPATH, sizeof (buffer));
2976 #else
2977     mutt_concat_path (buffer, MAILPATH, NONULL(Username), sizeof (buffer));
2978 #endif
2979     Spoolfile = safe_strdup (buffer);
2980   }
2981
2982   if ((p = getenv ("MAILCAPS")))
2983     MailcapPath = safe_strdup (p);
2984   else
2985   {
2986     /* Default search path from RFC1524 */
2987     MailcapPath = safe_strdup ("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap");
2988   }
2989
2990   Tempdir = safe_strdup ((p = getenv ("TMPDIR")) ? p : "/tmp");
2991
2992   p = getenv ("VISUAL");
2993   if (!p)
2994   {
2995     p = getenv ("EDITOR");
2996     if (!p)
2997       p = "vi";
2998   }
2999   Editor = safe_strdup (p);
3000   Visual = safe_strdup (p);
3001
3002   if ((p = getenv ("REPLYTO")) != NULL)
3003   {
3004     BUFFER buf, token;
3005
3006     snprintf (buffer, sizeof (buffer), "Reply-To: %s", p);
3007
3008     memset (&buf, 0, sizeof (buf));
3009     buf.data = buf.dptr = buffer;
3010     buf.dsize = mutt_strlen (buffer);
3011
3012     memset (&token, 0, sizeof (token));
3013     parse_my_hdr (&token, &buf, 0, &err);
3014     FREE (&token.data);
3015   }
3016
3017   if ((p = getenv ("EMAIL")) != NULL)
3018     From = rfc822_parse_adrlist (NULL, p);
3019   
3020   mutt_set_langinfo_charset ();
3021   mutt_set_charset (Charset);
3022   
3023   Matches = safe_calloc (Matches_listsize, sizeof (char *));
3024   
3025   /* Set standard defaults */
3026   for (i = 0; MuttVars[i].option; i++)
3027   {
3028     mutt_set_default (&MuttVars[i]);
3029     mutt_restore_default (&MuttVars[i]);
3030   }
3031
3032   CurrentMenu = MENU_MAIN;
3033
3034
3035 #ifndef LOCALES_HACK
3036   /* Do we have a locale definition? */
3037   if (((p = getenv ("LC_ALL")) != NULL && p[0]) ||
3038       ((p = getenv ("LANG")) != NULL && p[0]) ||
3039       ((p = getenv ("LC_CTYPE")) != NULL && p[0]))
3040     set_option (OPTLOCALES);
3041 #endif
3042
3043 #ifdef HAVE_GETSID
3044   /* Unset suspend by default if we're the session leader */
3045   if (getsid(0) == getpid())
3046     unset_option (OPTSUSPEND);
3047 #endif
3048
3049   mutt_init_history ();
3050
3051   
3052   
3053   
3054   /*
3055    * 
3056    *                       BIG FAT WARNING
3057    * 
3058    * When changing the code which looks for a configuration file,
3059    * please also change the corresponding code in muttbug.sh.in.
3060    * 
3061    * 
3062    */
3063   
3064   
3065   
3066   
3067   if (!Muttrc)
3068   {
3069     snprintf (buffer, sizeof(buffer), "%s/.muttrc-%s", NONULL(Homedir), MUTT_VERSION);
3070     if (access(buffer, F_OK) == -1)
3071       snprintf (buffer, sizeof(buffer), "%s/.muttrc", NONULL(Homedir));
3072     if (access(buffer, F_OK) == -1)
3073       snprintf (buffer, sizeof (buffer), "%s/.mutt/muttrc-%s", NONULL(Homedir), MUTT_VERSION);
3074     if (access(buffer, F_OK) == -1)
3075       snprintf (buffer, sizeof (buffer), "%s/.mutt/muttrc", NONULL(Homedir));
3076     if (access(buffer, F_OK) == -1) /* default to .muttrc for alias_file */
3077       snprintf (buffer, sizeof(buffer), "%s/.muttrc", NONULL(Homedir));
3078
3079     default_rc = 1;
3080     Muttrc = safe_strdup (buffer);
3081   }
3082   else
3083   {
3084     strfcpy (buffer, Muttrc, sizeof (buffer));
3085     FREE (&Muttrc);
3086     mutt_expand_path (buffer, sizeof (buffer));
3087     Muttrc = safe_strdup (buffer);
3088   }
3089   FREE (&AliasFile);
3090   AliasFile = safe_strdup (NONULL(Muttrc));
3091
3092   /* Process the global rc file if it exists and the user hasn't explicity
3093      requested not to via "-n".  */
3094   if (!skip_sys_rc)
3095   {
3096     snprintf (buffer, sizeof(buffer), "%s/Muttrc-%s", SYSCONFDIR, MUTT_VERSION);
3097     if (access (buffer, F_OK) == -1)
3098       snprintf (buffer, sizeof(buffer), "%s/Muttrc", SYSCONFDIR);
3099     if (access (buffer, F_OK) == -1)
3100       snprintf (buffer, sizeof (buffer), "%s/Muttrc-%s", PKGDATADIR, MUTT_VERSION);
3101     if (access (buffer, F_OK) == -1)
3102       snprintf (buffer, sizeof (buffer), "%s/Muttrc", PKGDATADIR);
3103     if (access (buffer, F_OK) != -1)
3104     {
3105       if (source_rc (buffer, &err) != 0)
3106       {
3107         fputs (err.data, stderr);
3108         fputc ('\n', stderr);
3109         need_pause = 1;
3110       }
3111     }
3112   }
3113
3114   /* Read the user's initialization file.  */
3115   if (access (Muttrc, F_OK) != -1)
3116   {
3117     if (!option (OPTNOCURSES))
3118       endwin ();
3119     if (source_rc (Muttrc, &err) != 0)
3120     {
3121       fputs (err.data, stderr);
3122       fputc ('\n', stderr);
3123       need_pause = 1;
3124     }
3125   }
3126   else if (!default_rc)
3127   {
3128     /* file specified by -F does not exist */
3129     snprintf (buffer, sizeof (buffer), "%s: %s", Muttrc, strerror (errno));
3130     mutt_endwin (buffer);
3131     exit (1);
3132   }
3133
3134   if (mutt_execute_commands (commands) != 0)
3135     need_pause = 1;
3136
3137   if (need_pause && !option (OPTNOCURSES))
3138   {
3139     if (mutt_any_key_to_continue (NULL) == -1)
3140       mutt_exit(1);
3141   }
3142
3143   mutt_read_histfile ();
3144
3145 #if 0
3146   set_option (OPTWEED); /* turn weeding on by default */
3147 #endif
3148 }
3149
3150 int mutt_get_hook_type (const char *name)
3151 {
3152   struct command_t *c;
3153
3154   for (c = Commands ; c->name ; c++)
3155     if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
3156       return c->data;
3157   return 0;
3158 }
3159
3160 static int parse_group_context (group_context_t **ctx, BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
3161 {
3162   while (!mutt_strcasecmp (buf->data, "-group"))
3163   {
3164     if (!MoreArgs (s)) 
3165     {
3166       strfcpy (err->data, _("-group: no group name"), err->dsize);
3167       goto bail;
3168     }
3169     
3170     mutt_extract_token (buf, s, 0);
3171
3172     mutt_group_context_add (ctx, mutt_pattern_group (buf->data));
3173     
3174     if (!MoreArgs (s))
3175     {
3176       strfcpy (err->data, _("out of arguments"), err->dsize);
3177       goto bail;
3178     }
3179     
3180     mutt_extract_token (buf, s, 0);
3181   }
3182   
3183   return 0;
3184   
3185   bail:
3186   mutt_group_context_destroy (ctx);
3187   return -1;
3188 }
3189
3190 static void myvar_set (const char* var, const char* val)
3191 {
3192   myvar_t** cur;
3193
3194   for (cur = &MyVars; *cur; cur = &((*cur)->next))
3195     if (!mutt_strcmp ((*cur)->name, var))
3196       break;
3197
3198   if (!*cur)
3199     *cur = safe_calloc (1, sizeof (myvar_t));
3200   
3201   if (!(*cur)->name)
3202     (*cur)->name = safe_strdup (var);
3203   
3204   mutt_str_replace (&(*cur)->value, val);
3205 }
3206
3207 static void myvar_del (const char* var)
3208 {
3209   myvar_t **cur;
3210   myvar_t *tmp;
3211   
3212
3213   for (cur = &MyVars; *cur; cur = &((*cur)->next))
3214     if (!mutt_strcmp ((*cur)->name, var))
3215       break;
3216   
3217   if (*cur) 
3218   {
3219     tmp = (*cur)->next;
3220     FREE (&(*cur)->name);
3221     FREE (&(*cur)->value);
3222     FREE (cur);         /* __FREE_CHECKED__ */
3223     *cur = tmp;
3224   }
3225 }
3226
3227 static const char* myvar_get (const char* var)
3228 {
3229   myvar_t* cur;
3230
3231   for (cur = MyVars; cur; cur = cur->next)
3232     if (!mutt_strcmp (cur->name, var))
3233       return NONULL(cur->value);
3234
3235   return NULL;
3236 }