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