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