]> git.llucax.com Git - software/mutt-debian.git/blob - init.c
debian/rules: adding a commented rule to enable tokyocabinet if we want
[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, 0);
257       safe_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       short val;
2073       int rc;
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       rc = mutt_atos (tmp->data, (short *) &val);
2092
2093       if (rc < 0 || !*tmp->data)
2094       {
2095         snprintf (err->data, err->dsize, _("%s: invalid value (%s)"), tmp->data,
2096                   rc == -1 ? _("format error") : _("number overflow"));
2097         r = -1;
2098         break;
2099       }
2100       else
2101         *ptr = val;
2102
2103       /* these ones need a sanity check */
2104       if (mutt_strcmp (MuttVars[idx].option, "history") == 0)
2105       {
2106         if (*ptr < 0)
2107           *ptr = 0;
2108         mutt_init_history ();
2109       }
2110       else if (mutt_strcmp (MuttVars[idx].option, "pager_index_lines") == 0)
2111       {
2112         if (*ptr < 0)
2113           *ptr = 0;
2114       }
2115       else if (mutt_strcmp (MuttVars[idx].option, "wrapmargin") == 0)
2116       {
2117         if (*ptr < 0)
2118           *ptr = 0;
2119         else
2120           *ptr = -*ptr;
2121       }
2122 #ifdef USE_IMAP
2123       else if (mutt_strcmp (MuttVars[idx].option, "imap_pipeline_depth") == 0)
2124       {
2125         if (*ptr < 0)
2126           *ptr = 0;
2127       }
2128 #endif
2129     }
2130     else if (DTYPE (MuttVars[idx].type) == DT_QUAD)
2131     {
2132       if (query)
2133       {
2134         char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
2135
2136         snprintf (err->data, err->dsize, "%s=%s", MuttVars[idx].option,
2137                   vals [ quadoption (MuttVars[idx].data) ]);
2138         break;
2139       }
2140
2141       CHECK_PAGER;
2142       if (*s->dptr == '=')
2143       {
2144         s->dptr++;
2145         mutt_extract_token (tmp, s, 0);
2146         if (ascii_strcasecmp ("yes", tmp->data) == 0)
2147           set_quadoption (MuttVars[idx].data, M_YES);
2148         else if (ascii_strcasecmp ("no", tmp->data) == 0)
2149           set_quadoption (MuttVars[idx].data, M_NO);
2150         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
2151           set_quadoption (MuttVars[idx].data, M_ASKYES);
2152         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
2153           set_quadoption (MuttVars[idx].data, M_ASKNO);
2154         else
2155         {
2156           snprintf (err->data, err->dsize, _("%s: invalid value"), tmp->data);
2157           r = -1;
2158           break;
2159         }
2160       }
2161       else
2162       {
2163         if (inv)
2164           toggle_quadoption (MuttVars[idx].data);
2165         else if (unset)
2166           set_quadoption (MuttVars[idx].data, M_NO);
2167         else
2168           set_quadoption (MuttVars[idx].data, M_YES);
2169       }
2170     }
2171     else if (DTYPE (MuttVars[idx].type) == DT_SORT)
2172     {
2173       const struct mapping_t *map = NULL;
2174
2175       switch (MuttVars[idx].type & DT_SUBTYPE_MASK)
2176       {
2177         case DT_SORT_ALIAS:
2178           map = SortAliasMethods;
2179           break;
2180         case DT_SORT_BROWSER:
2181           map = SortBrowserMethods;
2182           break;
2183         case DT_SORT_KEYS:
2184           if ((WithCrypto & APPLICATION_PGP))
2185             map = SortKeyMethods;
2186           break;
2187         case DT_SORT_AUX:
2188           map = SortAuxMethods;
2189           break;
2190         default:
2191           map = SortMethods;
2192           break;
2193       }
2194
2195       if (!map)
2196       {
2197         snprintf (err->data, err->dsize, _("%s: Unknown type."), MuttVars[idx].option);
2198         r = -1;
2199         break;
2200       }
2201       
2202       if (query || *s->dptr != '=')
2203       {
2204         p = mutt_getnamebyvalue (*((short *) MuttVars[idx].data) & SORT_MASK, map);
2205
2206         snprintf (err->data, err->dsize, "%s=%s%s%s", MuttVars[idx].option,
2207                   (*((short *) MuttVars[idx].data) & SORT_REVERSE) ? "reverse-" : "",
2208                   (*((short *) MuttVars[idx].data) & SORT_LAST) ? "last-" : "",
2209                   p);
2210         return 0;
2211       }
2212       CHECK_PAGER;
2213       s->dptr++;
2214       mutt_extract_token (tmp, s , 0);
2215
2216       if (parse_sort ((short *) MuttVars[idx].data, tmp->data, map, err) == -1)
2217       {
2218         r = -1;
2219         break;
2220       }
2221     }
2222     else
2223     {
2224       snprintf (err->data, err->dsize, _("%s: unknown type"), MuttVars[idx].option);
2225       r = -1;
2226       break;
2227     }
2228
2229     if (!myvar)
2230     {
2231       if (MuttVars[idx].flags & R_INDEX)
2232         set_option (OPTFORCEREDRAWINDEX);
2233       if (MuttVars[idx].flags & R_PAGER)
2234         set_option (OPTFORCEREDRAWPAGER);
2235       if (MuttVars[idx].flags & R_RESORT_SUB)
2236         set_option (OPTSORTSUBTHREADS);
2237       if (MuttVars[idx].flags & R_RESORT)
2238         set_option (OPTNEEDRESORT);
2239       if (MuttVars[idx].flags & R_RESORT_INIT)
2240         set_option (OPTRESORTINIT);
2241       if (MuttVars[idx].flags & R_TREE)
2242         set_option (OPTREDRAWTREE);
2243     }
2244   }
2245   return (r);
2246 }
2247
2248 #define MAXERRS 128
2249
2250 /* reads the specified initialization file.  returns -1 if errors were found
2251    so that we can pause to let the user know...  */
2252 static int source_rc (const char *rcfile, BUFFER *err)
2253 {
2254   FILE *f;
2255   int line = 0, rc = 0, conv = 0;
2256   BUFFER token;
2257   char *linebuf = NULL;
2258   char *currentline = NULL;
2259   size_t buflen;
2260   pid_t pid;
2261
2262   dprint (2, (debugfile, "Reading configuration file '%s'.\n",
2263           rcfile));
2264   
2265   if ((f = mutt_open_read (rcfile, &pid)) == NULL)
2266   {
2267     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
2268     return (-1);
2269   }
2270
2271   memset (&token, 0, sizeof (token));
2272   while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line, M_CONT)) != NULL)
2273   {
2274     conv=ConfigCharset && (*ConfigCharset) && Charset;
2275     if (conv) 
2276     {
2277       currentline=safe_strdup(linebuf);
2278       if (!currentline) continue;
2279       mutt_convert_string(&currentline, ConfigCharset, Charset, 0);
2280     } 
2281     else 
2282       currentline=linebuf;
2283
2284     if (mutt_parse_rc_line (currentline, &token, err) == -1)
2285     {
2286       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
2287       if (--rc < -MAXERRS) 
2288       {
2289         if (conv) FREE(&currentline);
2290         break;
2291       }
2292     }
2293     else
2294     {
2295       if (rc < 0)
2296         rc = -1;
2297     }
2298     if (conv) 
2299       FREE(&currentline);
2300   }
2301   FREE (&token.data);
2302   FREE (&linebuf);
2303   safe_fclose (&f);
2304   if (pid != -1)
2305     mutt_wait_filter (pid);
2306   if (rc)
2307   {
2308     /* the muttrc source keyword */
2309     snprintf (err->data, err->dsize, rc >= -MAXERRS ? _("source: errors in %s")
2310       : _("source: reading aborted due too many errors in %s"), rcfile);
2311     rc = -1;
2312   }
2313   return (rc);
2314 }
2315
2316 #undef MAXERRS
2317
2318 static int parse_source (BUFFER *tmp, BUFFER *s, unsigned long data, BUFFER *err)
2319 {
2320   char path[_POSIX_PATH_MAX];
2321
2322   if (mutt_extract_token (tmp, s, 0) != 0)
2323   {
2324     snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
2325     return (-1);
2326   }
2327   if (MoreArgs (s))
2328   {
2329     strfcpy (err->data, _("source: too many arguments"), err->dsize);
2330     return (-1);
2331   }
2332   strfcpy (path, tmp->data, sizeof (path));
2333   mutt_expand_path (path, sizeof (path));
2334   return (source_rc (path, err));
2335 }
2336
2337 /* line         command to execute
2338
2339    token        scratch buffer to be used by parser.  caller should free
2340                 token->data when finished.  the reason for this variable is
2341                 to avoid having to allocate and deallocate a lot of memory
2342                 if we are parsing many lines.  the caller can pass in the
2343                 memory to use, which avoids having to create new space for
2344                 every call to this function.
2345
2346    err          where to write error messages */
2347 int mutt_parse_rc_line (/* const */ char *line, BUFFER *token, BUFFER *err)
2348 {
2349   int i, r = -1;
2350   BUFFER expn;
2351
2352   if (!line || !*line)
2353     return 0;
2354
2355   memset (&expn, 0, sizeof (expn));
2356   expn.data = expn.dptr = line;
2357   expn.dsize = mutt_strlen (line);
2358
2359   *err->data = 0;
2360
2361   SKIPWS (expn.dptr);
2362   while (*expn.dptr)
2363   {
2364     if (*expn.dptr == '#')
2365       break; /* rest of line is a comment */
2366     if (*expn.dptr == ';')
2367     {
2368       expn.dptr++;
2369       continue;
2370     }
2371     mutt_extract_token (token, &expn, 0);
2372     for (i = 0; Commands[i].name; i++)
2373     {
2374       if (!mutt_strcmp (token->data, Commands[i].name))
2375       {
2376         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
2377           goto finish;
2378         break;
2379       }
2380     }
2381     if (!Commands[i].name)
2382     {
2383       snprintf (err->data, err->dsize, _("%s: unknown command"), NONULL (token->data));
2384       goto finish;
2385     }
2386   }
2387   r = 0;
2388 finish:
2389   if (expn.destroy)
2390     FREE (&expn.data);
2391   return (r);
2392 }
2393
2394
2395 #define NUMVARS (sizeof (MuttVars)/sizeof (MuttVars[0]))
2396 #define NUMCOMMANDS (sizeof (Commands)/sizeof (Commands[0]))
2397 /* initial string that starts completion. No telling how much crap 
2398  * the user has typed so far. Allocate LONG_STRING just to be sure! */
2399 static char User_typed [LONG_STRING] = {0}; 
2400
2401 static int  Num_matched = 0; /* Number of matches for completion */
2402 static char Completed [STRING] = {0}; /* completed string (command or variable) */
2403 static const char **Matches;
2404 /* this is a lie until mutt_init runs: */
2405 static int  Matches_listsize = MAX(NUMVARS,NUMCOMMANDS) + 10;
2406
2407 static void matches_ensure_morespace(int current)
2408 {
2409   int base_space, extra_space, space;
2410
2411   if (current > Matches_listsize - 2)
2412   {
2413     base_space = MAX(NUMVARS,NUMCOMMANDS) + 1; 
2414     extra_space = Matches_listsize - base_space;
2415     extra_space *= 2;
2416     space = base_space + extra_space;
2417     safe_realloc (&Matches, space * sizeof (char *));
2418     memset (&Matches[current + 1], 0, space - current);
2419     Matches_listsize = space;
2420   }
2421 }
2422
2423 /* helper function for completion.  Changes the dest buffer if
2424    necessary/possible to aid completion.
2425         dest == completion result gets here.
2426         src == candidate for completion.
2427         try == user entered data for completion.
2428         len == length of dest buffer.
2429 */
2430 static void candidate (char *dest, char *try, const char *src, int len)
2431 {
2432   int l;
2433
2434   if (strstr (src, try) == src)
2435   {
2436     matches_ensure_morespace (Num_matched);
2437     Matches[Num_matched++] = src;
2438     if (dest[0] == 0)
2439       strfcpy (dest, src, len);
2440     else
2441     {
2442       for (l = 0; src[l] && src[l] == dest[l]; l++);
2443       dest[l] = 0;
2444     }
2445   }
2446 }
2447
2448 int mutt_command_complete (char *buffer, size_t len, int pos, int numtabs)
2449 {
2450   char *pt = buffer;
2451   int num;
2452   int spaces; /* keep track of the number of leading spaces on the line */
2453   myvar_t *myv;
2454
2455   SKIPWS (buffer);
2456   spaces = buffer - pt;
2457
2458   pt = buffer + pos - spaces;
2459   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2460     pt--;
2461
2462   if (pt == buffer) /* complete cmd */
2463   {
2464     /* first TAB. Collect all the matches */
2465     if (numtabs == 1)
2466     {
2467       Num_matched = 0;
2468       strfcpy (User_typed, pt, sizeof (User_typed));
2469       memset (Matches, 0, Matches_listsize);
2470       memset (Completed, 0, sizeof (Completed));
2471       for (num = 0; Commands[num].name; num++)
2472         candidate (Completed, User_typed, Commands[num].name, sizeof (Completed));
2473       matches_ensure_morespace (Num_matched);
2474       Matches[Num_matched++] = User_typed;
2475
2476       /* All matches are stored. Longest non-ambiguous string is ""
2477        * i.e. dont change 'buffer'. Fake successful return this time */
2478       if (User_typed[0] == 0)
2479         return 1;
2480     }
2481
2482     if (Completed[0] == 0 && User_typed[0])
2483       return 0;
2484
2485      /* Num_matched will _always_ be atleast 1 since the initial
2486       * user-typed string is always stored */
2487     if (numtabs == 1 && Num_matched == 2)
2488       snprintf(Completed, sizeof(Completed),"%s", Matches[0]);
2489     else if (numtabs > 1 && Num_matched > 2)
2490       /* cycle thru all the matches */
2491       snprintf(Completed, sizeof(Completed), "%s", 
2492                Matches[(numtabs - 2) % Num_matched]);
2493
2494     /* return the completed command */
2495     strncpy (buffer, Completed, len - spaces);
2496   }
2497   else if (!mutt_strncmp (buffer, "set", 3)
2498            || !mutt_strncmp (buffer, "unset", 5)
2499            || !mutt_strncmp (buffer, "reset", 5)
2500            || !mutt_strncmp (buffer, "toggle", 6))
2501   {             /* complete variables */
2502     char *prefixes[] = { "no", "inv", "?", "&", 0 };
2503     
2504     pt++;
2505     /* loop through all the possible prefixes (no, inv, ...) */
2506     if (!mutt_strncmp (buffer, "set", 3))
2507     {
2508       for (num = 0; prefixes[num]; num++)
2509       {
2510         if (!mutt_strncmp (pt, prefixes[num], mutt_strlen (prefixes[num])))
2511         {
2512           pt += mutt_strlen (prefixes[num]);
2513           break;
2514         }
2515       }
2516     }
2517     
2518     /* first TAB. Collect all the matches */
2519     if (numtabs == 1)
2520     {
2521       Num_matched = 0;
2522       strfcpy (User_typed, pt, sizeof (User_typed));
2523       memset (Matches, 0, Matches_listsize);
2524       memset (Completed, 0, sizeof (Completed));
2525       for (num = 0; MuttVars[num].option; num++)
2526         candidate (Completed, User_typed, MuttVars[num].option, sizeof (Completed));
2527       for (myv = MyVars; myv; myv = myv->next)
2528         candidate (Completed, User_typed, myv->name, sizeof (Completed));
2529       matches_ensure_morespace (Num_matched);
2530       Matches[Num_matched++] = User_typed;
2531
2532       /* All matches are stored. Longest non-ambiguous string is ""
2533        * i.e. dont change 'buffer'. Fake successful return this time */
2534       if (User_typed[0] == 0)
2535         return 1;
2536     }
2537
2538     if (Completed[0] == 0 && User_typed[0])
2539       return 0;
2540
2541     /* Num_matched will _always_ be atleast 1 since the initial
2542      * user-typed string is always stored */
2543     if (numtabs == 1 && Num_matched == 2)
2544       snprintf(Completed, sizeof(Completed),"%s", Matches[0]);
2545     else if (numtabs > 1 && Num_matched > 2)
2546     /* cycle thru all the matches */
2547       snprintf(Completed, sizeof(Completed), "%s", 
2548                Matches[(numtabs - 2) % Num_matched]);
2549
2550     strncpy (pt, Completed, buffer + len - pt - spaces);
2551   }
2552   else if (!mutt_strncmp (buffer, "exec", 4))
2553   {
2554     struct binding_t *menu = km_get_table (CurrentMenu);
2555
2556     if (!menu && CurrentMenu != MENU_PAGER)
2557       menu = OpGeneric;
2558     
2559     pt++;
2560     /* first TAB. Collect all the matches */
2561     if (numtabs == 1)
2562     {
2563       Num_matched = 0;
2564       strfcpy (User_typed, pt, sizeof (User_typed));
2565       memset (Matches, 0, Matches_listsize);
2566       memset (Completed, 0, sizeof (Completed));
2567       for (num = 0; menu[num].name; num++)
2568         candidate (Completed, User_typed, menu[num].name, sizeof (Completed));
2569       /* try the generic menu */
2570       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) 
2571       {
2572         menu = OpGeneric;
2573         for (num = 0; menu[num].name; num++)
2574           candidate (Completed, User_typed, menu[num].name, sizeof (Completed));
2575       }
2576       matches_ensure_morespace (Num_matched);
2577       Matches[Num_matched++] = User_typed;
2578
2579       /* All matches are stored. Longest non-ambiguous string is ""
2580        * i.e. dont change 'buffer'. Fake successful return this time */
2581       if (User_typed[0] == 0)
2582         return 1;
2583     }
2584
2585     if (Completed[0] == 0 && User_typed[0])
2586       return 0;
2587
2588     /* Num_matched will _always_ be atleast 1 since the initial
2589      * user-typed string is always stored */
2590     if (numtabs == 1 && Num_matched == 2)
2591       snprintf(Completed, sizeof(Completed),"%s", Matches[0]);
2592     else if (numtabs > 1 && Num_matched > 2)
2593     /* cycle thru all the matches */
2594       snprintf(Completed, sizeof(Completed), "%s", 
2595                Matches[(numtabs - 2) % Num_matched]);
2596
2597     strncpy (pt, Completed, buffer + len - pt - spaces);
2598   }
2599   else
2600     return 0;
2601
2602   return 1;
2603 }
2604
2605 int mutt_var_value_complete (char *buffer, size_t len, int pos)
2606 {
2607   char var[STRING], *pt = buffer;
2608   int spaces;
2609   
2610   if (buffer[0] == 0)
2611     return 0;
2612
2613   SKIPWS (buffer);
2614   spaces = buffer - pt;
2615
2616   pt = buffer + pos - spaces;
2617   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2618     pt--;
2619   pt++; /* move past the space */
2620   if (*pt == '=') /* abort if no var before the '=' */
2621     return 0;
2622
2623   if (mutt_strncmp (buffer, "set", 3) == 0)
2624   {
2625     int idx;
2626     char val[LONG_STRING];
2627     const char *myvarval;
2628
2629     strfcpy (var, pt, sizeof (var));
2630     /* ignore the trailing '=' when comparing */
2631     var[mutt_strlen (var) - 1] = 0;
2632     if ((idx = mutt_option_index (var)) == -1)
2633     {
2634       if ((myvarval = myvar_get(var)) != NULL)
2635       {
2636         pretty_var (pt, len - (pt - buffer), var, myvarval);
2637         return 1;
2638       }
2639       return 0; /* no such variable. */
2640     }
2641     else if (var_to_string (idx, val, sizeof (val)))
2642     {
2643       snprintf (pt, len - (pt - buffer), "%s=\"%s\"", var, val);
2644       return 1;
2645     }
2646   }
2647   return 0;
2648 }
2649
2650 static int var_to_string (int idx, char* val, size_t len)
2651 {
2652   char tmp[LONG_STRING];
2653   char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
2654
2655   tmp[0] = '\0';
2656
2657   if ((DTYPE(MuttVars[idx].type) == DT_STR) ||
2658       (DTYPE(MuttVars[idx].type) == DT_PATH) ||
2659       (DTYPE(MuttVars[idx].type) == DT_RX))
2660   {
2661     strfcpy (tmp, NONULL (*((char **) MuttVars[idx].data)), sizeof (tmp));
2662     if (DTYPE (MuttVars[idx].type) == DT_PATH)
2663       mutt_pretty_mailbox (tmp, sizeof (tmp));
2664   }
2665   else if (DTYPE (MuttVars[idx].type) == DT_ADDR)
2666   {
2667     rfc822_write_address (tmp, sizeof (tmp), *((ADDRESS **) MuttVars[idx].data), 0);
2668   }
2669   else if (DTYPE (MuttVars[idx].type) == DT_QUAD)
2670     strfcpy (tmp, vals[quadoption (MuttVars[idx].data)], sizeof (tmp));
2671   else if (DTYPE (MuttVars[idx].type) == DT_NUM)
2672   {
2673     short sval = *((short *) MuttVars[idx].data);
2674
2675     /* avert your eyes, gentle reader */
2676     if (mutt_strcmp (MuttVars[idx].option, "wrapmargin") == 0)
2677       sval = sval > 0 ? 0 : -sval;
2678
2679     snprintf (tmp, sizeof (tmp), "%d", sval);
2680   }
2681   else if (DTYPE (MuttVars[idx].type) == DT_SORT)
2682   {
2683     const struct mapping_t *map;
2684     char *p;
2685
2686     switch (MuttVars[idx].type & DT_SUBTYPE_MASK)
2687     {
2688       case DT_SORT_ALIAS:
2689         map = SortAliasMethods;
2690         break;
2691       case DT_SORT_BROWSER:
2692         map = SortBrowserMethods;
2693         break;
2694       case DT_SORT_KEYS:
2695         if ((WithCrypto & APPLICATION_PGP))
2696           map = SortKeyMethods;
2697         else
2698           map = SortMethods;
2699         break;
2700       default:
2701         map = SortMethods;
2702         break;
2703     }
2704     p = mutt_getnamebyvalue (*((short *) MuttVars[idx].data) & SORT_MASK, map);
2705     snprintf (tmp, sizeof (tmp), "%s%s%s",
2706               (*((short *) MuttVars[idx].data) & SORT_REVERSE) ? "reverse-" : "",
2707               (*((short *) MuttVars[idx].data) & SORT_LAST) ? "last-" : "",
2708               p);
2709   }
2710   else if (DTYPE (MuttVars[idx].type) == DT_MAGIC)
2711   {
2712     char *p;
2713
2714     switch (DefaultMagic)
2715     {
2716       case M_MBOX:
2717         p = "mbox";
2718         break;
2719       case M_MMDF:
2720         p = "MMDF";
2721         break;
2722       case M_MH:
2723         p = "MH";
2724         break;
2725       case M_MAILDIR:
2726         p = "Maildir";
2727         break;
2728       default:
2729         p = "unknown";
2730     }
2731     strfcpy (tmp, p, sizeof (tmp));
2732   }
2733   else if (DTYPE (MuttVars[idx].type) == DT_BOOL)
2734     strfcpy (tmp, option (MuttVars[idx].data) ? "yes" : "no", sizeof (tmp));
2735   else
2736     return 0;
2737
2738   escape_string (val, len - 1, tmp);
2739
2740   return 1;
2741 }
2742
2743 /* Implement the -Q command line flag */
2744 int mutt_query_variables (LIST *queries)
2745 {
2746   LIST *p;
2747   
2748   char errbuff[LONG_STRING];
2749   char command[STRING];
2750   
2751   BUFFER err, token;
2752   
2753   memset (&err, 0, sizeof (err));
2754   memset (&token, 0, sizeof (token));
2755   
2756   err.data = errbuff;
2757   err.dsize = sizeof (errbuff);
2758   
2759   for (p = queries; p; p = p->next)
2760   {
2761     snprintf (command, sizeof (command), "set ?%s\n", p->data);
2762     if (mutt_parse_rc_line (command, &token, &err) == -1)
2763     {
2764       fprintf (stderr, "%s\n", err.data);
2765       FREE (&token.data);
2766       return 1;
2767     }
2768     printf ("%s\n", err.data);
2769   }
2770   
2771   FREE (&token.data);
2772   return 0;
2773 }
2774
2775 /* dump out the value of all the variables we have */
2776 int mutt_dump_variables (void)
2777 {
2778   int i;
2779   
2780   char errbuff[LONG_STRING];
2781   char command[STRING];
2782   
2783   BUFFER err, token;
2784   
2785   memset (&err, 0, sizeof (err));
2786   memset (&token, 0, sizeof (token));
2787   
2788   err.data = errbuff;
2789   err.dsize = sizeof (errbuff);
2790   
2791   for (i = 0; MuttVars[i].option; i++)
2792   {
2793     if (MuttVars[i].type == DT_SYN)
2794       continue;
2795
2796     snprintf (command, sizeof (command), "set ?%s\n", MuttVars[i].option);
2797     if (mutt_parse_rc_line (command, &token, &err) == -1)
2798     {
2799       fprintf (stderr, "%s\n", err.data);
2800       FREE (&token.data);
2801       return 1;
2802     }
2803     printf("%s\n", err.data);
2804   }
2805   
2806   FREE (&token.data);
2807   return 0;
2808 }
2809
2810 char *mutt_getnamebyvalue (int val, const struct mapping_t *map)
2811 {
2812   int i;
2813
2814   for (i=0; map[i].name; i++)
2815     if (map[i].value == val)
2816       return (map[i].name);
2817   return NULL;
2818 }
2819
2820 int mutt_getvaluebyname (const char *name, const struct mapping_t *map)
2821 {
2822   int i;
2823
2824   for (i = 0; map[i].name; i++)
2825     if (ascii_strcasecmp (map[i].name, name) == 0)
2826       return (map[i].value);
2827   return (-1);
2828 }
2829
2830 #ifdef DEBUG
2831 static void start_debug (void)
2832 {
2833   time_t t;
2834   int i;
2835   char buf[_POSIX_PATH_MAX];
2836   char buf2[_POSIX_PATH_MAX];
2837
2838   /* rotate the old debug logs */
2839   for (i=3; i>=0; i--)
2840   {
2841     snprintf (buf, sizeof(buf), "%s/.muttdebug%d", NONULL(Homedir), i);
2842     snprintf (buf2, sizeof(buf2), "%s/.muttdebug%d", NONULL(Homedir), i+1);
2843     rename (buf, buf2);
2844   }
2845   if ((debugfile = safe_fopen(buf, "w")) != NULL)
2846   {
2847     t = time (0);
2848     setbuf (debugfile, NULL); /* don't buffer the debugging output! */
2849     fprintf (debugfile, "Mutt %s started at %s.\nDebugging at level %d.\n\n",
2850              MUTT_VERSION, asctime (localtime (&t)), debuglevel);
2851   }
2852 }
2853 #endif
2854
2855 static int mutt_execute_commands (LIST *p)
2856 {
2857   BUFFER err, token;
2858   char errstr[SHORT_STRING];
2859
2860   memset (&err, 0, sizeof (err));
2861   err.data = errstr;
2862   err.dsize = sizeof (errstr);
2863   memset (&token, 0, sizeof (token));
2864   for (; p; p = p->next)
2865   {
2866     if (mutt_parse_rc_line (p->data, &token, &err) != 0)
2867     {
2868       fprintf (stderr, _("Error in command line: %s\n"), err.data);
2869       FREE (&token.data);
2870       return (-1);
2871     }
2872   }
2873   FREE (&token.data);
2874   return 0;
2875 }
2876
2877 void mutt_init (int skip_sys_rc, LIST *commands)
2878 {
2879   struct passwd *pw;
2880   struct utsname utsname;
2881   char *p, buffer[STRING], error[STRING];
2882   int i, default_rc = 0, need_pause = 0;
2883   BUFFER err;
2884
2885   memset (&err, 0, sizeof (err));
2886   err.data = error;
2887   err.dsize = sizeof (error);
2888
2889   Groups = hash_create (1031, 0);
2890   ReverseAlias = hash_create (1031, 1);
2891   
2892   mutt_menu_init ();
2893
2894   /* 
2895    * XXX - use something even more difficult to predict?
2896    */
2897   snprintf (AttachmentMarker, sizeof (AttachmentMarker),
2898             "\033]9;%ld\a", (long) time (NULL));
2899   
2900   /* on one of the systems I use, getcwd() does not return the same prefix
2901      as is listed in the passwd file */
2902   if ((p = getenv ("HOME")))
2903     Homedir = safe_strdup (p);
2904
2905   /* Get some information about the user */
2906   if ((pw = getpwuid (getuid ())))
2907   {
2908     char rnbuf[STRING];
2909
2910     Username = safe_strdup (pw->pw_name);
2911     if (!Homedir)
2912       Homedir = safe_strdup (pw->pw_dir);
2913
2914     Realname = safe_strdup (mutt_gecos_name (rnbuf, sizeof (rnbuf), pw));
2915     Shell = safe_strdup (pw->pw_shell);
2916     endpwent ();
2917   }
2918   else 
2919   {
2920     if (!Homedir)
2921     {
2922       mutt_endwin (NULL);
2923       fputs (_("unable to determine home directory"), stderr);
2924       exit (1);
2925     }
2926     if ((p = getenv ("USER")))
2927       Username = safe_strdup (p);
2928     else
2929     {
2930       mutt_endwin (NULL);
2931       fputs (_("unable to determine username"), stderr);
2932       exit (1);
2933     }
2934     Shell = safe_strdup ((p = getenv ("SHELL")) ? p : "/bin/sh");
2935   }
2936
2937 #ifdef DEBUG
2938   /* Start up debugging mode if requested */
2939   if (debuglevel > 0)
2940     start_debug ();
2941 #endif
2942
2943   /* And about the host... */
2944   uname (&utsname);
2945   /* some systems report the FQDN instead of just the hostname */
2946   if ((p = strchr (utsname.nodename, '.')))
2947   {
2948     Hostname = mutt_substrdup (utsname.nodename, p);
2949     p++;
2950     strfcpy (buffer, p, sizeof (buffer)); /* save the domain for below */
2951   }
2952   else
2953     Hostname = safe_strdup (utsname.nodename);
2954
2955 #ifndef DOMAIN
2956 #define DOMAIN buffer
2957   if (!p && getdnsdomainname (buffer, sizeof (buffer)) == -1)
2958     Fqdn = safe_strdup ("@");
2959   else
2960 #endif /* DOMAIN */
2961     if (*DOMAIN != '@')
2962   {
2963     Fqdn = safe_malloc (mutt_strlen (DOMAIN) + mutt_strlen (Hostname) + 2);
2964     sprintf (Fqdn, "%s.%s", NONULL(Hostname), DOMAIN);  /* __SPRINTF_CHECKED__ */
2965   }
2966   else
2967     Fqdn = safe_strdup(NONULL(Hostname));
2968
2969   if ((p = getenv ("MAIL")))
2970     Spoolfile = safe_strdup (p);
2971   else if ((p = getenv ("MAILDIR")))
2972     Spoolfile = safe_strdup (p);
2973   else
2974   {
2975 #ifdef HOMESPOOL
2976     mutt_concat_path (buffer, NONULL (Homedir), MAILPATH, sizeof (buffer));
2977 #else
2978     mutt_concat_path (buffer, MAILPATH, NONULL(Username), sizeof (buffer));
2979 #endif
2980     Spoolfile = safe_strdup (buffer);
2981   }
2982
2983   if ((p = getenv ("MAILCAPS")))
2984     MailcapPath = safe_strdup (p);
2985   else
2986   {
2987     /* Default search path from RFC1524 */
2988     MailcapPath = safe_strdup ("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap");
2989   }
2990
2991   Tempdir = safe_strdup ((p = getenv ("TMPDIR")) ? p : "/tmp");
2992
2993   p = getenv ("VISUAL");
2994   if (!p)
2995   {
2996     p = getenv ("EDITOR");
2997     if (!p)
2998       p = "vi";
2999   }
3000   Editor = safe_strdup (p);
3001   Visual = safe_strdup (p);
3002
3003   if ((p = getenv ("REPLYTO")) != NULL)
3004   {
3005     BUFFER buf, token;
3006
3007     snprintf (buffer, sizeof (buffer), "Reply-To: %s", p);
3008
3009     memset (&buf, 0, sizeof (buf));
3010     buf.data = buf.dptr = buffer;
3011     buf.dsize = mutt_strlen (buffer);
3012
3013     memset (&token, 0, sizeof (token));
3014     parse_my_hdr (&token, &buf, 0, &err);
3015     FREE (&token.data);
3016   }
3017
3018   if ((p = getenv ("EMAIL")) != NULL)
3019     From = rfc822_parse_adrlist (NULL, p);
3020   
3021   mutt_set_langinfo_charset ();
3022   mutt_set_charset (Charset);
3023   
3024   Matches = safe_calloc (Matches_listsize, sizeof (char *));
3025   
3026   /* Set standard defaults */
3027   for (i = 0; MuttVars[i].option; i++)
3028   {
3029     mutt_set_default (&MuttVars[i]);
3030     mutt_restore_default (&MuttVars[i]);
3031   }
3032
3033   CurrentMenu = MENU_MAIN;
3034
3035
3036 #ifndef LOCALES_HACK
3037   /* Do we have a locale definition? */
3038   if (((p = getenv ("LC_ALL")) != NULL && p[0]) ||
3039       ((p = getenv ("LANG")) != NULL && p[0]) ||
3040       ((p = getenv ("LC_CTYPE")) != NULL && p[0]))
3041     set_option (OPTLOCALES);
3042 #endif
3043
3044 #ifdef HAVE_GETSID
3045   /* Unset suspend by default if we're the session leader */
3046   if (getsid(0) == getpid())
3047     unset_option (OPTSUSPEND);
3048 #endif
3049
3050   mutt_init_history ();
3051
3052   
3053   
3054   
3055   /*
3056    * 
3057    *                       BIG FAT WARNING
3058    * 
3059    * When changing the code which looks for a configuration file,
3060    * please also change the corresponding code in muttbug.sh.in.
3061    * 
3062    * 
3063    */
3064   
3065   
3066   
3067   
3068   if (!Muttrc)
3069   {
3070     snprintf (buffer, sizeof(buffer), "%s/.muttrc-%s", NONULL(Homedir), MUTT_VERSION);
3071     if (access(buffer, F_OK) == -1)
3072       snprintf (buffer, sizeof(buffer), "%s/.muttrc", NONULL(Homedir));
3073     if (access(buffer, F_OK) == -1)
3074       snprintf (buffer, sizeof (buffer), "%s/.mutt/muttrc-%s", NONULL(Homedir), MUTT_VERSION);
3075     if (access(buffer, F_OK) == -1)
3076       snprintf (buffer, sizeof (buffer), "%s/.mutt/muttrc", NONULL(Homedir));
3077     if (access(buffer, F_OK) == -1) /* default to .muttrc for alias_file */
3078       snprintf (buffer, sizeof(buffer), "%s/.muttrc", NONULL(Homedir));
3079
3080     default_rc = 1;
3081     Muttrc = safe_strdup (buffer);
3082   }
3083   else
3084   {
3085     strfcpy (buffer, Muttrc, sizeof (buffer));
3086     FREE (&Muttrc);
3087     mutt_expand_path (buffer, sizeof (buffer));
3088     Muttrc = safe_strdup (buffer);
3089   }
3090   FREE (&AliasFile);
3091   AliasFile = safe_strdup (NONULL(Muttrc));
3092
3093   /* Process the global rc file if it exists and the user hasn't explicity
3094      requested not to via "-n".  */
3095   if (!skip_sys_rc)
3096   {
3097     snprintf (buffer, sizeof(buffer), "%s/Muttrc-%s", SYSCONFDIR, MUTT_VERSION);
3098     if (access (buffer, F_OK) == -1)
3099       snprintf (buffer, sizeof(buffer), "%s/Muttrc", SYSCONFDIR);
3100     if (access (buffer, F_OK) == -1)
3101       snprintf (buffer, sizeof (buffer), "%s/Muttrc-%s", PKGDATADIR, MUTT_VERSION);
3102     if (access (buffer, F_OK) == -1)
3103       snprintf (buffer, sizeof (buffer), "%s/Muttrc", PKGDATADIR);
3104     if (access (buffer, F_OK) != -1)
3105     {
3106       if (source_rc (buffer, &err) != 0)
3107       {
3108         fputs (err.data, stderr);
3109         fputc ('\n', stderr);
3110         need_pause = 1;
3111       }
3112     }
3113   }
3114
3115   /* Read the user's initialization file.  */
3116   if (access (Muttrc, F_OK) != -1)
3117   {
3118     if (!option (OPTNOCURSES))
3119       endwin ();
3120     if (source_rc (Muttrc, &err) != 0)
3121     {
3122       fputs (err.data, stderr);
3123       fputc ('\n', stderr);
3124       need_pause = 1;
3125     }
3126   }
3127   else if (!default_rc)
3128   {
3129     /* file specified by -F does not exist */
3130     snprintf (buffer, sizeof (buffer), "%s: %s", Muttrc, strerror (errno));
3131     mutt_endwin (buffer);
3132     exit (1);
3133   }
3134
3135   if (mutt_execute_commands (commands) != 0)
3136     need_pause = 1;
3137
3138   if (need_pause && !option (OPTNOCURSES))
3139   {
3140     if (mutt_any_key_to_continue (NULL) == -1)
3141       mutt_exit(1);
3142   }
3143
3144   mutt_read_histfile ();
3145
3146 #if 0
3147   set_option (OPTWEED); /* turn weeding on by default */
3148 #endif
3149 }
3150
3151 int mutt_get_hook_type (const char *name)
3152 {
3153   struct command_t *c;
3154
3155   for (c = Commands ; c->name ; c++)
3156     if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
3157       return c->data;
3158   return 0;
3159 }
3160
3161 static int parse_group_context (group_context_t **ctx, BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
3162 {
3163   while (!mutt_strcasecmp (buf->data, "-group"))
3164   {
3165     if (!MoreArgs (s)) 
3166     {
3167       strfcpy (err->data, _("-group: no group name"), err->dsize);
3168       goto bail;
3169     }
3170     
3171     mutt_extract_token (buf, s, 0);
3172
3173     mutt_group_context_add (ctx, mutt_pattern_group (buf->data));
3174     
3175     if (!MoreArgs (s))
3176     {
3177       strfcpy (err->data, _("out of arguments"), err->dsize);
3178       goto bail;
3179     }
3180     
3181     mutt_extract_token (buf, s, 0);
3182   }
3183   
3184   return 0;
3185   
3186   bail:
3187   mutt_group_context_destroy (ctx);
3188   return -1;
3189 }
3190
3191 static void myvar_set (const char* var, const char* val)
3192 {
3193   myvar_t** cur;
3194
3195   for (cur = &MyVars; *cur; cur = &((*cur)->next))
3196     if (!mutt_strcmp ((*cur)->name, var))
3197       break;
3198
3199   if (!*cur)
3200     *cur = safe_calloc (1, sizeof (myvar_t));
3201   
3202   if (!(*cur)->name)
3203     (*cur)->name = safe_strdup (var);
3204   
3205   mutt_str_replace (&(*cur)->value, val);
3206 }
3207
3208 static void myvar_del (const char* var)
3209 {
3210   myvar_t **cur;
3211   myvar_t *tmp;
3212   
3213
3214   for (cur = &MyVars; *cur; cur = &((*cur)->next))
3215     if (!mutt_strcmp ((*cur)->name, var))
3216       break;
3217   
3218   if (*cur) 
3219   {
3220     tmp = (*cur)->next;
3221     FREE (&(*cur)->name);
3222     FREE (&(*cur)->value);
3223     FREE (cur);         /* __FREE_CHECKED__ */
3224     *cur = tmp;
3225   }
3226 }
3227
3228 static const char* myvar_get (const char* var)
3229 {
3230   myvar_t* cur;
3231
3232   for (cur = MyVars; cur; cur = cur->next)
3233     if (!mutt_strcmp (cur->name, var))
3234       return NONULL(cur->value);
3235
3236   return NULL;
3237 }