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