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