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