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