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