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