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