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