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