force our cflags in subdirs as well.
[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 {
1585     char buf[LONG_STRING];
1586     struct option_t *ptr = p;
1587
1588     if (DTYPE(ptr->type) == DT_SYN) {
1589         if (!more)
1590             return;
1591         ptr = hash_find(ConfigOptions, (const char *)ptr->data);
1592     }
1593     if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_from_string)
1594         return;
1595
1596     mutt_option_value(ptr->option, buf, sizeof(buf));
1597     if (str_len(ptr->init) == 0 && buf && *buf)
1598         ptr->init = str_dup(buf);
1599 }
1600
1601 static struct option_t* add_option (const char* name, const char* init,
1602                                     short type, short dodup) {
1603   struct option_t* option = mem_calloc (1, sizeof(struct option_t));
1604
1605   debug_print (1, ("adding $%s\n", name));
1606
1607   option->option = str_dup (name);
1608   option->type = type;
1609   if (init)
1610     option->init = dodup ? str_dup (init) : (char*) init;
1611   return (option);
1612 }
1613
1614 /* creates new option_t* of type DT_USER for $user_ var */
1615 static struct option_t* add_user_option (const char* name) {
1616   return (add_option (name, NULL, DT_USER, 1));
1617 }
1618
1619 /* free()'s option_t* */
1620 static void del_option (void* p) {
1621   struct option_t* ptr = (struct option_t*) p;
1622   char* s = (char*) ptr->data;
1623   debug_print (1, ("removing option '%s' from table\n", NONULL (ptr->option)));
1624   mem_free (&ptr->option);
1625   mem_free (&s);
1626   mem_free (&ptr->init);
1627   mem_free (&ptr);
1628 }
1629
1630 static int init_expand (char** dst, struct option_t* src) {
1631   BUFFER token, in;
1632   size_t len = 0;
1633
1634   mem_free (dst);
1635
1636   if (DTYPE(src->type) == DT_STR ||
1637       DTYPE(src->type) == DT_PATH) {
1638     /* only expand for string as it's the only place where
1639      * we want to expand vars right now */
1640     if (src->init && *src->init) {
1641       memset (&token, 0, sizeof(BUFFER));
1642       memset (&in, 0, sizeof(BUFFER));
1643       len = str_len (src->init) + 2;
1644       in.data = mem_malloc (len+1);
1645       snprintf (in.data, len, "\"%s\"", src->init);
1646       in.dptr = in.data;
1647       in.dsize = len;
1648       mutt_extract_token (&token, &in, 0);
1649       if (token.data && *token.data)
1650         *dst = str_dup (token.data);
1651       else
1652         *dst = str_dup ("");
1653       mem_free (&in.data);
1654       mem_free (&token.data);
1655     } else
1656       *dst = str_dup ("");
1657   } else
1658     /* for non-string: take value as is */
1659     *dst = str_dup (src->init);
1660   return (1);
1661 }
1662
1663 /* if additional data more == 1, we want to resolve synonyms */
1664 static void mutt_restore_default (const char* name, void* p,
1665                                   unsigned long more) {
1666   char errbuf[STRING];
1667   struct option_t* ptr = (struct option_t*) p;
1668   char* init = NULL;
1669
1670   if (DTYPE (ptr->type) == DT_SYN) {
1671     if (!more)
1672       return;
1673     ptr = hash_find (ConfigOptions, (char*) ptr->data);
1674   }
1675   if (!ptr)
1676     return;
1677   if (FuncTable[DTYPE (ptr->type)].opt_from_string) {
1678     init_expand (&init, ptr);
1679     if (!FuncTable[DTYPE (ptr->type)].opt_from_string (ptr, init, errbuf,
1680                                                        sizeof(errbuf))) {
1681       if (!option (OPTNOCURSES))
1682         mutt_endwin (NULL);
1683       fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
1684                          "Please report this error: \"%s\"\n"),
1685                ptr->option, NONULL (init), errbuf);
1686       exit (1);
1687     }
1688     mem_free (&init);
1689   }
1690
1691   if (ptr->flags & R_INDEX)
1692     set_option (OPTFORCEREDRAWINDEX);
1693   if (ptr->flags & R_PAGER)
1694     set_option (OPTFORCEREDRAWPAGER);
1695   if (ptr->flags & R_RESORT_SUB)
1696     set_option (OPTSORTSUBTHREADS);
1697   if (ptr->flags & R_RESORT)
1698     set_option (OPTNEEDRESORT);
1699   if (ptr->flags & R_RESORT_INIT)
1700     set_option (OPTRESORTINIT);
1701   if (ptr->flags & R_TREE)
1702     set_option (OPTREDRAWTREE);
1703 }
1704
1705 /* check whether value for $dsn_return would be valid */
1706 static int check_dsn_return (const char* option, unsigned long p,
1707                              char* errbuf, size_t errlen) {
1708   char* val = (char*) p;
1709   if (val && *val && str_ncmp (val, "hdrs", 4) != 0 &&
1710       str_ncmp (val, "full", 4) != 0) {
1711     if (errbuf)
1712       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), val, "dsn_return");
1713     return (0);
1714   }
1715   return (1);
1716 }
1717
1718 /* check whether value for $dsn_notify would be valid */
1719 static int check_dsn_notify (const char* option, unsigned long p,
1720                              char* errbuf, size_t errlen) {
1721   list2_t* list = NULL;
1722   int i = 0, rc = 1;
1723   char* val = (char*) p;
1724
1725   if (!val || !*val)
1726     return (1);
1727   list = list_from_str (val, ",");
1728   if (list_empty (list))
1729     return (1);
1730
1731   for (i = 0; i < list->length; i++)
1732     if (str_ncmp (list->data[i], "never", 5) != 0 &&
1733         str_ncmp (list->data[i], "failure", 7) != 0 &&
1734         str_ncmp (list->data[i], "delay", 5) != 0 &&
1735         str_ncmp (list->data[i], "success", 7) != 0) {
1736       if (errbuf)
1737         snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
1738                   (char*) list->data[i], "dsn_notify");
1739       rc = 0;
1740       break;
1741     }
1742   list_del (&list, (list_del_t*) _mem_free);
1743   return (rc);
1744 }
1745
1746 static int check_num (const char* option, unsigned long p,
1747                       char* errbuf, size_t errlen) {
1748   if ((int) p < 0) {
1749     if (errbuf)
1750       snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
1751     return (0);
1752   }
1753   return (1);
1754 }
1755
1756 #ifdef DEBUG
1757 static int check_debug (const char* option, unsigned long p,
1758                         char* errbuf, size_t errlen) {
1759   if ((int) p <= DEBUG_MAX_LEVEL &&
1760       (int) p >= DEBUG_MIN_LEVEL)
1761     return (1);
1762
1763   if (errbuf)
1764     snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
1765   return (0);
1766 }
1767 #endif
1768
1769 static int check_history (const char* option, unsigned long p,
1770                           char* errbuf, size_t errlen) {
1771   if (!check_num ("history", p, errbuf, errlen))
1772     return (0);
1773   mutt_init_history ();
1774   return (1);
1775 }
1776
1777 static int check_special (const char* name, unsigned long val,
1778                           char* errbuf, size_t errlen) {
1779   int i = 0;
1780
1781   for (i = 0; SpecialVars[i].name; i++) {
1782     if (str_cmp (SpecialVars[i].name, name) == 0) {
1783       return (SpecialVars[i].check (SpecialVars[i].name,
1784                                     val, errbuf, errlen));
1785     }
1786   }
1787   return (1);
1788 }
1789
1790 static const struct mapping_t* get_sortmap (struct option_t* option) {
1791   const struct mapping_t* map = NULL;
1792
1793   switch (option->type & DT_SUBTYPE_MASK) {
1794   case DT_SORT_ALIAS:
1795     map = SortAliasMethods;
1796     break;
1797   case DT_SORT_BROWSER:
1798     map = SortBrowserMethods;
1799     break;
1800   case DT_SORT_KEYS:
1801     if ((WithCrypto & APPLICATION_PGP))
1802       map = SortKeyMethods;
1803     break;
1804   case DT_SORT_AUX:
1805     map = SortAuxMethods;
1806     break;
1807   default:
1808     map = SortMethods;
1809     break;
1810   }
1811   return (map);
1812 }
1813
1814 #define CHECK_PAGER \
1815   if ((CurrentMenu == MENU_PAGER) && \
1816       (!option || (option->flags & R_RESORT))) \
1817   { \
1818     snprintf (err->data, err->dsize, \
1819               _("Not available in this menu.")); \
1820     return (-1); \
1821   }
1822
1823 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1824                       BUFFER * err)
1825 {
1826   int query, unset, inv, reset, r = 0;
1827   struct option_t* option = NULL;
1828
1829   while (MoreArgs (s)) {
1830     /* reset state variables */
1831     query = 0;
1832     unset = data & M_SET_UNSET;
1833     inv = data & M_SET_INV;
1834     reset = data & M_SET_RESET;
1835
1836     if (*s->dptr == '?') {
1837       query = 1;
1838       s->dptr++;
1839     }
1840     else if (str_ncmp ("no", s->dptr, 2) == 0) {
1841       s->dptr += 2;
1842       unset = !unset;
1843     }
1844     else if (str_ncmp ("inv", s->dptr, 3) == 0) {
1845       s->dptr += 3;
1846       inv = !inv;
1847     }
1848     else if (*s->dptr == '&') {
1849       reset = 1;
1850       s->dptr++;
1851     }
1852
1853     /* get the variable name */
1854     mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1855
1856     /* resolve synonyms */
1857     if ((option = hash_find (ConfigOptions, tmp->data)) != NULL &&
1858         DTYPE (option->type == DT_SYN)) {
1859       struct option_t* newopt = hash_find (ConfigOptions, (char*) option->data);
1860       syn_add (newopt, option);
1861       option = newopt;
1862     }
1863
1864     /* see if we need to add $user_ var */
1865     if (!option && ascii_strncmp ("user_", tmp->data, 5) == 0) {
1866       /* there's no option named like this yet so only add one
1867        * if the action isn't any of: reset, unset, query */
1868       if (!(reset || unset || query || *s->dptr != '=')) {
1869         debug_print (1, ("adding user option '%s'\n", tmp->data));
1870         option = add_user_option (tmp->data);
1871         hash_insert (ConfigOptions, option->option, option, 0);
1872       }
1873     }
1874
1875     if (!option && !(reset && str_cmp ("all", tmp->data) == 0)) {
1876       snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1877       return (-1);
1878     }
1879     SKIPWS (s->dptr);
1880
1881     if (reset) {
1882       if (query || unset || inv) {
1883         snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1884         return (-1);
1885       }
1886
1887       if (s && *s->dptr == '=') {
1888         snprintf (err->data, err->dsize, _("value is illegal with reset"));
1889         return (-1);
1890       }
1891
1892       if (!str_cmp ("all", tmp->data)) {
1893         if (CurrentMenu == MENU_PAGER) {
1894           snprintf (err->data, err->dsize, _("Not available in this menu."));
1895           return (-1);
1896         }
1897         hash_map (ConfigOptions, mutt_restore_default, 1);
1898         set_option (OPTFORCEREDRAWINDEX);
1899         set_option (OPTFORCEREDRAWPAGER);
1900         set_option (OPTSORTSUBTHREADS);
1901         set_option (OPTNEEDRESORT);
1902         set_option (OPTRESORTINIT);
1903         set_option (OPTREDRAWTREE);
1904         return (0);
1905       }
1906       else if (!FuncTable[DTYPE (option->type)].opt_from_string) {
1907         snprintf (err->data, err->dsize, _("$%s is read-only"), option->option);
1908         r = -1;
1909         break;
1910       } else {
1911         CHECK_PAGER;
1912         mutt_restore_default (NULL, option, 1);
1913       }
1914     }
1915     else if (DTYPE (option->type) == DT_BOOL) {
1916       /* XXX this currently ignores the function table
1917        * as we don't get invert and stuff into it */
1918       if (s && *s->dptr == '=') {
1919         if (unset || inv || query) {
1920           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1921           return (-1);
1922         }
1923
1924         s->dptr++;
1925         mutt_extract_token (tmp, s, 0);
1926         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1927           unset = inv = 0;
1928         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1929           unset = 1;
1930         else {
1931           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1932           return (-1);
1933         }
1934       }
1935
1936       if (query) {
1937         bool_to_string (err->data, err->dsize, option);
1938         return 0;
1939       }
1940
1941       CHECK_PAGER;
1942       if (unset)
1943         unset_option (option->data);
1944       else if (inv)
1945         toggle_option (option->data);
1946       else
1947         set_option (option->data);
1948     }
1949     else if (DTYPE (option->type) == DT_STR ||
1950              DTYPE (option->type) == DT_PATH ||
1951              DTYPE (option->type) == DT_ADDR ||
1952              DTYPE (option->type) == DT_MAGIC ||
1953              DTYPE (option->type) == DT_NUM ||
1954              DTYPE (option->type) == DT_SORT ||
1955              DTYPE (option->type) == DT_RX ||
1956              DTYPE (option->type) == DT_USER ||
1957              DTYPE (option->type) == DT_SYS) {
1958
1959       /* XXX maybe we need to get unset into handlers? */
1960       if (DTYPE (option->type) == DT_STR ||
1961           DTYPE (option->type) == DT_PATH ||
1962           DTYPE (option->type) == DT_ADDR ||
1963           DTYPE (option->type) == DT_USER ||
1964           DTYPE (option->type) == DT_SYS) {
1965         if (unset) {
1966           CHECK_PAGER;
1967           if (!FuncTable[DTYPE (option->type)].opt_from_string) {
1968             snprintf (err->data, err->dsize, _("$%s is read-only"),
1969                       option->option);
1970             r = -1;
1971             break;
1972           } else if (DTYPE (option->type) == DT_ADDR)
1973             rfc822_free_address ((ADDRESS **) option->data);
1974           else if (DTYPE (option->type) == DT_USER)
1975             /* to unset $user_ means remove */
1976             hash_delete (ConfigOptions, option->option,
1977                          option, del_option);
1978           else
1979             mem_free ((void *) option->data);
1980           break;
1981         }
1982       }
1983
1984       if (query || *s->dptr != '=') {
1985         FuncTable[DTYPE (option->type)].opt_to_string
1986           (err->data, err->dsize, option);
1987         break;
1988       }
1989
1990       /* the $muttng_ variables are read-only */
1991       if (!FuncTable[DTYPE (option->type)].opt_from_string) {
1992         snprintf (err->data, err->dsize, _("$%s is read-only"),
1993                   option->option);
1994         r = -1;
1995         break;
1996       } else {
1997         CHECK_PAGER;
1998         s->dptr++;
1999         mutt_extract_token (tmp, s, 0);
2000         if (!FuncTable[DTYPE (option->type)].opt_from_string
2001             (option, tmp->data, err->data, err->dsize))
2002           r = -1;
2003       }
2004     }
2005     else if (DTYPE (option->type) == DT_QUAD) {
2006
2007       if (query) {
2008         quad_to_string (err->data, err->dsize, option);
2009         break;
2010       }
2011
2012       if (*s->dptr == '=') {
2013         CHECK_PAGER;
2014         s->dptr++;
2015         mutt_extract_token (tmp, s, 0);
2016         if (ascii_strcasecmp ("yes", tmp->data) == 0)
2017           set_quadoption (option->data, M_YES);
2018         else if (ascii_strcasecmp ("no", tmp->data) == 0)
2019           set_quadoption (option->data, M_NO);
2020         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
2021           set_quadoption (option->data, M_ASKYES);
2022         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
2023           set_quadoption (option->data, M_ASKNO);
2024         else {
2025           snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
2026                     tmp->data, option->option);
2027           r = -1;
2028           break;
2029         }
2030       }
2031       else {
2032         if (inv)
2033           toggle_quadoption (option->data);
2034         else if (unset)
2035           set_quadoption (option->data, M_NO);
2036         else
2037           set_quadoption (option->data, M_YES);
2038       }
2039     }
2040     else {
2041       snprintf (err->data, err->dsize, _("%s: unknown type"),
2042                 option->option);
2043       r = -1;
2044       break;
2045     }
2046
2047     if (option->flags & R_INDEX)
2048       set_option (OPTFORCEREDRAWINDEX);
2049     if (option->flags & R_PAGER)
2050       set_option (OPTFORCEREDRAWPAGER);
2051     if (option->flags & R_RESORT_SUB)
2052       set_option (OPTSORTSUBTHREADS);
2053     if (option->flags & R_RESORT)
2054       set_option (OPTNEEDRESORT);
2055     if (option->flags & R_RESORT_INIT)
2056       set_option (OPTRESORTINIT);
2057     if (option->flags & R_TREE)
2058       set_option (OPTREDRAWTREE);
2059   }
2060   return (r);
2061 }
2062
2063 #define MAXERRS 128
2064
2065 /* reads the specified initialization file.  returns -1 if errors were found
2066    so that we can pause to let the user know...  */
2067 static int source_rc (const char *rcfile, BUFFER * err)
2068 {
2069   FILE *f;
2070   int line = 0, rc = 0, conv = 0;
2071   BUFFER token;
2072   char *linebuf = NULL;
2073   char *currentline = NULL;
2074   size_t buflen;
2075   pid_t pid;
2076
2077   debug_print (2, ("reading configuration file '%s'.\n", rcfile));
2078
2079   if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
2080     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
2081     return (-1);
2082   }
2083
2084   memset (&token, 0, sizeof(token));
2085   while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line)) != NULL) {
2086     conv = ConfigCharset && (*ConfigCharset) && Charset;
2087     if (conv) {
2088       currentline = str_dup (linebuf);
2089       if (!currentline)
2090         continue;
2091       mutt_convert_string (&currentline, ConfigCharset, Charset, 0);
2092     }
2093     else
2094       currentline = linebuf;
2095
2096     CurRCLine = line;
2097     CurRCFile = rcfile;
2098
2099     if (mutt_parse_rc_line (currentline, &token, err) == -1) {
2100       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
2101       if (--rc < -MAXERRS) {
2102         if (conv)
2103           mem_free (&currentline);
2104         break;
2105       }
2106     }
2107     else {
2108       if (rc < 0)
2109         rc = -1;
2110     }
2111     if (conv)
2112       mem_free (&currentline);
2113   }
2114   mem_free (&token.data);
2115   mem_free (&linebuf);
2116   fclose (f);
2117   if (pid != -1)
2118     mutt_wait_filter (pid);
2119   if (rc) {
2120     /* the muttrc source keyword */
2121     snprintf (err->data, err->dsize,
2122               rc >= -MAXERRS ? _("source: errors in %s")
2123               : _("source: reading aborted due too many errors in %s"),
2124               rcfile);
2125     rc = -1;
2126   }
2127   return (rc);
2128 }
2129
2130 #undef MAXERRS
2131
2132 static int parse_source (BUFFER * tmp, BUFFER * s, unsigned long data,
2133                          BUFFER * err)
2134 {
2135   char path[_POSIX_PATH_MAX];
2136   int rc = 0;
2137
2138   do {
2139     if (mutt_extract_token (tmp, s, 0) != 0) {
2140       snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
2141       return (-1);
2142     }
2143
2144     strfcpy (path, tmp->data, sizeof(path));
2145     mutt_expand_path (path, sizeof(path));
2146
2147     rc += source_rc (path, err);
2148   }
2149   while (MoreArgs (s));
2150
2151   return ((rc < 0) ? -1 : 0);
2152 }
2153
2154 /* line         command to execute
2155
2156    token        scratch buffer to be used by parser.  caller should free
2157                 token->data when finished.  the reason for this variable is
2158                 to avoid having to allocate and deallocate a lot of memory
2159                 if we are parsing many lines.  the caller can pass in the
2160                 memory to use, which avoids having to create new space for
2161                 every call to this function.
2162
2163    err          where to write error messages */
2164 int mutt_parse_rc_line ( /* const */ char *line, BUFFER * token, BUFFER * err)
2165 {
2166   int i, r = -1;
2167   BUFFER expn;
2168
2169   memset (&expn, 0, sizeof(expn));
2170   expn.data = expn.dptr = line;
2171   expn.dsize = str_len (line);
2172
2173   *err->data = 0;
2174
2175   debug_print (1, ("expand '%s'\n", line));
2176
2177   SKIPWS (expn.dptr);
2178   while (*expn.dptr) {
2179     if (*expn.dptr == '#')
2180       break;                    /* rest of line is a comment */
2181     if (*expn.dptr == ';') {
2182       expn.dptr++;
2183       continue;
2184     }
2185     mutt_extract_token (token, &expn, 0);
2186     for (i = 0; Commands[i].name; i++) {
2187       if (!str_cmp (token->data, Commands[i].name)) {
2188         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
2189           goto finish;
2190         break;
2191       }
2192     }
2193     if (!Commands[i].name) {
2194       snprintf (err->data, err->dsize, _("%s: unknown command"),
2195                 NONULL (token->data));
2196       goto finish;
2197     }
2198   }
2199   r = 0;
2200 finish:
2201   if (expn.destroy)
2202     mem_free (&expn.data);
2203   return (r);
2204 }
2205
2206
2207 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
2208 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
2209 /* initial string that starts completion. No telling how much crap
2210  * the user has typed so far. Allocate LONG_STRING just to be sure! */
2211 char User_typed[LONG_STRING] = { 0 };
2212
2213 int Num_matched = 0;            /* Number of matches for completion */
2214 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
2215 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1];  /* all the matches + User_typed */
2216
2217 /* helper function for completion.  Changes the dest buffer if
2218    necessary/possible to aid completion.
2219         dest == completion result gets here.
2220         src == candidate for completion.
2221         try == user entered data for completion.
2222         len == length of dest buffer.
2223 */
2224 static void candidate (char *dest, char *try, const char *src, int len)
2225 {
2226   int l;
2227
2228   if (strstr (src, try) == src) {
2229     Matches[Num_matched++] = src;
2230     if (dest[0] == 0)
2231       strfcpy (dest, src, len);
2232     else {
2233       for (l = 0; src[l] && src[l] == dest[l]; l++);
2234       dest[l] = 0;
2235     }
2236   }
2237 }
2238
2239 int mutt_command_complete (char *buffer, size_t len, int pos, int numtabs)
2240 {
2241   char *pt = buffer;
2242   int num;
2243   int spaces;                   /* keep track of the number of leading spaces on the line */
2244
2245   SKIPWS (buffer);
2246   spaces = buffer - pt;
2247
2248   pt = buffer + pos - spaces;
2249   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2250     pt--;
2251
2252   if (pt == buffer) {           /* complete cmd */
2253     /* first TAB. Collect all the matches */
2254     if (numtabs == 1) {
2255       Num_matched = 0;
2256       strfcpy (User_typed, pt, sizeof(User_typed));
2257       memset (Matches, 0, sizeof(Matches));
2258       memset (Completed, 0, sizeof(Completed));
2259       for (num = 0; Commands[num].name; num++)
2260         candidate (Completed, User_typed, Commands[num].name,
2261                    sizeof(Completed));
2262       Matches[Num_matched++] = User_typed;
2263
2264       /* All matches are stored. Longest non-ambiguous string is ""
2265        * i.e. dont change 'buffer'. Fake successful return this time */
2266       if (User_typed[0] == 0)
2267         return 1;
2268     }
2269
2270     if (Completed[0] == 0 && User_typed[0])
2271       return 0;
2272
2273     /* Num_matched will _always_ be atleast 1 since the initial
2274      * user-typed string is always stored */
2275     if (numtabs == 1 && Num_matched == 2)
2276       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2277     else if (numtabs > 1 && Num_matched > 2)
2278       /* cycle thru all the matches */
2279       snprintf (Completed, sizeof(Completed), "%s",
2280                 Matches[(numtabs - 2) % Num_matched]);
2281
2282     /* return the completed command */
2283     strncpy (buffer, Completed, len - spaces);
2284   }
2285   else if (!str_ncmp (buffer, "set", 3)
2286            || !str_ncmp (buffer, "unset", 5)
2287            || !str_ncmp (buffer, "reset", 5)
2288            || !str_ncmp (buffer, "toggle", 6)) {    /* complete variables */
2289     const char *prefixes[] = { "no", "inv", "?", "&", NULL };
2290
2291     pt++;
2292     /* loop through all the possible prefixes (no, inv, ...) */
2293     if (!str_ncmp (buffer, "set", 3)) {
2294       for (num = 0; prefixes[num]; num++) {
2295         if (!str_ncmp (pt, prefixes[num], str_len (prefixes[num]))) {
2296           pt += str_len (prefixes[num]);
2297           break;
2298         }
2299       }
2300     }
2301
2302     /* first TAB. Collect all the matches */
2303     if (numtabs == 1) {
2304       Num_matched = 0;
2305       strfcpy (User_typed, pt, sizeof(User_typed));
2306       memset (Matches, 0, sizeof(Matches));
2307       memset (Completed, 0, sizeof(Completed));
2308       for (num = 0; MuttVars[num].option; num++)
2309         candidate (Completed, User_typed, MuttVars[num].option,
2310                    sizeof(Completed));
2311       Matches[Num_matched++] = User_typed;
2312
2313       /* All matches are stored. Longest non-ambiguous string is ""
2314        * i.e. dont change 'buffer'. Fake successful return this time */
2315       if (User_typed[0] == 0)
2316         return 1;
2317     }
2318
2319     if (Completed[0] == 0 && User_typed[0])
2320       return 0;
2321
2322     /* Num_matched will _always_ be atleast 1 since the initial
2323      * user-typed string is always stored */
2324     if (numtabs == 1 && Num_matched == 2)
2325       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2326     else if (numtabs > 1 && Num_matched > 2)
2327       /* cycle thru all the matches */
2328       snprintf (Completed, sizeof(Completed), "%s",
2329                 Matches[(numtabs - 2) % Num_matched]);
2330
2331     strncpy (pt, Completed, buffer + len - pt - spaces);
2332   }
2333   else if (!str_ncmp (buffer, "exec", 4)) {
2334     struct binding_t *menu = km_get_table (CurrentMenu);
2335
2336     if (!menu && CurrentMenu != MENU_PAGER)
2337       menu = OpGeneric;
2338
2339     pt++;
2340     /* first TAB. Collect all the matches */
2341     if (numtabs == 1) {
2342       Num_matched = 0;
2343       strfcpy (User_typed, pt, sizeof(User_typed));
2344       memset (Matches, 0, sizeof(Matches));
2345       memset (Completed, 0, sizeof(Completed));
2346       for (num = 0; menu[num].name; num++)
2347         candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
2348       /* try the generic menu */
2349       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
2350         menu = OpGeneric;
2351         for (num = 0; menu[num].name; num++)
2352           candidate (Completed, User_typed, menu[num].name,
2353                      sizeof(Completed));
2354       }
2355       Matches[Num_matched++] = User_typed;
2356
2357       /* All matches are stored. Longest non-ambiguous string is ""
2358        * i.e. dont change 'buffer'. Fake successful return this time */
2359       if (User_typed[0] == 0)
2360         return 1;
2361     }
2362
2363     if (Completed[0] == 0 && User_typed[0])
2364       return 0;
2365
2366     /* Num_matched will _always_ be atleast 1 since the initial
2367      * user-typed string is always stored */
2368     if (numtabs == 1 && Num_matched == 2)
2369       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2370     else if (numtabs > 1 && Num_matched > 2)
2371       /* cycle thru all the matches */
2372       snprintf (Completed, sizeof(Completed), "%s",
2373                 Matches[(numtabs - 2) % Num_matched]);
2374
2375     strncpy (pt, Completed, buffer + len - pt - spaces);
2376   }
2377   else
2378     return 0;
2379
2380   return 1;
2381 }
2382
2383 int mutt_var_value_complete (char *buffer, size_t len, int pos)
2384 {
2385   char var[STRING], *pt = buffer;
2386   int spaces;
2387   struct option_t* option = NULL;
2388
2389   if (buffer[0] == 0)
2390     return 0;
2391
2392   SKIPWS (buffer);
2393   spaces = buffer - pt;
2394
2395   pt = buffer + pos - spaces;
2396   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2397     pt--;
2398   pt++;                         /* move past the space */
2399   if (*pt == '=')               /* abort if no var before the '=' */
2400     return 0;
2401
2402   if (str_ncmp (buffer, "set", 3) == 0) {
2403     strfcpy (var, pt, sizeof(var));
2404     /* ignore the trailing '=' when comparing */
2405     var[str_len (var) - 1] = 0;
2406     if (!(option = hash_find (ConfigOptions, var)))
2407       return 0;                 /* no such variable. */
2408     else {
2409       char tmp[LONG_STRING], tmp2[LONG_STRING];
2410       char *s, *d;
2411       size_t dlen = buffer + len - pt - spaces;
2412       const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
2413
2414       tmp[0] = '\0';
2415
2416       if ((DTYPE (option->type) == DT_STR) ||
2417           (DTYPE (option->type) == DT_PATH) ||
2418           (DTYPE (option->type) == DT_RX)) {
2419         strfcpy (tmp, NONULL (*((char **) option->data)), sizeof(tmp));
2420         if (DTYPE (option->type) == DT_PATH)
2421           mutt_pretty_mailbox (tmp);
2422       }
2423       else if (DTYPE (option->type) == DT_ADDR) {
2424         rfc822_write_address (tmp, sizeof(tmp),
2425                               *((ADDRESS **) option->data), 0);
2426       }
2427       else if (DTYPE (option->type) == DT_QUAD)
2428         strfcpy (tmp, vals[quadoption (option->data)], sizeof(tmp));
2429       else if (DTYPE (option->type) == DT_NUM)
2430         snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
2431       else if (DTYPE (option->type) == DT_SORT) {
2432         const struct mapping_t *map;
2433         const char *p;
2434
2435         switch (option->type & DT_SUBTYPE_MASK) {
2436         case DT_SORT_ALIAS:
2437           map = SortAliasMethods;
2438           break;
2439         case DT_SORT_BROWSER:
2440           map = SortBrowserMethods;
2441           break;
2442         case DT_SORT_KEYS:
2443           if ((WithCrypto & APPLICATION_PGP))
2444             map = SortKeyMethods;
2445           else
2446             map = SortMethods;
2447           break;
2448         default:
2449           map = SortMethods;
2450           break;
2451         }
2452         p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
2453         snprintf(tmp, sizeof(tmp), "%s%s%s",
2454                  (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
2455                  (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
2456       }
2457       else if (DTYPE (option->type) == DT_MAGIC) {
2458         const char *p;
2459         switch (DefaultMagic) {
2460           case M_MBOX:
2461             p = "mbox";
2462             break;
2463           case M_MMDF:
2464             p = "MMDF";
2465             break;
2466           case M_MH:
2467             p = "MH";
2468           break;
2469           case M_MAILDIR:
2470             p = "Maildir";
2471             break;
2472           default:
2473             p = "unknown";
2474         }
2475         strfcpy (tmp, p, sizeof(tmp));
2476       }
2477       else if (DTYPE (option->type) == DT_BOOL)
2478         strfcpy (tmp, option (option->data) ? "yes" : "no",
2479                  sizeof(tmp));
2480       else
2481         return 0;
2482
2483       for (s = tmp, d = tmp2; *s && (d - tmp2) < sizeof(tmp2) - 2;) {
2484         if (*s == '\\' || *s == '"')
2485           *d++ = '\\';
2486         *d++ = *s++;
2487       }
2488       *d = '\0';
2489
2490       strfcpy (tmp, pt, sizeof(tmp));
2491       snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
2492
2493       return 1;
2494     }
2495   }
2496   return 0;
2497 }
2498
2499 /* Implement the -Q command line flag */
2500 int mutt_query_variables (LIST * queries)
2501 {
2502   LIST *p;
2503
2504   char errbuff[STRING];
2505   char command[STRING];
2506
2507   BUFFER err, token;
2508
2509   memset (&err, 0, sizeof(err));
2510   memset (&token, 0, sizeof(token));
2511
2512   err.data = errbuff;
2513   err.dsize = sizeof(errbuff);
2514
2515   for (p = queries; p; p = p->next) {
2516     snprintf (command, sizeof(command), "set ?%s\n", p->data);
2517     if (mutt_parse_rc_line (command, &token, &err) == -1) {
2518       fprintf (stderr, "%s\n", err.data);
2519       mem_free (&token.data);
2520       return 1;
2521     }
2522     printf ("%s\n", err.data);
2523   }
2524
2525   mem_free (&token.data);
2526   return 0;
2527 }
2528
2529 const char *mutt_getnamebyvalue (int val, const struct mapping_t *map)
2530 {
2531   int i;
2532
2533   for (i = 0; map[i].name; i++)
2534     if (map[i].value == val)
2535       return (map[i].name);
2536   return NULL;
2537 }
2538
2539 int mutt_getvaluebyname (const char *name, const struct mapping_t *map)
2540 {
2541   int i;
2542
2543   for (i = 0; map[i].name; i++)
2544     if (ascii_strcasecmp (map[i].name, name) == 0)
2545       return (map[i].value);
2546   return (-1);
2547 }
2548
2549 static int mutt_execute_commands (LIST * p)
2550 {
2551   BUFFER err, token;
2552   char errstr[SHORT_STRING];
2553
2554   memset (&err, 0, sizeof(err));
2555   err.data = errstr;
2556   err.dsize = sizeof(errstr);
2557   memset (&token, 0, sizeof(token));
2558   for (; p; p = p->next) {
2559     if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
2560       fprintf (stderr, _("Error in command line: %s\n"), err.data);
2561       mem_free (&token.data);
2562       return (-1);
2563     }
2564   }
2565   mem_free (&token.data);
2566   return 0;
2567 }
2568
2569 void mutt_init (int skip_sys_rc, LIST * commands)
2570 {
2571   struct passwd *pw;
2572   struct utsname utsname;
2573   const char *p;
2574   char buffer[STRING], error[STRING];
2575   int i, default_rc = 0, need_pause = 0;
2576   BUFFER err;
2577
2578   memset (&err, 0, sizeof(err));
2579   err.data = error;
2580   err.dsize = sizeof(error);
2581
2582   /* use 3*sizeof(muttvars) instead of 2*sizeof()
2583    * to have some room for $user_ vars */
2584   ConfigOptions = hash_create (sizeof(MuttVars) * 3);
2585   for (i = 0; MuttVars[i].option; i++) {
2586     if (DTYPE (MuttVars[i].type) != DT_SYS)
2587       hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i], 0);
2588     else
2589       hash_insert (ConfigOptions, MuttVars[i].option,
2590                    add_option (MuttVars[i].option, MuttVars[i].init,
2591                                DT_SYS, 0), 0);
2592   }
2593
2594   /*
2595    * XXX - use something even more difficult to predict?
2596    */
2597   snprintf (AttachmentMarker, sizeof(AttachmentMarker),
2598             "\033]9;%ld\a", (long) time (NULL));
2599
2600   /* on one of the systems I use, getcwd() does not return the same prefix
2601      as is listed in the passwd file */
2602   if ((p = getenv ("HOME")))
2603     Homedir = str_dup (p);
2604
2605   /* Get some information about the user */
2606   if ((pw = getpwuid (getuid ()))) {
2607     char rnbuf[STRING];
2608
2609     Username = str_dup (pw->pw_name);
2610     if (!Homedir)
2611       Homedir = str_dup (pw->pw_dir);
2612
2613     Realname = str_dup (mutt_gecos_name (rnbuf, sizeof(rnbuf), pw));
2614     Shell = str_dup (pw->pw_shell);
2615     endpwent ();
2616   }
2617   else {
2618     if (!Homedir) {
2619       mutt_endwin (NULL);
2620       fputs (_("unable to determine home directory"), stderr);
2621       exit (1);
2622     }
2623     if ((p = getenv ("USER")))
2624       Username = str_dup (p);
2625     else {
2626       mutt_endwin (NULL);
2627       fputs (_("unable to determine username"), stderr);
2628       exit (1);
2629     }
2630     Shell = str_dup ((p = getenv ("SHELL")) ? p : "/bin/sh");
2631   }
2632
2633   debug_start(Homedir);
2634
2635   /* And about the host... */
2636   uname (&utsname);
2637   /* some systems report the FQDN instead of just the hostname */
2638   if ((p = strchr (utsname.nodename, '.'))) {
2639     Hostname = str_substrdup (utsname.nodename, p);
2640     p++;
2641     strfcpy (buffer, p, sizeof(buffer));       /* save the domain for below */
2642   }
2643   else
2644     Hostname = str_dup (utsname.nodename);
2645
2646 #ifndef DOMAIN
2647 #define DOMAIN buffer
2648   if (!p && getdnsdomainname (buffer, sizeof(buffer)) == -1)
2649     Fqdn = str_dup ("@");
2650   else
2651 #endif /* DOMAIN */
2652   if (*DOMAIN != '@') {
2653     Fqdn = mem_malloc (str_len (DOMAIN) + str_len (Hostname) + 2);
2654     sprintf (Fqdn, "%s.%s", NONULL (Hostname), DOMAIN); /* __SPRINTF_CHECKED__ */
2655   }
2656   else
2657     Fqdn = str_dup (NONULL (Hostname));
2658
2659 #ifdef USE_NNTP
2660   {
2661     FILE *f;
2662     char *q;
2663
2664     if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
2665       buffer[0] = '\0';
2666       fgets (buffer, sizeof(buffer), f);
2667       p = buffer;
2668       SKIPWS (p);
2669       q = (char*)p;
2670       while (*q && !isspace(*q))
2671         q++;
2672       *q = '\0';
2673       NewsServer = str_dup (p);
2674       fclose (f);
2675     }
2676   }
2677   if ((p = getenv ("NNTPSERVER")))
2678     NewsServer = str_dup (p);
2679 #endif
2680
2681   if ((p = getenv ("MAIL")))
2682     Spoolfile = str_dup (p);
2683   else if ((p = getenv ("MAILDIR")))
2684     Spoolfile = str_dup (p);
2685   else {
2686 #ifdef HOMESPOOL
2687     mutt_concat_path (buffer, NONULL (Homedir), MAILPATH, sizeof(buffer));
2688 #else
2689     mutt_concat_path (buffer, MAILPATH, NONULL (Username), sizeof(buffer));
2690 #endif
2691     Spoolfile = str_dup (buffer);
2692   }
2693
2694   if ((p = getenv ("MAILCAPS")))
2695     MailcapPath = str_dup (p);
2696   else {
2697     /* Default search path from RFC1524 */
2698     MailcapPath =
2699       str_dup ("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR
2700                    "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap");
2701   }
2702
2703   Tempdir = str_dup ((p = getenv ("TMPDIR")) ? p : "/tmp");
2704
2705   p = getenv ("VISUAL");
2706   if (!p) {
2707     p = getenv ("EDITOR");
2708     if (!p)
2709       p = "vi";
2710   }
2711   Editor = str_dup (p);
2712   Visual = str_dup (p);
2713
2714   if ((p = getenv ("REPLYTO")) != NULL) {
2715     BUFFER buf, token;
2716
2717     snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
2718
2719     memset (&buf, 0, sizeof(buf));
2720     buf.data = buf.dptr = buffer;
2721     buf.dsize = str_len (buffer);
2722
2723     memset (&token, 0, sizeof(token));
2724     parse_my_hdr (&token, &buf, 0, &err);
2725     mem_free (&token.data);
2726   }
2727
2728   if ((p = getenv ("EMAIL")) != NULL)
2729     From = rfc822_parse_adrlist (NULL, p);
2730
2731   mutt_set_langinfo_charset ();
2732   mutt_set_charset (Charset);
2733
2734
2735   /* Set standard defaults */
2736   hash_map (ConfigOptions, mutt_set_default, 0);
2737   hash_map (ConfigOptions, mutt_restore_default, 0);
2738
2739   CurrentMenu = MENU_MAIN;
2740
2741
2742 #ifndef LOCALES_HACK
2743   /* Do we have a locale definition? */
2744   if (((p = getenv ("LC_ALL")) != NULL && p[0]) ||
2745       ((p = getenv ("LANG")) != NULL && p[0]) ||
2746       ((p = getenv ("LC_CTYPE")) != NULL && p[0]))
2747     set_option (OPTLOCALES);
2748 #endif
2749
2750 #ifdef HAVE_GETSID
2751   /* Unset suspend by default if we're the session leader */
2752   if (getsid (0) == getpid ())
2753     unset_option (OPTSUSPEND);
2754 #endif
2755
2756   mutt_init_history ();
2757
2758
2759
2760
2761   /*
2762    *
2763    *                       BIG FAT WARNING
2764    *
2765    * When changing the code which looks for a configuration file,
2766    * please also change the corresponding code in muttbug.sh.in.
2767    *
2768    *
2769    */
2770
2771
2772
2773
2774   if (!Muttrc) {
2775 #if 0
2776     snprintf (buffer, sizeof(buffer), "%s/.muttngrc-%s", NONULL (Homedir),
2777               MUTT_VERSION);
2778     if (access (buffer, F_OK) == -1)
2779 #endif
2780       snprintf (buffer, sizeof(buffer), "%s/.muttngrc", NONULL (Homedir));
2781     if (access (buffer, F_OK) == -1)
2782 #if 0
2783       snprintf (buffer, sizeof(buffer), "%s/.muttng/muttngrc-%s",
2784                 NONULL (Homedir), MUTT_VERSION);
2785     if (access (buffer, F_OK) == -1)
2786 #endif
2787       snprintf (buffer, sizeof(buffer), "%s/.muttng/muttngrc",
2788                 NONULL (Homedir));
2789
2790     default_rc = 1;
2791     Muttrc = str_dup (buffer);
2792   }
2793   else {
2794     strfcpy (buffer, Muttrc, sizeof(buffer));
2795     mem_free (&Muttrc);
2796     mutt_expand_path (buffer, sizeof(buffer));
2797     Muttrc = str_dup (buffer);
2798   }
2799   mem_free (&AliasFile);
2800   AliasFile = str_dup (NONULL (Muttrc));
2801
2802   /* Process the global rc file if it exists and the user hasn't explicity
2803      requested not to via "-n".  */
2804   if (!skip_sys_rc) {
2805     snprintf (buffer, sizeof(buffer), "%s/Muttngrc-%s", SYSCONFDIR,
2806               MUTT_VERSION);
2807     if (access (buffer, F_OK) == -1)
2808       snprintf (buffer, sizeof(buffer), "%s/Muttngrc", SYSCONFDIR);
2809     if (access (buffer, F_OK) == -1)
2810       snprintf (buffer, sizeof(buffer), "%s/Muttngrc-%s", PKGDATADIR,
2811                 MUTT_VERSION);
2812     if (access (buffer, F_OK) == -1)
2813       snprintf (buffer, sizeof(buffer), "%s/Muttngrc", PKGDATADIR);
2814     if (access (buffer, F_OK) != -1) {
2815       if (source_rc (buffer, &err) != 0) {
2816         fputs (err.data, stderr);
2817         fputc ('\n', stderr);
2818         need_pause = 1;
2819       }
2820     }
2821   }
2822
2823   /* Read the user's initialization file.  */
2824   if (access (Muttrc, F_OK) != -1) {
2825     if (!option (OPTNOCURSES))
2826       mutt_endwin (NULL);
2827     if (source_rc (Muttrc, &err) != 0) {
2828       fputs (err.data, stderr);
2829       fputc ('\n', stderr);
2830       need_pause = 1;
2831     }
2832   }
2833   else if (!default_rc) {
2834     /* file specified by -F does not exist */
2835     snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
2836     mutt_endwin (buffer);
2837     exit (1);
2838   }
2839
2840   if (mutt_execute_commands (commands) != 0)
2841     need_pause = 1;
2842
2843   /* warn about synonym variables */
2844   if (!list_empty(Synonyms)) {
2845     int i = 0;
2846     fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
2847     for (i = 0; i < Synonyms->length; i++) {
2848       struct option_t* newopt = NULL, *oldopt = NULL;
2849       newopt = (struct option_t*) ((syn_t*) Synonyms->data[i])->n;
2850       oldopt = (struct option_t*) ((syn_t*) Synonyms->data[i])->o;
2851       fprintf (stderr, "$%s ($%s should be used) (%s:%d)\n",
2852                oldopt ? NONULL (oldopt->option) : "",
2853                newopt ? NONULL (newopt->option) : "",
2854                NONULL(((syn_t*) Synonyms->data[i])->f),
2855                ((syn_t*) Synonyms->data[i])->l);
2856     }
2857     fprintf (stderr, _("Warning: synonym variables are scheduled"
2858                        " for removal.\n"));
2859     list_del (&Synonyms, syn_del);
2860     need_pause = 1;
2861   }
2862
2863   if (need_pause && !option (OPTNOCURSES)) {
2864     if (mutt_any_key_to_continue (NULL) == -1)
2865       mutt_exit (1);
2866   }
2867
2868 #if 0
2869   set_option (OPTWEED);         /* turn weeding on by default */
2870 #endif
2871 }
2872
2873 int mutt_get_hook_type (const char *name)
2874 {
2875   struct command_t *c;
2876
2877   for (c = Commands; c->name; c++)
2878     if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
2879       return c->data;
2880   return 0;
2881 }
2882
2883 /* compare two option_t*'s for sorting -t/-T output */
2884 static int opt_cmp (const void* a, const void* b) {
2885   return (str_cmp ((*(struct option_t**) a)->option,
2886                        (*(struct option_t**) b)->option));
2887 }
2888
2889 /* callback for hash_map() to put all non-synonym vars into list */
2890 static void opt_sel_full (const char* key, void* data,
2891                           unsigned long more) {
2892   list2_t** l = (list2_t**) more;
2893   struct option_t* option = (struct option_t*) data;
2894
2895   if (DTYPE (option->type) == DT_SYN)
2896     return;
2897   list_push_back (l, option);
2898 }
2899
2900 /* callback for hash_map() to put all changed non-synonym vars into list */
2901 static void opt_sel_diff (const char* key, void* data,
2902                           unsigned long more) {
2903   list2_t** l = (list2_t**) more;
2904   struct option_t* option = (struct option_t*) data;
2905   char buf[LONG_STRING];
2906
2907   if (DTYPE (option->type) == DT_SYN)
2908     return;
2909
2910   mutt_option_value (option->option, buf, sizeof(buf));
2911   if (str_cmp (buf, option->init) != 0)
2912     list_push_back (l, option);
2913 }
2914
2915 /* dump out the value of all the variables we have */
2916 int mutt_dump_variables (int full) {
2917   int i = 0;
2918   char outbuf[STRING];
2919   list2_t* tmp = NULL;
2920   struct option_t* option = NULL;
2921
2922   /* get all non-synonyms into list... */
2923   hash_map (ConfigOptions, full ? opt_sel_full : opt_sel_diff,
2924             (unsigned long) &tmp);
2925
2926   if (!list_empty(tmp)) {
2927     /* ...and dump list sorted */
2928     qsort (tmp->data, tmp->length, sizeof(void*), opt_cmp);
2929     for (i = 0; i < tmp->length; i++) {
2930       option = (struct option_t*) tmp->data[i];
2931       FuncTable[DTYPE (option->type)].opt_to_string
2932         (outbuf, sizeof(outbuf), option);
2933       printf ("%s\n", outbuf);
2934     }
2935   }
2936   list_del (&tmp, NULL);
2937   return 0;
2938 }