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