drop some useless crap.
[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   string_list_t *t, *last = NULL;
591
592   /* don't add a NULL or empty string to the list */
593   if (!str || *str == '\0')
594     return;
595
596   /* check to make sure the item is not already on this list */
597   for (last = *list; last; last = last->next) {
598     if (ascii_strcasecmp (str, last->data) == 0) {
599       /* already on the list, so just ignore it */
600       last = NULL;
601       break;
602     }
603     if (!last->next)
604       break;
605   }
606
607   if (!*list || last) {
608     t = p_new(string_list_t, 1);
609     t->data = m_strdup(str);
610     if (last) {
611       last->next = t;
612       last = last->next;
613     }
614     else
615       *list = last = t;
616   }
617 }
618
619 static int
620 add_to_rx_list(rx_t **list, const char *s, int flags, BUFFER *err)
621 {
622     rx_t* rx;
623
624     if (m_strisempty(s))
625         return 0;
626
627     if (rx_lookup(list, s))
628         return 0;
629
630     rx = rx_compile(s, flags);
631     if (!rx) {
632         snprintf(err->data, err->dsize, "Bad regexp: %s\n", s);
633         return -1;
634     }
635
636     rx_list_append(list, rx);
637     return 0;
638 }
639
640 static int add_to_spam_list(rx_t **list, const char *pat,
641                              const char *templ, BUFFER * err)
642 {
643     rx_t **last, *rx;
644
645     if (m_strisempty(pat) || !templ)
646         return 0;
647
648     if (!(rx = rx_compile (pat, REG_ICASE))) {
649         snprintf (err->data, err->dsize, _("Bad regexp: %s"), pat);
650         return -1;
651     }
652
653     /* check to make sure the item is not already on this list */
654     for (last = list; *last; last = &(*last)->next) {
655         if (!ascii_strcasecmp(rx->pattern, (*last)->pattern) == 0) {
656             rx_t *tmp = rx_list_pop(last);
657             rx_delete(&tmp);
658             last = rx_list_last(last);
659             break;
660         }
661     }
662
663     *last = rx;
664     rx_set_template(rx, templ);
665     return 0;
666 }
667
668 static int remove_from_spam_list (rx_t ** list, const char *pat)
669 {
670   int nremoved = 0;
671
672   while (*list) {
673       if (!m_strcmp((*list)->pattern, pat)) {
674           rx_t *spam = rx_list_pop(list);
675           rx_delete(&spam);
676           nremoved++;
677       } else {
678           list = &(*list)->next;
679       }
680   }
681
682   return nremoved;
683 }
684
685
686 static void remove_from_list (string_list_t ** l, const char *str)
687 {
688   string_list_t *p, *last = NULL;
689
690   if (m_strcmp("*", str) == 0)
691     string_list_wipe(l);         /* ``unCMD *'' means delete all current entries */
692   else {
693     p = *l;
694     last = NULL;
695     while (p) {
696       if (ascii_strcasecmp (str, p->data) == 0) {
697         p_delete(&p->data);
698         if (last)
699           last->next = p->next;
700         else
701           (*l) = p->next;
702         p_delete(&p);
703       }
704       else {
705         last = p;
706         p = p->next;
707       }
708     }
709   }
710 }
711
712 static int remove_from_rx_list(rx_t **l, const char *str)
713 {
714     if (m_strcmp("*", str) == 0) {
715         rx_list_wipe(l);
716         return 0;
717     }
718
719     l = rx_lookup(l, str);
720     if (l) {
721         rx_t *r = rx_list_pop(l);
722         rx_delete(&r);
723         return 0;
724     }
725
726     return -1;
727 }
728
729 static int parse_unignore (BUFFER * buf, BUFFER * s,
730                            unsigned long data __attribute__ ((unused)),
731                            BUFFER * err __attribute__ ((unused)))
732 {
733   do {
734     mutt_extract_token (buf, s, 0);
735
736     /* don't add "*" to the unignore list */
737     if (m_strcmp (buf->data, "*"))
738       add_to_list (&UnIgnore, buf->data);
739
740     remove_from_list (&Ignore, buf->data);
741   } while (MoreArgs (s));
742
743   return 0;
744 }
745
746 static int parse_ignore (BUFFER * buf, BUFFER * s,
747                          unsigned long data __attribute__ ((unused)),
748                          BUFFER * err __attribute__ ((unused)))
749 {
750   do {
751     mutt_extract_token (buf, s, 0);
752     remove_from_list (&UnIgnore, buf->data);
753     add_to_list (&Ignore, buf->data);
754   } while (MoreArgs(s));
755   return 0;
756 }
757
758 static int parse_list (BUFFER * buf, BUFFER * s,
759                        unsigned long data __attribute__ ((unused)),
760                        BUFFER * err __attribute__ ((unused)))
761 {
762   do {
763     mutt_extract_token (buf, s, 0);
764     add_to_list ((string_list_t **) data, buf->data);
765   } while (MoreArgs(s));
766   return 0;
767 }
768
769 static void _alternates_clean (void)
770 {
771   int i;
772
773   if (Context && Context->msgcount) {
774     for (i = 0; i < Context->msgcount; i++)
775       Context->hdrs[i]->recip_valid = 0;
776   }
777 }
778
779 static int parse_alternates (BUFFER * buf, BUFFER * s,
780                              unsigned long data __attribute__ ((unused)),
781                              BUFFER * err __attribute__ ((unused)))
782 {
783   _alternates_clean ();
784   do {
785     mutt_extract_token (buf, s, 0);
786     remove_from_rx_list (&UnAlternates, buf->data);
787
788     if (add_to_rx_list (&Alternates, buf->data, REG_ICASE, err) != 0)
789       return -1;
790   }
791   while (MoreArgs (s));
792
793   return 0;
794 }
795
796 static int parse_unalternates (BUFFER * buf, BUFFER * s,
797                                unsigned long data __attribute__ ((unused)),
798                                BUFFER * err __attribute__ ((unused)))
799 {
800   _alternates_clean ();
801   do {
802     mutt_extract_token (buf, s, 0);
803     remove_from_rx_list (&Alternates, buf->data);
804
805     if (m_strcmp(buf->data, "*") &&
806         add_to_rx_list (&UnAlternates, buf->data, REG_ICASE, err) != 0)
807       return -1;
808
809   }
810   while (MoreArgs (s));
811
812   return 0;
813 }
814
815 static int parse_spam_list (BUFFER * buf, BUFFER * s, unsigned long data,
816                             BUFFER * err)
817 {
818   BUFFER templ;
819
820   p_clear(&templ, 1);
821
822   /* Insist on at least one parameter */
823   if (!MoreArgs (s)) {
824     if (data == M_SPAM)
825       m_strcpy(err->data, err->dsize, _("spam: no matching pattern"));
826     else
827       m_strcpy(err->data, err->dsize, _("nospam: no matching pattern"));
828     return -1;
829   }
830
831   /* Extract the first token, a regexp */
832   mutt_extract_token (buf, s, 0);
833
834   /* data should be either M_SPAM or M_NOSPAM. M_SPAM is for spam commands. */
835   if (data == M_SPAM) {
836     /* If there's a second parameter, it's a template for the spam tag. */
837     if (MoreArgs (s)) {
838       mutt_extract_token (&templ, s, 0);
839
840       /* Add to the spam list. */
841       if (add_to_spam_list (&SpamList, buf->data, templ.data, err) != 0) {
842         p_delete(&templ.data);
843         return -1;
844       }
845       p_delete(&templ.data);
846     }
847
848     /* If not, try to remove from the nospam list. */
849     else {
850       remove_from_rx_list (&NoSpamList, buf->data);
851     }
852
853     return 0;
854   }
855
856   /* M_NOSPAM is for nospam commands. */
857   else if (data == M_NOSPAM) {
858     /* nospam only ever has one parameter. */
859
860     /* "*" is a special case. */
861     if (!m_strcmp(buf->data, "*")) {
862       rx_list_wipe(&SpamList);
863       rx_list_wipe(&NoSpamList);
864       return 0;
865     }
866
867     /* If it's on the spam list, just remove it. */
868     if (remove_from_spam_list (&SpamList, buf->data) != 0)
869       return 0;
870
871     /* Otherwise, add it to the nospam list. */
872     if (add_to_rx_list (&NoSpamList, buf->data, REG_ICASE, err) != 0)
873       return -1;
874
875     return 0;
876   }
877
878   /* This should not happen. */
879   m_strcpy(err->data, err->dsize, "This is no good at all.");
880   return -1;
881 }
882
883 static int parse_unlist (BUFFER * buf, BUFFER * s, unsigned long data,
884                          BUFFER * err __attribute__ ((unused)))
885 {
886   do {
887     mutt_extract_token (buf, s, 0);
888     /*
889      * Check for deletion of entire list
890      */
891     if (m_strcmp(buf->data, "*") == 0) {
892       string_list_wipe((string_list_t **) data);
893       break;
894     }
895     remove_from_list ((string_list_t **) data, buf->data);
896   }
897   while (MoreArgs (s));
898
899   return 0;
900 }
901
902 static int parse_lists (BUFFER * buf, BUFFER * s,
903                         unsigned long data __attribute__ ((unused)),
904                         BUFFER * err)
905 {
906   do {
907     mutt_extract_token (buf, s, 0);
908     remove_from_rx_list (&UnMailLists, buf->data);
909
910     if (add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
911       return -1;
912   }
913   while (MoreArgs (s));
914
915   return 0;
916 }
917
918 /* always wise to do what someone else did before */
919 static void _attachments_clean (void) {
920   int i;
921   if (Context && Context->msgcount) {
922     for (i = 0; i < Context->msgcount; i++)
923       Context->hdrs[i]->attach_valid = 0;
924   }
925 }
926
927 static int parse_attach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
928                               BUFFER *err __attribute__ ((unused))) {
929   ATTACH_MATCH *a;
930   string_list_t *listp, *lastp;
931   char *p;
932   char *tmpminor;
933   int len;
934
935   /* Find the last item in the list that data points to. */
936   lastp = NULL;
937   for (listp = *ldata; listp; listp = listp->next) {
938     a = (ATTACH_MATCH *)listp->data;
939     lastp = listp;
940   }
941
942   do {
943     mutt_extract_token (buf, s, 0);
944
945     if (!buf->data || *buf->data == '\0')
946       continue;
947
948     a = p_new(ATTACH_MATCH, 1);
949
950     /* some cheap hacks that I expect to remove */
951     if (!m_strcasecmp(buf->data, "any"))
952       a->major = m_strdup("*/.*");
953     else if (!m_strcasecmp(buf->data, "none"))
954       a->major = m_strdup("cheap_hack/this_should_never_match");
955     else
956       a->major = m_strdup(buf->data);
957
958     if ((p = strchr(a->major, '/'))) {
959       *p = '\0';
960       ++p;
961       a->minor = p;
962     } else {
963       a->minor = "unknown";
964     }
965
966     len = m_strlen(a->minor);
967     tmpminor = p_new(char, len + 3);
968     m_strcpy(&tmpminor[1], len + 3, a->minor);
969     tmpminor[0] = '^';
970     tmpminor[len+1] = '$';
971     tmpminor[len+2] = '\0';
972
973     a->major_int = mutt_check_mime_type(a->major);
974     regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
975
976     p_delete(&tmpminor);
977
978     listp = p_new(string_list_t, 1);
979     listp->data = (char *)a;
980     listp->next = NULL;
981     if (lastp) {
982       lastp->next = listp;
983     } else {
984       *ldata = listp;
985     }
986     lastp = listp;
987   }
988   while (MoreArgs (s));
989
990   _attachments_clean();
991   return 0;
992 }
993
994 static int parse_unattach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
995                                 BUFFER *err __attribute__ ((unused))) {
996   ATTACH_MATCH *a;
997   string_list_t *lp, *lastp, *newlp;
998   char *tmp;
999   int major;
1000   char *minor;
1001
1002   do {
1003     mutt_extract_token (buf, s, 0);
1004
1005     if (!m_strcasecmp(buf->data, "any"))
1006       tmp = m_strdup("*/.*");
1007     else if (!m_strcasecmp(buf->data, "none"))
1008       tmp = m_strdup("cheap_hack/this_should_never_match");
1009     else
1010       tmp = m_strdup(buf->data);
1011
1012     if ((minor = strchr(tmp, '/'))) {
1013       *minor = '\0';
1014       ++minor;
1015     } else {
1016       minor = m_strdup("unknown");
1017     }
1018     major = mutt_check_mime_type(tmp);
1019
1020     /* We must do our own walk here because remove_from_list() will only
1021      * remove the string_list_t->data, not anything pointed to by the string_list_t->data. */
1022     lastp = NULL;
1023     for(lp = *ldata; lp; ) {
1024       a = (ATTACH_MATCH *)lp->data;
1025       if (a->major_int == major && !m_strcasecmp(minor, a->minor)) {
1026         regfree(&a->minor_rx);
1027         p_delete(&a->major);
1028
1029         /* Relink backward */
1030         if (lastp)
1031           lastp->next = lp->next;
1032         else
1033           *ldata = lp->next;
1034
1035         newlp = lp->next;
1036         p_delete(&lp->data); /* same as a */
1037         p_delete(&lp);
1038         lp = newlp;
1039         continue;
1040       }
1041
1042       lastp = lp;
1043       lp = lp->next;
1044     }
1045   }
1046   while (MoreArgs (s));
1047
1048   p_delete(&tmp);
1049   _attachments_clean();
1050   return 0;
1051 }
1052
1053 static int print_attach_list (string_list_t *lp, char op, const char *name) {
1054   while (lp) {
1055     printf("attachments %c%s %s/%s\n", op, name,
1056            ((ATTACH_MATCH *)lp->data)->major,
1057            ((ATTACH_MATCH *)lp->data)->minor);
1058     lp = lp->next;
1059   }
1060
1061   return 0;
1062 }
1063
1064 static int parse_attachments (BUFFER *buf, BUFFER *s,
1065                               unsigned long data __attribute__ ((unused)),
1066                               BUFFER *err) {
1067   char op, *category;
1068   string_list_t **listp;
1069
1070   mutt_extract_token(buf, s, 0);
1071   if (!buf->data || *buf->data == '\0') {
1072     m_strcpy(err->data, err->dsize, _("attachments: no disposition"));
1073     return -1;
1074   }
1075
1076   category = buf->data;
1077   op = *category++;
1078
1079   if (op == '?') {
1080     mutt_endwin (NULL);
1081     fflush (stdout);
1082     printf("\nCurrent attachments settings:\n\n");
1083     print_attach_list(AttachAllow, '+', "A");
1084     print_attach_list(AttachExclude, '-', "A");
1085     print_attach_list(InlineAllow, '+', "I");
1086     print_attach_list(InlineExclude, '-', "I");
1087     set_option (OPTFORCEREDRAWINDEX);
1088     set_option (OPTFORCEREDRAWPAGER);
1089     mutt_any_key_to_continue (NULL);
1090     return 0;
1091   }
1092
1093   if (op != '+' && op != '-') {
1094     op = '+';
1095     category--;
1096   }
1097   if (!m_strncasecmp(category, "attachment", strlen(category))) {
1098     if (op == '+')
1099       listp = &AttachAllow;
1100     else
1101       listp = &AttachExclude;
1102   }
1103   else if (!m_strncasecmp(category, "inline", strlen(category))) {
1104     if (op == '+')
1105       listp = &InlineAllow;
1106     else
1107       listp = &InlineExclude;
1108   } else {
1109     m_strcpy(err->data, err->dsize, _("attachments: invalid disposition"));
1110     return -1;
1111   }
1112
1113   return parse_attach_list(buf, s, listp, err);
1114 }
1115
1116 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data __attribute__ ((unused)), BUFFER *err) {
1117   char op, *p;
1118   string_list_t **listp;
1119
1120   mutt_extract_token(buf, s, 0);
1121   if (!buf->data || *buf->data == '\0') {
1122     m_strcpy(err->data, err->dsize, _("unattachments: no disposition"));
1123     return -1;
1124   }
1125
1126   p = buf->data;
1127   op = *p++;
1128   if (op != '+' && op != '-') {
1129     op = '+';
1130     p--;
1131   }
1132   if (!m_strncasecmp(p, "attachment", strlen(p))) {
1133     if (op == '+')
1134       listp = &AttachAllow;
1135     else
1136       listp = &AttachExclude;
1137   }
1138   else if (!m_strncasecmp(p, "inline", strlen(p))) {
1139     if (op == '+')
1140       listp = &InlineAllow;
1141     else
1142       listp = &InlineExclude;
1143   }
1144   else {
1145     m_strcpy(err->data, err->dsize, _("unattachments: invalid disposition"));
1146     return -1;
1147   }
1148
1149   return parse_unattach_list(buf, s, listp, err);
1150 }
1151
1152 static int parse_unlists (BUFFER * buf, BUFFER * s,
1153                           unsigned long data __attribute__ ((unused)),
1154                           BUFFER * err __attribute__ ((unused)))
1155 {
1156   do {
1157     mutt_extract_token (buf, s, 0);
1158     remove_from_rx_list (&SubscribedLists, buf->data);
1159     remove_from_rx_list (&MailLists, buf->data);
1160
1161     if (m_strcmp(buf->data, "*") &&
1162         add_to_rx_list (&UnMailLists, buf->data, REG_ICASE, err) != 0)
1163       return -1;
1164   }
1165   while (MoreArgs (s));
1166
1167   return 0;
1168 }
1169
1170 static int parse_subscribe (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
1171                             BUFFER * err)
1172 {
1173   do {
1174     mutt_extract_token (buf, s, 0);
1175     remove_from_rx_list (&UnMailLists, buf->data);
1176     remove_from_rx_list (&UnSubscribedLists, buf->data);
1177
1178     if (add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
1179       return -1;
1180     if (add_to_rx_list (&SubscribedLists, buf->data, REG_ICASE, err) != 0)
1181       return -1;
1182   }
1183   while (MoreArgs (s));
1184
1185   return 0;
1186 }
1187
1188 static int parse_unsubscribe (BUFFER * buf, BUFFER * s,
1189                               unsigned long data __attribute__ ((unused)),
1190                               BUFFER * err __attribute__ ((unused)))
1191 {
1192   do {
1193     mutt_extract_token (buf, s, 0);
1194     remove_from_rx_list (&SubscribedLists, buf->data);
1195
1196     if (m_strcmp(buf->data, "*") &&
1197         add_to_rx_list (&UnSubscribedLists, buf->data, REG_ICASE, err) != 0)
1198       return -1;
1199   }
1200   while (MoreArgs (s));
1201
1202   return 0;
1203 }
1204
1205 static int parse_unalias (BUFFER * buf, BUFFER * s,
1206                           unsigned long data __attribute__ ((unused)),
1207                           BUFFER * err __attribute__ ((unused)))
1208 {
1209   alias_t *tmp, *last = NULL;
1210
1211   do {
1212     mutt_extract_token (buf, s, 0);
1213
1214     if (m_strcmp("*", buf->data) == 0) {
1215       if (CurrentMenu == MENU_ALIAS) {
1216         for (tmp = Aliases; tmp; tmp = tmp->next)
1217           tmp->del = 1;
1218         set_option (OPTFORCEREDRAWINDEX);
1219       }
1220       else
1221         alias_list_wipe(&Aliases);
1222       break;
1223     }
1224     else
1225       for (tmp = Aliases; tmp; tmp = tmp->next) {
1226         if (m_strcasecmp(buf->data, tmp->name) == 0) {
1227           if (CurrentMenu == MENU_ALIAS) {
1228             tmp->del = 1;
1229             set_option (OPTFORCEREDRAWINDEX);
1230             break;
1231           }
1232
1233           if (last)
1234             last->next = tmp->next;
1235           else
1236             Aliases = tmp->next;
1237           tmp->next = NULL;
1238           alias_list_wipe(&tmp);
1239           break;
1240         }
1241         last = tmp;
1242       }
1243   }
1244   while (MoreArgs (s));
1245   return 0;
1246 }
1247
1248 static int parse_alias (BUFFER * buf, BUFFER * s,
1249                         unsigned long data __attribute__ ((unused)),
1250                         BUFFER * err)
1251 {
1252   alias_t *tmp = Aliases;
1253   alias_t *last = NULL;
1254   char *estr = NULL;
1255
1256   if (!MoreArgs (s)) {
1257     m_strcpy(err->data, err->dsize, _("alias: no address"));
1258     return (-1);
1259   }
1260
1261   mutt_extract_token (buf, s, 0);
1262
1263   /* check to see if an alias with this name already exists */
1264   for (; tmp; tmp = tmp->next) {
1265     if (!m_strcasecmp(tmp->name, buf->data))
1266       break;
1267     last = tmp;
1268   }
1269
1270   if (!tmp) {
1271     /* create a new alias */
1272     tmp = alias_new();
1273     tmp->name = m_strdup(buf->data);
1274     /* give the main addressbook code a chance */
1275     if (CurrentMenu == MENU_ALIAS)
1276       set_option (OPTMENUCALLER);
1277   }
1278   else {
1279     /* override the previous value */
1280     address_list_wipe(&tmp->addr);
1281     if (CurrentMenu == MENU_ALIAS)
1282       set_option (OPTFORCEREDRAWINDEX);
1283   }
1284
1285   mutt_extract_token (buf, s,
1286                       M_TOKEN_QUOTE | M_TOKEN_SPACE | M_TOKEN_SEMICOLON);
1287   tmp->addr = mutt_parse_adrlist (tmp->addr, buf->data);
1288   if (last)
1289     last->next = tmp;
1290   else
1291     Aliases = tmp;
1292   if (mutt_addrlist_to_idna (tmp->addr, &estr)) {
1293     snprintf (err->data, err->dsize,
1294               _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, tmp->name);
1295     p_delete(&estr);
1296     return -1;
1297   }
1298
1299   return 0;
1300 }
1301
1302 static int
1303 parse_unmy_hdr (BUFFER * buf, BUFFER * s,
1304                 unsigned long data __attribute__ ((unused)),
1305                 BUFFER * err __attribute__ ((unused)))
1306 {
1307   string_list_t *last = NULL;
1308   string_list_t *tmp = UserHeader;
1309   string_list_t *ptr;
1310   ssize_t l;
1311
1312   do {
1313     mutt_extract_token (buf, s, 0);
1314     if (m_strcmp("*", buf->data) == 0)
1315       string_list_wipe(&UserHeader);
1316     else {
1317       tmp = UserHeader;
1318       last = NULL;
1319
1320       l = m_strlen(buf->data);
1321       if (buf->data[l - 1] == ':')
1322         l--;
1323
1324       while (tmp) {
1325         if (ascii_strncasecmp (buf->data, tmp->data, l) == 0
1326             && tmp->data[l] == ':') {
1327           ptr = tmp;
1328           if (last)
1329             last->next = tmp->next;
1330           else
1331             UserHeader = tmp->next;
1332           tmp = tmp->next;
1333           ptr->next = NULL;
1334           string_list_wipe(&ptr);
1335         }
1336         else {
1337           last = tmp;
1338           tmp = tmp->next;
1339         }
1340       }
1341     }
1342   }
1343   while (MoreArgs (s));
1344   return 0;
1345 }
1346
1347 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
1348                          BUFFER * err)
1349 {
1350   string_list_t *tmp;
1351   ssize_t keylen;
1352   char *p;
1353
1354   mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
1355   if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
1356     m_strcpy(err->data, err->dsize, _("invalid header field"));
1357     return (-1);
1358   }
1359   keylen = p - buf->data + 1;
1360
1361   if (UserHeader) {
1362     for (tmp = UserHeader;; tmp = tmp->next) {
1363       /* see if there is already a field by this name */
1364       if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
1365         /* replace the old value */
1366         p_delete(&tmp->data);
1367         tmp->data = buf->data;
1368         p_clear(buf, 1);
1369         return 0;
1370       }
1371       if (!tmp->next)
1372         break;
1373     }
1374     tmp->next = string_item_new();
1375     tmp = tmp->next;
1376   }
1377   else {
1378     tmp = string_item_new();
1379     UserHeader = tmp;
1380   }
1381   tmp->data = buf->data;
1382   p_clear(buf, 1);
1383   return 0;
1384 }
1385
1386 static int
1387 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
1388             char* errbuf, ssize_t errlen) {
1389   int i, flags = 0;
1390
1391   if (m_strncmp("reverse-", s, 8) == 0) {
1392     s += 8;
1393     flags = SORT_REVERSE;
1394   }
1395
1396   if (m_strncmp("last-", s, 5) == 0) {
1397     s += 5;
1398     flags |= SORT_LAST;
1399   }
1400
1401   if ((i = mutt_getvaluebyname (s, map)) == -1) {
1402     if (errbuf)
1403       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
1404     return (-1);
1405   }
1406
1407   *((short*) dst->data) = i | flags;
1408   return 0;
1409 }
1410
1411 /* if additional data more == 1, we want to resolve synonyms */
1412 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
1413 {
1414     char buf[LONG_STRING];
1415     struct option_t *ptr = p;
1416
1417     if (DTYPE(ptr->type) == DT_SYN) {
1418         if (!more)
1419             return;
1420         ptr = hash_find(ConfigOptions, (const char *)ptr->data);
1421     }
1422     if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_fromstr)
1423         return;
1424
1425     mutt_option_value(ptr->option, buf, sizeof(buf));
1426     if (m_strlen(ptr->init) == 0 && buf && *buf)
1427         ptr->init = m_strdup(buf);
1428 }
1429
1430 static struct option_t* add_option (const char* name, const char* init,
1431                                     short type, short dodup) {
1432   struct option_t* option = p_new(struct option_t, 1);
1433
1434   option->option = m_strdup(name);
1435   option->type = type;
1436   if (init)
1437     option->init = dodup ? m_strdup(init) : (char*) init;
1438   return (option);
1439 }
1440
1441 /* creates new option_t* of type DT_USER for $user_ var */
1442 static struct option_t* add_user_option (const char* name) {
1443   return (add_option (name, NULL, DT_USER, 1));
1444 }
1445
1446 /* free()'s option_t* */
1447 static void del_option (void* p) {
1448   struct option_t *ptr = (struct option_t*) p;
1449   char* s = (char*) ptr->data;
1450   p_delete(&ptr->option);
1451   p_delete(&s);
1452   p_delete(&ptr->init);
1453   p_delete(&ptr);
1454 }
1455
1456 static int init_expand (char** dst, struct option_t* src) {
1457   BUFFER token, in;
1458   ssize_t len = 0;
1459
1460   p_delete(dst);
1461
1462   if (DTYPE(src->type) == DT_STR ||
1463       DTYPE(src->type) == DT_PATH) {
1464     /* only expand for string as it's the only place where
1465      * we want to expand vars right now */
1466     if (src->init && *src->init) {
1467       p_clear(&token, 1);
1468       p_clear(&in, 1);
1469       len = m_strlen(src->init) + 2;
1470       in.data = p_new(char, len + 1);
1471       snprintf (in.data, len, "\"%s\"", src->init);
1472       in.dptr = in.data;
1473       in.dsize = len;
1474       mutt_extract_token (&token, &in, 0);
1475       if (token.data && *token.data)
1476         *dst = m_strdup(token.data);
1477       else
1478         *dst = m_strdup("");
1479       p_delete(&in.data);
1480       p_delete(&token.data);
1481     } else
1482       *dst = m_strdup("");
1483   } else
1484     /* for non-string: take value as is */
1485     *dst = m_strdup(src->init);
1486   return (1);
1487 }
1488
1489 /* if additional data more == 1, we want to resolve synonyms */
1490 static void mutt_restore_default (const char* name __attribute__ ((unused)),
1491                                   void* p, unsigned long more) {
1492   char errbuf[STRING];
1493   struct option_t* ptr = (struct option_t*) p;
1494   char* init = NULL;
1495
1496   if (DTYPE (ptr->type) == DT_SYN) {
1497     if (!more)
1498       return;
1499     ptr = hash_find (ConfigOptions, (char*) ptr->data);
1500   }
1501   if (!ptr)
1502     return;
1503   if (FuncTable[DTYPE (ptr->type)].opt_fromstr) {
1504     init_expand (&init, ptr);
1505     if (!FuncTable[DTYPE (ptr->type)].opt_fromstr (ptr, init, errbuf,
1506                                                        sizeof(errbuf))) {
1507       if (!option (OPTNOCURSES))
1508         mutt_endwin (NULL);
1509       fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
1510                          "Please report this error: \"%s\"\n"),
1511                ptr->option, NONULL (init), errbuf);
1512       exit (1);
1513     }
1514     p_delete(&init);
1515   }
1516
1517   if (ptr->flags & R_INDEX)
1518     set_option (OPTFORCEREDRAWINDEX);
1519   if (ptr->flags & R_PAGER)
1520     set_option (OPTFORCEREDRAWPAGER);
1521   if (ptr->flags & R_RESORT_SUB)
1522     set_option (OPTSORTSUBTHREADS);
1523   if (ptr->flags & R_RESORT)
1524     set_option (OPTNEEDRESORT);
1525   if (ptr->flags & R_RESORT_INIT)
1526     set_option (OPTRESORTINIT);
1527   if (ptr->flags & R_TREE)
1528     set_option (OPTREDRAWTREE);
1529 }
1530
1531 /* check whether value for $dsn_return would be valid */
1532 static int check_dsn_return (const char* option __attribute__ ((unused)), unsigned long p,
1533                              char* errbuf, ssize_t errlen) {
1534   char* val = (char*) p;
1535   if (val && *val && m_strncmp(val, "hdrs", 4) != 0 &&
1536       m_strncmp(val, "full", 4) != 0) {
1537     if (errbuf)
1538       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), val, "dsn_return");
1539     return (0);
1540   }
1541   return (1);
1542 }
1543
1544 /* check whether value for $dsn_notify would be valid */
1545 static int
1546 check_dsn_notify (const char* option __attribute__ ((unused)),
1547                   unsigned long val, char* errbuf, ssize_t errlen)
1548 {
1549     const char *p = (const char*)val;
1550
1551     while (p && *p) {
1552         const char *q = m_strchrnul(p, ',');
1553         int len = q - p;
1554
1555         if (!m_strncmp(p, "never", len)   && !m_strncmp(p, "delay", len)
1556         &&  !m_strncmp(p, "failure", len) && !m_strncmp(p, "success", len))
1557         {
1558             if (errbuf)
1559                 snprintf(errbuf, errlen, _("'%.*s' is invalid for $%s"),
1560                          len, p, "dsn_notify");
1561             return 0;
1562         }
1563
1564         p = q + 1;
1565     }
1566
1567     return 1;
1568 }
1569
1570 static int check_num (const char* option, unsigned long p,
1571                       char* errbuf, ssize_t errlen) {
1572   if ((int) p < 0) {
1573     if (errbuf)
1574       snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
1575     return (0);
1576   }
1577   return (1);
1578 }
1579
1580 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
1581                           char* errbuf, ssize_t errlen) {
1582   if (!check_num ("history", p, errbuf, errlen))
1583     return (0);
1584   mutt_init_history ();
1585   return (1);
1586 }
1587
1588 static int check_special (const char* name, unsigned long val,
1589                           char* errbuf, ssize_t errlen) {
1590   int i = 0;
1591
1592   for (i = 0; SpecialVars[i].name; i++) {
1593     if (m_strcmp(SpecialVars[i].name, name) == 0) {
1594       return (SpecialVars[i].check (SpecialVars[i].name,
1595                                     val, errbuf, errlen));
1596     }
1597   }
1598   return (1);
1599 }
1600
1601 static const struct mapping_t* get_sortmap (struct option_t* option) {
1602   const struct mapping_t* map = NULL;
1603
1604   switch (option->type & DT_SUBTYPE_MASK) {
1605   case DT_SORT_ALIAS:
1606     map = SortAliasMethods;
1607     break;
1608   case DT_SORT_BROWSER:
1609     map = SortBrowserMethods;
1610     break;
1611   case DT_SORT_KEYS:
1612     map = SortKeyMethods;
1613     break;
1614   case DT_SORT_AUX:
1615     map = SortAuxMethods;
1616     break;
1617   default:
1618     map = SortMethods;
1619     break;
1620   }
1621   return (map);
1622 }
1623
1624 #define CHECK_PAGER \
1625   if ((CurrentMenu == MENU_PAGER) && \
1626       (!option || (option->flags & R_RESORT))) \
1627   { \
1628     snprintf (err->data, err->dsize, \
1629               _("Not available in this menu.")); \
1630     return (-1); \
1631   }
1632
1633 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1634                       BUFFER * err)
1635 {
1636   int query, unset, inv, reset, r = 0;
1637   struct option_t* option = NULL;
1638
1639   while (MoreArgs (s)) {
1640     /* reset state variables */
1641     query = 0;
1642     unset = data & M_SET_UNSET;
1643     inv = data & M_SET_INV;
1644     reset = data & M_SET_RESET;
1645
1646     if (*s->dptr == '?') {
1647       query = 1;
1648       s->dptr++;
1649     }
1650     else if (m_strncmp("no", s->dptr, 2) == 0) {
1651       s->dptr += 2;
1652       unset = !unset;
1653     }
1654     else if (m_strncmp("inv", s->dptr, 3) == 0) {
1655       s->dptr += 3;
1656       inv = !inv;
1657     }
1658     else if (*s->dptr == '&') {
1659       reset = 1;
1660       s->dptr++;
1661     }
1662
1663     /* get the variable name */
1664     mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1665
1666     /* resolve synonyms */
1667     if ((option = hash_find (ConfigOptions, tmp->data)) != NULL &&
1668         DTYPE (option->type == DT_SYN))
1669     {
1670       struct option_t* newopt = hash_find (ConfigOptions, (char*) option->data);
1671       syn_t* syn = syn_new();
1672       syn->f = m_strdup(CurRCFile);
1673       syn->l = CurRCLine;
1674       syn->n = newopt;
1675       syn->o = option;
1676       syn_list_push(&Synonyms, syn);
1677       option = newopt;
1678     }
1679
1680     /* see if we need to add $user_ var */
1681     if (!option && m_strncmp("user_", tmp->data, 5) == 0) {
1682       /* there's no option named like this yet so only add one
1683        * if the action isn't any of: reset, unset, query */
1684       if (!(reset || unset || query || *s->dptr != '=')) {
1685         option = add_user_option (tmp->data);
1686         hash_insert (ConfigOptions, option->option, option, 0);
1687       }
1688     }
1689
1690     if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1691       snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1692       return (-1);
1693     }
1694     s->dptr = vskipspaces(s->dptr);
1695
1696     if (reset) {
1697       if (query || unset || inv) {
1698         snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1699         return (-1);
1700       }
1701
1702       if (s && *s->dptr == '=') {
1703         snprintf (err->data, err->dsize, _("value is illegal with reset"));
1704         return (-1);
1705       }
1706
1707       if (!m_strcmp("all", tmp->data)) {
1708         if (CurrentMenu == MENU_PAGER) {
1709           snprintf (err->data, err->dsize, _("Not available in this menu."));
1710           return (-1);
1711         }
1712         hash_map (ConfigOptions, mutt_restore_default, 1);
1713         set_option (OPTFORCEREDRAWINDEX);
1714         set_option (OPTFORCEREDRAWPAGER);
1715         set_option (OPTSORTSUBTHREADS);
1716         set_option (OPTNEEDRESORT);
1717         set_option (OPTRESORTINIT);
1718         set_option (OPTREDRAWTREE);
1719         return (0);
1720       }
1721       else if (!FuncTable[DTYPE (option->type)].opt_fromstr) {
1722         snprintf (err->data, err->dsize, _("$%s is read-only"), option->option);
1723         r = -1;
1724         break;
1725       } else {
1726         CHECK_PAGER;
1727         mutt_restore_default (NULL, option, 1);
1728       }
1729     }
1730     else if (DTYPE (option->type) == DT_BOOL) {
1731       /* XXX this currently ignores the function table
1732        * as we don't get invert and stuff into it */
1733       if (s && *s->dptr == '=') {
1734         if (unset || inv || query) {
1735           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1736           return (-1);
1737         }
1738
1739         s->dptr++;
1740         mutt_extract_token (tmp, s, 0);
1741         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1742           unset = inv = 0;
1743         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1744           unset = 1;
1745         else {
1746           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1747           return (-1);
1748         }
1749       }
1750
1751       if (query) {
1752         bool_to_string (err->data, err->dsize, option);
1753         return 0;
1754       }
1755
1756       CHECK_PAGER;
1757       if (unset)
1758         unset_option (option->data);
1759       else if (inv)
1760         toggle_option (option->data);
1761       else
1762         set_option (option->data);
1763     }
1764     else if (DTYPE (option->type) == DT_STR ||
1765              DTYPE (option->type) == DT_PATH ||
1766              DTYPE (option->type) == DT_ADDR ||
1767              DTYPE (option->type) == DT_MAGIC ||
1768              DTYPE (option->type) == DT_NUM ||
1769              DTYPE (option->type) == DT_SORT ||
1770              DTYPE (option->type) == DT_RX ||
1771              DTYPE (option->type) == DT_USER ||
1772              DTYPE (option->type) == DT_SYS) {
1773
1774       /* XXX maybe we need to get unset into handlers? */
1775       if (DTYPE (option->type) == DT_STR ||
1776           DTYPE (option->type) == DT_PATH ||
1777           DTYPE (option->type) == DT_ADDR ||
1778           DTYPE (option->type) == DT_USER ||
1779           DTYPE (option->type) == DT_SYS) {
1780         if (unset) {
1781           CHECK_PAGER;
1782           if (!FuncTable[DTYPE (option->type)].opt_fromstr) {
1783             snprintf (err->data, err->dsize, _("$%s is read-only"),
1784                       option->option);
1785             r = -1;
1786             break;
1787           } else if (DTYPE (option->type) == DT_ADDR)
1788             address_list_wipe((address_t **) option->data);
1789           else if (DTYPE (option->type) == DT_USER)
1790             /* to unset $user_ means remove */
1791             hash_delete (ConfigOptions, option->option,
1792                          option, del_option);
1793           else
1794             p_delete((void **)(void *)&option->data);
1795           break;
1796         }
1797       }
1798
1799       if (query || *s->dptr != '=') {
1800         FuncTable[DTYPE (option->type)].opt_tostr
1801           (err->data, err->dsize, option);
1802         break;
1803       }
1804
1805       /* the $madmutt_ variables are read-only */
1806       if (!FuncTable[DTYPE (option->type)].opt_fromstr) {
1807         snprintf (err->data, err->dsize, _("$%s is read-only"),
1808                   option->option);
1809         r = -1;
1810         break;
1811       } else {
1812         CHECK_PAGER;
1813         s->dptr++;
1814         mutt_extract_token (tmp, s, 0);
1815         if (!FuncTable[DTYPE (option->type)].opt_fromstr
1816             (option, tmp->data, err->data, err->dsize))
1817           r = -1;
1818       }
1819     }
1820     else if (DTYPE (option->type) == DT_QUAD) {
1821
1822       if (query) {
1823         quad_to_string (err->data, err->dsize, option);
1824         break;
1825       }
1826
1827       if (*s->dptr == '=') {
1828         CHECK_PAGER;
1829         s->dptr++;
1830         mutt_extract_token (tmp, s, 0);
1831         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1832           set_quadoption (option->data, M_YES);
1833         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1834           set_quadoption (option->data, M_NO);
1835         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1836           set_quadoption (option->data, M_ASKYES);
1837         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1838           set_quadoption (option->data, M_ASKNO);
1839         else {
1840           snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1841                     tmp->data, option->option);
1842           r = -1;
1843           break;
1844         }
1845       }
1846       else {
1847         if (inv)
1848           toggle_quadoption (option->data);
1849         else if (unset)
1850           set_quadoption (option->data, M_NO);
1851         else
1852           set_quadoption (option->data, M_YES);
1853       }
1854     }
1855     else {
1856       snprintf (err->data, err->dsize, _("%s: unknown type"),
1857                 option->option);
1858       r = -1;
1859       break;
1860     }
1861
1862     if (option->flags & R_INDEX)
1863       set_option (OPTFORCEREDRAWINDEX);
1864     if (option->flags & R_PAGER)
1865       set_option (OPTFORCEREDRAWPAGER);
1866     if (option->flags & R_RESORT_SUB)
1867       set_option (OPTSORTSUBTHREADS);
1868     if (option->flags & R_RESORT)
1869       set_option (OPTNEEDRESORT);
1870     if (option->flags & R_RESORT_INIT)
1871       set_option (OPTRESORTINIT);
1872     if (option->flags & R_TREE)
1873       set_option (OPTREDRAWTREE);
1874   }
1875   return (r);
1876 }
1877
1878 #define MAXERRS 128
1879
1880 /* reads the specified initialization file.  returns -1 if errors were found
1881    so that we can pause to let the user know...  */
1882 static int source_rc (const char *rcfile, BUFFER * err)
1883 {
1884   FILE *f;
1885   int line = 0, rc = 0, conv = 0;
1886   BUFFER token;
1887   char *linebuf = NULL;
1888   char *currentline = NULL;
1889   ssize_t buflen;
1890   pid_t pid;
1891
1892   if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1893     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1894     return (-1);
1895   }
1896
1897   p_clear(&token, 1);
1898   while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
1899     conv = ConfigCharset && (*ConfigCharset) && Charset;
1900     if (conv) {
1901       currentline = m_strdup(linebuf);
1902       if (!currentline)
1903         continue;
1904       mutt_convert_string (&currentline, ConfigCharset, Charset, 0);
1905     }
1906     else
1907       currentline = linebuf;
1908
1909     CurRCLine = line;
1910     CurRCFile = rcfile;
1911
1912     if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1913       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1914       if (--rc < -MAXERRS) {
1915         if (conv)
1916           p_delete(&currentline);
1917         break;
1918       }
1919     }
1920     else {
1921       if (rc < 0)
1922         rc = -1;
1923     }
1924     if (conv)
1925       p_delete(&currentline);
1926   }
1927   p_delete(&token.data);
1928   p_delete(&linebuf);
1929   m_fclose(&f);
1930   if (pid != -1)
1931     mutt_wait_filter (pid);
1932   if (rc) {
1933     /* the muttrc source keyword */
1934     snprintf (err->data, err->dsize,
1935               rc >= -MAXERRS ? _("source: errors in %s")
1936               : _("source: reading aborted due too many errors in %s"),
1937               rcfile);
1938     rc = -1;
1939   }
1940   return (rc);
1941 }
1942
1943 #undef MAXERRS
1944
1945 static int parse_source (BUFFER * tmp, BUFFER * s,
1946                          unsigned long data __attribute__ ((unused)),
1947                          BUFFER * err)
1948 {
1949   char path[_POSIX_PATH_MAX];
1950   int rc = 0;
1951
1952   do {
1953     if (mutt_extract_token (tmp, s, 0) != 0) {
1954       snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1955       return (-1);
1956     }
1957
1958     m_strcpy(path, sizeof(path), tmp->data);
1959     mutt_expand_path (path, sizeof(path));
1960
1961     rc += source_rc (path, err);
1962   }
1963   while (MoreArgs (s));
1964
1965   return ((rc < 0) ? -1 : 0);
1966 }
1967
1968 /* line         command to execute
1969
1970    token        scratch buffer to be used by parser.  caller should free
1971                 token->data when finished.  the reason for this variable is
1972                 to avoid having to allocate and deallocate a lot of memory
1973                 if we are parsing many lines.  the caller can pass in the
1974                 memory to use, which avoids having to create new space for
1975                 every call to this function.
1976
1977    err          where to write error messages */
1978 int mutt_parse_rc_line ( /* const */ char *line, BUFFER * token, BUFFER * err)
1979 {
1980   int i, r = -1;
1981   BUFFER expn;
1982
1983   p_clear(&expn, 1);
1984   expn.data = expn.dptr = line;
1985   expn.dsize = m_strlen(line);
1986
1987   *err->data = 0;
1988
1989   expn.dptr = vskipspaces(expn.dptr);
1990   while (*expn.dptr) {
1991     if (*expn.dptr == '#')
1992       break;                    /* rest of line is a comment */
1993     if (*expn.dptr == ';') {
1994       expn.dptr++;
1995       continue;
1996     }
1997     mutt_extract_token (token, &expn, 0);
1998     for (i = 0; Commands[i].name; i++) {
1999       if (!m_strcmp(token->data, Commands[i].name)) {
2000         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
2001           goto finish;
2002         break;
2003       }
2004     }
2005     if (!Commands[i].name) {
2006       snprintf (err->data, err->dsize, _("%s: unknown command"),
2007                 NONULL (token->data));
2008       goto finish;
2009     }
2010   }
2011   r = 0;
2012 finish:
2013   if (expn.destroy)
2014     p_delete(&expn.data);
2015   return (r);
2016 }
2017
2018
2019 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
2020 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
2021 /* initial string that starts completion. No telling how much crap
2022  * the user has typed so far. Allocate LONG_STRING just to be sure! */
2023 char User_typed[LONG_STRING] = { 0 };
2024
2025 int Num_matched = 0;            /* Number of matches for completion */
2026 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
2027 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1];  /* all the matches + User_typed */
2028
2029 /* helper function for completion.  Changes the dest buffer if
2030    necessary/possible to aid completion.
2031         dest == completion result gets here.
2032         src == candidate for completion.
2033         try == user entered data for completion.
2034         len == length of dest buffer.
2035 */
2036 static void candidate (char *dest, char *try, const char *src, int len)
2037 {
2038   int l;
2039
2040   if (strstr (src, try) == src) {
2041     Matches[Num_matched++] = src;
2042     if (dest[0] == 0)
2043       m_strcpy(dest, len, src);
2044     else {
2045       for (l = 0; src[l] && src[l] == dest[l]; l++);
2046       dest[l] = 0;
2047     }
2048   }
2049 }
2050
2051 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
2052 {
2053   char *pt = buffer;
2054   int num;
2055   int spaces;                   /* keep track of the number of leading spaces on the line */
2056
2057   buffer = vskipspaces(buffer);
2058   spaces = buffer - pt;
2059
2060   pt = buffer + pos - spaces;
2061   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2062     pt--;
2063
2064   if (pt == buffer) {           /* complete cmd */
2065     /* first TAB. Collect all the matches */
2066     if (numtabs == 1) {
2067       Num_matched = 0;
2068       m_strcpy(User_typed, sizeof(User_typed), pt);
2069       p_clear(Matches, countof(Matches));
2070       p_clear(Completed, countof(Completed));
2071       for (num = 0; Commands[num].name; num++)
2072         candidate (Completed, User_typed, Commands[num].name,
2073                    sizeof(Completed));
2074       Matches[Num_matched++] = User_typed;
2075
2076       /* All matches are stored. Longest non-ambiguous string is ""
2077        * i.e. dont change 'buffer'. Fake successful return this time */
2078       if (User_typed[0] == 0)
2079         return 1;
2080     }
2081
2082     if (Completed[0] == 0 && User_typed[0])
2083       return 0;
2084
2085     /* Num_matched will _always_ be atleast 1 since the initial
2086      * user-typed string is always stored */
2087     if (numtabs == 1 && Num_matched == 2)
2088       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2089     else if (numtabs > 1 && Num_matched > 2)
2090       /* cycle thru all the matches */
2091       snprintf (Completed, sizeof(Completed), "%s",
2092                 Matches[(numtabs - 2) % Num_matched]);
2093
2094     /* return the completed command */
2095     m_strcpy(buffer, len - spaces, Completed);
2096   }
2097   else if (!m_strncmp(buffer, "set", 3)
2098            || !m_strncmp(buffer, "unset", 5)
2099            || !m_strncmp(buffer, "reset", 5)
2100            || !m_strncmp(buffer, "toggle", 6)) {    /* complete variables */
2101     const char *prefixes[] = { "no", "inv", "?", "&", NULL };
2102
2103     pt++;
2104     /* loop through all the possible prefixes (no, inv, ...) */
2105     if (!m_strncmp(buffer, "set", 3)) {
2106       for (num = 0; prefixes[num]; num++) {
2107         if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
2108           pt += m_strlen(prefixes[num]);
2109           break;
2110         }
2111       }
2112     }
2113
2114     /* first TAB. Collect all the matches */
2115     if (numtabs == 1) {
2116       Num_matched = 0;
2117       m_strcpy(User_typed, sizeof(User_typed), pt);
2118       p_clear(Matches, countof(Matches));
2119       p_clear(Completed, countof(Completed));
2120       for (num = 0; MuttVars[num].option; num++)
2121         candidate(Completed, User_typed, MuttVars[num].option,
2122                   sizeof(Completed));
2123       Matches[Num_matched++] = User_typed;
2124
2125       /* All matches are stored. Longest non-ambiguous string is ""
2126        * i.e. dont change 'buffer'. Fake successful return this time */
2127       if (User_typed[0] == 0)
2128         return 1;
2129     }
2130
2131     if (Completed[0] == 0 && User_typed[0])
2132       return 0;
2133
2134     /* Num_matched will _always_ be atleast 1 since the initial
2135      * user-typed string is always stored */
2136     if (numtabs == 1 && Num_matched == 2)
2137       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2138     else if (numtabs > 1 && Num_matched > 2)
2139       /* cycle thru all the matches */
2140       snprintf (Completed, sizeof(Completed), "%s",
2141                 Matches[(numtabs - 2) % Num_matched]);
2142
2143     m_strcpy(pt, buffer + len - pt - spaces, Completed);
2144   }
2145   else if (!m_strncmp(buffer, "exec", 4)) {
2146     struct binding_t *menu = km_get_table (CurrentMenu);
2147
2148     if (!menu && CurrentMenu != MENU_PAGER)
2149       menu = OpGeneric;
2150
2151     pt++;
2152     /* first TAB. Collect all the matches */
2153     if (numtabs == 1) {
2154       Num_matched = 0;
2155       m_strcpy(User_typed, sizeof(User_typed), pt);
2156       p_clear(Matches, countof(Matches));
2157       p_clear(Completed, countof(Completed));
2158       for (num = 0; menu[num].name; num++)
2159         candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
2160       /* try the generic menu */
2161       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
2162         menu = OpGeneric;
2163         for (num = 0; menu[num].name; num++)
2164           candidate (Completed, User_typed, menu[num].name,
2165                      sizeof(Completed));
2166       }
2167       Matches[Num_matched++] = User_typed;
2168
2169       /* All matches are stored. Longest non-ambiguous string is ""
2170        * i.e. dont change 'buffer'. Fake successful return this time */
2171       if (User_typed[0] == 0)
2172         return 1;
2173     }
2174
2175     if (Completed[0] == 0 && User_typed[0])
2176       return 0;
2177
2178     /* Num_matched will _always_ be atleast 1 since the initial
2179      * user-typed string is always stored */
2180     if (numtabs == 1 && Num_matched == 2)
2181       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2182     else if (numtabs > 1 && Num_matched > 2)
2183       /* cycle thru all the matches */
2184       snprintf (Completed, sizeof(Completed), "%s",
2185                 Matches[(numtabs - 2) % Num_matched]);
2186
2187     m_strcpy(pt, buffer + len - pt - spaces, Completed);
2188   }
2189   else
2190     return 0;
2191
2192   return 1;
2193 }
2194
2195 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
2196 {
2197   char var[STRING], *pt = buffer;
2198   int spaces;
2199   struct option_t* option = NULL;
2200
2201   if (buffer[0] == 0)
2202     return 0;
2203
2204   buffer = vskipspaces(buffer);
2205   spaces = buffer - pt;
2206
2207   pt = buffer + pos - spaces;
2208   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2209     pt--;
2210   pt++;                         /* move past the space */
2211   if (*pt == '=')               /* abort if no var before the '=' */
2212     return 0;
2213
2214   if (m_strncmp(buffer, "set", 3) == 0) {
2215     m_strcpy(var, sizeof(var), pt);
2216     /* ignore the trailing '=' when comparing */
2217     var[m_strlen(var) - 1] = 0;
2218     if (!(option = hash_find (ConfigOptions, var)))
2219       return 0;                 /* no such variable. */
2220     else {
2221       char tmp[LONG_STRING], tmp2[LONG_STRING];
2222       char *s, *d;
2223       ssize_t dlen = buffer + len - pt - spaces;
2224       const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
2225
2226       tmp[0] = '\0';
2227
2228       if ((DTYPE (option->type) == DT_STR) ||
2229           (DTYPE (option->type) == DT_PATH) ||
2230           (DTYPE (option->type) == DT_RX)) {
2231         m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
2232         if (DTYPE (option->type) == DT_PATH)
2233           mutt_pretty_mailbox (tmp);
2234       }
2235       else if (DTYPE (option->type) == DT_ADDR) {
2236         rfc822_addrcat(tmp, sizeof(tmp), *((address_t **) option->data), 0);
2237       }
2238       else if (DTYPE (option->type) == DT_QUAD)
2239         m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
2240       else if (DTYPE (option->type) == DT_NUM)
2241         snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
2242       else if (DTYPE (option->type) == DT_SORT) {
2243         const struct mapping_t *map;
2244         const char *p;
2245
2246         switch (option->type & DT_SUBTYPE_MASK) {
2247         case DT_SORT_ALIAS:
2248           map = SortAliasMethods;
2249           break;
2250         case DT_SORT_BROWSER:
2251           map = SortBrowserMethods;
2252           break;
2253         case DT_SORT_KEYS:
2254           map = SortKeyMethods;
2255           break;
2256         default:
2257           map = SortMethods;
2258           break;
2259         }
2260         p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
2261         snprintf(tmp, sizeof(tmp), "%s%s%s",
2262                  (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
2263                  (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
2264       }
2265       else if (DTYPE (option->type) == DT_MAGIC) {
2266         const char *p;
2267         switch (DefaultMagic) {
2268           case M_MBOX:
2269             p = "mbox";
2270             break;
2271           case M_MMDF:
2272             p = "MMDF";
2273             break;
2274           case M_MH:
2275             p = "MH";
2276           break;
2277           case M_MAILDIR:
2278             p = "Maildir";
2279             break;
2280           default:
2281             p = "unknown";
2282         }
2283         m_strcpy(tmp, sizeof(tmp), p);
2284       }
2285       else if (DTYPE (option->type) == DT_BOOL)
2286         m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
2287       else
2288         return 0;
2289
2290       for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
2291         if (*s == '\\' || *s == '"')
2292           *d++ = '\\';
2293         *d++ = *s++;
2294       }
2295       *d = '\0';
2296
2297       m_strcpy(tmp, sizeof(tmp), pt);
2298       snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
2299
2300       return 1;
2301     }
2302   }
2303   return 0;
2304 }
2305
2306 /* Implement the -Q command line flag */
2307 int mutt_query_variables (string_list_t * queries)
2308 {
2309   string_list_t *p;
2310
2311   char errbuff[STRING];
2312   char command[STRING];
2313
2314   BUFFER err, token;
2315
2316   p_clear(&err, 1);
2317   p_clear(&token, 1);
2318
2319   err.data = errbuff;
2320   err.dsize = sizeof(errbuff);
2321
2322   for (p = queries; p; p = p->next) {
2323     snprintf (command, sizeof(command), "set ?%s\n", p->data);
2324     if (mutt_parse_rc_line (command, &token, &err) == -1) {
2325       fprintf (stderr, "%s\n", err.data);
2326       p_delete(&token.data);
2327       return 1;
2328     }
2329     printf ("%s\n", err.data);
2330   }
2331
2332   p_delete(&token.data);
2333   return 0;
2334 }
2335
2336 static int mutt_execute_commands (string_list_t * p)
2337 {
2338   BUFFER err, token;
2339   char errstr[STRING];
2340
2341   p_clear(&err, 1);
2342   err.data = errstr;
2343   err.dsize = sizeof(errstr);
2344   p_clear(&token, 1);
2345   for (; p; p = p->next) {
2346     if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
2347       fprintf (stderr, _("Error in command line: %s\n"), err.data);
2348       p_delete(&token.data);
2349       return (-1);
2350     }
2351   }
2352   p_delete(&token.data);
2353   return 0;
2354 }
2355
2356 void mutt_init (int skip_sys_rc, string_list_t * commands)
2357 {
2358   struct passwd *pw;
2359   struct utsname utsname;
2360   const char *p;
2361   char buffer[STRING], error[STRING];
2362   int default_rc = 0, need_pause = 0;
2363   int i;
2364   BUFFER err;
2365
2366   p_clear(&err, 1);
2367   err.data = error;
2368   err.dsize = sizeof(error);
2369
2370   /* use 3*sizeof(muttvars) instead of 2*sizeof()
2371    * to have some room for $user_ vars */
2372   ConfigOptions = hash_create (sizeof(MuttVars) * 3);
2373   for (i = 0; MuttVars[i].option; i++) {
2374     if (DTYPE (MuttVars[i].type) != DT_SYS)
2375       hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i], 0);
2376     else
2377       hash_insert (ConfigOptions, MuttVars[i].option,
2378                    add_option (MuttVars[i].option, MuttVars[i].init,
2379                                DT_SYS, 0), 0);
2380   }
2381
2382   /*
2383    * XXX - use something even more difficult to predict?
2384    */
2385   snprintf (AttachmentMarker, sizeof(AttachmentMarker),
2386             "\033]9;%ld\a", (long) time (NULL));
2387
2388   /* on one of the systems I use, getcwd() does not return the same prefix
2389      as is listed in the passwd file */
2390   if ((p = getenv ("HOME")))
2391     Homedir = m_strdup(p);
2392
2393   /* Get some information about the user */
2394   if ((pw = getpwuid (getuid ()))) {
2395     char rnbuf[STRING];
2396
2397     Username = m_strdup(pw->pw_name);
2398     if (!Homedir)
2399       Homedir = m_strdup(pw->pw_dir);
2400
2401     mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, GecosMask.rx);
2402     Realname = m_strdup(rnbuf);
2403     Shell = m_strdup(pw->pw_shell);
2404     endpwent ();
2405   }
2406   else {
2407     if (!Homedir) {
2408       mutt_endwin (NULL);
2409       fputs (_("unable to determine home directory"), stderr);
2410       exit (1);
2411     }
2412     if ((p = getenv ("USER")))
2413       Username = m_strdup(p);
2414     else {
2415       mutt_endwin (NULL);
2416       fputs (_("unable to determine username"), stderr);
2417       exit (1);
2418     }
2419     Shell = m_strdup((p = getenv ("SHELL")) ? p : "/bin/sh");
2420   }
2421
2422   /* And about the host... */
2423   uname (&utsname);
2424   /* some systems report the FQDN instead of just the hostname */
2425   if ((p = strchr (utsname.nodename, '.'))) {
2426     Hostname = p_dupstr(utsname.nodename, p - utsname.nodename);
2427     p++;
2428     m_strcpy(buffer, sizeof(buffer), p);       /* save the domain for below */
2429   }
2430   else
2431     Hostname = m_strdup(utsname.nodename);
2432
2433   if (!p && getdnsdomainname(buffer, sizeof(buffer)) == -1)
2434     Fqdn = m_strdup("@");
2435   else
2436   if (*buffer != '@') {
2437     Fqdn = p_new(char, m_strlen(buffer) + m_strlen(Hostname) + 2);
2438     sprintf (Fqdn, "%s.%s", NONULL(Hostname), buffer);
2439   }
2440   else
2441     Fqdn = m_strdup(NONULL (Hostname));
2442
2443 #ifdef USE_NNTP
2444   {
2445     FILE *f;
2446     char *q;
2447
2448     if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
2449       buffer[0] = '\0';
2450       fgets (buffer, sizeof(buffer), f);
2451       p = vskipspaces(buffer);
2452       q = (char*)p;
2453       while (*q && !isspace(*q))
2454         q++;
2455       *q = '\0';
2456       NewsServer = m_strdup(p);
2457       m_fclose(&f);
2458     }
2459   }
2460   if ((p = getenv ("NNTPSERVER")))
2461     NewsServer = m_strdup(p);
2462 #endif
2463
2464   if ((p = getenv ("MAIL")))
2465     Spoolfile = m_strdup(p);
2466   else if ((p = getenv ("MAILDIR")))
2467     Spoolfile = m_strdup(p);
2468   else {
2469 #ifdef HOMESPOOL
2470     mutt_concat_path(buffer, sizeof(buffer), NONULL(Homedir), MAILPATH);
2471 #else
2472     mutt_concat_path(buffer, sizeof(buffer), MAILPATH, NONULL(Username));
2473 #endif
2474     Spoolfile = m_strdup(buffer);
2475   }
2476
2477   if ((p = getenv ("MAILCAPS")))
2478     MailcapPath = m_strdup(p);
2479   else {
2480     /* Default search path from RFC1524 */
2481     MailcapPath =
2482       m_strdup("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR
2483                    "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap");
2484   }
2485
2486   Tempdir = m_strdup((p = getenv ("TMPDIR")) ? p : "/tmp");
2487
2488   p = getenv ("VISUAL");
2489   if (!p) {
2490     p = getenv ("EDITOR");
2491     if (!p)
2492       p = "vi";
2493   }
2494   Editor = m_strdup(p);
2495
2496   if ((p = getenv ("REPLYTO")) != NULL) {
2497     BUFFER buf, token;
2498
2499     snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
2500
2501     p_clear(&buf, 1);
2502     buf.data = buf.dptr = buffer;
2503     buf.dsize = m_strlen(buffer);
2504
2505     p_clear(&token, 1);
2506     parse_my_hdr (&token, &buf, 0, &err);
2507     p_delete(&token.data);
2508   }
2509
2510   if ((p = getenv ("EMAIL")) != NULL)
2511     From = rfc822_parse_adrlist (NULL, p);
2512
2513   charset_initialize();
2514
2515   /* Set standard defaults */
2516   hash_map (ConfigOptions, mutt_set_default, 0);
2517   hash_map (ConfigOptions, mutt_restore_default, 0);
2518
2519   CurrentMenu = MENU_MAIN;
2520
2521 #ifdef HAVE_GETSID
2522   /* Unset suspend by default if we're the session leader */
2523   if (getsid (0) == getpid ())
2524     unset_option (OPTSUSPEND);
2525 #endif
2526
2527   mutt_init_history ();
2528
2529   if (!Muttrc) {
2530       snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL (Homedir));
2531     if (access (buffer, F_OK) == -1)
2532       snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
2533                 NONULL (Homedir));
2534
2535     default_rc = 1;
2536     Muttrc = m_strdup(buffer);
2537   }
2538   else {
2539     m_strcpy(buffer, sizeof(buffer), Muttrc);
2540     p_delete(&Muttrc);
2541     mutt_expand_path (buffer, sizeof(buffer));
2542     Muttrc = m_strdup(buffer);
2543   }
2544   p_delete(&AliasFile);
2545   AliasFile = m_strdup(NONULL (Muttrc));
2546
2547   /* Process the global rc file if it exists and the user hasn't explicity
2548      requested not to via "-n".  */
2549   if (!skip_sys_rc) {
2550     snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
2551               MUTT_VERSION);
2552     if (access (buffer, F_OK) == -1)
2553       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
2554     if (access (buffer, F_OK) == -1)
2555       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
2556                 MUTT_VERSION);
2557     if (access (buffer, F_OK) == -1)
2558       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
2559     if (access (buffer, F_OK) != -1) {
2560       if (source_rc (buffer, &err) != 0) {
2561         fputs (err.data, stderr);
2562         fputc ('\n', stderr);
2563         need_pause = 1;
2564       }
2565     }
2566   }
2567
2568   /* Read the user's initialization file.  */
2569   if (access (Muttrc, F_OK) != -1) {
2570     if (!option (OPTNOCURSES))
2571       mutt_endwin (NULL);
2572     if (source_rc (Muttrc, &err) != 0) {
2573       fputs (err.data, stderr);
2574       fputc ('\n', stderr);
2575       need_pause = 1;
2576     }
2577   }
2578   else if (!default_rc) {
2579     /* file specified by -F does not exist */
2580     snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
2581     mutt_endwin (buffer);
2582     exit (1);
2583   }
2584
2585   if (mutt_execute_commands (commands) != 0)
2586     need_pause = 1;
2587
2588   /* warn about synonym variables */
2589   if (Synonyms) {
2590     syn_t *syn;
2591
2592     fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
2593
2594     for (syn = Synonyms; syn; syn = syn->next) {
2595       fprintf(stderr, "$%s ($%s should be used) (%s:%d)\n",
2596               syn->o ? NONULL(syn->o->option) : "",
2597               syn->n ? NONULL(syn->n->option) : "",
2598               NONULL(syn->f), syn->l);
2599     }
2600     fprintf (stderr, _("Warning: synonym variables are scheduled"
2601                        " for removal.\n"));
2602     syn_list_wipe(&Synonyms);
2603     need_pause = 1;
2604   }
2605
2606   if (need_pause && !option (OPTNOCURSES)) {
2607     if (mutt_any_key_to_continue (NULL) == -1)
2608       mutt_exit (1);
2609   }
2610 }
2611
2612 int mutt_get_hook_type (const char *name)
2613 {
2614   struct command_t *c;
2615
2616   for (c = Commands; c->name; c++)
2617     if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
2618       return c->data;
2619   return 0;
2620 }
2621
2622 /* dump out the value of all the variables we have */
2623 int mutt_dump_variables (int full) {
2624     ssize_t i = 0;
2625
2626     /* get all non-synonyms into list... */
2627     for (i = 0; MuttVars[i].option; i++) {
2628         struct option_t *option = MuttVars + i;
2629         char buf[LONG_STRING];
2630
2631         if (DTYPE(option->type) == DT_SYN)
2632             continue;
2633
2634         if (!full) {
2635             mutt_option_value(option->option, buf, sizeof(buf));
2636             if (!m_strcmp(buf, option->init))
2637                 continue;
2638         }
2639
2640         printf("set ");
2641         FuncTable[DTYPE(option->type)].opt_tostr(buf, sizeof(buf), option);
2642         printf ("%s\n", buf);
2643     }
2644
2645     printf ("\n# vi""m:set ft=muttrc:\n");
2646     return 0;
2647 }