Rocco Rutte:
[apps/madmutt.git] / init.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  *
5  * Parts were written/modified by:
6  * Rocco Rutte <pdmef@cs.tu-berlin.de>
7  *
8  * This file is part of mutt-ng, see http://www.muttng.org/.
9  * It's licensed under the GNU General Public License,
10  * please see the file GPL in the top level source directory.
11  */
12
13 #if HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include "mutt.h"
18 #include "buffer.h"
19 #include "ascii.h"
20 #include "mapping.h"
21 #include "mutt_curses.h"
22 #include "history.h"
23 #include "keymap.h"
24 #include "mbyte.h"
25 #include "charset.h"
26 #include "thread.h"
27 #include "mutt_crypt.h"
28 #include "mutt_idna.h"
29
30 #if defined(USE_SSL) || defined(USE_GNUTLS)
31 #include "mutt_ssl.h"
32 #endif
33
34 #if defined (USE_LIBESMTP) && (defined (USE_SSL) || defined (USE_GNUTLS))
35 #include "mutt_libesmtp.h"
36 #endif
37
38 #include "mx.h"
39 #include "init.h"
40
41 #include "lib/mem.h"
42 #include "lib/intl.h"
43 #include "lib/str.h"
44 #include "lib/rx.h"
45 #include "lib/list.h"
46 #include "lib/debug.h"
47
48 #include <ctype.h>
49 #include <stdlib.h>
50 #include <unistd.h>
51 #include <string.h>
52 #include <sys/utsname.h>
53 #include <errno.h>
54 #include <sys/wait.h>
55
56 /*
57  * prototypes
58  */
59 static const struct mapping_t* get_sortmap (struct option_t* option);
60 static int parse_sort (struct option_t* dst, const char *s,
61                        const struct mapping_t *map,
62                        char* errbuf, size_t errlen);
63
64 static HASH* ConfigOptions = NULL;
65
66 /* for synonym warning reports: synonym found during parsing */
67 typedef struct {
68   char* f;              /* file */
69   int l;                /* line */
70   struct option_t* n;   /* new */
71   struct option_t* o;   /* old */
72 } syn_t;
73
74 /* for synonym warning reports: list of synonyms found */
75 static list2_t* Synonyms;
76 /* for synonym warning reports: current rc file */
77 static const char* CurRCFile = NULL;
78 /* for synonym warning reports: current rc line */
79 static int CurRCLine = 0;
80
81 /* prototypes for checking for special vars */
82 static int check_dsn_return (const char*);
83 static int check_dsn_notify (const char*);
84
85 /* variable <-> sanity check function mappings */
86 static struct {
87   const char* name;
88   int (*check) (const char*);
89 } SpecialVars[] = {
90   { "dsn_notify", check_dsn_notify },
91   { "dsn_return", check_dsn_return },
92 #if defined (USE_LIBESMTP) && (defined (USE_SSL) || defined (USE_GNUTLS))
93   { "smtp_use_tls", mutt_libesmtp_check_usetls },
94 #endif
95   /* last */
96   { NULL,         NULL }
97 };
98
99 /* protos for config type handles */
100 static void bool_to_string  (char* dst, size_t dstlen, int idx);
101 static void num_to_string   (char* dst, size_t dstlen, int idx);
102 static void str_to_string   (char* dst, size_t dstlen, int idx);
103 static void quad_to_string  (char* dst, size_t dstlen, int idx);
104 static void sort_to_string  (char* dst, size_t dstlen, int idx);
105 static void rx_to_string    (char* dst, size_t dstlen, int idx);
106 static void magic_to_string (char* dst, size_t dstlen, int idx);
107 static void syn_to_string   (char* dst, size_t dstlen, int idx);
108 static void addr_to_string  (char* dst, size_t dstlen, int idx);
109
110 static struct {
111   unsigned short type;
112   void (*opt_to_string) (char* dst, size_t dstlen, int idx);
113 } FuncTable[] = {
114   { 0,          NULL            }, /* there's no DT_ type with 0 */
115   { DT_BOOL,    bool_to_string  },
116   { DT_NUM,     num_to_string   },
117   { DT_STR,     str_to_string   },
118   { DT_PATH,    str_to_string   },
119   { DT_QUAD,    quad_to_string  },
120   { DT_SORT,    sort_to_string  },
121   { DT_RX,      rx_to_string    },
122   { DT_MAGIC,   magic_to_string },
123   { DT_SYN,     syn_to_string   },
124   { DT_ADDR,    addr_to_string  }
125 };
126
127 static void bool_to_string (char* dst, size_t dstlen, int idx) {
128   snprintf (dst, dstlen, "%s=%s", MuttVars[idx].option,
129             option (MuttVars[idx].data) ? "yes" : "no");
130 }
131
132 static void num_to_string (char* dst, size_t dstlen, int idx) {
133   /* XXX puke */
134   const char* fmt = (idx == mutt_option_index ("umask")) ? "%s=%04o" : "%s=%d";
135   snprintf (dst, dstlen, fmt, MuttVars[idx].option,
136             *((short*) MuttVars[idx].data));
137 }
138
139 static void str_to_string (char* dst, size_t dstlen, int idx) {
140   snprintf (dst, dstlen, "%s=\"%s\"", MuttVars[idx].option,
141             NONULL (*((char**) MuttVars[idx].data)));
142 }
143
144 static void quad_to_string (char* dst, size_t dstlen, int idx) {
145   char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
146   snprintf (dst, dstlen, "%s=%s", MuttVars[idx].option,
147             vals[quadoption (MuttVars[idx].data)]);
148 }
149
150 static void sort_to_string (char* dst, size_t dstlen, int idx) {
151   const struct mapping_t *map = get_sortmap (idx);
152   char* p = NULL;
153
154   if (!map) {
155     snprintf (dst, sizeof (dst), "%s=unknown", MuttVars[idx].option);
156     return;
157   }
158
159   p = mutt_getnamebyvalue (*((short *) MuttVars[idx].data) & SORT_MASK,
160                            map);
161
162   snprintf (dst, dstlen, "%s=%s%s%s", MuttVars[idx].option,
163             (*((short *) MuttVars[idx].data) & SORT_REVERSE) ?
164             "reverse-" : "",
165             (*((short *) MuttVars[idx].data) & SORT_LAST) ? "last-" :
166             "", NONULL (p));
167 }
168
169 static void rx_to_string (char* dst, size_t dstlen, int idx) {
170   rx_t* p = (rx_t*) MuttVars[idx].data;
171   snprintf (dst, dstlen, "%s=\"%s\"", MuttVars[idx].option,
172             NONULL (p->pattern));
173 }
174
175 static void magic_to_string (char* dst, size_t dstlen, int idx) {
176   const char* s = NULL;
177   switch (MuttVars[idx].data) {
178     case M_MBOX:    s = "mbox"; break;
179     case M_MMDF:    s = "MMDF"; break;
180     case M_MH:      s = "MH"; break;
181     case M_MAILDIR: s = "Maildir"; break;
182     default:        s = "unknown"; break;
183   }
184   snprintf (dst, dstlen, "%s=%s", MuttVars[idx].option, s);
185 }
186
187 static void syn_to_string (char* dst, size_t dstlen, int idx) {
188   int i = mutt_option_index ((char*) MuttVars[idx].data);
189   FuncTable[MuttVars[i].type].opt_to_string (dst, dstlen, i);
190 }
191
192 static void addr_to_string (char* dst, size_t dstlen, int idx) {
193   char s[STRING];
194   s[0] = '\0';
195   rfc822_write_address (s, sizeof (s), *((ADDRESS**) MuttVars[idx].data), 0);
196   snprintf (dst, dstlen, "%s=\"%s\"", MuttVars[idx].option, NONULL (s));
197 }
198
199 int mutt_option_value (const char* val, char* dst, size_t dstlen) {
200   int i = mutt_option_index ((char*) val);
201   char* tmp = NULL, *t = NULL;
202   size_t l = 0;
203
204   if (i < 0) {
205     debug_print (1, ("var '%s' not found, i = %d\n", val, i));
206     *dst = '\0';
207     return (0);
208   }
209   tmp = mem_malloc (dstlen+1);
210   FuncTable[DTYPE (MuttVars[i].type)].opt_to_string (tmp, dstlen, i);
211
212   /* as we get things of type $var=value and don't want to bloat the
213    * above "just" for expansion, we do the stripping here */
214   debug_print (1, ("orig == '%s'\n", tmp));
215   t = strchr (tmp, '=');
216   t++;
217   l = str_len (t);
218   if (l >= 2) {
219     if (t[l-1] == '"' && *t == '"') {
220       t[l-1] = '\0';
221       t++;
222     }
223   }
224   memcpy (dst, t, l+1);
225   mem_free (&tmp);
226   debug_print (1, ("stripped == '%s'\n", dst));
227
228   return (1);
229 }
230
231 /* for synonym warning reports: adds synonym to end of list */
232 static void syn_add (struct option_t* n, struct option_t* o) {
233   syn_t* tmp = mem_malloc (sizeof (syn_t));
234   tmp->f = str_dup (CurRCFile);
235   tmp->l = CurRCLine;
236   tmp->n = n;
237   tmp->o = o;
238   list_push_back (&Synonyms, tmp);
239 }
240
241 /* for synonym warning reports: free single item (for list_del()) */
242 static void syn_del (void** p) {
243   mem_free(&(*(syn_t**) p)->f);
244   mem_free(p);
245 }
246
247 void toggle_quadoption (int opt)
248 {
249   int n = opt / 4;
250   int b = (opt % 4) * 2;
251
252   QuadOptions[n] ^= (1 << b);
253 }
254
255 void set_quadoption (int opt, int flag)
256 {
257   int n = opt / 4;
258   int b = (opt % 4) * 2;
259
260   QuadOptions[n] &= ~(0x3 << b);
261   QuadOptions[n] |= (flag & 0x3) << b;
262 }
263
264 int quadoption (int opt)
265 {
266   int n = opt / 4;
267   int b = (opt % 4) * 2;
268
269   return (QuadOptions[n] >> b) & 0x3;
270 }
271
272 int query_quadoption (int opt, const char *prompt)
273 {
274   int v = quadoption (opt);
275
276   switch (v) {
277   case M_YES:
278   case M_NO:
279     return (v);
280
281   default:
282     v = mutt_yesorno (prompt, (v == M_ASKYES));
283     CLEARLINE (LINES - 1);
284     return (v);
285   }
286
287   /* not reached */
288 }
289
290 static void add_to_list (LIST ** list, const char *str)
291 {
292   LIST *t, *last = NULL;
293
294   /* don't add a NULL or empty string to the list */
295   if (!str || *str == '\0')
296     return;
297
298   /* check to make sure the item is not already on this list */
299   for (last = *list; last; last = last->next) {
300     if (ascii_strcasecmp (str, last->data) == 0) {
301       /* already on the list, so just ignore it */
302       last = NULL;
303       break;
304     }
305     if (!last->next)
306       break;
307   }
308
309   if (!*list || last) {
310     t = (LIST *) mem_calloc (1, sizeof (LIST));
311     t->data = str_dup (str);
312     if (last) {
313       last->next = t;
314       last = last->next;
315     }
316     else
317       *list = last = t;
318   }
319 }
320
321 static int add_to_rx_list (list2_t** list, const char *s, int flags,
322                            BUFFER * err)
323 {
324   rx_t* rx;
325   int i = 0;
326
327   if (!s || !*s)
328     return 0;
329
330   if (!(rx = rx_compile (s, flags))) {
331     snprintf (err->data, err->dsize, "Bad regexp: %s\n", s);
332     return -1;
333   }
334
335   i = rx_lookup ((*list), rx->pattern);
336   if (i >= 0)
337     rx_free (&rx);
338   else
339     list_push_back (list, rx);
340   return 0;
341 }
342
343 static int add_to_spam_list (SPAM_LIST ** list, const char *pat,
344                              const char *templ, BUFFER * err)
345 {
346   SPAM_LIST *t = NULL, *last = NULL;
347   rx_t* rx;
348   int n;
349   const char *p;
350
351   if (!pat || !*pat || !templ)
352     return 0;
353
354   if (!(rx = rx_compile (pat, REG_ICASE))) {
355     snprintf (err->data, err->dsize, _("Bad regexp: %s"), pat);
356     return -1;
357   }
358
359   /* check to make sure the item is not already on this list */
360   for (last = *list; last; last = last->next) {
361     if (ascii_strcasecmp (rx->pattern, last->rx->pattern) == 0) {
362       /* Already on the list. Formerly we just skipped this case, but
363        * now we're supporting removals, which means we're supporting
364        * re-adds conceptually. So we probably want this to imply a
365        * removal, then do an add. We can achieve the removal by freeing
366        * the template, and leaving t pointed at the current item.
367        */
368       t = last;
369       mem_free(t->template);
370       break;
371     }
372     if (!last->next)
373       break;
374   }
375
376   /* If t is set, it's pointing into an extant SPAM_LIST* that we want to
377    * update. Otherwise we want to make a new one to link at the list's end.
378    */
379   if (!t) {
380     t = mutt_new_spam_list ();
381     t->rx = rx;
382     if (last)
383       last->next = t;
384     else
385       *list = t;
386   }
387
388   /* Now t is the SPAM_LIST* that we want to modify. It is prepared. */
389   t->template = str_dup (templ);
390
391   /* Find highest match number in template string */
392   t->nmatch = 0;
393   for (p = templ; *p;) {
394     if (*p == '%') {
395       n = atoi (++p);
396       if (n > t->nmatch)
397         t->nmatch = n;
398       while (*p && isdigit ((int) *p))
399         ++p;
400     }
401     else
402       ++p;
403   }
404   t->nmatch++;                  /* match 0 is always the whole expr */
405
406   return 0;
407 }
408
409 static int remove_from_spam_list (SPAM_LIST ** list, const char *pat)
410 {
411   SPAM_LIST *spam, *prev;
412   int nremoved = 0;
413
414   /* Being first is a special case. */
415   spam = *list;
416   if (!spam)
417     return 0;
418   if (spam->rx && !str_cmp (spam->rx->pattern, pat)) {
419     *list = spam->next;
420     rx_free (&spam->rx);
421     mem_free(&spam->template);
422     mem_free(&spam);
423     return 1;
424   }
425
426   prev = spam;
427   for (spam = prev->next; spam;) {
428     if (!str_cmp (spam->rx->pattern, pat)) {
429       prev->next = spam->next;
430       rx_free (&spam->rx);
431       mem_free(spam->template);
432       mem_free(spam);
433       spam = prev->next;
434       ++nremoved;
435     }
436     else
437       spam = spam->next;
438   }
439
440   return nremoved;
441 }
442
443
444 static void remove_from_list (LIST ** l, const char *str)
445 {
446   LIST *p, *last = NULL;
447
448   if (str_cmp ("*", str) == 0)
449     mutt_free_list (l);         /* ``unCMD *'' means delete all current entries */
450   else {
451     p = *l;
452     last = NULL;
453     while (p) {
454       if (ascii_strcasecmp (str, p->data) == 0) {
455         mem_free (&p->data);
456         if (last)
457           last->next = p->next;
458         else
459           (*l) = p->next;
460         mem_free (&p);
461       }
462       else {
463         last = p;
464         p = p->next;
465       }
466     }
467   }
468 }
469
470 static int remove_from_rx_list (list2_t** l, const char *str)
471 {
472   int i = 0;
473
474   if (str_cmp ("*", str) == 0) {
475     list_del (l, (list_del_t*) rx_free);
476     return (0);
477   }
478   else {
479     i = rx_lookup ((*l), str);
480     if (i >= 0) {
481       rx_t* r = list_pop_idx ((*l), i);
482       rx_free (&r);
483       return (0);
484     }
485   }
486   return (-1);
487 }
488
489 static int parse_ifdef (BUFFER * tmp, BUFFER * s, unsigned long data,
490                         BUFFER * err)
491 {
492   int i, j, res = 0;
493   BUFFER token;
494   struct option_t* option = NULL;
495
496   memset (&token, 0, sizeof (token));
497   mutt_extract_token (tmp, s, 0);
498
499   /* is the item defined as a variable or a function? */
500   if ((option = hash_find (ConfigOptions, tmp->data)))
501     res = 1;
502   else {
503     for (i = 0; !res && i < MENU_MAX; i++) {
504       struct binding_t *b = km_get_table (Menus[i].value);
505
506       if (!b)
507         continue;
508
509       for (j = 0; b[j].name; j++)
510         if (!ascii_strncasecmp (tmp->data, b[j].name, str_len (tmp->data))
511             && (str_len (b[j].name) == str_len (tmp->data))) {
512           res = 1;
513           break;
514         }
515     }
516   }
517   /* check for feature_* */
518   if (!res) {
519     char *p = NULL;
520
521     i = 0;
522     j = str_len (tmp->data);
523     /* need at least input of 'feature_X' */
524     if (j >= 7) {
525       p = tmp->data + 7;
526       j -= 7;
527       while (Features[i].name) {
528         if (str_len (Features[i].name) == j &&
529             ascii_strncasecmp (Features[i].name, p, j)) {
530           res = 1;
531           break;
532         }
533         i++;
534       }
535     }
536   }
537
538   if (!MoreArgs (s)) {
539     if (data)
540       snprintf (err->data, err->dsize, _("ifdef: too few arguments"));
541     else
542       snprintf (err->data, err->dsize, _("ifndef: too few arguments"));
543     return (-1);
544   }
545   mutt_extract_token (tmp, s, M_TOKEN_SPACE);
546
547   if ((data && res) || (!data && !res)) {
548     if (mutt_parse_rc_line (tmp->data, &token, err) == -1) {
549       mutt_error ("Error: %s", err->data);
550       mem_free (&token.data);
551       return (-1);
552     }
553     mem_free (&token.data);
554   }
555   return 0;
556 }
557
558 static int parse_unignore (BUFFER * buf, BUFFER * s, unsigned long data,
559                            BUFFER * err)
560 {
561   do {
562     mutt_extract_token (buf, s, 0);
563
564     /* don't add "*" to the unignore list */
565     if (strcmp (buf->data, "*"))
566       add_to_list (&UnIgnore, buf->data);
567
568     remove_from_list (&Ignore, buf->data);
569   }
570   while (MoreArgs (s));
571
572   return 0;
573 }
574
575 static int parse_ignore (BUFFER * buf, BUFFER * s, unsigned long data,
576                          BUFFER * err)
577 {
578   do {
579     mutt_extract_token (buf, s, 0);
580     remove_from_list (&UnIgnore, buf->data);
581     add_to_list (&Ignore, buf->data);
582   }
583   while (MoreArgs (s));
584
585   return 0;
586 }
587
588 static int parse_list (BUFFER * buf, BUFFER * s, unsigned long data,
589                        BUFFER * err)
590 {
591   do {
592     mutt_extract_token (buf, s, 0);
593     add_to_list ((LIST **) data, buf->data);
594   }
595   while (MoreArgs (s));
596
597   return 0;
598 }
599
600 static void _alternates_clean (void)
601 {
602   int i;
603
604   if (Context && Context->msgcount) {
605     for (i = 0; i < Context->msgcount; i++)
606       Context->hdrs[i]->recip_valid = 0;
607   }
608 }
609
610 static int parse_alternates (BUFFER * buf, BUFFER * s, unsigned long data,
611                              BUFFER * err)
612 {
613   _alternates_clean ();
614   do {
615     mutt_extract_token (buf, s, 0);
616     remove_from_rx_list (&UnAlternates, buf->data);
617
618     if (add_to_rx_list (&Alternates, buf->data, REG_ICASE, err) != 0)
619       return -1;
620   }
621   while (MoreArgs (s));
622
623   return 0;
624 }
625
626 static int parse_unalternates (BUFFER * buf, BUFFER * s, unsigned long data,
627                                BUFFER * err)
628 {
629   _alternates_clean ();
630   do {
631     mutt_extract_token (buf, s, 0);
632     remove_from_rx_list (&Alternates, buf->data);
633
634     if (str_cmp (buf->data, "*") &&
635         add_to_rx_list (&UnAlternates, buf->data, REG_ICASE, err) != 0)
636       return -1;
637
638   }
639   while (MoreArgs (s));
640
641   return 0;
642 }
643
644 static int parse_spam_list (BUFFER * buf, BUFFER * s, unsigned long data,
645                             BUFFER * err)
646 {
647   BUFFER templ;
648
649   memset (&templ, 0, sizeof (templ));
650
651   /* Insist on at least one parameter */
652   if (!MoreArgs (s)) {
653     if (data == M_SPAM)
654       strfcpy (err->data, _("spam: no matching pattern"), err->dsize);
655     else
656       strfcpy (err->data, _("nospam: no matching pattern"), err->dsize);
657     return -1;
658   }
659
660   /* Extract the first token, a regexp */
661   mutt_extract_token (buf, s, 0);
662
663   /* data should be either M_SPAM or M_NOSPAM. M_SPAM is for spam commands. */
664   if (data == M_SPAM) {
665     /* If there's a second parameter, it's a template for the spam tag. */
666     if (MoreArgs (s)) {
667       mutt_extract_token (&templ, s, 0);
668
669       /* Add to the spam list. */
670       if (add_to_spam_list (&SpamList, buf->data, templ.data, err) != 0) {
671         mem_free (&templ.data);
672         return -1;
673       }
674       mem_free (&templ.data);
675     }
676
677     /* If not, try to remove from the nospam list. */
678     else {
679       remove_from_rx_list (&NoSpamList, buf->data);
680     }
681
682     return 0;
683   }
684
685   /* M_NOSPAM is for nospam commands. */
686   else if (data == M_NOSPAM) {
687     /* nospam only ever has one parameter. */
688
689     /* "*" is a special case. */
690     if (!str_cmp (buf->data, "*")) {
691       mutt_free_spam_list (&SpamList);
692       list_del (&NoSpamList, (list_del_t*) rx_free);
693       return 0;
694     }
695
696     /* If it's on the spam list, just remove it. */
697     if (remove_from_spam_list (&SpamList, buf->data) != 0)
698       return 0;
699
700     /* Otherwise, add it to the nospam list. */
701     if (add_to_rx_list (&NoSpamList, buf->data, REG_ICASE, err) != 0)
702       return -1;
703
704     return 0;
705   }
706
707   /* This should not happen. */
708   strfcpy (err->data, "This is no good at all.", err->dsize);
709   return -1;
710 }
711
712 static int parse_unlist (BUFFER * buf, BUFFER * s, unsigned long data,
713                          BUFFER * err)
714 {
715   do {
716     mutt_extract_token (buf, s, 0);
717     /*
718      * Check for deletion of entire list
719      */
720     if (str_cmp (buf->data, "*") == 0) {
721       mutt_free_list ((LIST **) data);
722       break;
723     }
724     remove_from_list ((LIST **) data, buf->data);
725   }
726   while (MoreArgs (s));
727
728   return 0;
729 }
730
731 static int parse_lists (BUFFER * buf, BUFFER * s, unsigned long data,
732                         BUFFER * err)
733 {
734   do {
735     mutt_extract_token (buf, s, 0);
736     remove_from_rx_list (&UnMailLists, buf->data);
737
738     if (add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
739       return -1;
740   }
741   while (MoreArgs (s));
742
743   return 0;
744 }
745
746 static int parse_unlists (BUFFER * buf, BUFFER * s, unsigned long data,
747                           BUFFER * err)
748 {
749   do {
750     mutt_extract_token (buf, s, 0);
751     remove_from_rx_list (&SubscribedLists, buf->data);
752     remove_from_rx_list (&MailLists, buf->data);
753
754     if (str_cmp (buf->data, "*") &&
755         add_to_rx_list (&UnMailLists, buf->data, REG_ICASE, err) != 0)
756       return -1;
757   }
758   while (MoreArgs (s));
759
760   return 0;
761 }
762
763 static int parse_subscribe (BUFFER * buf, BUFFER * s, unsigned long data,
764                             BUFFER * err)
765 {
766   do {
767     mutt_extract_token (buf, s, 0);
768     remove_from_rx_list (&UnMailLists, buf->data);
769     remove_from_rx_list (&UnSubscribedLists, buf->data);
770
771     if (add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
772       return -1;
773     if (add_to_rx_list (&SubscribedLists, buf->data, REG_ICASE, err) != 0)
774       return -1;
775   }
776   while (MoreArgs (s));
777
778   return 0;
779 }
780
781 static int parse_unsubscribe (BUFFER * buf, BUFFER * s, unsigned long data,
782                               BUFFER * err)
783 {
784   do {
785     mutt_extract_token (buf, s, 0);
786     remove_from_rx_list (&SubscribedLists, buf->data);
787
788     if (str_cmp (buf->data, "*") &&
789         add_to_rx_list (&UnSubscribedLists, buf->data, REG_ICASE, err) != 0)
790       return -1;
791   }
792   while (MoreArgs (s));
793
794   return 0;
795 }
796
797 static int parse_unalias (BUFFER * buf, BUFFER * s, unsigned long data,
798                           BUFFER * err)
799 {
800   ALIAS *tmp, *last = NULL;
801
802   do {
803     mutt_extract_token (buf, s, 0);
804
805     if (str_cmp ("*", buf->data) == 0) {
806       if (CurrentMenu == MENU_ALIAS) {
807         for (tmp = Aliases; tmp; tmp = tmp->next)
808           tmp->del = 1;
809         set_option (OPTFORCEREDRAWINDEX);
810       }
811       else
812         mutt_free_alias (&Aliases);
813       break;
814     }
815     else
816       for (tmp = Aliases; tmp; tmp = tmp->next) {
817         if (str_casecmp (buf->data, tmp->name) == 0) {
818           if (CurrentMenu == MENU_ALIAS) {
819             tmp->del = 1;
820             set_option (OPTFORCEREDRAWINDEX);
821             break;
822           }
823
824           if (last)
825             last->next = tmp->next;
826           else
827             Aliases = tmp->next;
828           tmp->next = NULL;
829           mutt_free_alias (&tmp);
830           break;
831         }
832         last = tmp;
833       }
834   }
835   while (MoreArgs (s));
836   return 0;
837 }
838
839 static int parse_alias (BUFFER * buf, BUFFER * s, unsigned long data,
840                         BUFFER * err)
841 {
842   ALIAS *tmp = Aliases;
843   ALIAS *last = NULL;
844   char *estr = NULL;
845
846   if (!MoreArgs (s)) {
847     strfcpy (err->data, _("alias: no address"), err->dsize);
848     return (-1);
849   }
850
851   mutt_extract_token (buf, s, 0);
852
853   debug_print (2, ("first token is '%s'.\n", buf->data));
854
855   /* check to see if an alias with this name already exists */
856   for (; tmp; tmp = tmp->next) {
857     if (!str_casecmp (tmp->name, buf->data))
858       break;
859     last = tmp;
860   }
861
862   if (!tmp) {
863     /* create a new alias */
864     tmp = (ALIAS *) mem_calloc (1, sizeof (ALIAS));
865     tmp->self = tmp;
866     tmp->name = str_dup (buf->data);
867     /* give the main addressbook code a chance */
868     if (CurrentMenu == MENU_ALIAS)
869       set_option (OPTMENUCALLER);
870   }
871   else {
872     /* override the previous value */
873     rfc822_free_address (&tmp->addr);
874     if (CurrentMenu == MENU_ALIAS)
875       set_option (OPTFORCEREDRAWINDEX);
876   }
877
878   mutt_extract_token (buf, s,
879                       M_TOKEN_QUOTE | M_TOKEN_SPACE | M_TOKEN_SEMICOLON);
880   debug_print (2, ("second token is '%s'.\n", buf->data));
881   tmp->addr = mutt_parse_adrlist (tmp->addr, buf->data);
882   if (last)
883     last->next = tmp;
884   else
885     Aliases = tmp;
886   if (mutt_addrlist_to_idna (tmp->addr, &estr)) {
887     snprintf (err->data, err->dsize,
888               _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, tmp->name);
889     return -1;
890   }
891 #ifdef DEBUG
892   if (DebugLevel >= 2) {
893     ADDRESS *a;
894
895     for (a = tmp->addr; a; a = a->next) {
896       if (!a->group)
897         debug_print (2, ("%s\n", a->mailbox));
898       else
899         debug_print (2, ("group %s\n", a->mailbox));
900     }
901   }
902 #endif
903   return 0;
904 }
905
906 static int
907 parse_unmy_hdr (BUFFER * buf, BUFFER * s, unsigned long data, BUFFER * err)
908 {
909   LIST *last = NULL;
910   LIST *tmp = UserHeader;
911   LIST *ptr;
912   size_t l;
913
914   do {
915     mutt_extract_token (buf, s, 0);
916     if (str_cmp ("*", buf->data) == 0)
917       mutt_free_list (&UserHeader);
918     else {
919       tmp = UserHeader;
920       last = NULL;
921
922       l = str_len (buf->data);
923       if (buf->data[l - 1] == ':')
924         l--;
925
926       while (tmp) {
927         if (ascii_strncasecmp (buf->data, tmp->data, l) == 0
928             && tmp->data[l] == ':') {
929           ptr = tmp;
930           if (last)
931             last->next = tmp->next;
932           else
933             UserHeader = tmp->next;
934           tmp = tmp->next;
935           ptr->next = NULL;
936           mutt_free_list (&ptr);
937         }
938         else {
939           last = tmp;
940           tmp = tmp->next;
941         }
942       }
943     }
944   }
945   while (MoreArgs (s));
946   return 0;
947 }
948
949 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data,
950                          BUFFER * err)
951 {
952   LIST *tmp;
953   size_t keylen;
954   char *p;
955
956   mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
957   if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
958     strfcpy (err->data, _("invalid header field"), err->dsize);
959     return (-1);
960   }
961   keylen = p - buf->data + 1;
962
963   if (UserHeader) {
964     for (tmp = UserHeader;; tmp = tmp->next) {
965       /* see if there is already a field by this name */
966       if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
967         /* replace the old value */
968         mem_free (&tmp->data);
969         tmp->data = buf->data;
970         memset (buf, 0, sizeof (BUFFER));
971         return 0;
972       }
973       if (!tmp->next)
974         break;
975     }
976     tmp->next = mutt_new_list ();
977     tmp = tmp->next;
978   }
979   else {
980     tmp = mutt_new_list ();
981     UserHeader = tmp;
982   }
983   tmp->data = buf->data;
984   memset (buf, 0, sizeof (BUFFER));
985   return 0;
986 }
987
988 static int
989 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
990             char* errbuf, size_t errlen) {
991   int i, flags = 0;
992
993   if (str_ncmp ("reverse-", s, 8) == 0) {
994     s += 8;
995     flags = SORT_REVERSE;
996   }
997
998   if (str_ncmp ("last-", s, 5) == 0) {
999     s += 5;
1000     flags |= SORT_LAST;
1001   }
1002
1003   if ((i = mutt_getvaluebyname (s, map)) == -1) {
1004     if (errbuf)
1005       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
1006     return (-1);
1007   }
1008
1009   *((short*) dst->data) = i | flags;
1010   return 0;
1011 }
1012
1013 /* if additional data more == 1, we want to resolve synonyms */
1014 static void mutt_set_default (const char* name, void* p, unsigned long more) {
1015   char buf[LONG_STRING];
1016   struct option_t* ptr = (struct option_t*) p;
1017
1018   if (DTYPE (ptr->type) == DT_SYN) {
1019     if (!more)
1020       return;
1021     ptr = hash_find (ConfigOptions, (char*) ptr->data);
1022   }
1023   if (!ptr || *ptr->init)
1024     return;
1025   mutt_option_value (ptr->option, buf, sizeof (buf));
1026   if (str_len (ptr->init) == 0 && buf && *buf)
1027     ptr->init = str_dup (buf);
1028 }
1029
1030 /* if additional data more == 1, we want to resolve synonyms */
1031 static void mutt_restore_default (const char* name, void* p, unsigned long more) {
1032   char errbuf[STRING];
1033   struct option_t* ptr = (struct option_t*) p;
1034
1035   if (DTYPE (ptr->type) == DT_SYN) {
1036     if (!more)
1037       return;
1038     ptr = hash_find (ConfigOptions, (char*) ptr->data);
1039   }
1040   if (!ptr)
1041     return;
1042   if (FuncTable[DTYPE (ptr->type)].opt_from_string (ptr, ptr->init, errbuf,
1043                                                     sizeof (errbuf)) < 0) {
1044     mutt_endwin (NULL);
1045     fprintf (stderr, _("Invalid default setting found. Please report this "
1046                        "error:\n\"%s\"\n"), errbuf);
1047     exit (1);
1048   }
1049
1050   if (ptr->flags & R_INDEX)
1051     set_option (OPTFORCEREDRAWINDEX);
1052   if (ptr->flags & R_PAGER)
1053     set_option (OPTFORCEREDRAWPAGER);
1054   if (ptr->flags & R_RESORT_SUB)
1055     set_option (OPTSORTSUBTHREADS);
1056   if (ptr->flags & R_RESORT)
1057     set_option (OPTNEEDRESORT);
1058   if (ptr->flags & R_RESORT_INIT)
1059     set_option (OPTRESORTINIT);
1060   if (ptr->flags & R_TREE)
1061     set_option (OPTREDRAWTREE);
1062 }
1063
1064 /* check whether value for $dsn_return would be valid */
1065 static int check_dsn_return (const char* option, unsigned long p,
1066                              char* errbuf, size_t errlen) {
1067   char* val = (char*) p;
1068   if (val && *val && str_ncmp (val, "hdrs", 4) != 0 &&
1069       str_ncmp (val, "full", 4) != 0) {
1070     if (errbuf)
1071       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), val, "dsn_return");
1072     return (0);
1073   }
1074   return (1);
1075 }
1076
1077 /* check whether value for $dsn_notify would be valid */
1078 static int check_dsn_notify (const char* option, unsigned long p,
1079                              char* errbuf, size_t errlen) {
1080   list2_t* list = NULL;
1081   int i = 0, rc = 1;
1082   char* val = (char*) p;
1083
1084   if (!val || !*val)
1085     return (1);
1086   list = list_from_str (val, ",");
1087   if (list_empty (list))
1088     return (1);
1089
1090   for (i = 0; i < list->length; i++)
1091     if (str_ncmp (list->data[i], "never", 5) != 0 &&
1092         str_ncmp (list->data[i], "failure", 7) != 0 &&
1093         str_ncmp (list->data[i], "delay", 5) != 0 &&
1094         str_ncmp (list->data[i], "success", 7) != 0) {
1095       if (errbuf)
1096         snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
1097                   (char*) list->data[i], "dsn_notify");
1098       rc = 0;
1099       break;
1100     }
1101   list_del (&list, (list_del_t*) _mem_free);
1102   return (rc);
1103 }
1104
1105 static int check_num (const char* option, unsigned long p,
1106                       char* errbuf, size_t errlen) {
1107   if ((int) p < 0) {
1108     if (errbuf)
1109       snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
1110     return (0);
1111   }
1112   return (1);
1113 }
1114
1115 static int check_history (const char* option, unsigned long p,
1116                           char* errbuf, size_t errlen) {
1117   if (!check_num ("history", p, errbuf, errlen))
1118     return (0);
1119   mutt_init_history ();
1120   return (1);
1121 }
1122
1123 static int check_special (const char* name, unsigned long val,
1124                           char* errbuf, size_t errlen) {
1125   int i = 0;
1126
1127   for (i = 0; SpecialVars[i].name; i++) {
1128     if (str_cmp (SpecialVars[i].name, name) == 0) {
1129       return (SpecialVars[i].check (SpecialVars[i].name,
1130                                     val, errbuf, errlen));
1131     }
1132   }
1133   return (1);
1134 }
1135
1136 static const struct mapping_t* get_sortmap (struct option_t* option) {
1137   const struct mapping_t* map = NULL;
1138
1139   switch (option->type & DT_SUBTYPE_MASK) {
1140   case DT_SORT_ALIAS:
1141     map = SortAliasMethods;
1142     break;
1143   case DT_SORT_BROWSER:
1144     map = SortBrowserMethods;
1145     break;
1146   case DT_SORT_KEYS:
1147     if ((WithCrypto & APPLICATION_PGP))
1148       map = SortKeyMethods;
1149     break;
1150   case DT_SORT_AUX:
1151     map = SortAuxMethods;
1152     break;
1153   default:
1154     map = SortMethods;
1155     break;
1156   }
1157   return (map);
1158 }
1159
1160 /* creates new option_t* of type DT_USER for $user_ var */
1161 static struct option_t* add_user_option (const char* name) {
1162   struct option_t* option = mem_calloc (1, sizeof (struct option_t));
1163   option->option = str_dup (name);
1164   option->type = DT_USER;
1165   return (option);
1166 }
1167
1168 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1169                       BUFFER * err)
1170 {
1171   int query, unset, inv, reset, r = 0;
1172   struct option_t* option = NULL;
1173
1174   while (MoreArgs (s)) {
1175     /* reset state variables */
1176     query = 0;
1177     unset = data & M_SET_UNSET;
1178     inv = data & M_SET_INV;
1179     reset = data & M_SET_RESET;
1180
1181     if (*s->dptr == '?') {
1182       query = 1;
1183       s->dptr++;
1184     }
1185     else if (str_ncmp ("no", s->dptr, 2) == 0) {
1186       s->dptr += 2;
1187       unset = !unset;
1188     }
1189     else if (str_ncmp ("inv", s->dptr, 3) == 0) {
1190       s->dptr += 3;
1191       inv = !inv;
1192     }
1193     else if (*s->dptr == '&') {
1194       reset = 1;
1195       s->dptr++;
1196     }
1197
1198     /* get the variable name */
1199     mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1200
1201     /* resolve synonyms */
1202     if ((option = hash_find (ConfigOptions, tmp->data)) != NULL && 
1203         DTYPE (option->type == DT_SYN)) {
1204       struct option_t* newopt = hash_find (ConfigOptions, (char*) option->data);
1205       syn_add (newopt, option);
1206       option = newopt;
1207     }
1208
1209     /* see if we need to add $user_ var */
1210     if (!option && !reset && !unset && 
1211         ascii_strncasecmp ("user_", tmp->data, 5) == 0) {
1212       debug_print (1, ("adding user option '%s'\n", tmp->data));
1213       option = add_user_option (tmp->data);
1214       hash_insert (ConfigOptions, option->option, option, 0);
1215     }
1216
1217     if (!option && !(reset && str_cmp ("all", tmp->data) == 0)) {
1218       snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1219       return (-1);
1220     }
1221     SKIPWS (s->dptr);
1222
1223     if (reset) {
1224       if (query || unset || inv) {
1225         snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1226         return (-1);
1227       }
1228
1229       if (s && *s->dptr == '=') {
1230         snprintf (err->data, err->dsize, _("value is illegal with reset"));
1231         return (-1);
1232       }
1233
1234       if (!str_cmp ("all", tmp->data)) {
1235         hash_map (ConfigOptions, mutt_restore_default, 1);
1236         return (0);
1237       }
1238       else
1239         mutt_restore_default (NULL, option, 1);
1240     }
1241     else if (DTYPE (option->type) == DT_BOOL) {
1242       /* XXX this currently ignores the function table
1243        * as we don't get invert and stuff into it */
1244       if (s && *s->dptr == '=') {
1245         if (unset || inv || query) {
1246           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1247           return (-1);
1248         }
1249
1250         s->dptr++;
1251         mutt_extract_token (tmp, s, 0);
1252         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1253           unset = inv = 0;
1254         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1255           unset = 1;
1256         else {
1257           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1258           return (-1);
1259         }
1260       }
1261
1262       if (query) {
1263         bool_to_string (err->data, err->dsize, option);
1264         return 0;
1265       }
1266
1267       if (unset)
1268         unset_option (option->data);
1269       else if (inv)
1270         toggle_option (option->data);
1271       else
1272         set_option (option->data);
1273     }
1274     else if (DTYPE (option->type) == DT_STR ||
1275              DTYPE (option->type) == DT_PATH ||
1276              DTYPE (option->type) == DT_ADDR ||
1277              DTYPE (option->type) == DT_MAGIC ||
1278              DTYPE (option->type) == DT_NUM ||
1279              DTYPE (option->type) == DT_SORT ||
1280              DTYPE (option->type) == DT_RX ||
1281              DTYPE (option->type) == DT_USER) {
1282
1283       /* XXX maybe we need to get unset into handlers? */
1284       if (DTYPE (option->type) == DT_STR ||
1285           DTYPE (option->type) == DT_PATH ||
1286           DTYPE (option->type) == DT_ADDR ||
1287           DTYPE (option->type) == DT_USER) {
1288         if (unset) {
1289           if (DTYPE (option->type) == DT_ADDR)
1290             rfc822_free_address ((ADDRESS **) option->data);
1291           else if (DTYPE (option->type == DT_USER)) {
1292             void* p = (void*) option->data;
1293             mem_free (&p);
1294           } else
1295             mem_free ((void *) option->data);
1296           break;
1297         }
1298       }
1299
1300       if (query || *s->dptr != '=') {
1301         FuncTable[DTYPE (option->type)].opt_to_string
1302           (err->data, err->dsize, option);
1303         break;
1304       }
1305
1306       s->dptr++;
1307       mutt_extract_token (tmp, s, 0);
1308       if (!FuncTable[DTYPE (option->type)].opt_from_string
1309           (option, tmp->data, err->data, err->dsize))
1310         r = -1;
1311     }
1312     else if (DTYPE (option->type) == DT_QUAD) {
1313
1314       if (query) {
1315         quad_to_string (err->data, err->dsize, option);
1316         break;
1317       }
1318
1319       if (*s->dptr == '=') {
1320         s->dptr++;
1321         mutt_extract_token (tmp, s, 0);
1322         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1323           set_quadoption (option->data, M_YES);
1324         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1325           set_quadoption (option->data, M_NO);
1326         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1327           set_quadoption (option->data, M_ASKYES);
1328         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1329           set_quadoption (option->data, M_ASKNO);
1330         else {
1331           snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1332                     tmp->data, option->option);
1333           r = -1;
1334           break;
1335         }
1336       }
1337       else {
1338         if (inv)
1339           toggle_quadoption (option->data);
1340         else if (unset)
1341           set_quadoption (option->data, M_NO);
1342         else
1343           set_quadoption (option->data, M_YES);
1344       }
1345     }
1346     else {
1347       snprintf (err->data, err->dsize, _("%s: unknown type"),
1348                 option->option);
1349       r = -1;
1350       break;
1351     }
1352
1353     if (option->flags & R_INDEX)
1354       set_option (OPTFORCEREDRAWINDEX);
1355     if (option->flags & R_PAGER)
1356       set_option (OPTFORCEREDRAWPAGER);
1357     if (option->flags & R_RESORT_SUB)
1358       set_option (OPTSORTSUBTHREADS);
1359     if (option->flags & R_RESORT)
1360       set_option (OPTNEEDRESORT);
1361     if (option->flags & R_RESORT_INIT)
1362       set_option (OPTRESORTINIT);
1363     if (option->flags & R_TREE)
1364       set_option (OPTREDRAWTREE);
1365   }
1366   return (r);
1367 }
1368
1369 #define MAXERRS 128
1370
1371 /* reads the specified initialization file.  returns -1 if errors were found
1372    so that we can pause to let the user know...  */
1373 static int source_rc (const char *rcfile, BUFFER * err)
1374 {
1375   FILE *f;
1376   int line = 0, rc = 0, conv = 0;
1377   BUFFER token;
1378   char *linebuf = NULL;
1379   char *currentline = NULL;
1380   size_t buflen;
1381   pid_t pid;
1382
1383   debug_print (2, ("reading configuration file '%s'.\n", rcfile));
1384
1385   if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1386     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1387     return (-1);
1388   }
1389
1390   memset (&token, 0, sizeof (token));
1391   while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line)) != NULL) {
1392     conv = ConfigCharset && (*ConfigCharset) && Charset;
1393     if (conv) {
1394       currentline = str_dup (linebuf);
1395       if (!currentline)
1396         continue;
1397       mutt_convert_string (&currentline, ConfigCharset, Charset, 0);
1398     }
1399     else
1400       currentline = linebuf;
1401
1402     CurRCLine = line;
1403     CurRCFile = rcfile;
1404
1405     if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1406       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1407       if (--rc < -MAXERRS) {
1408         if (conv)
1409           mem_free (&currentline);
1410         break;
1411       }
1412     }
1413     else {
1414       if (rc < 0)
1415         rc = -1;
1416     }
1417     if (conv)
1418       mem_free (&currentline);
1419   }
1420   mem_free (&token.data);
1421   mem_free (&linebuf);
1422   fclose (f);
1423   if (pid != -1)
1424     mutt_wait_filter (pid);
1425   if (rc) {
1426     /* the muttrc source keyword */
1427     snprintf (err->data, err->dsize,
1428               rc >= -MAXERRS ? _("source: errors in %s")
1429               : _("source: reading aborted due too many errors in %s"),
1430               rcfile);
1431     rc = -1;
1432   }
1433   return (rc);
1434 }
1435
1436 #undef MAXERRS
1437
1438 static int parse_source (BUFFER * tmp, BUFFER * s, unsigned long data,
1439                          BUFFER * err)
1440 {
1441   char path[_POSIX_PATH_MAX];
1442   int rc = 0;
1443
1444   do {
1445     if (mutt_extract_token (tmp, s, 0) != 0) {
1446       snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1447       return (-1);
1448     }
1449
1450     strfcpy (path, tmp->data, sizeof (path));
1451     mutt_expand_path (path, sizeof (path));
1452
1453     rc += source_rc (path, err);
1454   }
1455   while (MoreArgs (s));
1456
1457   return ((rc < 0) ? -1 : 0);
1458 }
1459
1460 /* line         command to execute
1461
1462    token        scratch buffer to be used by parser.  caller should free
1463                 token->data when finished.  the reason for this variable is
1464                 to avoid having to allocate and deallocate a lot of memory
1465                 if we are parsing many lines.  the caller can pass in the
1466                 memory to use, which avoids having to create new space for
1467                 every call to this function.
1468
1469    err          where to write error messages */
1470 int mutt_parse_rc_line ( /* const */ char *line, BUFFER * token, BUFFER * err)
1471 {
1472   int i, r = -1;
1473   BUFFER expn;
1474
1475   memset (&expn, 0, sizeof (expn));
1476   expn.data = expn.dptr = line;
1477   expn.dsize = str_len (line);
1478
1479   *err->data = 0;
1480
1481   SKIPWS (expn.dptr);
1482   while (*expn.dptr) {
1483     if (*expn.dptr == '#')
1484       break;                    /* rest of line is a comment */
1485     if (*expn.dptr == ';') {
1486       expn.dptr++;
1487       continue;
1488     }
1489     mutt_extract_token (token, &expn, 0);
1490     for (i = 0; Commands[i].name; i++) {
1491       if (!str_cmp (token->data, Commands[i].name)) {
1492         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1493           goto finish;
1494         break;
1495       }
1496     }
1497     if (!Commands[i].name) {
1498       snprintf (err->data, err->dsize, _("%s: unknown command"),
1499                 NONULL (token->data));
1500       goto finish;
1501     }
1502   }
1503   r = 0;
1504 finish:
1505   if (expn.destroy)
1506     mem_free (&expn.data);
1507   return (r);
1508 }
1509
1510
1511 #define NUMVARS (sizeof (MuttVars)/sizeof (MuttVars[0]))
1512 #define NUMCOMMANDS (sizeof (Commands)/sizeof (Commands[0]))
1513 /* initial string that starts completion. No telling how much crap 
1514  * the user has typed so far. Allocate LONG_STRING just to be sure! */
1515 char User_typed[LONG_STRING] = { 0 };
1516
1517 int Num_matched = 0;            /* Number of matches for completion */
1518 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
1519 char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1];  /* all the matches + User_typed */
1520
1521 /* helper function for completion.  Changes the dest buffer if
1522    necessary/possible to aid completion.
1523         dest == completion result gets here.
1524         src == candidate for completion.
1525         try == user entered data for completion.
1526         len == length of dest buffer.
1527 */
1528 static void candidate (char *dest, char *try, char *src, int len)
1529 {
1530   int l;
1531
1532   if (strstr (src, try) == src) {
1533     Matches[Num_matched++] = src;
1534     if (dest[0] == 0)
1535       strfcpy (dest, src, len);
1536     else {
1537       for (l = 0; src[l] && src[l] == dest[l]; l++);
1538       dest[l] = 0;
1539     }
1540   }
1541 }
1542
1543 int mutt_command_complete (char *buffer, size_t len, int pos, int numtabs)
1544 {
1545   char *pt = buffer;
1546   int num;
1547   int spaces;                   /* keep track of the number of leading spaces on the line */
1548
1549   SKIPWS (buffer);
1550   spaces = buffer - pt;
1551
1552   pt = buffer + pos - spaces;
1553   while ((pt > buffer) && !isspace ((unsigned char) *pt))
1554     pt--;
1555
1556   if (pt == buffer) {           /* complete cmd */
1557     /* first TAB. Collect all the matches */
1558     if (numtabs == 1) {
1559       Num_matched = 0;
1560       strfcpy (User_typed, pt, sizeof (User_typed));
1561       memset (Matches, 0, sizeof (Matches));
1562       memset (Completed, 0, sizeof (Completed));
1563       for (num = 0; Commands[num].name; num++)
1564         candidate (Completed, User_typed, Commands[num].name,
1565                    sizeof (Completed));
1566       Matches[Num_matched++] = User_typed;
1567
1568       /* All matches are stored. Longest non-ambiguous string is ""
1569        * i.e. dont change 'buffer'. Fake successful return this time */
1570       if (User_typed[0] == 0)
1571         return 1;
1572     }
1573
1574     if (Completed[0] == 0 && User_typed[0])
1575       return 0;
1576
1577     /* Num_matched will _always_ be atleast 1 since the initial
1578      * user-typed string is always stored */
1579     if (numtabs == 1 && Num_matched == 2)
1580       snprintf (Completed, sizeof (Completed), "%s", Matches[0]);
1581     else if (numtabs > 1 && Num_matched > 2)
1582       /* cycle thru all the matches */
1583       snprintf (Completed, sizeof (Completed), "%s",
1584                 Matches[(numtabs - 2) % Num_matched]);
1585
1586     /* return the completed command */
1587     strncpy (buffer, Completed, len - spaces);
1588   }
1589   else if (!str_ncmp (buffer, "set", 3)
1590            || !str_ncmp (buffer, "unset", 5)
1591            || !str_ncmp (buffer, "reset", 5)
1592            || !str_ncmp (buffer, "toggle", 6)) {    /* complete variables */
1593     char *prefixes[] = { "no", "inv", "?", "&", 0 };
1594
1595     pt++;
1596     /* loop through all the possible prefixes (no, inv, ...) */
1597     if (!str_ncmp (buffer, "set", 3)) {
1598       for (num = 0; prefixes[num]; num++) {
1599         if (!str_ncmp (pt, prefixes[num], str_len (prefixes[num]))) {
1600           pt += str_len (prefixes[num]);
1601           break;
1602         }
1603       }
1604     }
1605
1606     /* first TAB. Collect all the matches */
1607     if (numtabs == 1) {
1608       Num_matched = 0;
1609       strfcpy (User_typed, pt, sizeof (User_typed));
1610       memset (Matches, 0, sizeof (Matches));
1611       memset (Completed, 0, sizeof (Completed));
1612       for (num = 0; MuttVars[num].option; num++)
1613         candidate (Completed, User_typed, MuttVars[num].option,
1614                    sizeof (Completed));
1615       Matches[Num_matched++] = User_typed;
1616
1617       /* All matches are stored. Longest non-ambiguous string is ""
1618        * i.e. dont change 'buffer'. Fake successful return this time */
1619       if (User_typed[0] == 0)
1620         return 1;
1621     }
1622
1623     if (Completed[0] == 0 && User_typed[0])
1624       return 0;
1625
1626     /* Num_matched will _always_ be atleast 1 since the initial
1627      * user-typed string is always stored */
1628     if (numtabs == 1 && Num_matched == 2)
1629       snprintf (Completed, sizeof (Completed), "%s", Matches[0]);
1630     else if (numtabs > 1 && Num_matched > 2)
1631       /* cycle thru all the matches */
1632       snprintf (Completed, sizeof (Completed), "%s",
1633                 Matches[(numtabs - 2) % Num_matched]);
1634
1635     strncpy (pt, Completed, buffer + len - pt - spaces);
1636   }
1637   else if (!str_ncmp (buffer, "exec", 4)) {
1638     struct binding_t *menu = km_get_table (CurrentMenu);
1639
1640     if (!menu && CurrentMenu != MENU_PAGER)
1641       menu = OpGeneric;
1642
1643     pt++;
1644     /* first TAB. Collect all the matches */
1645     if (numtabs == 1) {
1646       Num_matched = 0;
1647       strfcpy (User_typed, pt, sizeof (User_typed));
1648       memset (Matches, 0, sizeof (Matches));
1649       memset (Completed, 0, sizeof (Completed));
1650       for (num = 0; menu[num].name; num++)
1651         candidate (Completed, User_typed, menu[num].name, sizeof (Completed));
1652       /* try the generic menu */
1653       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
1654         menu = OpGeneric;
1655         for (num = 0; menu[num].name; num++)
1656           candidate (Completed, User_typed, menu[num].name,
1657                      sizeof (Completed));
1658       }
1659       Matches[Num_matched++] = User_typed;
1660
1661       /* All matches are stored. Longest non-ambiguous string is ""
1662        * i.e. dont change 'buffer'. Fake successful return this time */
1663       if (User_typed[0] == 0)
1664         return 1;
1665     }
1666
1667     if (Completed[0] == 0 && User_typed[0])
1668       return 0;
1669
1670     /* Num_matched will _always_ be atleast 1 since the initial
1671      * user-typed string is always stored */
1672     if (numtabs == 1 && Num_matched == 2)
1673       snprintf (Completed, sizeof (Completed), "%s", Matches[0]);
1674     else if (numtabs > 1 && Num_matched > 2)
1675       /* cycle thru all the matches */
1676       snprintf (Completed, sizeof (Completed), "%s",
1677                 Matches[(numtabs - 2) % Num_matched]);
1678
1679     strncpy (pt, Completed, buffer + len - pt - spaces);
1680   }
1681   else
1682     return 0;
1683
1684   return 1;
1685 }
1686
1687 int mutt_var_value_complete (char *buffer, size_t len, int pos)
1688 {
1689   char var[STRING], *pt = buffer;
1690   int spaces;
1691   struct option_t* option = NULL;
1692
1693   if (buffer[0] == 0)
1694     return 0;
1695
1696   SKIPWS (buffer);
1697   spaces = buffer - pt;
1698
1699   pt = buffer + pos - spaces;
1700   while ((pt > buffer) && !isspace ((unsigned char) *pt))
1701     pt--;
1702   pt++;                         /* move past the space */
1703   if (*pt == '=')               /* abort if no var before the '=' */
1704     return 0;
1705
1706   if (str_ncmp (buffer, "set", 3) == 0) {
1707     strfcpy (var, pt, sizeof (var));
1708     /* ignore the trailing '=' when comparing */
1709     var[str_len (var) - 1] = 0;
1710     if (!(option = hash_find (ConfigOptions, var)))
1711       return 0;                 /* no such variable. */
1712     else {
1713       char tmp[LONG_STRING], tmp2[LONG_STRING];
1714       char *s, *d;
1715       size_t dlen = buffer + len - pt - spaces;
1716       char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1717
1718       tmp[0] = '\0';
1719
1720       if ((DTYPE (option->type) == DT_STR) ||
1721           (DTYPE (option->type) == DT_PATH) ||
1722           (DTYPE (option->type) == DT_RX)) {
1723         strfcpy (tmp, NONULL (*((char **) option->data)), sizeof (tmp));
1724         if (DTYPE (option->type) == DT_PATH)
1725           mutt_pretty_mailbox (tmp);
1726       }
1727       else if (DTYPE (option->type) == DT_ADDR) {
1728         rfc822_write_address (tmp, sizeof (tmp),
1729                               *((ADDRESS **) option->data), 0);
1730       }
1731       else if (DTYPE (option->type) == DT_QUAD)
1732         strfcpy (tmp, vals[quadoption (option->data)], sizeof (tmp));
1733       else if (DTYPE (option->type) == DT_NUM)
1734         snprintf (tmp, sizeof (tmp), "%d", (*((short *) option->data)));
1735       else if (DTYPE (option->type) == DT_SORT) {
1736         const struct mapping_t *map;
1737         char *p;
1738
1739         switch (option->type & DT_SUBTYPE_MASK) {
1740         case DT_SORT_ALIAS:
1741           map = SortAliasMethods;
1742           break;
1743         case DT_SORT_BROWSER:
1744           map = SortBrowserMethods;
1745           break;
1746         case DT_SORT_KEYS:
1747           if ((WithCrypto & APPLICATION_PGP))
1748             map = SortKeyMethods;
1749           else
1750             map = SortMethods;
1751           break;
1752         default:
1753           map = SortMethods;
1754           break;
1755         }
1756         p =
1757           mutt_getnamebyvalue (*((short *) option->data) & SORT_MASK,
1758                                map);
1759         snprintf (tmp, sizeof (tmp), "%s%s%s",
1760                   (*((short *) option->data) & SORT_REVERSE) ?
1761                   "reverse-" : "",
1762                   (*((short *) option->data) & SORT_LAST) ? "last-" :
1763                   "", p);
1764       } 
1765       else if (DTYPE (option->type) == DT_MAGIC) {
1766         char *p;
1767         switch (DefaultMagic) {
1768           case M_MBOX:
1769             p = "mbox";
1770             break;
1771           case M_MMDF:
1772             p = "MMDF";
1773             break;
1774           case M_MH:
1775             p = "MH";
1776           break;
1777           case M_MAILDIR:
1778             p = "Maildir";
1779             break;
1780           default:
1781             p = "unknown";
1782         }
1783         strfcpy (tmp, p, sizeof (tmp));
1784       }
1785       else if (DTYPE (option->type) == DT_BOOL)
1786         strfcpy (tmp, option (option->data) ? "yes" : "no",
1787                  sizeof (tmp));
1788       else
1789         return 0;
1790
1791       for (s = tmp, d = tmp2; *s && (d - tmp2) < sizeof (tmp2) - 2;) {
1792         if (*s == '\\' || *s == '"')
1793           *d++ = '\\';
1794         *d++ = *s++;
1795       }
1796       *d = '\0';
1797
1798       strfcpy (tmp, pt, sizeof (tmp));
1799       snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
1800
1801       return 1;
1802     }
1803   }
1804   return 0;
1805 }
1806
1807 /* Implement the -Q command line flag */
1808 int mutt_query_variables (LIST * queries)
1809 {
1810   LIST *p;
1811
1812   char errbuff[STRING];
1813   char command[STRING];
1814
1815   BUFFER err, token;
1816
1817   memset (&err, 0, sizeof (err));
1818   memset (&token, 0, sizeof (token));
1819
1820   err.data = errbuff;
1821   err.dsize = sizeof (errbuff);
1822
1823   for (p = queries; p; p = p->next) {
1824     snprintf (command, sizeof (command), "set ?%s\n", p->data);
1825     if (mutt_parse_rc_line (command, &token, &err) == -1) {
1826       fprintf (stderr, "%s\n", err.data);
1827       mem_free (&token.data);
1828       return 1;
1829     }
1830     printf ("%s\n", err.data);
1831   }
1832
1833   mem_free (&token.data);
1834   return 0;
1835 }
1836
1837 char *mutt_getnamebyvalue (int val, const struct mapping_t *map)
1838 {
1839   int i;
1840
1841   for (i = 0; map[i].name; i++)
1842     if (map[i].value == val)
1843       return (map[i].name);
1844   return NULL;
1845 }
1846
1847 int mutt_getvaluebyname (const char *name, const struct mapping_t *map)
1848 {
1849   int i;
1850
1851   for (i = 0; map[i].name; i++)
1852     if (ascii_strcasecmp (map[i].name, name) == 0)
1853       return (map[i].value);
1854   return (-1);
1855 }
1856
1857 static int mutt_execute_commands (LIST * p)
1858 {
1859   BUFFER err, token;
1860   char errstr[SHORT_STRING];
1861
1862   memset (&err, 0, sizeof (err));
1863   err.data = errstr;
1864   err.dsize = sizeof (errstr);
1865   memset (&token, 0, sizeof (token));
1866   for (; p; p = p->next) {
1867     if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
1868       fprintf (stderr, _("Error in command line: %s\n"), err.data);
1869       mem_free (&token.data);
1870       return (-1);
1871     }
1872   }
1873   mem_free (&token.data);
1874   return 0;
1875 }
1876
1877 void mutt_init (int skip_sys_rc, LIST * commands)
1878 {
1879   struct passwd *pw;
1880   struct utsname utsname;
1881   char *p, buffer[STRING], error[STRING];
1882   int i, default_rc = 0, need_pause = 0;
1883   BUFFER err;
1884
1885   memset (&err, 0, sizeof (err));
1886   err.data = error;
1887   err.dsize = sizeof (error);
1888
1889   /* use 3*sizeof(muttvars) to have some room for $user_ vars */
1890   ConfigOptions = hash_create (sizeof (MuttVars) * 3);
1891   for (i = 0; MuttVars[i].option; i++)
1892     hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i], 0);
1893
1894   /* 
1895    * XXX - use something even more difficult to predict?
1896    */
1897   snprintf (AttachmentMarker, sizeof (AttachmentMarker),
1898             "\033]9;%ld\a", (long) time (NULL));
1899
1900   /* on one of the systems I use, getcwd() does not return the same prefix
1901      as is listed in the passwd file */
1902   if ((p = getenv ("HOME")))
1903     Homedir = str_dup (p);
1904
1905   /* Get some information about the user */
1906   if ((pw = getpwuid (getuid ()))) {
1907     char rnbuf[STRING];
1908
1909     Username = str_dup (pw->pw_name);
1910     if (!Homedir)
1911       Homedir = str_dup (pw->pw_dir);
1912
1913     Realname = str_dup (mutt_gecos_name (rnbuf, sizeof (rnbuf), pw));
1914     Shell = str_dup (pw->pw_shell);
1915   }
1916   else {
1917     if (!Homedir) {
1918       mutt_endwin (NULL);
1919       fputs (_("unable to determine home directory"), stderr);
1920       exit (1);
1921     }
1922     if ((p = getenv ("USER")))
1923       Username = str_dup (p);
1924     else {
1925       mutt_endwin (NULL);
1926       fputs (_("unable to determine username"), stderr);
1927       exit (1);
1928     }
1929     Shell = str_dup ((p = getenv ("SHELL")) ? p : "/bin/sh");
1930   }
1931
1932   debug_start(Homedir);
1933
1934   /* And about the host... */
1935   uname (&utsname);
1936   /* some systems report the FQDN instead of just the hostname */
1937   if ((p = strchr (utsname.nodename, '.'))) {
1938     Hostname = str_substrdup (utsname.nodename, p);
1939     p++;
1940     strfcpy (buffer, p, sizeof (buffer));       /* save the domain for below */
1941   }
1942   else
1943     Hostname = str_dup (utsname.nodename);
1944
1945 #ifndef DOMAIN
1946 #define DOMAIN buffer
1947   if (!p && getdnsdomainname (buffer, sizeof (buffer)) == -1)
1948     Fqdn = str_dup ("@");
1949   else
1950 #endif /* DOMAIN */
1951   if (*DOMAIN != '@') {
1952     Fqdn = mem_malloc (str_len (DOMAIN) + str_len (Hostname) + 2);
1953     sprintf (Fqdn, "%s.%s", NONULL (Hostname), DOMAIN); /* __SPRINTF_CHECKED__ */
1954   }
1955   else
1956     Fqdn = str_dup (NONULL (Hostname));
1957
1958 #ifdef USE_NNTP
1959   {
1960     FILE *f;
1961     char *i;
1962
1963     if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
1964       buffer[0] = '\0';
1965       fgets (buffer, sizeof (buffer), f);
1966       p = (char*) &buffer;
1967       SKIPWS (p);
1968       i = p;
1969       while (*i && (*i != ' ') && (*i != '\t') && (*i != '\r')
1970              && (*i != '\n'))
1971         i++;
1972       *i = '\0';
1973       NewsServer = str_dup (p);
1974       fclose (f);
1975     }
1976   }
1977   if ((p = getenv ("NNTPSERVER")))
1978     NewsServer = str_dup (p);
1979 #endif
1980
1981   if ((p = getenv ("MAIL")))
1982     Spoolfile = str_dup (p);
1983   else if ((p = getenv ("MAILDIR")))
1984     Spoolfile = str_dup (p);
1985   else {
1986 #ifdef HOMESPOOL
1987     mutt_concat_path (buffer, NONULL (Homedir), MAILPATH, sizeof (buffer));
1988 #else
1989     mutt_concat_path (buffer, MAILPATH, NONULL (Username), sizeof (buffer));
1990 #endif
1991     Spoolfile = str_dup (buffer);
1992   }
1993
1994   if ((p = getenv ("MAILCAPS")))
1995     MailcapPath = str_dup (p);
1996   else {
1997     /* Default search path from RFC1524 */
1998     MailcapPath =
1999       str_dup ("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR
2000                    "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap");
2001   }
2002
2003   Tempdir = str_dup ((p = getenv ("TMPDIR")) ? p : "/tmp");
2004
2005   p = getenv ("VISUAL");
2006   if (!p) {
2007     p = getenv ("EDITOR");
2008     if (!p)
2009       p = "vi";
2010   }
2011   Editor = str_dup (p);
2012   Visual = str_dup (p);
2013
2014   if ((p = getenv ("REPLYTO")) != NULL) {
2015     BUFFER buf, token;
2016
2017     snprintf (buffer, sizeof (buffer), "Reply-To: %s", p);
2018
2019     memset (&buf, 0, sizeof (buf));
2020     buf.data = buf.dptr = buffer;
2021     buf.dsize = str_len (buffer);
2022
2023     memset (&token, 0, sizeof (token));
2024     parse_my_hdr (&token, &buf, 0, &err);
2025     mem_free (&token.data);
2026   }
2027
2028   if ((p = getenv ("EMAIL")) != NULL)
2029     From = rfc822_parse_adrlist (NULL, p);
2030
2031   mutt_set_langinfo_charset ();
2032   mutt_set_charset (Charset);
2033
2034
2035   /* Set standard defaults */
2036   hash_map (ConfigOptions, mutt_set_default, 0);
2037   hash_map (ConfigOptions, mutt_restore_default, 0);
2038
2039   CurrentMenu = MENU_MAIN;
2040
2041
2042 #ifndef LOCALES_HACK
2043   /* Do we have a locale definition? */
2044   if (((p = getenv ("LC_ALL")) != NULL && p[0]) ||
2045       ((p = getenv ("LANG")) != NULL && p[0]) ||
2046       ((p = getenv ("LC_CTYPE")) != NULL && p[0]))
2047     set_option (OPTLOCALES);
2048 #endif
2049
2050 #ifdef HAVE_GETSID
2051   /* Unset suspend by default if we're the session leader */
2052   if (getsid (0) == getpid ())
2053     unset_option (OPTSUSPEND);
2054 #endif
2055
2056   mutt_init_history ();
2057
2058
2059
2060
2061   /*
2062    * 
2063    *                       BIG FAT WARNING
2064    * 
2065    * When changing the code which looks for a configuration file,
2066    * please also change the corresponding code in muttbug.sh.in.
2067    * 
2068    * 
2069    */
2070
2071
2072
2073
2074   if (!Muttrc) {
2075 #if 0
2076     snprintf (buffer, sizeof (buffer), "%s/.muttngrc-%s", NONULL (Homedir),
2077               MUTT_VERSION);
2078     if (access (buffer, F_OK) == -1)
2079 #endif
2080       snprintf (buffer, sizeof (buffer), "%s/.muttngrc", NONULL (Homedir));
2081     if (access (buffer, F_OK) == -1)
2082 #if 0
2083       snprintf (buffer, sizeof (buffer), "%s/.muttng/muttngrc-%s",
2084                 NONULL (Homedir), MUTT_VERSION);
2085     if (access (buffer, F_OK) == -1)
2086 #endif
2087       snprintf (buffer, sizeof (buffer), "%s/.muttng/muttngrc",
2088                 NONULL (Homedir));
2089
2090     default_rc = 1;
2091     Muttrc = str_dup (buffer);
2092   }
2093   else {
2094     strfcpy (buffer, Muttrc, sizeof (buffer));
2095     mem_free (&Muttrc);
2096     mutt_expand_path (buffer, sizeof (buffer));
2097     Muttrc = str_dup (buffer);
2098   }
2099   mem_free (&AliasFile);
2100   AliasFile = str_dup (NONULL (Muttrc));
2101
2102   /* Process the global rc file if it exists and the user hasn't explicity
2103      requested not to via "-n".  */
2104   if (!skip_sys_rc) {
2105     snprintf (buffer, sizeof (buffer), "%s/Muttngrc-%s", SYSCONFDIR,
2106               MUTT_VERSION);
2107     if (access (buffer, F_OK) == -1)
2108       snprintf (buffer, sizeof (buffer), "%s/Muttngrc", SYSCONFDIR);
2109     if (access (buffer, F_OK) == -1)
2110       snprintf (buffer, sizeof (buffer), "%s/Muttngrc-%s", PKGDATADIR,
2111                 MUTT_VERSION);
2112     if (access (buffer, F_OK) == -1)
2113       snprintf (buffer, sizeof (buffer), "%s/Muttngrc", PKGDATADIR);
2114     if (access (buffer, F_OK) != -1) {
2115       if (source_rc (buffer, &err) != 0) {
2116         fputs (err.data, stderr);
2117         fputc ('\n', stderr);
2118         need_pause = 1;
2119       }
2120     }
2121   }
2122
2123   /* Read the user's initialization file.  */
2124   if (access (Muttrc, F_OK) != -1) {
2125     if (!option (OPTNOCURSES))
2126       endwin ();
2127     if (source_rc (Muttrc, &err) != 0) {
2128       fputs (err.data, stderr);
2129       fputc ('\n', stderr);
2130       need_pause = 1;
2131     }
2132   }
2133   else if (!default_rc) {
2134     /* file specified by -F does not exist */
2135     snprintf (buffer, sizeof (buffer), "%s: %s", Muttrc, strerror (errno));
2136     mutt_endwin (buffer);
2137     exit (1);
2138   }
2139
2140   if (mutt_execute_commands (commands) != 0)
2141     need_pause = 1;
2142
2143   /* warn about synonym variables */
2144   if (!list_empty(Synonyms)) {
2145     int i = 0;
2146     fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
2147     for (i = 0; i < Synonyms->length; i++) {
2148       struct option_t* newopt = NULL, *oldopt = NULL;
2149       newopt = (struct option_t*) ((syn_t*) Synonyms->data[i])->n;
2150       oldopt = (struct option_t*) ((syn_t*) Synonyms->data[i])->o;
2151       fprintf (stderr, "$%s ($%s should be used) (%s:%d)\n",
2152                oldopt ? NONULL (oldopt->option) : "",
2153                newopt ? NONULL (newopt->option) : "",
2154                NONULL(((syn_t*) Synonyms->data[i])->f),
2155                ((syn_t*) Synonyms->data[i])->l);
2156     }
2157     fprintf (stderr, _("Warning: synonym variables are scheduled"
2158                        " for removal.\n"));
2159     list_del (&Synonyms, syn_del);
2160     need_pause = 1;
2161   }
2162
2163   if (need_pause && !option (OPTNOCURSES)) {
2164     if (mutt_any_key_to_continue (NULL) == -1)
2165       mutt_exit (1);
2166   }
2167
2168 #if 0
2169   set_option (OPTWEED);         /* turn weeding on by default */
2170 #endif
2171 }
2172
2173 int mutt_get_hook_type (const char *name)
2174 {
2175   struct command_t *c;
2176
2177   for (c = Commands; c->name; c++)
2178     if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
2179       return c->data;
2180   return 0;
2181 }
2182
2183 /* compare two option_t*'s for sorting -t/-T output */
2184 static int opt_cmp (const void* a, const void* b) {
2185   return (str_cmp ((*(struct option_t**) a)->option,
2186                        (*(struct option_t**) b)->option));
2187 }
2188
2189 /* callback for hash_map() to put all non-synonym vars into list */
2190 static void opt_sel_full (const char* key, void* data,
2191                           unsigned long more) {
2192   list2_t** l = (list2_t**) more;
2193   struct option_t* option = (struct option_t*) data;
2194
2195   if (DTYPE (option->type) == DT_SYN)
2196     return;
2197   list_push_back (l, option);
2198 }
2199
2200 /* callback for hash_map() to put all changed non-synonym vars into list */
2201 static void opt_sel_diff (const char* key, void* data,
2202                           unsigned long more) {
2203   list2_t** l = (list2_t**) more;
2204   struct option_t* option = (struct option_t*) data;
2205   char buf[LONG_STRING];
2206
2207   if (DTYPE (option->type) == DT_SYN)
2208     return;
2209
2210   mutt_option_value (option->option, buf, sizeof (buf));
2211   if (str_cmp (buf, option->init) != 0)
2212     list_push_back (l, option);
2213 }
2214
2215 /* dump out the value of all the variables we have */
2216 int mutt_dump_variables (int full) {
2217   int i = 0;
2218   char outbuf[STRING];
2219   list2_t* tmp = NULL;
2220   struct option_t* option = NULL;
2221
2222   /* get all non-synonyms into list... */
2223   hash_map (ConfigOptions, full ? opt_sel_full : opt_sel_diff,
2224             (unsigned long) &tmp);
2225
2226   if (!list_empty(tmp)) {
2227     /* ...and dump list sorted */
2228     qsort (tmp->data, tmp->length, sizeof (void*), opt_cmp);
2229     for (i = 0; i < tmp->length; i++) {
2230       option = (struct option_t*) tmp->data[i];
2231       FuncTable[DTYPE (option->type)].opt_to_string
2232         (outbuf, sizeof (outbuf), option);
2233       printf ("%s\n", outbuf);
2234     }
2235   }
2236   list_del (&tmp, NULL);
2237   return 0;
2238 }