impressive how mutt upstream don't get how list work.
[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 = NULL;
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       }
1197       else
1198         alias_list_wipe(&Aliases);
1199       break;
1200     }
1201     else
1202       for (tmp = Aliases; tmp; tmp = tmp->next) {
1203         if (m_strcasecmp(buf->data, tmp->name) == 0) {
1204           if (CurrentMenu == MENU_ALIAS) {
1205             tmp->del = 1;
1206             set_option (OPTFORCEREDRAWINDEX);
1207             break;
1208           }
1209
1210           if (last)
1211             last->next = tmp->next;
1212           else
1213             Aliases = tmp->next;
1214           tmp->next = NULL;
1215           alias_list_wipe(&tmp);
1216           break;
1217         }
1218         last = tmp;
1219       }
1220   }
1221   while (MoreArgs (s));
1222   return 0;
1223 }
1224
1225 static int parse_alias (BUFFER * buf, BUFFER * s,
1226                         unsigned long data __attribute__ ((unused)),
1227                         BUFFER * err)
1228 {
1229   alias_t *tmp = Aliases;
1230   alias_t *last = NULL;
1231   char *estr = NULL;
1232
1233   if (!MoreArgs (s)) {
1234     m_strcpy(err->data, err->dsize, _("alias: no address"));
1235     return (-1);
1236   }
1237
1238   mutt_extract_token (buf, s, 0);
1239
1240   /* check to see if an alias with this name already exists */
1241   for (; tmp; tmp = tmp->next) {
1242     if (!m_strcasecmp(tmp->name, buf->data))
1243       break;
1244     last = tmp;
1245   }
1246
1247   if (!tmp) {
1248     /* create a new alias */
1249     tmp = alias_new();
1250     tmp->name = m_strdup(buf->data);
1251     /* give the main addressbook code a chance */
1252     if (CurrentMenu == MENU_ALIAS)
1253       set_option (OPTMENUCALLER);
1254   }
1255   else {
1256     /* override the previous value */
1257     address_list_wipe(&tmp->addr);
1258     if (CurrentMenu == MENU_ALIAS)
1259       set_option (OPTFORCEREDRAWINDEX);
1260   }
1261
1262   mutt_extract_token (buf, s,
1263                       M_TOKEN_QUOTE | M_TOKEN_SPACE | M_TOKEN_SEMICOLON);
1264   tmp->addr = mutt_parse_adrlist (tmp->addr, buf->data);
1265   if (last)
1266     last->next = tmp;
1267   else
1268     Aliases = tmp;
1269   if (mutt_addrlist_to_idna (tmp->addr, &estr)) {
1270     snprintf (err->data, err->dsize,
1271               _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, tmp->name);
1272     p_delete(&estr);
1273     return -1;
1274   }
1275
1276   return 0;
1277 }
1278
1279 static int
1280 parse_unmy_hdr (BUFFER * buf, BUFFER * s,
1281                 unsigned long data __attribute__ ((unused)),
1282                 BUFFER * err __attribute__ ((unused)))
1283 {
1284   string_list_t *last = NULL;
1285   string_list_t *tmp = UserHeader;
1286   string_list_t *ptr;
1287   ssize_t l;
1288
1289   do {
1290     mutt_extract_token (buf, s, 0);
1291     if (m_strcmp("*", buf->data) == 0)
1292       string_list_wipe(&UserHeader);
1293     else {
1294       tmp = UserHeader;
1295       last = NULL;
1296
1297       l = m_strlen(buf->data);
1298       if (buf->data[l - 1] == ':')
1299         l--;
1300
1301       while (tmp) {
1302         if (ascii_strncasecmp (buf->data, tmp->data, l) == 0
1303             && tmp->data[l] == ':') {
1304           ptr = tmp;
1305           if (last)
1306             last->next = tmp->next;
1307           else
1308             UserHeader = tmp->next;
1309           tmp = tmp->next;
1310           ptr->next = NULL;
1311           string_list_wipe(&ptr);
1312         }
1313         else {
1314           last = tmp;
1315           tmp = tmp->next;
1316         }
1317       }
1318     }
1319   }
1320   while (MoreArgs (s));
1321   return 0;
1322 }
1323
1324 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
1325                          BUFFER * err)
1326 {
1327   string_list_t *tmp;
1328   ssize_t keylen;
1329   char *p;
1330
1331   mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
1332   if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
1333     m_strcpy(err->data, err->dsize, _("invalid header field"));
1334     return (-1);
1335   }
1336   keylen = p - buf->data + 1;
1337
1338   if (UserHeader) {
1339     for (tmp = UserHeader;; tmp = tmp->next) {
1340       /* see if there is already a field by this name */
1341       if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
1342         /* replace the old value */
1343         p_delete(&tmp->data);
1344         tmp->data = buf->data;
1345         p_clear(buf, 1);
1346         return 0;
1347       }
1348       if (!tmp->next)
1349         break;
1350     }
1351     tmp->next = string_item_new();
1352     tmp = tmp->next;
1353   }
1354   else {
1355     tmp = string_item_new();
1356     UserHeader = tmp;
1357   }
1358   tmp->data = buf->data;
1359   p_clear(buf, 1);
1360   return 0;
1361 }
1362
1363 static int
1364 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
1365             char* errbuf, ssize_t errlen) {
1366   int i, flags = 0;
1367
1368   if (m_strncmp("reverse-", s, 8) == 0) {
1369     s += 8;
1370     flags = SORT_REVERSE;
1371   }
1372
1373   if (m_strncmp("last-", s, 5) == 0) {
1374     s += 5;
1375     flags |= SORT_LAST;
1376   }
1377
1378   if ((i = mutt_getvaluebyname (s, map)) == -1) {
1379     if (errbuf)
1380       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
1381     return (-1);
1382   }
1383
1384   *((short*) dst->data) = i | flags;
1385   return 0;
1386 }
1387
1388 /* if additional data more == 1, we want to resolve synonyms */
1389 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
1390 {
1391     char buf[LONG_STRING];
1392     struct option_t *ptr = p;
1393
1394     if (DTYPE(ptr->type) == DT_SYN) {
1395         if (!more)
1396             return;
1397         ptr = hash_find(ConfigOptions, (const char *)ptr->data);
1398     }
1399     if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_fromstr)
1400         return;
1401
1402     mutt_option_value(ptr->option, buf, sizeof(buf));
1403     if (m_strlen(ptr->init) == 0 && buf && *buf)
1404         ptr->init = m_strdup(buf);
1405 }
1406
1407 static struct option_t* add_option (const char* name, const char* init,
1408                                     short type, short dodup) {
1409   struct option_t* option = p_new(struct option_t, 1);
1410
1411   option->option = m_strdup(name);
1412   option->type = type;
1413   if (init)
1414     option->init = dodup ? m_strdup(init) : (char*) init;
1415   return (option);
1416 }
1417
1418 /* creates new option_t* of type DT_USER for $user_ var */
1419 static struct option_t* add_user_option (const char* name) {
1420   return (add_option (name, NULL, DT_USER, 1));
1421 }
1422
1423 /* free()'s option_t* */
1424 static void del_option (void* p) {
1425   struct option_t *ptr = (struct option_t*) p;
1426   char* s = (char*) ptr->data;
1427   p_delete(&ptr->option);
1428   p_delete(&s);
1429   p_delete(&ptr->init);
1430   p_delete(&ptr);
1431 }
1432
1433 static int init_expand (char** dst, struct option_t* src) {
1434   BUFFER token, in;
1435   ssize_t len = 0;
1436
1437   p_delete(dst);
1438
1439   if (DTYPE(src->type) == DT_STR ||
1440       DTYPE(src->type) == DT_PATH) {
1441     /* only expand for string as it's the only place where
1442      * we want to expand vars right now */
1443     if (src->init && *src->init) {
1444       p_clear(&token, 1);
1445       p_clear(&in, 1);
1446       len = m_strlen(src->init) + 2;
1447       in.data = p_new(char, len + 1);
1448       snprintf (in.data, len, "\"%s\"", src->init);
1449       in.dptr = in.data;
1450       in.dsize = len;
1451       mutt_extract_token (&token, &in, 0);
1452       if (token.data && *token.data)
1453         *dst = m_strdup(token.data);
1454       else
1455         *dst = m_strdup("");
1456       p_delete(&in.data);
1457       p_delete(&token.data);
1458     } else
1459       *dst = m_strdup("");
1460   } else
1461     /* for non-string: take value as is */
1462     *dst = m_strdup(src->init);
1463   return (1);
1464 }
1465
1466 /* if additional data more == 1, we want to resolve synonyms */
1467 static void mutt_restore_default (const char* name __attribute__ ((unused)),
1468                                   void* p, unsigned long more) {
1469   char errbuf[STRING];
1470   struct option_t* ptr = (struct option_t*) p;
1471   char* init = NULL;
1472
1473   if (DTYPE (ptr->type) == DT_SYN) {
1474     if (!more)
1475       return;
1476     ptr = hash_find (ConfigOptions, (char*) ptr->data);
1477   }
1478   if (!ptr)
1479     return;
1480   if (FuncTable[DTYPE (ptr->type)].opt_fromstr) {
1481     init_expand (&init, ptr);
1482     if (!FuncTable[DTYPE (ptr->type)].opt_fromstr (ptr, init, errbuf,
1483                                                        sizeof(errbuf))) {
1484       if (!option (OPTNOCURSES))
1485         mutt_endwin (NULL);
1486       fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
1487                          "Please report this error: \"%s\"\n"),
1488                ptr->option, NONULL (init), errbuf);
1489       exit (1);
1490     }
1491     p_delete(&init);
1492   }
1493
1494   if (ptr->flags & R_INDEX)
1495     set_option (OPTFORCEREDRAWINDEX);
1496   if (ptr->flags & R_PAGER)
1497     set_option (OPTFORCEREDRAWPAGER);
1498   if (ptr->flags & R_RESORT_SUB)
1499     set_option (OPTSORTSUBTHREADS);
1500   if (ptr->flags & R_RESORT)
1501     set_option (OPTNEEDRESORT);
1502   if (ptr->flags & R_RESORT_INIT)
1503     set_option (OPTRESORTINIT);
1504   if (ptr->flags & R_TREE)
1505     set_option (OPTREDRAWTREE);
1506 }
1507
1508 /* check whether value for $dsn_return would be valid */
1509 static int check_dsn_return (const char* option __attribute__ ((unused)), unsigned long p,
1510                              char* errbuf, ssize_t errlen) {
1511   char* val = (char*) p;
1512   if (val && *val && m_strncmp(val, "hdrs", 4) != 0 &&
1513       m_strncmp(val, "full", 4) != 0) {
1514     if (errbuf)
1515       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), val, "dsn_return");
1516     return (0);
1517   }
1518   return (1);
1519 }
1520
1521 /* check whether value for $dsn_notify would be valid */
1522 static int
1523 check_dsn_notify (const char* option __attribute__ ((unused)),
1524                   unsigned long val, char* errbuf, ssize_t errlen)
1525 {
1526     const char *p = (const char*)val;
1527
1528     while (p && *p) {
1529         const char *q = m_strchrnul(p, ',');
1530         int len = q - p;
1531
1532         if (!m_strncmp(p, "never", len)   && !m_strncmp(p, "delay", len)
1533         &&  !m_strncmp(p, "failure", len) && !m_strncmp(p, "success", len))
1534         {
1535             if (errbuf)
1536                 snprintf(errbuf, errlen, _("'%.*s' is invalid for $%s"),
1537                          len, p, "dsn_notify");
1538             return 0;
1539         }
1540
1541         p = q + 1;
1542     }
1543
1544     return 1;
1545 }
1546
1547 static int check_num (const char* option, unsigned long p,
1548                       char* errbuf, ssize_t errlen) {
1549   if ((int) p < 0) {
1550     if (errbuf)
1551       snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
1552     return (0);
1553   }
1554   return (1);
1555 }
1556
1557 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
1558                           char* errbuf, ssize_t errlen) {
1559   if (!check_num ("history", p, errbuf, errlen))
1560     return (0);
1561   mutt_init_history ();
1562   return (1);
1563 }
1564
1565 static int check_special (const char* name, unsigned long val,
1566                           char* errbuf, ssize_t errlen) {
1567   int i = 0;
1568
1569   for (i = 0; SpecialVars[i].name; i++) {
1570     if (m_strcmp(SpecialVars[i].name, name) == 0) {
1571       return (SpecialVars[i].check (SpecialVars[i].name,
1572                                     val, errbuf, errlen));
1573     }
1574   }
1575   return (1);
1576 }
1577
1578 static const struct mapping_t* get_sortmap (struct option_t* option) {
1579   const struct mapping_t* map = NULL;
1580
1581   switch (option->type & DT_SUBTYPE_MASK) {
1582   case DT_SORT_ALIAS:
1583     map = SortAliasMethods;
1584     break;
1585   case DT_SORT_BROWSER:
1586     map = SortBrowserMethods;
1587     break;
1588   case DT_SORT_KEYS:
1589     map = SortKeyMethods;
1590     break;
1591   case DT_SORT_AUX:
1592     map = SortAuxMethods;
1593     break;
1594   default:
1595     map = SortMethods;
1596     break;
1597   }
1598   return (map);
1599 }
1600
1601 #define CHECK_PAGER \
1602   if ((CurrentMenu == MENU_PAGER) && \
1603       (!option || (option->flags & R_RESORT))) \
1604   { \
1605     snprintf (err->data, err->dsize, \
1606               _("Not available in this menu.")); \
1607     return (-1); \
1608   }
1609
1610 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1611                       BUFFER * err)
1612 {
1613   int query, unset, inv, reset, r = 0;
1614   struct option_t* option = NULL;
1615
1616   while (MoreArgs (s)) {
1617     /* reset state variables */
1618     query = 0;
1619     unset = data & M_SET_UNSET;
1620     inv = data & M_SET_INV;
1621     reset = data & M_SET_RESET;
1622
1623     if (*s->dptr == '?') {
1624       query = 1;
1625       s->dptr++;
1626     }
1627     else if (m_strncmp("no", s->dptr, 2) == 0) {
1628       s->dptr += 2;
1629       unset = !unset;
1630     }
1631     else if (m_strncmp("inv", s->dptr, 3) == 0) {
1632       s->dptr += 3;
1633       inv = !inv;
1634     }
1635     else if (*s->dptr == '&') {
1636       reset = 1;
1637       s->dptr++;
1638     }
1639
1640     /* get the variable name */
1641     mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1642
1643     /* resolve synonyms */
1644     if ((option = hash_find (ConfigOptions, tmp->data)) != NULL &&
1645         DTYPE (option->type == DT_SYN))
1646     {
1647       struct option_t* newopt = hash_find (ConfigOptions, (char*) option->data);
1648       syn_t* syn = syn_new();
1649       syn->f = m_strdup(CurRCFile);
1650       syn->l = CurRCLine;
1651       syn->n = newopt;
1652       syn->o = option;
1653       syn_list_push(&Synonyms, syn);
1654       option = newopt;
1655     }
1656
1657     /* see if we need to add $user_ var */
1658     if (!option && m_strncmp("user_", tmp->data, 5) == 0) {
1659       /* there's no option named like this yet so only add one
1660        * if the action isn't any of: reset, unset, query */
1661       if (!(reset || unset || query || *s->dptr != '=')) {
1662         option = add_user_option (tmp->data);
1663         hash_insert (ConfigOptions, option->option, option, 0);
1664       }
1665     }
1666
1667     if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1668       snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1669       return (-1);
1670     }
1671     s->dptr = vskipspaces(s->dptr);
1672
1673     if (reset) {
1674       if (query || unset || inv) {
1675         snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1676         return (-1);
1677       }
1678
1679       if (s && *s->dptr == '=') {
1680         snprintf (err->data, err->dsize, _("value is illegal with reset"));
1681         return (-1);
1682       }
1683
1684       if (!m_strcmp("all", tmp->data)) {
1685         if (CurrentMenu == MENU_PAGER) {
1686           snprintf (err->data, err->dsize, _("Not available in this menu."));
1687           return (-1);
1688         }
1689         hash_map (ConfigOptions, mutt_restore_default, 1);
1690         set_option (OPTFORCEREDRAWINDEX);
1691         set_option (OPTFORCEREDRAWPAGER);
1692         set_option (OPTSORTSUBTHREADS);
1693         set_option (OPTNEEDRESORT);
1694         set_option (OPTRESORTINIT);
1695         set_option (OPTREDRAWTREE);
1696         return (0);
1697       }
1698       else if (!FuncTable[DTYPE (option->type)].opt_fromstr) {
1699         snprintf (err->data, err->dsize, _("$%s is read-only"), option->option);
1700         r = -1;
1701         break;
1702       } else {
1703         CHECK_PAGER;
1704         mutt_restore_default (NULL, option, 1);
1705       }
1706     }
1707     else if (DTYPE (option->type) == DT_BOOL) {
1708       /* XXX this currently ignores the function table
1709        * as we don't get invert and stuff into it */
1710       if (s && *s->dptr == '=') {
1711         if (unset || inv || query) {
1712           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1713           return (-1);
1714         }
1715
1716         s->dptr++;
1717         mutt_extract_token (tmp, s, 0);
1718         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1719           unset = inv = 0;
1720         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1721           unset = 1;
1722         else {
1723           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1724           return (-1);
1725         }
1726       }
1727
1728       if (query) {
1729         bool_to_string (err->data, err->dsize, option);
1730         return 0;
1731       }
1732
1733       CHECK_PAGER;
1734       if (unset)
1735         unset_option (option->data);
1736       else if (inv)
1737         toggle_option (option->data);
1738       else
1739         set_option (option->data);
1740     }
1741     else if (DTYPE (option->type) == DT_STR ||
1742              DTYPE (option->type) == DT_PATH ||
1743              DTYPE (option->type) == DT_ADDR ||
1744              DTYPE (option->type) == DT_MAGIC ||
1745              DTYPE (option->type) == DT_NUM ||
1746              DTYPE (option->type) == DT_SORT ||
1747              DTYPE (option->type) == DT_RX ||
1748              DTYPE (option->type) == DT_USER ||
1749              DTYPE (option->type) == DT_SYS) {
1750
1751       /* XXX maybe we need to get unset into handlers? */
1752       if (DTYPE (option->type) == DT_STR ||
1753           DTYPE (option->type) == DT_PATH ||
1754           DTYPE (option->type) == DT_ADDR ||
1755           DTYPE (option->type) == DT_USER ||
1756           DTYPE (option->type) == DT_SYS) {
1757         if (unset) {
1758           CHECK_PAGER;
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 if (DTYPE (option->type) == DT_ADDR)
1765             address_list_wipe((address_t **) option->data);
1766           else if (DTYPE (option->type) == DT_USER)
1767             /* to unset $user_ means remove */
1768             hash_delete (ConfigOptions, option->option,
1769                          option, del_option);
1770           else
1771             p_delete((void **)(void *)&option->data);
1772           break;
1773         }
1774       }
1775
1776       if (query || *s->dptr != '=') {
1777         FuncTable[DTYPE (option->type)].opt_tostr
1778           (err->data, err->dsize, option);
1779         break;
1780       }
1781
1782       /* the $madmutt_ variables are read-only */
1783       if (!FuncTable[DTYPE (option->type)].opt_fromstr) {
1784         snprintf (err->data, err->dsize, _("$%s is read-only"),
1785                   option->option);
1786         r = -1;
1787         break;
1788       } else {
1789         CHECK_PAGER;
1790         s->dptr++;
1791         mutt_extract_token (tmp, s, 0);
1792         if (!FuncTable[DTYPE (option->type)].opt_fromstr
1793             (option, tmp->data, err->data, err->dsize))
1794           r = -1;
1795       }
1796     }
1797     else if (DTYPE (option->type) == DT_QUAD) {
1798
1799       if (query) {
1800         quad_to_string (err->data, err->dsize, option);
1801         break;
1802       }
1803
1804       if (*s->dptr == '=') {
1805         CHECK_PAGER;
1806         s->dptr++;
1807         mutt_extract_token (tmp, s, 0);
1808         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1809           set_quadoption (option->data, M_YES);
1810         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1811           set_quadoption (option->data, M_NO);
1812         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1813           set_quadoption (option->data, M_ASKYES);
1814         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1815           set_quadoption (option->data, M_ASKNO);
1816         else {
1817           snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1818                     tmp->data, option->option);
1819           r = -1;
1820           break;
1821         }
1822       }
1823       else {
1824         if (inv)
1825           toggle_quadoption (option->data);
1826         else if (unset)
1827           set_quadoption (option->data, M_NO);
1828         else
1829           set_quadoption (option->data, M_YES);
1830       }
1831     }
1832     else {
1833       snprintf (err->data, err->dsize, _("%s: unknown type"),
1834                 option->option);
1835       r = -1;
1836       break;
1837     }
1838
1839     if (option->flags & R_INDEX)
1840       set_option (OPTFORCEREDRAWINDEX);
1841     if (option->flags & R_PAGER)
1842       set_option (OPTFORCEREDRAWPAGER);
1843     if (option->flags & R_RESORT_SUB)
1844       set_option (OPTSORTSUBTHREADS);
1845     if (option->flags & R_RESORT)
1846       set_option (OPTNEEDRESORT);
1847     if (option->flags & R_RESORT_INIT)
1848       set_option (OPTRESORTINIT);
1849     if (option->flags & R_TREE)
1850       set_option (OPTREDRAWTREE);
1851   }
1852   return (r);
1853 }
1854
1855 #define MAXERRS 128
1856
1857 /* reads the specified initialization file.  returns -1 if errors were found
1858    so that we can pause to let the user know...  */
1859 static int source_rc (const char *rcfile, BUFFER * err)
1860 {
1861   FILE *f;
1862   int line = 0, rc = 0, conv = 0;
1863   BUFFER token;
1864   char *linebuf = NULL;
1865   char *currentline = NULL;
1866   ssize_t buflen;
1867   pid_t pid;
1868
1869   if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1870     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1871     return (-1);
1872   }
1873
1874   p_clear(&token, 1);
1875   while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
1876     conv = ConfigCharset && (*ConfigCharset) && Charset;
1877     if (conv) {
1878       currentline = m_strdup(linebuf);
1879       if (!currentline)
1880         continue;
1881       mutt_convert_string (&currentline, ConfigCharset, Charset, 0);
1882     }
1883     else
1884       currentline = linebuf;
1885
1886     CurRCLine = line;
1887     CurRCFile = rcfile;
1888
1889     if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1890       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1891       if (--rc < -MAXERRS) {
1892         if (conv)
1893           p_delete(&currentline);
1894         break;
1895       }
1896     }
1897     else {
1898       if (rc < 0)
1899         rc = -1;
1900     }
1901     if (conv)
1902       p_delete(&currentline);
1903   }
1904   p_delete(&token.data);
1905   p_delete(&linebuf);
1906   m_fclose(&f);
1907   if (pid != -1)
1908     mutt_wait_filter (pid);
1909   if (rc) {
1910     /* the muttrc source keyword */
1911     snprintf (err->data, err->dsize,
1912               rc >= -MAXERRS ? _("source: errors in %s")
1913               : _("source: reading aborted due too many errors in %s"),
1914               rcfile);
1915     rc = -1;
1916   }
1917   return (rc);
1918 }
1919
1920 #undef MAXERRS
1921
1922 static int parse_source (BUFFER * tmp, BUFFER * s,
1923                          unsigned long data __attribute__ ((unused)),
1924                          BUFFER * err)
1925 {
1926   char path[_POSIX_PATH_MAX];
1927   int rc = 0;
1928
1929   do {
1930     if (mutt_extract_token (tmp, s, 0) != 0) {
1931       snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1932       return (-1);
1933     }
1934
1935     m_strcpy(path, sizeof(path), tmp->data);
1936     mutt_expand_path (path, sizeof(path));
1937
1938     rc += source_rc (path, err);
1939   }
1940   while (MoreArgs (s));
1941
1942   return ((rc < 0) ? -1 : 0);
1943 }
1944
1945 /* line         command to execute
1946
1947    token        scratch buffer to be used by parser.  caller should free
1948                 token->data when finished.  the reason for this variable is
1949                 to avoid having to allocate and deallocate a lot of memory
1950                 if we are parsing many lines.  the caller can pass in the
1951                 memory to use, which avoids having to create new space for
1952                 every call to this function.
1953
1954    err          where to write error messages */
1955 int mutt_parse_rc_line ( /* const */ char *line, BUFFER * token, BUFFER * err)
1956 {
1957   int i, r = -1;
1958   BUFFER expn;
1959
1960   p_clear(&expn, 1);
1961   expn.data = expn.dptr = line;
1962   expn.dsize = m_strlen(line);
1963
1964   *err->data = 0;
1965
1966   expn.dptr = vskipspaces(expn.dptr);
1967   while (*expn.dptr) {
1968     if (*expn.dptr == '#')
1969       break;                    /* rest of line is a comment */
1970     if (*expn.dptr == ';') {
1971       expn.dptr++;
1972       continue;
1973     }
1974     mutt_extract_token (token, &expn, 0);
1975     for (i = 0; Commands[i].name; i++) {
1976       if (!m_strcmp(token->data, Commands[i].name)) {
1977         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1978           goto finish;
1979         break;
1980       }
1981     }
1982     if (!Commands[i].name) {
1983       snprintf (err->data, err->dsize, _("%s: unknown command"),
1984                 NONULL (token->data));
1985       goto finish;
1986     }
1987   }
1988   r = 0;
1989 finish:
1990   if (expn.destroy)
1991     p_delete(&expn.data);
1992   return (r);
1993 }
1994
1995
1996 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
1997 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
1998 /* initial string that starts completion. No telling how much crap
1999  * the user has typed so far. Allocate LONG_STRING just to be sure! */
2000 char User_typed[LONG_STRING] = { 0 };
2001
2002 int Num_matched = 0;            /* Number of matches for completion */
2003 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
2004 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1];  /* all the matches + User_typed */
2005
2006 /* helper function for completion.  Changes the dest buffer if
2007    necessary/possible to aid completion.
2008         dest == completion result gets here.
2009         src == candidate for completion.
2010         try == user entered data for completion.
2011         len == length of dest buffer.
2012 */
2013 static void candidate (char *dest, char *try, const char *src, int len)
2014 {
2015   int l;
2016
2017   if (strstr (src, try) == src) {
2018     Matches[Num_matched++] = src;
2019     if (dest[0] == 0)
2020       m_strcpy(dest, len, src);
2021     else {
2022       for (l = 0; src[l] && src[l] == dest[l]; l++);
2023       dest[l] = 0;
2024     }
2025   }
2026 }
2027
2028 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
2029 {
2030   char *pt = buffer;
2031   int num;
2032   int spaces;                   /* keep track of the number of leading spaces on the line */
2033
2034   buffer = vskipspaces(buffer);
2035   spaces = buffer - pt;
2036
2037   pt = buffer + pos - spaces;
2038   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2039     pt--;
2040
2041   if (pt == buffer) {           /* complete cmd */
2042     /* first TAB. Collect all the matches */
2043     if (numtabs == 1) {
2044       Num_matched = 0;
2045       m_strcpy(User_typed, sizeof(User_typed), pt);
2046       p_clear(Matches, countof(Matches));
2047       p_clear(Completed, countof(Completed));
2048       for (num = 0; Commands[num].name; num++)
2049         candidate (Completed, User_typed, Commands[num].name,
2050                    sizeof(Completed));
2051       Matches[Num_matched++] = User_typed;
2052
2053       /* All matches are stored. Longest non-ambiguous string is ""
2054        * i.e. dont change 'buffer'. Fake successful return this time */
2055       if (User_typed[0] == 0)
2056         return 1;
2057     }
2058
2059     if (Completed[0] == 0 && User_typed[0])
2060       return 0;
2061
2062     /* Num_matched will _always_ be atleast 1 since the initial
2063      * user-typed string is always stored */
2064     if (numtabs == 1 && Num_matched == 2)
2065       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2066     else if (numtabs > 1 && Num_matched > 2)
2067       /* cycle thru all the matches */
2068       snprintf (Completed, sizeof(Completed), "%s",
2069                 Matches[(numtabs - 2) % Num_matched]);
2070
2071     /* return the completed command */
2072     m_strcpy(buffer, len - spaces, Completed);
2073   }
2074   else if (!m_strncmp(buffer, "set", 3)
2075            || !m_strncmp(buffer, "unset", 5)
2076            || !m_strncmp(buffer, "reset", 5)
2077            || !m_strncmp(buffer, "toggle", 6)) {    /* complete variables */
2078     const char *prefixes[] = { "no", "inv", "?", "&", NULL };
2079
2080     pt++;
2081     /* loop through all the possible prefixes (no, inv, ...) */
2082     if (!m_strncmp(buffer, "set", 3)) {
2083       for (num = 0; prefixes[num]; num++) {
2084         if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
2085           pt += m_strlen(prefixes[num]);
2086           break;
2087         }
2088       }
2089     }
2090
2091     /* first TAB. Collect all the matches */
2092     if (numtabs == 1) {
2093       Num_matched = 0;
2094       m_strcpy(User_typed, sizeof(User_typed), pt);
2095       p_clear(Matches, countof(Matches));
2096       p_clear(Completed, countof(Completed));
2097       for (num = 0; MuttVars[num].option; num++)
2098         candidate(Completed, User_typed, MuttVars[num].option,
2099                   sizeof(Completed));
2100       Matches[Num_matched++] = User_typed;
2101
2102       /* All matches are stored. Longest non-ambiguous string is ""
2103        * i.e. dont change 'buffer'. Fake successful return this time */
2104       if (User_typed[0] == 0)
2105         return 1;
2106     }
2107
2108     if (Completed[0] == 0 && User_typed[0])
2109       return 0;
2110
2111     /* Num_matched will _always_ be atleast 1 since the initial
2112      * user-typed string is always stored */
2113     if (numtabs == 1 && Num_matched == 2)
2114       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2115     else if (numtabs > 1 && Num_matched > 2)
2116       /* cycle thru all the matches */
2117       snprintf (Completed, sizeof(Completed), "%s",
2118                 Matches[(numtabs - 2) % Num_matched]);
2119
2120     m_strcpy(pt, buffer + len - pt - spaces, Completed);
2121   }
2122   else if (!m_strncmp(buffer, "exec", 4)) {
2123     struct binding_t *menu = km_get_table (CurrentMenu);
2124
2125     if (!menu && CurrentMenu != MENU_PAGER)
2126       menu = OpGeneric;
2127
2128     pt++;
2129     /* first TAB. Collect all the matches */
2130     if (numtabs == 1) {
2131       Num_matched = 0;
2132       m_strcpy(User_typed, sizeof(User_typed), pt);
2133       p_clear(Matches, countof(Matches));
2134       p_clear(Completed, countof(Completed));
2135       for (num = 0; menu[num].name; num++)
2136         candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
2137       /* try the generic menu */
2138       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
2139         menu = OpGeneric;
2140         for (num = 0; menu[num].name; num++)
2141           candidate (Completed, User_typed, menu[num].name,
2142                      sizeof(Completed));
2143       }
2144       Matches[Num_matched++] = User_typed;
2145
2146       /* All matches are stored. Longest non-ambiguous string is ""
2147        * i.e. dont change 'buffer'. Fake successful return this time */
2148       if (User_typed[0] == 0)
2149         return 1;
2150     }
2151
2152     if (Completed[0] == 0 && User_typed[0])
2153       return 0;
2154
2155     /* Num_matched will _always_ be atleast 1 since the initial
2156      * user-typed string is always stored */
2157     if (numtabs == 1 && Num_matched == 2)
2158       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2159     else if (numtabs > 1 && Num_matched > 2)
2160       /* cycle thru all the matches */
2161       snprintf (Completed, sizeof(Completed), "%s",
2162                 Matches[(numtabs - 2) % Num_matched]);
2163
2164     m_strcpy(pt, buffer + len - pt - spaces, Completed);
2165   }
2166   else
2167     return 0;
2168
2169   return 1;
2170 }
2171
2172 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
2173 {
2174   char var[STRING], *pt = buffer;
2175   int spaces;
2176   struct option_t* option = NULL;
2177
2178   if (buffer[0] == 0)
2179     return 0;
2180
2181   buffer = vskipspaces(buffer);
2182   spaces = buffer - pt;
2183
2184   pt = buffer + pos - spaces;
2185   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2186     pt--;
2187   pt++;                         /* move past the space */
2188   if (*pt == '=')               /* abort if no var before the '=' */
2189     return 0;
2190
2191   if (m_strncmp(buffer, "set", 3) == 0) {
2192     m_strcpy(var, sizeof(var), pt);
2193     /* ignore the trailing '=' when comparing */
2194     var[m_strlen(var) - 1] = 0;
2195     if (!(option = hash_find (ConfigOptions, var)))
2196       return 0;                 /* no such variable. */
2197     else {
2198       char tmp[LONG_STRING], tmp2[LONG_STRING];
2199       char *s, *d;
2200       ssize_t dlen = buffer + len - pt - spaces;
2201       const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
2202
2203       tmp[0] = '\0';
2204
2205       if ((DTYPE (option->type) == DT_STR) ||
2206           (DTYPE (option->type) == DT_PATH) ||
2207           (DTYPE (option->type) == DT_RX)) {
2208         m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
2209         if (DTYPE (option->type) == DT_PATH)
2210           mutt_pretty_mailbox (tmp);
2211       }
2212       else if (DTYPE (option->type) == DT_ADDR) {
2213         rfc822_addrcat(tmp, sizeof(tmp), *((address_t **) option->data), 0);
2214       }
2215       else if (DTYPE (option->type) == DT_QUAD)
2216         m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
2217       else if (DTYPE (option->type) == DT_NUM)
2218         snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
2219       else if (DTYPE (option->type) == DT_SORT) {
2220         const struct mapping_t *map;
2221         const char *p;
2222
2223         switch (option->type & DT_SUBTYPE_MASK) {
2224         case DT_SORT_ALIAS:
2225           map = SortAliasMethods;
2226           break;
2227         case DT_SORT_BROWSER:
2228           map = SortBrowserMethods;
2229           break;
2230         case DT_SORT_KEYS:
2231           map = SortKeyMethods;
2232           break;
2233         default:
2234           map = SortMethods;
2235           break;
2236         }
2237         p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
2238         snprintf(tmp, sizeof(tmp), "%s%s%s",
2239                  (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
2240                  (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
2241       }
2242       else if (DTYPE (option->type) == DT_MAGIC) {
2243         const char *p;
2244         switch (DefaultMagic) {
2245           case M_MBOX:
2246             p = "mbox";
2247             break;
2248           case M_MMDF:
2249             p = "MMDF";
2250             break;
2251           case M_MH:
2252             p = "MH";
2253           break;
2254           case M_MAILDIR:
2255             p = "Maildir";
2256             break;
2257           default:
2258             p = "unknown";
2259         }
2260         m_strcpy(tmp, sizeof(tmp), p);
2261       }
2262       else if (DTYPE (option->type) == DT_BOOL)
2263         m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
2264       else
2265         return 0;
2266
2267       for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
2268         if (*s == '\\' || *s == '"')
2269           *d++ = '\\';
2270         *d++ = *s++;
2271       }
2272       *d = '\0';
2273
2274       m_strcpy(tmp, sizeof(tmp), pt);
2275       snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
2276
2277       return 1;
2278     }
2279   }
2280   return 0;
2281 }
2282
2283 /* Implement the -Q command line flag */
2284 int mutt_query_variables (string_list_t * queries)
2285 {
2286   string_list_t *p;
2287
2288   char errbuff[STRING];
2289   char command[STRING];
2290
2291   BUFFER err, token;
2292
2293   p_clear(&err, 1);
2294   p_clear(&token, 1);
2295
2296   err.data = errbuff;
2297   err.dsize = sizeof(errbuff);
2298
2299   for (p = queries; p; p = p->next) {
2300     snprintf (command, sizeof(command), "set ?%s\n", p->data);
2301     if (mutt_parse_rc_line (command, &token, &err) == -1) {
2302       fprintf (stderr, "%s\n", err.data);
2303       p_delete(&token.data);
2304       return 1;
2305     }
2306     printf ("%s\n", err.data);
2307   }
2308
2309   p_delete(&token.data);
2310   return 0;
2311 }
2312
2313 static int mutt_execute_commands (string_list_t * p)
2314 {
2315   BUFFER err, token;
2316   char errstr[STRING];
2317
2318   p_clear(&err, 1);
2319   err.data = errstr;
2320   err.dsize = sizeof(errstr);
2321   p_clear(&token, 1);
2322   for (; p; p = p->next) {
2323     if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
2324       fprintf (stderr, _("Error in command line: %s\n"), err.data);
2325       p_delete(&token.data);
2326       return (-1);
2327     }
2328   }
2329   p_delete(&token.data);
2330   return 0;
2331 }
2332
2333 void mutt_init (int skip_sys_rc, string_list_t * commands)
2334 {
2335   struct passwd *pw;
2336   struct utsname utsname;
2337   const char *p;
2338   char buffer[STRING], error[STRING];
2339   int default_rc = 0, need_pause = 0;
2340   int i;
2341   BUFFER err;
2342
2343   p_clear(&err, 1);
2344   err.data = error;
2345   err.dsize = sizeof(error);
2346
2347   /* use 3*sizeof(muttvars) instead of 2*sizeof()
2348    * to have some room for $user_ vars */
2349   ConfigOptions = hash_create (sizeof(MuttVars) * 3);
2350   for (i = 0; MuttVars[i].option; i++) {
2351     if (DTYPE (MuttVars[i].type) != DT_SYS)
2352       hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i], 0);
2353     else
2354       hash_insert (ConfigOptions, MuttVars[i].option,
2355                    add_option (MuttVars[i].option, MuttVars[i].init,
2356                                DT_SYS, 0), 0);
2357   }
2358
2359   /*
2360    * XXX - use something even more difficult to predict?
2361    */
2362   snprintf (AttachmentMarker, sizeof(AttachmentMarker),
2363             "\033]9;%ld\a", (long) time (NULL));
2364
2365   /* on one of the systems I use, getcwd() does not return the same prefix
2366      as is listed in the passwd file */
2367   if ((p = getenv ("HOME")))
2368     Homedir = m_strdup(p);
2369
2370   /* Get some information about the user */
2371   if ((pw = getpwuid (getuid ()))) {
2372     char rnbuf[STRING];
2373
2374     Username = m_strdup(pw->pw_name);
2375     if (!Homedir)
2376       Homedir = m_strdup(pw->pw_dir);
2377
2378     mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, GecosMask.rx);
2379     Realname = m_strdup(rnbuf);
2380     Shell = m_strdup(pw->pw_shell);
2381     endpwent ();
2382   }
2383   else {
2384     if (!Homedir) {
2385       mutt_endwin (NULL);
2386       fputs (_("unable to determine home directory"), stderr);
2387       exit (1);
2388     }
2389     if ((p = getenv ("USER")))
2390       Username = m_strdup(p);
2391     else {
2392       mutt_endwin (NULL);
2393       fputs (_("unable to determine username"), stderr);
2394       exit (1);
2395     }
2396     Shell = m_strdup((p = getenv ("SHELL")) ? p : "/bin/sh");
2397   }
2398
2399   /* And about the host... */
2400   uname (&utsname);
2401   /* some systems report the FQDN instead of just the hostname */
2402   if ((p = strchr (utsname.nodename, '.'))) {
2403     Hostname = p_dupstr(utsname.nodename, p - utsname.nodename);
2404     p++;
2405     m_strcpy(buffer, sizeof(buffer), p);       /* save the domain for below */
2406   }
2407   else
2408     Hostname = m_strdup(utsname.nodename);
2409
2410   if (!p && getdnsdomainname(buffer, sizeof(buffer)) == -1)
2411     Fqdn = m_strdup("@");
2412   else
2413   if (*buffer != '@') {
2414     Fqdn = p_new(char, m_strlen(buffer) + m_strlen(Hostname) + 2);
2415     sprintf (Fqdn, "%s.%s", NONULL(Hostname), buffer);
2416   }
2417   else
2418     Fqdn = m_strdup(NONULL (Hostname));
2419
2420 #ifdef USE_NNTP
2421   {
2422     FILE *f;
2423     char *q;
2424
2425     if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
2426       buffer[0] = '\0';
2427       fgets (buffer, sizeof(buffer), f);
2428       p = vskipspaces(buffer);
2429       q = (char*)p;
2430       while (*q && !isspace(*q))
2431         q++;
2432       *q = '\0';
2433       NewsServer = m_strdup(p);
2434       m_fclose(&f);
2435     }
2436   }
2437   if ((p = getenv ("NNTPSERVER")))
2438     NewsServer = m_strdup(p);
2439 #endif
2440
2441   if ((p = getenv ("MAIL")))
2442     Spoolfile = m_strdup(p);
2443   else if ((p = getenv ("MAILDIR")))
2444     Spoolfile = m_strdup(p);
2445   else {
2446 #ifdef HOMESPOOL
2447     mutt_concat_path(buffer, sizeof(buffer), NONULL(Homedir), MAILPATH);
2448 #else
2449     mutt_concat_path(buffer, sizeof(buffer), MAILPATH, NONULL(Username));
2450 #endif
2451     Spoolfile = m_strdup(buffer);
2452   }
2453
2454   if ((p = getenv ("MAILCAPS")))
2455     MailcapPath = m_strdup(p);
2456   else {
2457     /* Default search path from RFC1524 */
2458     MailcapPath =
2459       m_strdup("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR
2460                    "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap");
2461   }
2462
2463   Tempdir = m_strdup((p = getenv ("TMPDIR")) ? p : "/tmp");
2464
2465   p = getenv ("VISUAL");
2466   if (!p) {
2467     p = getenv ("EDITOR");
2468     if (!p)
2469       p = "vi";
2470   }
2471   Editor = m_strdup(p);
2472
2473   if ((p = getenv ("REPLYTO")) != NULL) {
2474     BUFFER buf, token;
2475
2476     snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
2477
2478     p_clear(&buf, 1);
2479     buf.data = buf.dptr = buffer;
2480     buf.dsize = m_strlen(buffer);
2481
2482     p_clear(&token, 1);
2483     parse_my_hdr (&token, &buf, 0, &err);
2484     p_delete(&token.data);
2485   }
2486
2487   if ((p = getenv ("EMAIL")) != NULL)
2488     From = rfc822_parse_adrlist (NULL, p);
2489
2490   charset_initialize();
2491
2492   /* Set standard defaults */
2493   hash_map (ConfigOptions, mutt_set_default, 0);
2494   hash_map (ConfigOptions, mutt_restore_default, 0);
2495
2496   CurrentMenu = MENU_MAIN;
2497
2498 #ifdef HAVE_GETSID
2499   /* Unset suspend by default if we're the session leader */
2500   if (getsid (0) == getpid ())
2501     unset_option (OPTSUSPEND);
2502 #endif
2503
2504   mutt_init_history ();
2505
2506   if (!Muttrc) {
2507       snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL (Homedir));
2508     if (access (buffer, F_OK) == -1)
2509       snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
2510                 NONULL (Homedir));
2511
2512     default_rc = 1;
2513     Muttrc = m_strdup(buffer);
2514   }
2515   else {
2516     m_strcpy(buffer, sizeof(buffer), Muttrc);
2517     p_delete(&Muttrc);
2518     mutt_expand_path (buffer, sizeof(buffer));
2519     Muttrc = m_strdup(buffer);
2520   }
2521   p_delete(&AliasFile);
2522   AliasFile = m_strdup(NONULL (Muttrc));
2523
2524   /* Process the global rc file if it exists and the user hasn't explicity
2525      requested not to via "-n".  */
2526   if (!skip_sys_rc) {
2527     snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
2528               MUTT_VERSION);
2529     if (access (buffer, F_OK) == -1)
2530       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
2531     if (access (buffer, F_OK) == -1)
2532       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
2533                 MUTT_VERSION);
2534     if (access (buffer, F_OK) == -1)
2535       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
2536     if (access (buffer, F_OK) != -1) {
2537       if (source_rc (buffer, &err) != 0) {
2538         fputs (err.data, stderr);
2539         fputc ('\n', stderr);
2540         need_pause = 1;
2541       }
2542     }
2543   }
2544
2545   /* Read the user's initialization file.  */
2546   if (access (Muttrc, F_OK) != -1) {
2547     if (!option (OPTNOCURSES))
2548       mutt_endwin (NULL);
2549     if (source_rc (Muttrc, &err) != 0) {
2550       fputs (err.data, stderr);
2551       fputc ('\n', stderr);
2552       need_pause = 1;
2553     }
2554   }
2555   else if (!default_rc) {
2556     /* file specified by -F does not exist */
2557     snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
2558     mutt_endwin (buffer);
2559     exit (1);
2560   }
2561
2562   if (mutt_execute_commands (commands) != 0)
2563     need_pause = 1;
2564
2565   /* warn about synonym variables */
2566   if (Synonyms) {
2567     syn_t *syn;
2568
2569     fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
2570
2571     for (syn = Synonyms; syn; syn = syn->next) {
2572       fprintf(stderr, "$%s ($%s should be used) (%s:%d)\n",
2573               syn->o ? NONULL(syn->o->option) : "",
2574               syn->n ? NONULL(syn->n->option) : "",
2575               NONULL(syn->f), syn->l);
2576     }
2577     fprintf (stderr, _("Warning: synonym variables are scheduled"
2578                        " for removal.\n"));
2579     syn_list_wipe(&Synonyms);
2580     need_pause = 1;
2581   }
2582
2583   if (need_pause && !option (OPTNOCURSES)) {
2584     if (mutt_any_key_to_continue (NULL) == -1)
2585       mutt_exit (1);
2586   }
2587 }
2588
2589 int mutt_get_hook_type (const char *name)
2590 {
2591   struct command_t *c;
2592
2593   for (c = Commands; c->name; c++)
2594     if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
2595       return c->data;
2596   return 0;
2597 }
2598
2599 /* dump out the value of all the variables we have */
2600 int mutt_dump_variables (int full) {
2601     ssize_t i = 0;
2602
2603     /* get all non-synonyms into list... */
2604     for (i = 0; MuttVars[i].option; i++) {
2605         struct option_t *option = MuttVars + i;
2606         char buf[LONG_STRING];
2607
2608         if (DTYPE(option->type) == DT_SYN)
2609             continue;
2610
2611         if (!full) {
2612             mutt_option_value(option->option, buf, sizeof(buf));
2613             if (!m_strcmp(buf, option->init))
2614                 continue;
2615         }
2616
2617         printf("set ");
2618         FuncTable[DTYPE(option->type)].opt_tostr(buf, sizeof(buf), option);
2619         printf ("%s\n", buf);
2620     }
2621
2622     printf ("\n# vi""m:set ft=muttrc:\n");
2623     return 0;
2624 }