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