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