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