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