rework includes a bit
[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 <ctype.h>
18 #include <stdlib.h>
19 #include <unistd.h>
20 #include <string.h>
21 #include <sys/utsname.h>
22 #include <errno.h>
23 #include <sys/wait.h>
24
25 #include <lib-lib/mem.h>
26 #include <lib-lib/str.h>
27 #include <lib-lib/file.h>
28 #include <lib-lib/ascii.h>
29 #include <lib-lib/macros.h>
30 #include <lib-lib/buffer.h>
31 #include <lib-lib/mapping.h>
32 #include <lib-lib/debug.h>
33 #include <lib-lib/rx.h>
34
35 #include <lib-sys/unix.h>
36 #include <lib-sys/mutt_ssl.h>
37
38 #include <lib-ui/curses.h>
39 #include <lib-ui/history.h>
40
41 #include "mutt.h"
42 #include "keymap.h"
43 #include "charset.h"
44 #include "thread.h"
45 #include <lib-crypt/crypt.h>
46 #include "mutt_idna.h"
47
48 #if defined (USE_LIBESMTP) && (defined (USE_SSL) || defined (USE_GNUTLS))
49 #include "mutt_libesmtp.h"
50 #endif
51
52 #include "alias.h"
53 #include "mx.h"
54 #include "init.h"
55
56 #include "lib/list.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, ssize_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, ssize_t errlen);
86 static int check_dsn_notify (const char* option, unsigned long val,
87                              char* errbuf, ssize_t errlen);
88 static int check_history    (const char* option, unsigned long val,
89                              char* errbuf, ssize_t errlen);
90 /* this checks that numbers are >= 0 */
91 static int check_num        (const char* option, unsigned long val,
92                              char* errbuf, ssize_t errlen);
93 #ifdef DEBUG
94 static int check_debug      (const char* option, unsigned long val,
95                              char* errbuf, ssize_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, ssize_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, ssize_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, ssize_t dstlen, struct option_t* option);
127 static void num_to_string   (char* dst, ssize_t dstlen, struct option_t* option);
128 static void str_to_string   (char* dst, ssize_t dstlen, struct option_t* option);
129 static void quad_to_string  (char* dst, ssize_t dstlen, struct option_t* option);
130 static void sort_to_string  (char* dst, ssize_t dstlen, struct option_t* option);
131 static void rx_to_string    (char* dst, ssize_t dstlen, struct option_t* option);
132 static void magic_to_string (char* dst, ssize_t dstlen, struct option_t* option);
133 static void addr_to_string  (char* dst, ssize_t dstlen, struct option_t* option);
134 static void user_to_string  (char* dst, ssize_t dstlen, struct option_t* option);
135 static void sys_to_string   (char* dst, ssize_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, ssize_t errlen);
140 static int num_from_string   (struct option_t* dst, const char* val,
141                               char* errbuf, ssize_t errlen);
142 static int str_from_string   (struct option_t* dst, const char* val,
143                               char* errbuf, ssize_t errlen);
144 static int path_from_string  (struct option_t* dst, const char* val,
145                               char* errbuf, ssize_t errlen);
146 static int quad_from_string  (struct option_t* dst, const char* val,
147                               char* errbuf, ssize_t errlen);
148 static int sort_from_string  (struct option_t* dst, const char* val,
149                               char* errbuf, ssize_t errlen);
150 static int rx_from_string    (struct option_t* dst, const char* val,
151                               char* errbuf, ssize_t errlen);
152 static int magic_from_string (struct option_t* dst, const char* val,
153                               char* errbuf, ssize_t errlen);
154 static int addr_from_string  (struct option_t* dst, const char* val,
155                               char* errbuf, ssize_t errlen);
156 static int user_from_string  (struct option_t* dst, const char* val,
157                               char* errbuf, ssize_t errlen);
158
159 static struct {
160   unsigned short type;
161   void (*opt_to_string) (char* dst, ssize_t dstlen, struct option_t* option);
162   int (*opt_from_string) (struct option_t* dst, const char* val,
163                           char* errbuf, ssize_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, ssize_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                              ssize_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, ssize_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, ssize_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, ssize_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, ssize_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, ssize_t dstlen,
262                            struct option_t* option) {
263   char *val = NULL, *t = NULL;
264   int clean = 0;
265
266   /* get some $madmutt_ values dynamically */
267   if (ascii_strcmp ("madmutt_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 ("madmutt_folder_path", option->option) == 0 &&
272              CurrentFolder && *CurrentFolder) {
273     val = CurrentFolder;
274   } else if (ascii_strcmp ("madmutt_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)), ssize_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, ssize_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)), ssize_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, ssize_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)), ssize_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, ssize_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, ssize_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, ssize_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, ssize_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, ssize_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)), ssize_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, ssize_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)), ssize_t errlen __attribute__ ((unused))) {
527   if (!dst)
528     return (0);
529   address_list_wipe((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, ssize_t dstlen) {
536   struct option_t* option = NULL;
537   char* tmp = NULL, *t = NULL;
538   ssize_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 (string_list_t ** list, const char *str)
627 {
628   string_list_t *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(string_list_t, 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_delete(&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_delete(&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_delete(&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 (string_list_t ** l, const char *str)
781 {
782   string_list_t *p, *last = NULL;
783
784   if (m_strcmp("*", str) == 0)
785     string_list_wipe(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_delete);
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_delete(&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 ((string_list_t **) 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_delete);
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       string_list_wipe((string_list_t **) data);
1058       break;
1059     }
1060     remove_from_list ((string_list_t **) 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, string_list_t **ldata,
1093                               BUFFER *err __attribute__ ((unused))) {
1094   ATTACH_MATCH *a;
1095   string_list_t *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(string_list_t, 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, string_list_t **ldata,
1166                                 BUFFER *err __attribute__ ((unused))) {
1167   ATTACH_MATCH *a;
1168   string_list_t *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 string_list_t->data, not anything pointed to by the string_list_t->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 (string_list_t *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   string_list_t **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   string_list_t **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_t *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         alias_list_wipe(&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           alias_list_wipe(&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_t *tmp = Aliases;
1428   alias_t *last = NULL;
1429   const 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 = alias_new();
1450     tmp->name = m_strdup(buf->data);
1451     /* give the main addressbook code a chance */
1452     if (CurrentMenu == MENU_ALIAS)
1453       set_option (OPTMENUCALLER);
1454   }
1455   else {
1456     /* override the previous value */
1457     address_list_wipe(&tmp->addr);
1458     if (CurrentMenu == MENU_ALIAS)
1459       set_option (OPTFORCEREDRAWINDEX);
1460   }
1461
1462   mutt_extract_token (buf, s,
1463                       M_TOKEN_QUOTE | M_TOKEN_SPACE | M_TOKEN_SEMICOLON);
1464   debug_print (2, ("second token is '%s'.\n", buf->data));
1465   tmp->addr = mutt_parse_adrlist (tmp->addr, buf->data);
1466   if (last)
1467     last->next = tmp;
1468   else
1469     Aliases = tmp;
1470   if (mutt_addrlist_to_idna (tmp->addr, &estr)) {
1471     snprintf (err->data, err->dsize,
1472               _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, tmp->name);
1473     return -1;
1474   }
1475 #ifdef DEBUG
1476   if (DebugLevel >= 2) {
1477     address_t *a;
1478
1479     /* A group is terminated with an empty address, so check a->mailbox */
1480     for (a = tmp->addr; a && a->mailbox; a = a->next) {
1481       if (!a->group)
1482         debug_print (2, ("%s\n", a->mailbox));
1483       else
1484         debug_print (2, ("group %s\n", a->mailbox));
1485     }
1486   }
1487 #endif
1488   return 0;
1489 }
1490
1491 static int
1492 parse_unmy_hdr (BUFFER * buf, BUFFER * s,
1493                 unsigned long data __attribute__ ((unused)),
1494                 BUFFER * err __attribute__ ((unused)))
1495 {
1496   string_list_t *last = NULL;
1497   string_list_t *tmp = UserHeader;
1498   string_list_t *ptr;
1499   ssize_t l;
1500
1501   do {
1502     mutt_extract_token (buf, s, 0);
1503     if (m_strcmp("*", buf->data) == 0)
1504       string_list_wipe(&UserHeader);
1505     else {
1506       tmp = UserHeader;
1507       last = NULL;
1508
1509       l = m_strlen(buf->data);
1510       if (buf->data[l - 1] == ':')
1511         l--;
1512
1513       while (tmp) {
1514         if (ascii_strncasecmp (buf->data, tmp->data, l) == 0
1515             && tmp->data[l] == ':') {
1516           ptr = tmp;
1517           if (last)
1518             last->next = tmp->next;
1519           else
1520             UserHeader = tmp->next;
1521           tmp = tmp->next;
1522           ptr->next = NULL;
1523           string_list_wipe(&ptr);
1524         }
1525         else {
1526           last = tmp;
1527           tmp = tmp->next;
1528         }
1529       }
1530     }
1531   }
1532   while (MoreArgs (s));
1533   return 0;
1534 }
1535
1536 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
1537                          BUFFER * err)
1538 {
1539   string_list_t *tmp;
1540   ssize_t keylen;
1541   char *p;
1542
1543   mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
1544   if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
1545     m_strcpy(err->data, err->dsize, _("invalid header field"));
1546     return (-1);
1547   }
1548   keylen = p - buf->data + 1;
1549
1550   if (UserHeader) {
1551     for (tmp = UserHeader;; tmp = tmp->next) {
1552       /* see if there is already a field by this name */
1553       if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
1554         /* replace the old value */
1555         p_delete(&tmp->data);
1556         tmp->data = buf->data;
1557         p_clear(buf, 1);
1558         return 0;
1559       }
1560       if (!tmp->next)
1561         break;
1562     }
1563     tmp->next = string_item_new();
1564     tmp = tmp->next;
1565   }
1566   else {
1567     tmp = string_item_new();
1568     UserHeader = tmp;
1569   }
1570   tmp->data = buf->data;
1571   p_clear(buf, 1);
1572   return 0;
1573 }
1574
1575 static int
1576 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
1577             char* errbuf, ssize_t errlen) {
1578   int i, flags = 0;
1579
1580   if (m_strncmp("reverse-", s, 8) == 0) {
1581     s += 8;
1582     flags = SORT_REVERSE;
1583   }
1584
1585   if (m_strncmp("last-", s, 5) == 0) {
1586     s += 5;
1587     flags |= SORT_LAST;
1588   }
1589
1590   if ((i = mutt_getvaluebyname (s, map)) == -1) {
1591     if (errbuf)
1592       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
1593     return (-1);
1594   }
1595
1596   *((short*) dst->data) = i | flags;
1597   return 0;
1598 }
1599
1600 /* if additional data more == 1, we want to resolve synonyms */
1601 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
1602 {
1603     char buf[LONG_STRING];
1604     struct option_t *ptr = p;
1605
1606     if (DTYPE(ptr->type) == DT_SYN) {
1607         if (!more)
1608             return;
1609         ptr = hash_find(ConfigOptions, (const char *)ptr->data);
1610     }
1611     if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_from_string)
1612         return;
1613
1614     mutt_option_value(ptr->option, buf, sizeof(buf));
1615     if (m_strlen(ptr->init) == 0 && buf && *buf)
1616         ptr->init = m_strdup(buf);
1617 }
1618
1619 static struct option_t* add_option (const char* name, const char* init,
1620                                     short type, short dodup) {
1621   struct option_t* option = p_new(struct option_t, 1);
1622
1623   debug_print (1, ("adding $%s\n", name));
1624
1625   option->option = m_strdup(name);
1626   option->type = type;
1627   if (init)
1628     option->init = dodup ? m_strdup(init) : (char*) init;
1629   return (option);
1630 }
1631
1632 /* creates new option_t* of type DT_USER for $user_ var */
1633 static struct option_t* add_user_option (const char* name) {
1634   return (add_option (name, NULL, DT_USER, 1));
1635 }
1636
1637 /* free()'s option_t* */
1638 static void del_option (void* p) {
1639   struct option_t *ptr = (struct option_t*) p;
1640   char* s = (char*) ptr->data;
1641   debug_print (1, ("removing option '%s' from table\n", NONULL (ptr->option)));
1642   p_delete(&ptr->option);
1643   p_delete(&s);
1644   p_delete(&ptr->init);
1645   p_delete(&ptr);
1646 }
1647
1648 static int init_expand (char** dst, struct option_t* src) {
1649   BUFFER token, in;
1650   ssize_t len = 0;
1651
1652   p_delete(dst);
1653
1654   if (DTYPE(src->type) == DT_STR ||
1655       DTYPE(src->type) == DT_PATH) {
1656     /* only expand for string as it's the only place where
1657      * we want to expand vars right now */
1658     if (src->init && *src->init) {
1659       p_clear(&token, 1);
1660       p_clear(&in, 1);
1661       len = m_strlen(src->init) + 2;
1662       in.data = p_new(char, len + 1);
1663       snprintf (in.data, len, "\"%s\"", src->init);
1664       in.dptr = in.data;
1665       in.dsize = len;
1666       mutt_extract_token (&token, &in, 0);
1667       if (token.data && *token.data)
1668         *dst = m_strdup(token.data);
1669       else
1670         *dst = m_strdup("");
1671       p_delete(&in.data);
1672       p_delete(&token.data);
1673     } else
1674       *dst = m_strdup("");
1675   } else
1676     /* for non-string: take value as is */
1677     *dst = m_strdup(src->init);
1678   return (1);
1679 }
1680
1681 /* if additional data more == 1, we want to resolve synonyms */
1682 static void mutt_restore_default (const char* name __attribute__ ((unused)),
1683                                   void* p, unsigned long more) {
1684   char errbuf[STRING];
1685   struct option_t* ptr = (struct option_t*) p;
1686   char* init = NULL;
1687
1688   if (DTYPE (ptr->type) == DT_SYN) {
1689     if (!more)
1690       return;
1691     ptr = hash_find (ConfigOptions, (char*) ptr->data);
1692   }
1693   if (!ptr)
1694     return;
1695   if (FuncTable[DTYPE (ptr->type)].opt_from_string) {
1696     init_expand (&init, ptr);
1697     if (!FuncTable[DTYPE (ptr->type)].opt_from_string (ptr, init, errbuf,
1698                                                        sizeof(errbuf))) {
1699       if (!option (OPTNOCURSES))
1700         mutt_endwin (NULL);
1701       fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
1702                          "Please report this error: \"%s\"\n"),
1703                ptr->option, NONULL (init), errbuf);
1704       exit (1);
1705     }
1706     p_delete(&init);
1707   }
1708
1709   if (ptr->flags & R_INDEX)
1710     set_option (OPTFORCEREDRAWINDEX);
1711   if (ptr->flags & R_PAGER)
1712     set_option (OPTFORCEREDRAWPAGER);
1713   if (ptr->flags & R_RESORT_SUB)
1714     set_option (OPTSORTSUBTHREADS);
1715   if (ptr->flags & R_RESORT)
1716     set_option (OPTNEEDRESORT);
1717   if (ptr->flags & R_RESORT_INIT)
1718     set_option (OPTRESORTINIT);
1719   if (ptr->flags & R_TREE)
1720     set_option (OPTREDRAWTREE);
1721 }
1722
1723 /* check whether value for $dsn_return would be valid */
1724 static int check_dsn_return (const char* option __attribute__ ((unused)), unsigned long p,
1725                              char* errbuf, ssize_t errlen) {
1726   char* val = (char*) p;
1727   if (val && *val && m_strncmp(val, "hdrs", 4) != 0 &&
1728       m_strncmp(val, "full", 4) != 0) {
1729     if (errbuf)
1730       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), val, "dsn_return");
1731     return (0);
1732   }
1733   return (1);
1734 }
1735
1736 /* check whether value for $dsn_notify would be valid */
1737 static int check_dsn_notify (const char* option, unsigned long p,
1738                              char* errbuf, ssize_t errlen) {
1739   list2_t* list = NULL;
1740   ssize_t i = 0;
1741   int rc = 1;
1742   char* val = (char*) p;
1743
1744   if (!val || !*val)
1745     return (1);
1746   list = list_from_str (val, ",");
1747   if (list_empty (list))
1748     return (1);
1749
1750   for (i = 0; i < list->length; i++)
1751     if (m_strncmp(list->data[i], "never", 5) != 0 &&
1752         m_strncmp(list->data[i], "failure", 7) != 0 &&
1753         m_strncmp(list->data[i], "delay", 5) != 0 &&
1754         m_strncmp(list->data[i], "success", 7) != 0) {
1755       if (errbuf)
1756         snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
1757                   (char*) list->data[i], "dsn_notify");
1758       rc = 0;
1759       break;
1760     }
1761   list_del (&list, (list_del_t*)xmemfree);
1762   return (rc);
1763 }
1764
1765 static int check_num (const char* option, unsigned long p,
1766                       char* errbuf, ssize_t errlen) {
1767   if ((int) p < 0) {
1768     if (errbuf)
1769       snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
1770     return (0);
1771   }
1772   return (1);
1773 }
1774
1775 #ifdef DEBUG
1776 static int check_debug (const char* option, unsigned long p,
1777                         char* errbuf, ssize_t errlen) {
1778   if ((int) p <= DEBUG_MAX_LEVEL &&
1779       (int) p >= DEBUG_MIN_LEVEL)
1780     return (1);
1781
1782   if (errbuf)
1783     snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
1784   return (0);
1785 }
1786 #endif
1787
1788 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
1789                           char* errbuf, ssize_t errlen) {
1790   if (!check_num ("history", p, errbuf, errlen))
1791     return (0);
1792   mutt_init_history ();
1793   return (1);
1794 }
1795
1796 static int check_special (const char* name, unsigned long val,
1797                           char* errbuf, ssize_t errlen) {
1798   int i = 0;
1799
1800   for (i = 0; SpecialVars[i].name; i++) {
1801     if (m_strcmp(SpecialVars[i].name, name) == 0) {
1802       return (SpecialVars[i].check (SpecialVars[i].name,
1803                                     val, errbuf, errlen));
1804     }
1805   }
1806   return (1);
1807 }
1808
1809 static const struct mapping_t* get_sortmap (struct option_t* option) {
1810   const struct mapping_t* map = NULL;
1811
1812   switch (option->type & DT_SUBTYPE_MASK) {
1813   case DT_SORT_ALIAS:
1814     map = SortAliasMethods;
1815     break;
1816   case DT_SORT_BROWSER:
1817     map = SortBrowserMethods;
1818     break;
1819   case DT_SORT_KEYS:
1820     map = SortKeyMethods;
1821     break;
1822   case DT_SORT_AUX:
1823     map = SortAuxMethods;
1824     break;
1825   default:
1826     map = SortMethods;
1827     break;
1828   }
1829   return (map);
1830 }
1831
1832 #define CHECK_PAGER \
1833   if ((CurrentMenu == MENU_PAGER) && \
1834       (!option || (option->flags & R_RESORT))) \
1835   { \
1836     snprintf (err->data, err->dsize, \
1837               _("Not available in this menu.")); \
1838     return (-1); \
1839   }
1840
1841 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1842                       BUFFER * err)
1843 {
1844   int query, unset, inv, reset, r = 0;
1845   struct option_t* option = NULL;
1846
1847   while (MoreArgs (s)) {
1848     /* reset state variables */
1849     query = 0;
1850     unset = data & M_SET_UNSET;
1851     inv = data & M_SET_INV;
1852     reset = data & M_SET_RESET;
1853
1854     if (*s->dptr == '?') {
1855       query = 1;
1856       s->dptr++;
1857     }
1858     else if (m_strncmp("no", s->dptr, 2) == 0) {
1859       s->dptr += 2;
1860       unset = !unset;
1861     }
1862     else if (m_strncmp("inv", s->dptr, 3) == 0) {
1863       s->dptr += 3;
1864       inv = !inv;
1865     }
1866     else if (*s->dptr == '&') {
1867       reset = 1;
1868       s->dptr++;
1869     }
1870
1871     /* get the variable name */
1872     mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1873
1874     /* resolve synonyms */
1875     if ((option = hash_find (ConfigOptions, tmp->data)) != NULL &&
1876         DTYPE (option->type == DT_SYN)) {
1877       struct option_t* newopt = hash_find (ConfigOptions, (char*) option->data);
1878       syn_add (newopt, option);
1879       option = newopt;
1880     }
1881
1882     /* see if we need to add $user_ var */
1883     if (!option && ascii_strncmp ("user_", tmp->data, 5) == 0) {
1884       /* there's no option named like this yet so only add one
1885        * if the action isn't any of: reset, unset, query */
1886       if (!(reset || unset || query || *s->dptr != '=')) {
1887         debug_print (1, ("adding user option '%s'\n", tmp->data));
1888         option = add_user_option (tmp->data);
1889         hash_insert (ConfigOptions, option->option, option, 0);
1890       }
1891     }
1892
1893     if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1894       snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1895       return (-1);
1896     }
1897     s->dptr = vskipspaces(s->dptr);
1898
1899     if (reset) {
1900       if (query || unset || inv) {
1901         snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1902         return (-1);
1903       }
1904
1905       if (s && *s->dptr == '=') {
1906         snprintf (err->data, err->dsize, _("value is illegal with reset"));
1907         return (-1);
1908       }
1909
1910       if (!m_strcmp("all", tmp->data)) {
1911         if (CurrentMenu == MENU_PAGER) {
1912           snprintf (err->data, err->dsize, _("Not available in this menu."));
1913           return (-1);
1914         }
1915         hash_map (ConfigOptions, mutt_restore_default, 1);
1916         set_option (OPTFORCEREDRAWINDEX);
1917         set_option (OPTFORCEREDRAWPAGER);
1918         set_option (OPTSORTSUBTHREADS);
1919         set_option (OPTNEEDRESORT);
1920         set_option (OPTRESORTINIT);
1921         set_option (OPTREDRAWTREE);
1922         return (0);
1923       }
1924       else if (!FuncTable[DTYPE (option->type)].opt_from_string) {
1925         snprintf (err->data, err->dsize, _("$%s is read-only"), option->option);
1926         r = -1;
1927         break;
1928       } else {
1929         CHECK_PAGER;
1930         mutt_restore_default (NULL, option, 1);
1931       }
1932     }
1933     else if (DTYPE (option->type) == DT_BOOL) {
1934       /* XXX this currently ignores the function table
1935        * as we don't get invert and stuff into it */
1936       if (s && *s->dptr == '=') {
1937         if (unset || inv || query) {
1938           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1939           return (-1);
1940         }
1941
1942         s->dptr++;
1943         mutt_extract_token (tmp, s, 0);
1944         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1945           unset = inv = 0;
1946         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1947           unset = 1;
1948         else {
1949           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1950           return (-1);
1951         }
1952       }
1953
1954       if (query) {
1955         bool_to_string (err->data, err->dsize, option);
1956         return 0;
1957       }
1958
1959       CHECK_PAGER;
1960       if (unset)
1961         unset_option (option->data);
1962       else if (inv)
1963         toggle_option (option->data);
1964       else
1965         set_option (option->data);
1966     }
1967     else if (DTYPE (option->type) == DT_STR ||
1968              DTYPE (option->type) == DT_PATH ||
1969              DTYPE (option->type) == DT_ADDR ||
1970              DTYPE (option->type) == DT_MAGIC ||
1971              DTYPE (option->type) == DT_NUM ||
1972              DTYPE (option->type) == DT_SORT ||
1973              DTYPE (option->type) == DT_RX ||
1974              DTYPE (option->type) == DT_USER ||
1975              DTYPE (option->type) == DT_SYS) {
1976
1977       /* XXX maybe we need to get unset into handlers? */
1978       if (DTYPE (option->type) == DT_STR ||
1979           DTYPE (option->type) == DT_PATH ||
1980           DTYPE (option->type) == DT_ADDR ||
1981           DTYPE (option->type) == DT_USER ||
1982           DTYPE (option->type) == DT_SYS) {
1983         if (unset) {
1984           CHECK_PAGER;
1985           if (!FuncTable[DTYPE (option->type)].opt_from_string) {
1986             snprintf (err->data, err->dsize, _("$%s is read-only"),
1987                       option->option);
1988             r = -1;
1989             break;
1990           } else if (DTYPE (option->type) == DT_ADDR)
1991             address_list_wipe((address_t **) option->data);
1992           else if (DTYPE (option->type) == DT_USER)
1993             /* to unset $user_ means remove */
1994             hash_delete (ConfigOptions, option->option,
1995                          option, del_option);
1996           else
1997             p_delete((void **)(void *)&option->data);
1998           break;
1999         }
2000       }
2001
2002       if (query || *s->dptr != '=') {
2003         FuncTable[DTYPE (option->type)].opt_to_string
2004           (err->data, err->dsize, option);
2005         break;
2006       }
2007
2008       /* the $madmutt_ variables are read-only */
2009       if (!FuncTable[DTYPE (option->type)].opt_from_string) {
2010         snprintf (err->data, err->dsize, _("$%s is read-only"),
2011                   option->option);
2012         r = -1;
2013         break;
2014       } else {
2015         CHECK_PAGER;
2016         s->dptr++;
2017         mutt_extract_token (tmp, s, 0);
2018         if (!FuncTable[DTYPE (option->type)].opt_from_string
2019             (option, tmp->data, err->data, err->dsize))
2020           r = -1;
2021       }
2022     }
2023     else if (DTYPE (option->type) == DT_QUAD) {
2024
2025       if (query) {
2026         quad_to_string (err->data, err->dsize, option);
2027         break;
2028       }
2029
2030       if (*s->dptr == '=') {
2031         CHECK_PAGER;
2032         s->dptr++;
2033         mutt_extract_token (tmp, s, 0);
2034         if (ascii_strcasecmp ("yes", tmp->data) == 0)
2035           set_quadoption (option->data, M_YES);
2036         else if (ascii_strcasecmp ("no", tmp->data) == 0)
2037           set_quadoption (option->data, M_NO);
2038         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
2039           set_quadoption (option->data, M_ASKYES);
2040         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
2041           set_quadoption (option->data, M_ASKNO);
2042         else {
2043           snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
2044                     tmp->data, option->option);
2045           r = -1;
2046           break;
2047         }
2048       }
2049       else {
2050         if (inv)
2051           toggle_quadoption (option->data);
2052         else if (unset)
2053           set_quadoption (option->data, M_NO);
2054         else
2055           set_quadoption (option->data, M_YES);
2056       }
2057     }
2058     else {
2059       snprintf (err->data, err->dsize, _("%s: unknown type"),
2060                 option->option);
2061       r = -1;
2062       break;
2063     }
2064
2065     if (option->flags & R_INDEX)
2066       set_option (OPTFORCEREDRAWINDEX);
2067     if (option->flags & R_PAGER)
2068       set_option (OPTFORCEREDRAWPAGER);
2069     if (option->flags & R_RESORT_SUB)
2070       set_option (OPTSORTSUBTHREADS);
2071     if (option->flags & R_RESORT)
2072       set_option (OPTNEEDRESORT);
2073     if (option->flags & R_RESORT_INIT)
2074       set_option (OPTRESORTINIT);
2075     if (option->flags & R_TREE)
2076       set_option (OPTREDRAWTREE);
2077   }
2078   return (r);
2079 }
2080
2081 #define MAXERRS 128
2082
2083 /* reads the specified initialization file.  returns -1 if errors were found
2084    so that we can pause to let the user know...  */
2085 static int source_rc (const char *rcfile, BUFFER * err)
2086 {
2087   FILE *f;
2088   int line = 0, rc = 0, conv = 0;
2089   BUFFER token;
2090   char *linebuf = NULL;
2091   char *currentline = NULL;
2092   ssize_t buflen;
2093   pid_t pid;
2094
2095   debug_print (2, ("reading configuration file '%s'.\n", rcfile));
2096
2097   if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
2098     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
2099     return (-1);
2100   }
2101
2102   p_clear(&token, 1);
2103   while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
2104     conv = ConfigCharset && (*ConfigCharset) && Charset;
2105     if (conv) {
2106       currentline = m_strdup(linebuf);
2107       if (!currentline)
2108         continue;
2109       mutt_convert_string (&currentline, ConfigCharset, Charset, 0);
2110     }
2111     else
2112       currentline = linebuf;
2113
2114     CurRCLine = line;
2115     CurRCFile = rcfile;
2116
2117     if (mutt_parse_rc_line (currentline, &token, err) == -1) {
2118       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
2119       if (--rc < -MAXERRS) {
2120         if (conv)
2121           p_delete(&currentline);
2122         break;
2123       }
2124     }
2125     else {
2126       if (rc < 0)
2127         rc = -1;
2128     }
2129     if (conv)
2130       p_delete(&currentline);
2131   }
2132   p_delete(&token.data);
2133   p_delete(&linebuf);
2134   fclose (f);
2135   if (pid != -1)
2136     mutt_wait_filter (pid);
2137   if (rc) {
2138     /* the muttrc source keyword */
2139     snprintf (err->data, err->dsize,
2140               rc >= -MAXERRS ? _("source: errors in %s")
2141               : _("source: reading aborted due too many errors in %s"),
2142               rcfile);
2143     rc = -1;
2144   }
2145   return (rc);
2146 }
2147
2148 #undef MAXERRS
2149
2150 static int parse_source (BUFFER * tmp, BUFFER * s,
2151                          unsigned long data __attribute__ ((unused)),
2152                          BUFFER * err)
2153 {
2154   char path[_POSIX_PATH_MAX];
2155   int rc = 0;
2156
2157   do {
2158     if (mutt_extract_token (tmp, s, 0) != 0) {
2159       snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
2160       return (-1);
2161     }
2162
2163     m_strcpy(path, sizeof(path), tmp->data);
2164     mutt_expand_path (path, sizeof(path));
2165
2166     rc += source_rc (path, err);
2167   }
2168   while (MoreArgs (s));
2169
2170   return ((rc < 0) ? -1 : 0);
2171 }
2172
2173 /* line         command to execute
2174
2175    token        scratch buffer to be used by parser.  caller should free
2176                 token->data when finished.  the reason for this variable is
2177                 to avoid having to allocate and deallocate a lot of memory
2178                 if we are parsing many lines.  the caller can pass in the
2179                 memory to use, which avoids having to create new space for
2180                 every call to this function.
2181
2182    err          where to write error messages */
2183 int mutt_parse_rc_line ( /* const */ char *line, BUFFER * token, BUFFER * err)
2184 {
2185   int i, r = -1;
2186   BUFFER expn;
2187
2188   p_clear(&expn, 1);
2189   expn.data = expn.dptr = line;
2190   expn.dsize = m_strlen(line);
2191
2192   *err->data = 0;
2193
2194   debug_print (1, ("expand '%s'\n", line));
2195
2196   expn.dptr = vskipspaces(expn.dptr);
2197   while (*expn.dptr) {
2198     if (*expn.dptr == '#')
2199       break;                    /* rest of line is a comment */
2200     if (*expn.dptr == ';') {
2201       expn.dptr++;
2202       continue;
2203     }
2204     mutt_extract_token (token, &expn, 0);
2205     for (i = 0; Commands[i].name; i++) {
2206       if (!m_strcmp(token->data, Commands[i].name)) {
2207         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
2208           goto finish;
2209         break;
2210       }
2211     }
2212     if (!Commands[i].name) {
2213       snprintf (err->data, err->dsize, _("%s: unknown command"),
2214                 NONULL (token->data));
2215       goto finish;
2216     }
2217   }
2218   r = 0;
2219 finish:
2220   if (expn.destroy)
2221     p_delete(&expn.data);
2222   return (r);
2223 }
2224
2225
2226 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
2227 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
2228 /* initial string that starts completion. No telling how much crap
2229  * the user has typed so far. Allocate LONG_STRING just to be sure! */
2230 char User_typed[LONG_STRING] = { 0 };
2231
2232 int Num_matched = 0;            /* Number of matches for completion */
2233 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
2234 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1];  /* all the matches + User_typed */
2235
2236 /* helper function for completion.  Changes the dest buffer if
2237    necessary/possible to aid completion.
2238         dest == completion result gets here.
2239         src == candidate for completion.
2240         try == user entered data for completion.
2241         len == length of dest buffer.
2242 */
2243 static void candidate (char *dest, char *try, const char *src, int len)
2244 {
2245   int l;
2246
2247   if (strstr (src, try) == src) {
2248     Matches[Num_matched++] = src;
2249     if (dest[0] == 0)
2250       m_strcpy(dest, len, src);
2251     else {
2252       for (l = 0; src[l] && src[l] == dest[l]; l++);
2253       dest[l] = 0;
2254     }
2255   }
2256 }
2257
2258 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
2259 {
2260   char *pt = buffer;
2261   int num;
2262   int spaces;                   /* keep track of the number of leading spaces on the line */
2263
2264   buffer = vskipspaces(buffer);
2265   spaces = buffer - pt;
2266
2267   pt = buffer + pos - spaces;
2268   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2269     pt--;
2270
2271   if (pt == buffer) {           /* complete cmd */
2272     /* first TAB. Collect all the matches */
2273     if (numtabs == 1) {
2274       Num_matched = 0;
2275       m_strcpy(User_typed, sizeof(User_typed), pt);
2276       p_clear(Matches, countof(Matches));
2277       p_clear(Completed, countof(Completed));
2278       for (num = 0; Commands[num].name; num++)
2279         candidate (Completed, User_typed, Commands[num].name,
2280                    sizeof(Completed));
2281       Matches[Num_matched++] = User_typed;
2282
2283       /* All matches are stored. Longest non-ambiguous string is ""
2284        * i.e. dont change 'buffer'. Fake successful return this time */
2285       if (User_typed[0] == 0)
2286         return 1;
2287     }
2288
2289     if (Completed[0] == 0 && User_typed[0])
2290       return 0;
2291
2292     /* Num_matched will _always_ be atleast 1 since the initial
2293      * user-typed string is always stored */
2294     if (numtabs == 1 && Num_matched == 2)
2295       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2296     else if (numtabs > 1 && Num_matched > 2)
2297       /* cycle thru all the matches */
2298       snprintf (Completed, sizeof(Completed), "%s",
2299                 Matches[(numtabs - 2) % Num_matched]);
2300
2301     /* return the completed command */
2302     m_strcpy(buffer, len - spaces, Completed);
2303   }
2304   else if (!m_strncmp(buffer, "set", 3)
2305            || !m_strncmp(buffer, "unset", 5)
2306            || !m_strncmp(buffer, "reset", 5)
2307            || !m_strncmp(buffer, "toggle", 6)) {    /* complete variables */
2308     const char *prefixes[] = { "no", "inv", "?", "&", NULL };
2309
2310     pt++;
2311     /* loop through all the possible prefixes (no, inv, ...) */
2312     if (!m_strncmp(buffer, "set", 3)) {
2313       for (num = 0; prefixes[num]; num++) {
2314         if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
2315           pt += m_strlen(prefixes[num]);
2316           break;
2317         }
2318       }
2319     }
2320
2321     /* first TAB. Collect all the matches */
2322     if (numtabs == 1) {
2323       Num_matched = 0;
2324       m_strcpy(User_typed, sizeof(User_typed), pt);
2325       p_clear(Matches, countof(Matches));
2326       p_clear(Completed, countof(Completed));
2327       for (num = 0; MuttVars[num].option; num++)
2328         candidate(Completed, User_typed, MuttVars[num].option,
2329                   sizeof(Completed));
2330       Matches[Num_matched++] = User_typed;
2331
2332       /* All matches are stored. Longest non-ambiguous string is ""
2333        * i.e. dont change 'buffer'. Fake successful return this time */
2334       if (User_typed[0] == 0)
2335         return 1;
2336     }
2337
2338     if (Completed[0] == 0 && User_typed[0])
2339       return 0;
2340
2341     /* Num_matched will _always_ be atleast 1 since the initial
2342      * user-typed string is always stored */
2343     if (numtabs == 1 && Num_matched == 2)
2344       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2345     else if (numtabs > 1 && Num_matched > 2)
2346       /* cycle thru all the matches */
2347       snprintf (Completed, sizeof(Completed), "%s",
2348                 Matches[(numtabs - 2) % Num_matched]);
2349
2350     m_strcpy(pt, buffer + len - pt - spaces, Completed);
2351   }
2352   else if (!m_strncmp(buffer, "exec", 4)) {
2353     struct binding_t *menu = km_get_table (CurrentMenu);
2354
2355     if (!menu && CurrentMenu != MENU_PAGER)
2356       menu = OpGeneric;
2357
2358     pt++;
2359     /* first TAB. Collect all the matches */
2360     if (numtabs == 1) {
2361       Num_matched = 0;
2362       m_strcpy(User_typed, sizeof(User_typed), pt);
2363       p_clear(Matches, countof(Matches));
2364       p_clear(Completed, countof(Completed));
2365       for (num = 0; menu[num].name; num++)
2366         candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
2367       /* try the generic menu */
2368       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
2369         menu = OpGeneric;
2370         for (num = 0; menu[num].name; num++)
2371           candidate (Completed, User_typed, menu[num].name,
2372                      sizeof(Completed));
2373       }
2374       Matches[Num_matched++] = User_typed;
2375
2376       /* All matches are stored. Longest non-ambiguous string is ""
2377        * i.e. dont change 'buffer'. Fake successful return this time */
2378       if (User_typed[0] == 0)
2379         return 1;
2380     }
2381
2382     if (Completed[0] == 0 && User_typed[0])
2383       return 0;
2384
2385     /* Num_matched will _always_ be atleast 1 since the initial
2386      * user-typed string is always stored */
2387     if (numtabs == 1 && Num_matched == 2)
2388       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2389     else if (numtabs > 1 && Num_matched > 2)
2390       /* cycle thru all the matches */
2391       snprintf (Completed, sizeof(Completed), "%s",
2392                 Matches[(numtabs - 2) % Num_matched]);
2393
2394     m_strcpy(pt, buffer + len - pt - spaces, Completed);
2395   }
2396   else
2397     return 0;
2398
2399   return 1;
2400 }
2401
2402 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
2403 {
2404   char var[STRING], *pt = buffer;
2405   int spaces;
2406   struct option_t* option = NULL;
2407
2408   if (buffer[0] == 0)
2409     return 0;
2410
2411   buffer = vskipspaces(buffer);
2412   spaces = buffer - pt;
2413
2414   pt = buffer + pos - spaces;
2415   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2416     pt--;
2417   pt++;                         /* move past the space */
2418   if (*pt == '=')               /* abort if no var before the '=' */
2419     return 0;
2420
2421   if (m_strncmp(buffer, "set", 3) == 0) {
2422     m_strcpy(var, sizeof(var), pt);
2423     /* ignore the trailing '=' when comparing */
2424     var[m_strlen(var) - 1] = 0;
2425     if (!(option = hash_find (ConfigOptions, var)))
2426       return 0;                 /* no such variable. */
2427     else {
2428       char tmp[LONG_STRING], tmp2[LONG_STRING];
2429       char *s, *d;
2430       ssize_t dlen = buffer + len - pt - spaces;
2431       const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
2432
2433       tmp[0] = '\0';
2434
2435       if ((DTYPE (option->type) == DT_STR) ||
2436           (DTYPE (option->type) == DT_PATH) ||
2437           (DTYPE (option->type) == DT_RX)) {
2438         m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
2439         if (DTYPE (option->type) == DT_PATH)
2440           mutt_pretty_mailbox (tmp);
2441       }
2442       else if (DTYPE (option->type) == DT_ADDR) {
2443         rfc822_write_address (tmp, sizeof(tmp),
2444                               *((address_t **) option->data), 0);
2445       }
2446       else if (DTYPE (option->type) == DT_QUAD)
2447         m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
2448       else if (DTYPE (option->type) == DT_NUM)
2449         snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
2450       else if (DTYPE (option->type) == DT_SORT) {
2451         const struct mapping_t *map;
2452         const char *p;
2453
2454         switch (option->type & DT_SUBTYPE_MASK) {
2455         case DT_SORT_ALIAS:
2456           map = SortAliasMethods;
2457           break;
2458         case DT_SORT_BROWSER:
2459           map = SortBrowserMethods;
2460           break;
2461         case DT_SORT_KEYS:
2462           map = SortKeyMethods;
2463           break;
2464         default:
2465           map = SortMethods;
2466           break;
2467         }
2468         p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
2469         snprintf(tmp, sizeof(tmp), "%s%s%s",
2470                  (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
2471                  (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
2472       }
2473       else if (DTYPE (option->type) == DT_MAGIC) {
2474         const char *p;
2475         switch (DefaultMagic) {
2476           case M_MBOX:
2477             p = "mbox";
2478             break;
2479           case M_MMDF:
2480             p = "MMDF";
2481             break;
2482           case M_MH:
2483             p = "MH";
2484           break;
2485           case M_MAILDIR:
2486             p = "Maildir";
2487             break;
2488           default:
2489             p = "unknown";
2490         }
2491         m_strcpy(tmp, sizeof(tmp), p);
2492       }
2493       else if (DTYPE (option->type) == DT_BOOL)
2494         m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
2495       else
2496         return 0;
2497
2498       for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
2499         if (*s == '\\' || *s == '"')
2500           *d++ = '\\';
2501         *d++ = *s++;
2502       }
2503       *d = '\0';
2504
2505       m_strcpy(tmp, sizeof(tmp), pt);
2506       snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
2507
2508       return 1;
2509     }
2510   }
2511   return 0;
2512 }
2513
2514 /* Implement the -Q command line flag */
2515 int mutt_query_variables (string_list_t * queries)
2516 {
2517   string_list_t *p;
2518
2519   char errbuff[STRING];
2520   char command[STRING];
2521
2522   BUFFER err, token;
2523
2524   p_clear(&err, 1);
2525   p_clear(&token, 1);
2526
2527   err.data = errbuff;
2528   err.dsize = sizeof(errbuff);
2529
2530   for (p = queries; p; p = p->next) {
2531     snprintf (command, sizeof(command), "set ?%s\n", p->data);
2532     if (mutt_parse_rc_line (command, &token, &err) == -1) {
2533       fprintf (stderr, "%s\n", err.data);
2534       p_delete(&token.data);
2535       return 1;
2536     }
2537     printf ("%s\n", err.data);
2538   }
2539
2540   p_delete(&token.data);
2541   return 0;
2542 }
2543
2544 static int mutt_execute_commands (string_list_t * p)
2545 {
2546   BUFFER err, token;
2547   char errstr[SHORT_STRING];
2548
2549   p_clear(&err, 1);
2550   err.data = errstr;
2551   err.dsize = sizeof(errstr);
2552   p_clear(&token, 1);
2553   for (; p; p = p->next) {
2554     if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
2555       fprintf (stderr, _("Error in command line: %s\n"), err.data);
2556       p_delete(&token.data);
2557       return (-1);
2558     }
2559   }
2560   p_delete(&token.data);
2561   return 0;
2562 }
2563
2564 void mutt_init (int skip_sys_rc, string_list_t * commands)
2565 {
2566   struct passwd *pw;
2567   struct utsname utsname;
2568   const char *p;
2569   char buffer[STRING], error[STRING];
2570   int default_rc = 0, need_pause = 0;
2571   unsigned int i;
2572   BUFFER err;
2573
2574   p_clear(&err, 1);
2575   err.data = error;
2576   err.dsize = sizeof(error);
2577
2578   /* use 3*sizeof(muttvars) instead of 2*sizeof()
2579    * to have some room for $user_ vars */
2580   ConfigOptions = hash_create (sizeof(MuttVars) * 3);
2581   for (i = 0; MuttVars[i].option; i++) {
2582     if (DTYPE (MuttVars[i].type) != DT_SYS)
2583       hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i], 0);
2584     else
2585       hash_insert (ConfigOptions, MuttVars[i].option,
2586                    add_option (MuttVars[i].option, MuttVars[i].init,
2587                                DT_SYS, 0), 0);
2588   }
2589
2590   /*
2591    * XXX - use something even more difficult to predict?
2592    */
2593   snprintf (AttachmentMarker, sizeof(AttachmentMarker),
2594             "\033]9;%ld\a", (long) time (NULL));
2595
2596   /* on one of the systems I use, getcwd() does not return the same prefix
2597      as is listed in the passwd file */
2598   if ((p = getenv ("HOME")))
2599     Homedir = m_strdup(p);
2600
2601   /* Get some information about the user */
2602   if ((pw = getpwuid (getuid ()))) {
2603     char rnbuf[STRING];
2604
2605     Username = m_strdup(pw->pw_name);
2606     if (!Homedir)
2607       Homedir = m_strdup(pw->pw_dir);
2608
2609     mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, GecosMask.rx);
2610     Realname = m_strdup(rnbuf);
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
2706   if ((p = getenv ("REPLYTO")) != NULL) {
2707     BUFFER buf, token;
2708
2709     snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
2710
2711     p_clear(&buf, 1);
2712     buf.data = buf.dptr = buffer;
2713     buf.dsize = m_strlen(buffer);
2714
2715     p_clear(&token, 1);
2716     parse_my_hdr (&token, &buf, 0, &err);
2717     p_delete(&token.data);
2718   }
2719
2720   if ((p = getenv ("EMAIL")) != NULL)
2721     From = rfc822_parse_adrlist (NULL, p);
2722
2723   mutt_set_langinfo_charset ();
2724   mutt_set_charset (Charset);
2725
2726
2727   /* Set standard defaults */
2728   hash_map (ConfigOptions, mutt_set_default, 0);
2729   hash_map (ConfigOptions, mutt_restore_default, 0);
2730
2731   CurrentMenu = MENU_MAIN;
2732
2733   /* Do we have a locale definition? */
2734   if (((p = getenv ("LC_ALL")) != NULL && p[0]) ||
2735       ((p = getenv ("LANG")) != NULL && p[0]) ||
2736       ((p = getenv ("LC_CTYPE")) != NULL && p[0]))
2737     set_option (OPTLOCALES);
2738
2739 #ifdef HAVE_GETSID
2740   /* Unset suspend by default if we're the session leader */
2741   if (getsid (0) == getpid ())
2742     unset_option (OPTSUSPEND);
2743 #endif
2744
2745   mutt_init_history ();
2746
2747
2748
2749
2750   /*
2751    *
2752    *                       BIG FAT WARNING
2753    *
2754    * When changing the code which looks for a configuration file,
2755    * please also change the corresponding code in muttbug.sh.in.
2756    *
2757    *
2758    */
2759
2760
2761
2762
2763   if (!Muttrc) {
2764 #if 0
2765     snprintf (buffer, sizeof(buffer), "%s/.madmuttrc-%s", NONULL (Homedir),
2766               MUTT_VERSION);
2767     if (access (buffer, F_OK) == -1)
2768 #endif
2769       snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL (Homedir));
2770     if (access (buffer, F_OK) == -1)
2771 #if 0
2772       snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc-%s",
2773                 NONULL (Homedir), MUTT_VERSION);
2774     if (access (buffer, F_OK) == -1)
2775 #endif
2776       snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
2777                 NONULL (Homedir));
2778
2779     default_rc = 1;
2780     Muttrc = m_strdup(buffer);
2781   }
2782   else {
2783     m_strcpy(buffer, sizeof(buffer), Muttrc);
2784     p_delete(&Muttrc);
2785     mutt_expand_path (buffer, sizeof(buffer));
2786     Muttrc = m_strdup(buffer);
2787   }
2788   p_delete(&AliasFile);
2789   AliasFile = m_strdup(NONULL (Muttrc));
2790
2791   /* Process the global rc file if it exists and the user hasn't explicity
2792      requested not to via "-n".  */
2793   if (!skip_sys_rc) {
2794     snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
2795               MUTT_VERSION);
2796     if (access (buffer, F_OK) == -1)
2797       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
2798     if (access (buffer, F_OK) == -1)
2799       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
2800                 MUTT_VERSION);
2801     if (access (buffer, F_OK) == -1)
2802       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
2803     if (access (buffer, F_OK) != -1) {
2804       if (source_rc (buffer, &err) != 0) {
2805         fputs (err.data, stderr);
2806         fputc ('\n', stderr);
2807         need_pause = 1;
2808       }
2809     }
2810   }
2811
2812   /* Read the user's initialization file.  */
2813   if (access (Muttrc, F_OK) != -1) {
2814     if (!option (OPTNOCURSES))
2815       mutt_endwin (NULL);
2816     if (source_rc (Muttrc, &err) != 0) {
2817       fputs (err.data, stderr);
2818       fputc ('\n', stderr);
2819       need_pause = 1;
2820     }
2821   }
2822   else if (!default_rc) {
2823     /* file specified by -F does not exist */
2824     snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
2825     mutt_endwin (buffer);
2826     exit (1);
2827   }
2828
2829   if (mutt_execute_commands (commands) != 0)
2830     need_pause = 1;
2831
2832   /* warn about synonym variables */
2833   if (!list_empty(Synonyms)) {
2834     i = 0;
2835     fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
2836     for (i = 0; i < Synonyms->length; i++) {
2837       struct option_t* newopt = NULL, *oldopt = NULL;
2838       newopt = (struct option_t*) ((syn_t*) Synonyms->data[i])->n;
2839       oldopt = (struct option_t*) ((syn_t*) Synonyms->data[i])->o;
2840       fprintf (stderr, "$%s ($%s should be used) (%s:%d)\n",
2841                oldopt ? NONULL (oldopt->option) : "",
2842                newopt ? NONULL (newopt->option) : "",
2843                NONULL(((syn_t*) Synonyms->data[i])->f),
2844                ((syn_t*) Synonyms->data[i])->l);
2845     }
2846     fprintf (stderr, _("Warning: synonym variables are scheduled"
2847                        " for removal.\n"));
2848     list_del (&Synonyms, syn_del);
2849     need_pause = 1;
2850   }
2851
2852   if (need_pause && !option (OPTNOCURSES)) {
2853     if (mutt_any_key_to_continue (NULL) == -1)
2854       mutt_exit (1);
2855   }
2856
2857 #if 0
2858   set_option (OPTWEED);         /* turn weeding on by default */
2859 #endif
2860 }
2861
2862 int mutt_get_hook_type (const char *name)
2863 {
2864   struct command_t *c;
2865
2866   for (c = Commands; c->name; c++)
2867     if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
2868       return c->data;
2869   return 0;
2870 }
2871
2872 /* compare two option_t*'s for sorting -t/-T output */
2873 static int opt_cmp (const void* a, const void* b) {
2874   return (m_strcmp((*(struct option_t**) a)->option,
2875                        (*(struct option_t**) b)->option));
2876 }
2877
2878 /* callback for hash_map() to put all non-synonym vars into list */
2879 static void opt_sel_full (const char* key __attribute__ ((unused)),
2880                           void* data,
2881                           unsigned long more) {
2882   list2_t** l = (list2_t**) more;
2883   struct option_t* option = (struct option_t*) data;
2884
2885   if (DTYPE (option->type) == DT_SYN)
2886     return;
2887   list_push_back (l, option);
2888 }
2889
2890 /* callback for hash_map() to put all changed non-synonym vars into list */
2891 static void opt_sel_diff (const char* key __attribute__ ((unused)),
2892                           void* data,
2893                           unsigned long more) {
2894   list2_t** l = (list2_t**) more;
2895   struct option_t* option = (struct option_t*) data;
2896   char buf[LONG_STRING];
2897
2898   if (DTYPE (option->type) == DT_SYN)
2899     return;
2900
2901   mutt_option_value (option->option, buf, sizeof(buf));
2902   if (m_strcmp(buf, option->init) != 0)
2903     list_push_back (l, option);
2904 }
2905
2906 /* dump out the value of all the variables we have */
2907 int mutt_dump_variables (int full) {
2908   ssize_t i = 0;
2909   char outbuf[STRING];
2910   list2_t* tmp = NULL;
2911   struct option_t* option = NULL;
2912
2913   /* get all non-synonyms into list... */
2914   hash_map (ConfigOptions, full ? opt_sel_full : opt_sel_diff,
2915             (unsigned long) &tmp);
2916
2917   if (!list_empty(tmp)) {
2918     /* ...and dump list sorted */
2919     qsort (tmp->data, tmp->length, sizeof(void*), opt_cmp);
2920     for (i = 0; i < tmp->length; i++) {
2921       option = (struct option_t*) tmp->data[i];
2922       FuncTable[DTYPE (option->type)].opt_to_string
2923         (outbuf, sizeof(outbuf), option);
2924       printf ("%s\n", outbuf);
2925     }
2926   }
2927   list_del (&tmp, NULL);
2928   return 0;
2929 }