sort out some prototypes, put them where they belong.
[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 #if HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include <ctype.h>
18 #include <stdlib.h>
19 #include <unistd.h>
20 #include <string.h>
21 #include <sys/utsname.h>
22 #include <errno.h>
23 #include <sys/wait.h>
24
25 #include <lib-lib/lib-lib.h>
26
27 #include <lib-sys/unix.h>
28 #include <lib-sys/mutt_ssl.h>
29
30 #include <lib-ui/curses.h>
31 #include <lib-ui/history.h>
32
33 #include "mutt.h"
34 #include "keymap.h"
35 #include "charset.h"
36 #include "thread.h"
37 #include <lib-crypt/crypt.h>
38 #include "mutt_idna.h"
39
40 #if defined (USE_LIBESMTP) && (defined (USE_SSL) || defined (USE_GNUTLS))
41 #include "mutt_libesmtp.h"
42 #endif
43
44 #include "alias.h"
45 #include "mx.h"
46 #include "init.h"
47
48 #include "lib/list.h"
49
50 /*
51  * prototypes
52  */
53 static const struct mapping_t* get_sortmap (struct option_t* option);
54 static int parse_sort (struct option_t* dst, const char *s,
55                        const struct mapping_t *map,
56                        char* errbuf, ssize_t errlen);
57
58 static HASH *ConfigOptions = NULL;
59
60 /* for synonym warning reports: synonym found during parsing */
61 typedef struct {
62   char* f;              /* file */
63   int l;                /* line */
64   struct option_t* n;   /* new */
65   struct option_t* o;   /* old */
66 } syn_t;
67
68 /* for synonym warning reports: list of synonyms found */
69 static list2_t* Synonyms;
70 /* for synonym warning reports: current rc file */
71 static const char* CurRCFile = NULL;
72 /* for synonym warning reports: current rc line */
73 static int CurRCLine = 0;
74
75 /* prototypes for checking for special vars */
76 static int check_dsn_return (const char* option, unsigned long val,
77                              char* errbuf, ssize_t errlen);
78 static int check_dsn_notify (const char* option, unsigned long val,
79                              char* errbuf, ssize_t errlen);
80 static int check_history    (const char* option, unsigned long val,
81                              char* errbuf, ssize_t errlen);
82 /* this checks that numbers are >= 0 */
83 static int check_num        (const char* option, unsigned long val,
84                              char* errbuf, ssize_t errlen);
85
86 /* use this to check only */
87 static int check_special (const char* option, unsigned long val,
88                           char* errbuf, ssize_t errlen);
89
90 /* variable <-> sanity check function mappings
91  * when changing these, make sure the proper _from_string handler
92  * does this checking!
93  */
94 static struct {
95   const char* name;
96   int (*check) (const char* option, unsigned long val,
97                 char* errbuf, ssize_t errlen);
98 } SpecialVars[] = {
99   { "dsn_notify",               check_dsn_notify },
100   { "dsn_return",               check_dsn_return },
101 #if defined (USE_LIBESMTP) && (defined (USE_SSL) || defined (USE_GNUTLS))
102   { "smtp_use_tls",             mutt_libesmtp_check_usetls },
103 #endif
104   { "history",                  check_history },
105   { "pager_index_lines",        check_num },
106   /* last */
107   { NULL,         NULL }
108 };
109
110 /* protos for config type handles: convert value to string */
111 static void bool_to_string  (char* dst, ssize_t dstlen, struct option_t* option);
112 static void num_to_string   (char* dst, ssize_t dstlen, struct option_t* option);
113 static void str_to_string   (char* dst, ssize_t dstlen, struct option_t* option);
114 static void quad_to_string  (char* dst, ssize_t dstlen, struct option_t* option);
115 static void sort_to_string  (char* dst, ssize_t dstlen, struct option_t* option);
116 static void rx_to_string    (char* dst, ssize_t dstlen, struct option_t* option);
117 static void magic_to_string (char* dst, ssize_t dstlen, struct option_t* option);
118 static void addr_to_string  (char* dst, ssize_t dstlen, struct option_t* option);
119 static void user_to_string  (char* dst, ssize_t dstlen, struct option_t* option);
120 static void sys_to_string   (char* dst, ssize_t dstlen, struct option_t* option);
121
122 /* protos for config type handles: convert to value from string */
123 static int bool_from_string  (struct option_t* dst, const char* val,
124                               char* errbuf, ssize_t errlen);
125 static int num_from_string   (struct option_t* dst, const char* val,
126                               char* errbuf, ssize_t errlen);
127 static int str_from_string   (struct option_t* dst, const char* val,
128                               char* errbuf, ssize_t errlen);
129 static int path_from_string  (struct option_t* dst, const char* val,
130                               char* errbuf, ssize_t errlen);
131 static int quad_from_string  (struct option_t* dst, const char* val,
132                               char* errbuf, ssize_t errlen);
133 static int sort_from_string  (struct option_t* dst, const char* val,
134                               char* errbuf, ssize_t errlen);
135 static int rx_from_string    (struct option_t* dst, const char* val,
136                               char* errbuf, ssize_t errlen);
137 static int magic_from_string (struct option_t* dst, const char* val,
138                               char* errbuf, ssize_t errlen);
139 static int addr_from_string  (struct option_t* dst, const char* val,
140                               char* errbuf, ssize_t errlen);
141 static int user_from_string  (struct option_t* dst, const char* val,
142                               char* errbuf, ssize_t errlen);
143
144 static struct {
145   unsigned short type;
146   void (*opt_to_string) (char* dst, ssize_t dstlen, struct option_t* option);
147   int (*opt_from_string) (struct option_t* dst, const char* val,
148                           char* errbuf, ssize_t errlen);
149 } FuncTable[] = {
150   { 0,          NULL,             NULL }, /* there's no DT_ type with 0 */
151   { DT_BOOL,    bool_to_string,   bool_from_string },
152   { DT_NUM,     num_to_string,    num_from_string },
153   { DT_STR,     str_to_string,    str_from_string },
154   { DT_PATH,    str_to_string,    path_from_string },
155   { DT_QUAD,    quad_to_string,   quad_from_string },
156   { DT_SORT,    sort_to_string,   sort_from_string },
157   { DT_RX,      rx_to_string,     rx_from_string },
158   { DT_MAGIC,   magic_to_string,  magic_from_string },
159   /* synonyms should be resolved already so we don't need this
160    * but must define it as DT_ is used for indexing */
161   { DT_SYN,     NULL,             NULL },
162   { DT_ADDR,    addr_to_string,   addr_from_string },
163   { DT_USER,    user_to_string,   user_from_string },
164   { DT_SYS,     sys_to_string,    NULL },
165 };
166
167 static void bool_to_string (char* dst, ssize_t dstlen,
168                             struct option_t* option) {
169   snprintf (dst, dstlen, "%s=%s", option->option,
170             option (option->data) ? "yes" : "no");
171 }
172
173 static int bool_from_string (struct option_t* dst, const char* val,
174                              char* errbuf __attribute__ ((unused)),
175                              ssize_t errlen __attribute__ ((unused))) {
176   int flag = -1;
177
178   if (!dst)
179     return (0);
180   if (ascii_strncasecmp (val, "yes", 3) == 0)
181     flag = 1;
182   else if (ascii_strncasecmp (val, "no", 2) == 0)
183     flag = 0;
184
185   if (flag < 0)
186     return (0);
187   if (flag)
188     set_option (dst->data);
189   else
190     unset_option (dst->data);
191   return (1);
192 }
193
194 static void num_to_string (char* dst, ssize_t dstlen,
195                            struct option_t* option) {
196   /* XXX puke */
197   const char* fmt = (m_strcmp(option->option, "umask") == 0) ?
198                     "%s=%04o" : "%s=%d";
199   snprintf (dst, dstlen, fmt, option->option,
200             *((short*) option->data));
201 }
202
203 static int num_from_string (struct option_t* dst, const char* val,
204                             char* errbuf, ssize_t errlen) {
205   int num = 0, old = 0;
206   char* t = NULL;
207
208   if (!dst)
209     return (0);
210
211   num = strtol (val, &t, 0);
212
213   if (!*val || *t || (short) num != num) {
214     if (errbuf) {
215       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
216                 val, dst->option);
217     }
218     return (0);
219   }
220
221   /* just temporarily accept new val so that check_special for
222    * $history already has it when doing history's init() */
223   old = *((short*) dst->data);
224   *((short*) dst->data) = (short) num;
225
226   if (!check_special (dst->option, (unsigned long) num, errbuf, errlen)) {
227     *((short*) dst->data) = old;
228     return (0);
229   }
230
231   return (1);
232 }
233
234 static void str_to_string (char* dst, ssize_t dstlen,
235                            struct option_t* option) {
236   snprintf (dst, dstlen, "%s=\"%s\"", option->option,
237             NONULL (*((char**) option->data)));
238 }
239
240 static void user_to_string (char* dst, ssize_t dstlen,
241                             struct option_t* option) {
242   snprintf (dst, dstlen, "%s=\"%s\"", option->option,
243             NONULL (((char*) option->data)));
244 }
245
246 static void sys_to_string (char* dst, ssize_t dstlen,
247                            struct option_t* option) {
248   char *val = NULL, *t = NULL;
249   int clean = 0;
250
251   /* get some $madmutt_ values dynamically */
252   if (ascii_strcmp ("madmutt_pwd", option->option) == 0) {
253     val = p_new(char, _POSIX_PATH_MAX);
254     val = getcwd (val, _POSIX_PATH_MAX-1);
255     clean = 1;
256   } else if (ascii_strcmp ("madmutt_folder_path", option->option) == 0 &&
257              CurrentFolder && *CurrentFolder) {
258     val = CurrentFolder;
259   } else if (ascii_strcmp ("madmutt_folder_name", option->option) == 0 &&
260              CurrentFolder && *CurrentFolder) {
261
262     ssize_t Maildirlength = m_strlen(Maildir);
263
264     /*
265      * if name starts with $folder, just strip it to keep hierarchy
266      * $folder=imap://host, path=imap://host/inbox/b -> inbox/b
267      */
268     if (Maildirlength > 0 && m_strncmp(CurrentFolder, Maildir,
269                                       Maildirlength) == 0 &&
270        m_strlen(CurrentFolder) > Maildirlength) {
271      val = CurrentFolder + Maildirlength;
272      if (Maildir[strlen(Maildir)-1]!='/')
273        val += 1;
274      /* if not $folder, just use everything after last / */
275     } else if ((t = strrchr (CurrentFolder, '/')) != NULL)
276       val = t+1;
277     /* default: use as-is */
278     else
279       val = (char *) CurrentFolder;
280
281   } else
282     val = (char *) option->init;
283
284   snprintf (dst, dstlen, "%s=\"%s\"", option->option, NONULL (val));
285   if (clean)
286     p_delete(&val);
287 }
288
289 static int path_from_string (struct option_t* dst, const char* val,
290                              char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
291   char path[_POSIX_PATH_MAX];
292
293   if (!dst)
294     return (0);
295
296   if (!val || !*val) {
297     p_delete((char**) dst->data);
298     return (1);
299   }
300
301   path[0] = '\0';
302   m_strcpy(path, sizeof(path), val);
303   mutt_expand_path (path, sizeof(path));
304   m_strreplace((char **) dst->data, path);
305   return (1);
306 }
307
308 static int str_from_string (struct option_t* dst, const char* val,
309                             char* errbuf, ssize_t errlen) {
310   if (!dst)
311     return (0);
312
313   if (!check_special (dst->option, (unsigned long) val, errbuf, errlen))
314     return (0);
315
316   m_strreplace((char**) dst->data, val);
317   return (1);
318 }
319
320 static int user_from_string (struct option_t* dst, const char* val,
321                              char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
322   /* if dst == NULL, we may get here in case the user did unset it,
323    * see parse_set() where item is free()'d before coming here; so
324    * just silently ignore it */
325   if (!dst)
326     return (1);
327   if (m_strlen((char*) dst->data) == 0)
328     dst->data = (unsigned long) m_strdup(val);
329   else {
330     char* s = (char*) dst->data;
331     m_strreplace(&s, val);
332   }
333   if (m_strlen(dst->init) == 0)
334     dst->init = m_strdup((char*) dst->data);
335   return (1);
336 }
337
338 static void quad_to_string (char* dst, ssize_t dstlen,
339                             struct option_t* option) {
340   const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
341   snprintf (dst, dstlen, "%s=%s", option->option,
342             vals[quadoption (option->data)]);
343 }
344
345 static int quad_from_string (struct option_t* dst, const char* val,
346                              char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
347   int flag = -1;
348
349   if (!dst)
350     return (0);
351   if (ascii_strncasecmp (val, "yes", 3) == 0)
352     flag = M_YES;
353   else if (ascii_strncasecmp (val, "no", 2) == 0)
354     flag = M_NO;
355   else if (ascii_strncasecmp (val, "ask-yes", 7) == 0)
356     flag = M_ASKYES;
357   else if (ascii_strncasecmp (val, "ask-no", 6) == 0)
358     flag = M_ASKNO;
359
360   if (flag < 0)
361     return (0);
362
363   set_quadoption (dst->data, flag);
364   return (1);
365 }
366
367 static void sort_to_string (char* dst, ssize_t dstlen,
368                             struct option_t* option) {
369   const struct mapping_t *map = get_sortmap (option);
370   const char *p = NULL;
371
372   if (!map) {
373     snprintf (dst, sizeof(dst), "%s=unknown", option->option);
374     return;
375   }
376
377   p = mutt_getnamebyvalue(*((short *)option->data) & SORT_MASK, map);
378
379   snprintf (dst, dstlen, "%s=%s%s%s", option->option,
380             (*((short *) option->data) & SORT_REVERSE) ?
381             "reverse-" : "",
382             (*((short *) option->data) & SORT_LAST) ? "last-" :
383             "", NONULL (p));
384 }
385
386 static int sort_from_string (struct option_t* dst, const char* val,
387                              char* errbuf, ssize_t errlen) {
388   const struct mapping_t *map = NULL;
389   if (!(map = get_sortmap (dst))) {
390     if (errbuf)
391       snprintf (errbuf, errlen, _("%s: Unknown type."),
392                 dst->option);
393     return (0);
394   }
395   if (parse_sort (dst, val, map, errbuf, errlen) == -1)
396     return (0);
397   return (1);
398 }
399
400 static void rx_to_string (char* dst, ssize_t dstlen,
401                           struct option_t* option) {
402   rx_t* p = (rx_t*) option->data;
403   snprintf (dst, dstlen, "%s=\"%s\"", option->option,
404             NONULL (p->pattern));
405 }
406
407 static int rx_from_string (struct option_t* dst, const char* val,
408                            char* errbuf, ssize_t errlen) {
409   rx_t* p = NULL;
410   regex_t* rx = NULL;
411   int flags = 0, e = 0, not = 0;
412   char* s = NULL;
413
414   if (!dst)
415     return (0);
416
417   if (option (OPTATTACHMSG) && !m_strcmp(dst->option, "reply_regexp")) {
418     if (errbuf)
419       snprintf (errbuf, errlen,
420                 "Operation not permitted when in attach-message mode.");
421     return (0);
422   }
423
424   if (!((rx_t*) dst->data))
425     *((rx_t**) dst->data) = p_new(rx_t, 1);
426
427   p = (rx_t*) dst->data;
428
429   /* something to do? */
430   if (!val || !*val || (p->pattern && m_strcmp(p->pattern, val) == 0))
431     return (1);
432
433   if (m_strcmp(dst->option, "mask") != 0)
434     flags |= mutt_which_case (val);
435
436   s = (char*) val;
437   if (m_strcmp(dst->option, "mask") == 0 && *s == '!') {
438     not = 1;
439     s++;
440   }
441
442   rx = p_new(regex_t, 1);
443
444   if ((e = REGCOMP (rx, s, flags)) != 0) {
445     regerror (e, rx, errbuf, errlen);
446     regfree (rx);
447     p_delete(&rx);
448     return (0);
449   }
450
451   if (p->rx) {
452     regfree (p->rx);
453     p_delete(&p->rx);
454   }
455
456   m_strreplace(&p->pattern, val);
457   p->rx = rx;
458   p->not = not;
459
460   if (m_strcmp(dst->option, "reply_regexp") == 0)
461     mutt_adjust_all_subjects ();
462
463   return (1);
464 }
465
466 static void magic_to_string (char* dst, ssize_t dstlen,
467                              struct option_t* option) {
468   const char* s = NULL;
469   switch (option->data) {
470     case M_MBOX:    s = "mbox"; break;
471     case M_MMDF:    s = "MMDF"; break;
472     case M_MH:      s = "MH"; break;
473     case M_MAILDIR: s = "Maildir"; break;
474     default:        s = "unknown"; break;
475   }
476   snprintf (dst, dstlen, "%s=%s", option->option, s);
477 }
478
479 static int magic_from_string (struct option_t* dst, const char* val,
480                               char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
481   int flag = -1;
482
483   if (!dst || !val || !*val)
484     return (0);
485   if (ascii_strncasecmp (val, "mbox", 4) == 0)
486     flag = M_MBOX;
487   else if (ascii_strncasecmp (val, "mmdf", 4) == 0)
488     flag = M_MMDF;
489   else if (ascii_strncasecmp (val, "mh", 2) == 0)
490     flag = M_MH;
491   else if (ascii_strncasecmp (val, "maildir", 7) == 0)
492     flag = M_MAILDIR;
493
494   if (flag < 0)
495     return (0);
496
497   *((short*) dst->data) = flag;
498   return (1);
499
500 }
501
502 static void addr_to_string (char* dst, ssize_t dstlen,
503                             struct option_t* option) {
504   char s[HUGE_STRING];
505   s[0] = '\0';
506   rfc822_write_address (s, sizeof(s), *((address_t**) option->data), 0);
507   snprintf (dst, dstlen, "%s=\"%s\"", option->option, NONULL (s));
508 }
509
510 static int addr_from_string (struct option_t* dst, const char* val,
511                              char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
512   if (!dst)
513     return (0);
514   address_list_wipe((address_t**) dst->data);
515   if (val && *val)
516     *((address_t**) dst->data) = rfc822_parse_adrlist (NULL, val);
517   return (1);
518 }
519
520 int mutt_option_value (const char* val, char* dst, ssize_t dstlen) {
521   struct option_t* option = NULL;
522   char* tmp = NULL, *t = NULL;
523   ssize_t l = 0;
524
525   if (!(option = hash_find (ConfigOptions, val))) {
526     *dst = '\0';
527     return (0);
528   }
529   tmp = p_new(char, dstlen+1);
530   FuncTable[DTYPE (option->type)].opt_to_string (tmp, dstlen, option);
531
532   /* as we get things of type $var=value and don't want to bloat the
533    * above "just" for expansion, we do the stripping here */
534   t = strchr (tmp, '=');
535   t++;
536   l = m_strlen(t);
537   if (l >= 2) {
538     if (t[l-1] == '"' && *t == '"') {
539       t[l-1] = '\0';
540       t++;
541     }
542   }
543   memcpy (dst, t, l+1);
544   p_delete(&tmp);
545
546   return (1);
547 }
548
549 /* for synonym warning reports: adds synonym to end of list */
550 static void syn_add (struct option_t* n, struct option_t* o) {
551   syn_t* tmp = p_new(syn_t, 1);
552   tmp->f = m_strdup(CurRCFile);
553   tmp->l = CurRCLine;
554   tmp->n = n;
555   tmp->o = o;
556   list_push_back (&Synonyms, tmp);
557 }
558
559 /* for synonym warning reports: free single item (for list_del()) */
560 static void syn_del (void** p) {
561   p_delete(&(*(syn_t**) p)->f);
562   p_delete(p);
563 }
564
565 void toggle_quadoption (int opt)
566 {
567   int n = opt / 4;
568   int b = (opt % 4) * 2;
569
570   QuadOptions[n] ^= (1 << b);
571 }
572
573 void set_quadoption (int opt, int flag)
574 {
575   int n = opt / 4;
576   int b = (opt % 4) * 2;
577
578   QuadOptions[n] &= ~(0x3 << b);
579   QuadOptions[n] |= (flag & 0x3) << b;
580 }
581
582 int quadoption (int opt)
583 {
584   int n = opt / 4;
585   int b = (opt % 4) * 2;
586
587   return (QuadOptions[n] >> b) & 0x3;
588 }
589
590 int query_quadoption (int opt, const char *prompt)
591 {
592   int v = quadoption (opt);
593
594   switch (v) {
595   case M_YES:
596   case M_NO:
597     return (v);
598
599   default:
600     v = mutt_yesorno (prompt, (v == M_ASKYES));
601     CLEARLINE (LINES - 1);
602     return (v);
603   }
604
605   /* not reached */
606 }
607
608 static void add_to_list (string_list_t ** list, const char *str)
609 {
610   string_list_t *t, *last = NULL;
611
612   /* don't add a NULL or empty string to the list */
613   if (!str || *str == '\0')
614     return;
615
616   /* check to make sure the item is not already on this list */
617   for (last = *list; last; last = last->next) {
618     if (ascii_strcasecmp (str, last->data) == 0) {
619       /* already on the list, so just ignore it */
620       last = NULL;
621       break;
622     }
623     if (!last->next)
624       break;
625   }
626
627   if (!*list || last) {
628     t = p_new(string_list_t, 1);
629     t->data = m_strdup(str);
630     if (last) {
631       last->next = t;
632       last = last->next;
633     }
634     else
635       *list = last = t;
636   }
637 }
638
639 static int add_to_rx_list (list2_t** list, const char *s, int flags,
640                            BUFFER * err)
641 {
642   rx_t* rx;
643   int i = 0;
644
645   if (!s || !*s)
646     return 0;
647
648   if (!(rx = rx_compile (s, flags))) {
649     snprintf (err->data, err->dsize, "Bad regexp: %s\n", s);
650     return -1;
651   }
652
653   i = rx_lookup ((*list), rx->pattern);
654   if (i >= 0)
655     rx_delete(&rx);
656   else
657     list_push_back (list, rx);
658   return 0;
659 }
660
661 static int add_to_spam_list (SPAM_LIST ** list, const char *pat,
662                              const char *templ, BUFFER * err)
663 {
664   SPAM_LIST *t = NULL, *last = NULL;
665   rx_t* rx;
666   int n;
667   const char *p;
668
669   if (!pat || !*pat || !templ)
670     return 0;
671
672   if (!(rx = rx_compile (pat, REG_ICASE))) {
673     snprintf (err->data, err->dsize, _("Bad regexp: %s"), pat);
674     return -1;
675   }
676
677   /* check to make sure the item is not already on this list */
678   for (last = *list; last; last = last->next) {
679     if (ascii_strcasecmp (rx->pattern, last->rx->pattern) == 0) {
680       /* Already on the list. Formerly we just skipped this case, but
681        * now we're supporting removals, which means we're supporting
682        * re-adds conceptually. So we probably want this to imply a
683        * removal, then do an add. We can achieve the removal by freeing
684        * the template, and leaving t pointed at the current item.
685        */
686       t = last;
687       p_delete(&t->template);
688       break;
689     }
690     if (!last->next)
691       break;
692   }
693
694   /* If t is set, it's pointing into an extant SPAM_LIST* that we want to
695    * update. Otherwise we want to make a new one to link at the list's end.
696    */
697   if (!t) {
698     t = mutt_new_spam_list ();
699     t->rx = rx;
700     if (last)
701       last->next = t;
702     else
703       *list = t;
704   }
705
706   /* Now t is the SPAM_LIST* that we want to modify. It is prepared. */
707   t->template = m_strdup(templ);
708
709   /* Find highest match number in template string */
710   t->nmatch = 0;
711   for (p = templ; *p;) {
712     if (*p == '%') {
713       n = atoi (++p);
714       if (n > t->nmatch)
715         t->nmatch = n;
716       while (*p && isdigit ((int) *p))
717         ++p;
718     }
719     else
720       ++p;
721   }
722   t->nmatch++;                  /* match 0 is always the whole expr */
723
724   return 0;
725 }
726
727 static int remove_from_spam_list (SPAM_LIST ** list, const char *pat)
728 {
729   SPAM_LIST *spam, *prev;
730   int nremoved = 0;
731
732   /* Being first is a special case. */
733   spam = *list;
734   if (!spam)
735     return 0;
736   if (spam->rx && !m_strcmp(spam->rx->pattern, pat)) {
737     *list = spam->next;
738     rx_delete(&spam->rx);
739     p_delete(&spam->template);
740     p_delete(&spam);
741     return 1;
742   }
743
744   prev = spam;
745   for (spam = prev->next; spam;) {
746     if (!m_strcmp(spam->rx->pattern, pat)) {
747       prev->next = spam->next;
748       rx_delete(&spam->rx);
749       p_delete(&spam->template);
750       p_delete(&spam);
751       spam = prev->next;
752       ++nremoved;
753     }
754     else
755       spam = spam->next;
756   }
757
758   return nremoved;
759 }
760
761
762 static void remove_from_list (string_list_t ** l, const char *str)
763 {
764   string_list_t *p, *last = NULL;
765
766   if (m_strcmp("*", str) == 0)
767     string_list_wipe(l);         /* ``unCMD *'' means delete all current entries */
768   else {
769     p = *l;
770     last = NULL;
771     while (p) {
772       if (ascii_strcasecmp (str, p->data) == 0) {
773         p_delete(&p->data);
774         if (last)
775           last->next = p->next;
776         else
777           (*l) = p->next;
778         p_delete(&p);
779       }
780       else {
781         last = p;
782         p = p->next;
783       }
784     }
785   }
786 }
787
788 static int remove_from_rx_list (list2_t** l, const char *str)
789 {
790   int i = 0;
791
792   if (m_strcmp("*", str) == 0) {
793     list_del (l, (list_del_t*) rx_delete);
794     return (0);
795   }
796   else {
797     i = rx_lookup ((*l), str);
798     if (i >= 0) {
799       rx_t* r = list_pop_idx ((*l), i);
800       rx_delete(&r);
801       return (0);
802     }
803   }
804   return (-1);
805 }
806
807 static int parse_ifdef (BUFFER * tmp, BUFFER * s, unsigned long data,
808                         BUFFER * err)
809 {
810   int i, j;
811   unsigned long res = 0;
812   BUFFER token;
813   struct option_t* option = NULL;
814
815   p_clear(&token, 1);
816   mutt_extract_token (tmp, s, 0);
817
818   /* is the item defined as a variable or a function? */
819   if ((option = hash_find (ConfigOptions, tmp->data)) != NULL)
820     res = 1;
821   else {
822     for (i = 0; !res && i < MENU_MAX; i++) {
823       struct binding_t *b = km_get_table (Menus[i].value);
824
825       if (!b)
826         continue;
827
828       for (j = 0; b[j].name; j++)
829         if (!ascii_strncasecmp (tmp->data, b[j].name, m_strlen(tmp->data))
830             && (m_strlen(b[j].name) == m_strlen(tmp->data))) {
831           res = 1;
832           break;
833         }
834     }
835   }
836   /* check for feature_* */
837   if (!res && ascii_strncasecmp (tmp->data, "feature_", 8) == 0 &&
838       (j = m_strlen(tmp->data)) > 8) {
839     i = 0;
840     while (Features[i]) {
841       if (m_strlen(Features[i]) == j-8 &&
842           ascii_strncasecmp (Features[i], tmp->data+8, j-8) == 0) {
843         res = 1;
844         break;
845       }
846       i++;
847     }
848   }
849
850   if (!MoreArgs (s)) {
851     if (data)
852       snprintf (err->data, err->dsize, _("ifdef: too few arguments"));
853     else
854       snprintf (err->data, err->dsize, _("ifndef: too few arguments"));
855     return (-1);
856   }
857
858   mutt_extract_token (tmp, s, M_TOKEN_SPACE);
859
860   if (data == res) {
861     if (mutt_parse_rc_line (tmp->data, &token, err) == -1) {
862       mutt_error ("Error: %s", err->data);
863       p_delete(&token.data);
864       return (-1);
865     }
866     p_delete(&token.data);
867   }
868   return 0;
869 }
870
871 static int parse_unignore (BUFFER * buf, BUFFER * s,
872                            unsigned long data __attribute__ ((unused)),
873                            BUFFER * err __attribute__ ((unused)))
874 {
875   do {
876     mutt_extract_token (buf, s, 0);
877
878     /* don't add "*" to the unignore list */
879     if (strcmp (buf->data, "*"))
880       add_to_list (&UnIgnore, buf->data);
881
882     remove_from_list (&Ignore, buf->data);
883   }
884   while (MoreArgs (s));
885
886   return 0;
887 }
888
889 static int parse_ignore (BUFFER * buf, BUFFER * s,
890                          unsigned long data __attribute__ ((unused)),
891                          BUFFER * err __attribute__ ((unused)))
892 {
893   do {
894     mutt_extract_token (buf, s, 0);
895     remove_from_list (&UnIgnore, buf->data);
896     add_to_list (&Ignore, buf->data);
897   }
898   while (MoreArgs (s));
899
900   return 0;
901 }
902
903 static int parse_list (BUFFER * buf, BUFFER * s,
904                        unsigned long data __attribute__ ((unused)),
905                        BUFFER * err __attribute__ ((unused)))
906 {
907   do {
908     mutt_extract_token (buf, s, 0);
909     add_to_list ((string_list_t **) data, buf->data);
910   }
911   while (MoreArgs (s));
912
913   return 0;
914 }
915
916 static void _alternates_clean (void)
917 {
918   int i;
919
920   if (Context && Context->msgcount) {
921     for (i = 0; i < Context->msgcount; i++)
922       Context->hdrs[i]->recip_valid = 0;
923   }
924 }
925
926 static int parse_alternates (BUFFER * buf, BUFFER * s,
927                              unsigned long data __attribute__ ((unused)),
928                              BUFFER * err __attribute__ ((unused)))
929 {
930   _alternates_clean ();
931   do {
932     mutt_extract_token (buf, s, 0);
933     remove_from_rx_list (&UnAlternates, buf->data);
934
935     if (add_to_rx_list (&Alternates, buf->data, REG_ICASE, err) != 0)
936       return -1;
937   }
938   while (MoreArgs (s));
939
940   return 0;
941 }
942
943 static int parse_unalternates (BUFFER * buf, BUFFER * s,
944                                unsigned long data __attribute__ ((unused)),
945                                BUFFER * err __attribute__ ((unused)))
946 {
947   _alternates_clean ();
948   do {
949     mutt_extract_token (buf, s, 0);
950     remove_from_rx_list (&Alternates, buf->data);
951
952     if (m_strcmp(buf->data, "*") &&
953         add_to_rx_list (&UnAlternates, buf->data, REG_ICASE, err) != 0)
954       return -1;
955
956   }
957   while (MoreArgs (s));
958
959   return 0;
960 }
961
962 static int parse_spam_list (BUFFER * buf, BUFFER * s, unsigned long data,
963                             BUFFER * err)
964 {
965   BUFFER templ;
966
967   p_clear(&templ, 1);
968
969   /* Insist on at least one parameter */
970   if (!MoreArgs (s)) {
971     if (data == M_SPAM)
972       m_strcpy(err->data, err->dsize, _("spam: no matching pattern"));
973     else
974       m_strcpy(err->data, err->dsize, _("nospam: no matching pattern"));
975     return -1;
976   }
977
978   /* Extract the first token, a regexp */
979   mutt_extract_token (buf, s, 0);
980
981   /* data should be either M_SPAM or M_NOSPAM. M_SPAM is for spam commands. */
982   if (data == M_SPAM) {
983     /* If there's a second parameter, it's a template for the spam tag. */
984     if (MoreArgs (s)) {
985       mutt_extract_token (&templ, s, 0);
986
987       /* Add to the spam list. */
988       if (add_to_spam_list (&SpamList, buf->data, templ.data, err) != 0) {
989         p_delete(&templ.data);
990         return -1;
991       }
992       p_delete(&templ.data);
993     }
994
995     /* If not, try to remove from the nospam list. */
996     else {
997       remove_from_rx_list (&NoSpamList, buf->data);
998     }
999
1000     return 0;
1001   }
1002
1003   /* M_NOSPAM is for nospam commands. */
1004   else if (data == M_NOSPAM) {
1005     /* nospam only ever has one parameter. */
1006
1007     /* "*" is a special case. */
1008     if (!m_strcmp(buf->data, "*")) {
1009       mutt_free_spam_list (&SpamList);
1010       list_del (&NoSpamList, (list_del_t*) rx_delete);
1011       return 0;
1012     }
1013
1014     /* If it's on the spam list, just remove it. */
1015     if (remove_from_spam_list (&SpamList, buf->data) != 0)
1016       return 0;
1017
1018     /* Otherwise, add it to the nospam list. */
1019     if (add_to_rx_list (&NoSpamList, buf->data, REG_ICASE, err) != 0)
1020       return -1;
1021
1022     return 0;
1023   }
1024
1025   /* This should not happen. */
1026   m_strcpy(err->data, err->dsize, "This is no good at all.");
1027   return -1;
1028 }
1029
1030 static int parse_unlist (BUFFER * buf, BUFFER * s, unsigned long data,
1031                          BUFFER * err __attribute__ ((unused)))
1032 {
1033   do {
1034     mutt_extract_token (buf, s, 0);
1035     /*
1036      * Check for deletion of entire list
1037      */
1038     if (m_strcmp(buf->data, "*") == 0) {
1039       string_list_wipe((string_list_t **) data);
1040       break;
1041     }
1042     remove_from_list ((string_list_t **) data, buf->data);
1043   }
1044   while (MoreArgs (s));
1045
1046   return 0;
1047 }
1048
1049 static int parse_lists (BUFFER * buf, BUFFER * s,
1050                         unsigned long data __attribute__ ((unused)),
1051                         BUFFER * err)
1052 {
1053   do {
1054     mutt_extract_token (buf, s, 0);
1055     remove_from_rx_list (&UnMailLists, buf->data);
1056
1057     if (add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
1058       return -1;
1059   }
1060   while (MoreArgs (s));
1061
1062   return 0;
1063 }
1064
1065 /* always wise to do what someone else did before */
1066 static void _attachments_clean (void) {
1067   int i;
1068   if (Context && Context->msgcount) {
1069     for (i = 0; i < Context->msgcount; i++)
1070       Context->hdrs[i]->attach_valid = 0;
1071   }
1072 }
1073
1074 static int parse_attach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
1075                               BUFFER *err __attribute__ ((unused))) {
1076   ATTACH_MATCH *a;
1077   string_list_t *listp, *lastp;
1078   char *p;
1079   char *tmpminor;
1080   int len;
1081
1082   /* Find the last item in the list that data points to. */
1083   lastp = NULL;
1084   for (listp = *ldata; listp; listp = listp->next) {
1085     a = (ATTACH_MATCH *)listp->data;
1086     lastp = listp;
1087   }
1088
1089   do {
1090     mutt_extract_token (buf, s, 0);
1091
1092     if (!buf->data || *buf->data == '\0')
1093       continue;
1094
1095     a = p_new(ATTACH_MATCH, 1);
1096
1097     /* some cheap hacks that I expect to remove */
1098     if (!m_strcasecmp(buf->data, "any"))
1099       a->major = m_strdup("*/.*");
1100     else if (!m_strcasecmp(buf->data, "none"))
1101       a->major = m_strdup("cheap_hack/this_should_never_match");
1102     else
1103       a->major = m_strdup(buf->data);
1104
1105     if ((p = strchr(a->major, '/'))) {
1106       *p = '\0';
1107       ++p;
1108       a->minor = p;
1109     } else {
1110       a->minor = "unknown";
1111     }
1112
1113     len = m_strlen(a->minor);
1114     tmpminor = p_new(char, len + 3);
1115     strcpy(&tmpminor[1], a->minor); /* __STRCPY_CHECKED__ */
1116     tmpminor[0] = '^';
1117     tmpminor[len+1] = '$';
1118     tmpminor[len+2] = '\0';
1119
1120     a->major_int = mutt_check_mime_type(a->major);
1121     regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
1122
1123     p_delete(&tmpminor);
1124
1125     listp = p_new(string_list_t, 1);
1126     listp->data = (char *)a;
1127     listp->next = NULL;
1128     if (lastp) {
1129       lastp->next = listp;
1130     } else {
1131       *ldata = listp;
1132     }
1133     lastp = listp;
1134   }
1135   while (MoreArgs (s));
1136
1137   _attachments_clean();
1138   return 0;
1139 }
1140
1141 static int parse_unattach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
1142                                 BUFFER *err __attribute__ ((unused))) {
1143   ATTACH_MATCH *a;
1144   string_list_t *lp, *lastp, *newlp;
1145   char *tmp;
1146   int major;
1147   char *minor;
1148
1149   do {
1150     mutt_extract_token (buf, s, 0);
1151
1152     if (!m_strcasecmp(buf->data, "any"))
1153       tmp = m_strdup("*/.*");
1154     else if (!m_strcasecmp(buf->data, "none"))
1155       tmp = m_strdup("cheap_hack/this_should_never_match");
1156     else
1157       tmp = m_strdup(buf->data);
1158
1159     if ((minor = strchr(tmp, '/'))) {
1160       *minor = '\0';
1161       ++minor;
1162     } else {
1163       minor = m_strdup("unknown");
1164     }
1165     major = mutt_check_mime_type(tmp);
1166
1167     /* We must do our own walk here because remove_from_list() will only
1168      * remove the string_list_t->data, not anything pointed to by the string_list_t->data. */
1169     lastp = NULL;
1170     for(lp = *ldata; lp; ) {
1171       a = (ATTACH_MATCH *)lp->data;
1172       if (a->major_int == major && !m_strcasecmp(minor, a->minor)) {
1173         regfree(&a->minor_rx);
1174         p_delete(&a->major);
1175
1176         /* Relink backward */
1177         if (lastp)
1178           lastp->next = lp->next;
1179         else
1180           *ldata = lp->next;
1181
1182         newlp = lp->next;
1183         p_delete(&lp->data); /* same as a */
1184         p_delete(&lp);
1185         lp = newlp;
1186         continue;
1187       }
1188
1189       lastp = lp;
1190       lp = lp->next;
1191     }
1192   }
1193   while (MoreArgs (s));
1194
1195   p_delete(&tmp);
1196   _attachments_clean();
1197   return 0;
1198 }
1199
1200 static int print_attach_list (string_list_t *lp, char op, const char *name) {
1201   while (lp) {
1202     printf("attachments %c%s %s/%s\n", op, name,
1203            ((ATTACH_MATCH *)lp->data)->major,
1204            ((ATTACH_MATCH *)lp->data)->minor);
1205     lp = lp->next;
1206   }
1207
1208   return 0;
1209 }
1210
1211 static int parse_attachments (BUFFER *buf, BUFFER *s,
1212                               unsigned long data __attribute__ ((unused)),
1213                               BUFFER *err) {
1214   char op, *category;
1215   string_list_t **listp;
1216
1217   mutt_extract_token(buf, s, 0);
1218   if (!buf->data || *buf->data == '\0') {
1219     m_strcpy(err->data, err->dsize, _("attachments: no disposition"));
1220     return -1;
1221   }
1222
1223   category = buf->data;
1224   op = *category++;
1225
1226   if (op == '?') {
1227     mutt_endwin (NULL);
1228     fflush (stdout);
1229     printf("\nCurrent attachments settings:\n\n");
1230     print_attach_list(AttachAllow, '+', "A");
1231     print_attach_list(AttachExclude, '-', "A");
1232     print_attach_list(InlineAllow, '+', "I");
1233     print_attach_list(InlineExclude, '-', "I");
1234     set_option (OPTFORCEREDRAWINDEX);
1235     set_option (OPTFORCEREDRAWPAGER);
1236     mutt_any_key_to_continue (NULL);
1237     return 0;
1238   }
1239
1240   if (op != '+' && op != '-') {
1241     op = '+';
1242     category--;
1243   }
1244   if (!m_strncasecmp(category, "attachment", strlen(category))) {
1245     if (op == '+')
1246       listp = &AttachAllow;
1247     else
1248       listp = &AttachExclude;
1249   }
1250   else if (!m_strncasecmp(category, "inline", strlen(category))) {
1251     if (op == '+')
1252       listp = &InlineAllow;
1253     else
1254       listp = &InlineExclude;
1255   } else {
1256     m_strcpy(err->data, err->dsize, _("attachments: invalid disposition"));
1257     return -1;
1258   }
1259
1260   return parse_attach_list(buf, s, listp, err);
1261 }
1262
1263 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data __attribute__ ((unused)), BUFFER *err) {
1264   char op, *p;
1265   string_list_t **listp;
1266
1267   mutt_extract_token(buf, s, 0);
1268   if (!buf->data || *buf->data == '\0') {
1269     m_strcpy(err->data, err->dsize, _("unattachments: no disposition"));
1270     return -1;
1271   }
1272
1273   p = buf->data;
1274   op = *p++;
1275   if (op != '+' && op != '-') {
1276     op = '+';
1277     p--;
1278   }
1279   if (!m_strncasecmp(p, "attachment", strlen(p))) {
1280     if (op == '+')
1281       listp = &AttachAllow;
1282     else
1283       listp = &AttachExclude;
1284   }
1285   else if (!m_strncasecmp(p, "inline", strlen(p))) {
1286     if (op == '+')
1287       listp = &InlineAllow;
1288     else
1289       listp = &InlineExclude;
1290   }
1291   else {
1292     m_strcpy(err->data, err->dsize, _("unattachments: invalid disposition"));
1293     return -1;
1294   }
1295
1296   return parse_unattach_list(buf, s, listp, err);
1297 }
1298
1299 static int parse_unlists (BUFFER * buf, BUFFER * s,
1300                           unsigned long data __attribute__ ((unused)),
1301                           BUFFER * err __attribute__ ((unused)))
1302 {
1303   do {
1304     mutt_extract_token (buf, s, 0);
1305     remove_from_rx_list (&SubscribedLists, buf->data);
1306     remove_from_rx_list (&MailLists, buf->data);
1307
1308     if (m_strcmp(buf->data, "*") &&
1309         add_to_rx_list (&UnMailLists, buf->data, REG_ICASE, err) != 0)
1310       return -1;
1311   }
1312   while (MoreArgs (s));
1313
1314   return 0;
1315 }
1316
1317 static int parse_subscribe (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
1318                             BUFFER * err)
1319 {
1320   do {
1321     mutt_extract_token (buf, s, 0);
1322     remove_from_rx_list (&UnMailLists, buf->data);
1323     remove_from_rx_list (&UnSubscribedLists, buf->data);
1324
1325     if (add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
1326       return -1;
1327     if (add_to_rx_list (&SubscribedLists, buf->data, REG_ICASE, err) != 0)
1328       return -1;
1329   }
1330   while (MoreArgs (s));
1331
1332   return 0;
1333 }
1334
1335 static int parse_unsubscribe (BUFFER * buf, BUFFER * s,
1336                               unsigned long data __attribute__ ((unused)),
1337                               BUFFER * err __attribute__ ((unused)))
1338 {
1339   do {
1340     mutt_extract_token (buf, s, 0);
1341     remove_from_rx_list (&SubscribedLists, buf->data);
1342
1343     if (m_strcmp(buf->data, "*") &&
1344         add_to_rx_list (&UnSubscribedLists, buf->data, REG_ICASE, err) != 0)
1345       return -1;
1346   }
1347   while (MoreArgs (s));
1348
1349   return 0;
1350 }
1351
1352 static int parse_unalias (BUFFER * buf, BUFFER * s,
1353                           unsigned long data __attribute__ ((unused)),
1354                           BUFFER * err __attribute__ ((unused)))
1355 {
1356   alias_t *tmp, *last = NULL;
1357
1358   do {
1359     mutt_extract_token (buf, s, 0);
1360
1361     if (m_strcmp("*", buf->data) == 0) {
1362       if (CurrentMenu == MENU_ALIAS) {
1363         for (tmp = Aliases; tmp; tmp = tmp->next)
1364           tmp->del = 1;
1365         set_option (OPTFORCEREDRAWINDEX);
1366       }
1367       else
1368         alias_list_wipe(&Aliases);
1369       break;
1370     }
1371     else
1372       for (tmp = Aliases; tmp; tmp = tmp->next) {
1373         if (m_strcasecmp(buf->data, tmp->name) == 0) {
1374           if (CurrentMenu == MENU_ALIAS) {
1375             tmp->del = 1;
1376             set_option (OPTFORCEREDRAWINDEX);
1377             break;
1378           }
1379
1380           if (last)
1381             last->next = tmp->next;
1382           else
1383             Aliases = tmp->next;
1384           tmp->next = NULL;
1385           alias_list_wipe(&tmp);
1386           break;
1387         }
1388         last = tmp;
1389       }
1390   }
1391   while (MoreArgs (s));
1392   return 0;
1393 }
1394
1395 static int parse_alias (BUFFER * buf, BUFFER * s,
1396                         unsigned long data __attribute__ ((unused)),
1397                         BUFFER * err)
1398 {
1399   alias_t *tmp = Aliases;
1400   alias_t *last = NULL;
1401   char *estr = NULL;
1402
1403   if (!MoreArgs (s)) {
1404     m_strcpy(err->data, err->dsize, _("alias: no address"));
1405     return (-1);
1406   }
1407
1408   mutt_extract_token (buf, s, 0);
1409
1410   /* check to see if an alias with this name already exists */
1411   for (; tmp; tmp = tmp->next) {
1412     if (!m_strcasecmp(tmp->name, buf->data))
1413       break;
1414     last = tmp;
1415   }
1416
1417   if (!tmp) {
1418     /* create a new alias */
1419     tmp = alias_new();
1420     tmp->name = m_strdup(buf->data);
1421     /* give the main addressbook code a chance */
1422     if (CurrentMenu == MENU_ALIAS)
1423       set_option (OPTMENUCALLER);
1424   }
1425   else {
1426     /* override the previous value */
1427     address_list_wipe(&tmp->addr);
1428     if (CurrentMenu == MENU_ALIAS)
1429       set_option (OPTFORCEREDRAWINDEX);
1430   }
1431
1432   mutt_extract_token (buf, s,
1433                       M_TOKEN_QUOTE | M_TOKEN_SPACE | M_TOKEN_SEMICOLON);
1434   tmp->addr = mutt_parse_adrlist (tmp->addr, buf->data);
1435   if (last)
1436     last->next = tmp;
1437   else
1438     Aliases = tmp;
1439   if (mutt_addrlist_to_idna (tmp->addr, &estr)) {
1440     snprintf (err->data, err->dsize,
1441               _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, tmp->name);
1442     p_delete(&estr);
1443     return -1;
1444   }
1445
1446   return 0;
1447 }
1448
1449 static int
1450 parse_unmy_hdr (BUFFER * buf, BUFFER * s,
1451                 unsigned long data __attribute__ ((unused)),
1452                 BUFFER * err __attribute__ ((unused)))
1453 {
1454   string_list_t *last = NULL;
1455   string_list_t *tmp = UserHeader;
1456   string_list_t *ptr;
1457   ssize_t l;
1458
1459   do {
1460     mutt_extract_token (buf, s, 0);
1461     if (m_strcmp("*", buf->data) == 0)
1462       string_list_wipe(&UserHeader);
1463     else {
1464       tmp = UserHeader;
1465       last = NULL;
1466
1467       l = m_strlen(buf->data);
1468       if (buf->data[l - 1] == ':')
1469         l--;
1470
1471       while (tmp) {
1472         if (ascii_strncasecmp (buf->data, tmp->data, l) == 0
1473             && tmp->data[l] == ':') {
1474           ptr = tmp;
1475           if (last)
1476             last->next = tmp->next;
1477           else
1478             UserHeader = tmp->next;
1479           tmp = tmp->next;
1480           ptr->next = NULL;
1481           string_list_wipe(&ptr);
1482         }
1483         else {
1484           last = tmp;
1485           tmp = tmp->next;
1486         }
1487       }
1488     }
1489   }
1490   while (MoreArgs (s));
1491   return 0;
1492 }
1493
1494 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
1495                          BUFFER * err)
1496 {
1497   string_list_t *tmp;
1498   ssize_t keylen;
1499   char *p;
1500
1501   mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
1502   if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
1503     m_strcpy(err->data, err->dsize, _("invalid header field"));
1504     return (-1);
1505   }
1506   keylen = p - buf->data + 1;
1507
1508   if (UserHeader) {
1509     for (tmp = UserHeader;; tmp = tmp->next) {
1510       /* see if there is already a field by this name */
1511       if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
1512         /* replace the old value */
1513         p_delete(&tmp->data);
1514         tmp->data = buf->data;
1515         p_clear(buf, 1);
1516         return 0;
1517       }
1518       if (!tmp->next)
1519         break;
1520     }
1521     tmp->next = string_item_new();
1522     tmp = tmp->next;
1523   }
1524   else {
1525     tmp = string_item_new();
1526     UserHeader = tmp;
1527   }
1528   tmp->data = buf->data;
1529   p_clear(buf, 1);
1530   return 0;
1531 }
1532
1533 static int
1534 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
1535             char* errbuf, ssize_t errlen) {
1536   int i, flags = 0;
1537
1538   if (m_strncmp("reverse-", s, 8) == 0) {
1539     s += 8;
1540     flags = SORT_REVERSE;
1541   }
1542
1543   if (m_strncmp("last-", s, 5) == 0) {
1544     s += 5;
1545     flags |= SORT_LAST;
1546   }
1547
1548   if ((i = mutt_getvaluebyname (s, map)) == -1) {
1549     if (errbuf)
1550       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
1551     return (-1);
1552   }
1553
1554   *((short*) dst->data) = i | flags;
1555   return 0;
1556 }
1557
1558 /* if additional data more == 1, we want to resolve synonyms */
1559 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
1560 {
1561     char buf[LONG_STRING];
1562     struct option_t *ptr = p;
1563
1564     if (DTYPE(ptr->type) == DT_SYN) {
1565         if (!more)
1566             return;
1567         ptr = hash_find(ConfigOptions, (const char *)ptr->data);
1568     }
1569     if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_from_string)
1570         return;
1571
1572     mutt_option_value(ptr->option, buf, sizeof(buf));
1573     if (m_strlen(ptr->init) == 0 && buf && *buf)
1574         ptr->init = m_strdup(buf);
1575 }
1576
1577 static struct option_t* add_option (const char* name, const char* init,
1578                                     short type, short dodup) {
1579   struct option_t* option = p_new(struct option_t, 1);
1580
1581   option->option = m_strdup(name);
1582   option->type = type;
1583   if (init)
1584     option->init = dodup ? m_strdup(init) : (char*) init;
1585   return (option);
1586 }
1587
1588 /* creates new option_t* of type DT_USER for $user_ var */
1589 static struct option_t* add_user_option (const char* name) {
1590   return (add_option (name, NULL, DT_USER, 1));
1591 }
1592
1593 /* free()'s option_t* */
1594 static void del_option (void* p) {
1595   struct option_t *ptr = (struct option_t*) p;
1596   char* s = (char*) ptr->data;
1597   p_delete(&ptr->option);
1598   p_delete(&s);
1599   p_delete(&ptr->init);
1600   p_delete(&ptr);
1601 }
1602
1603 static int init_expand (char** dst, struct option_t* src) {
1604   BUFFER token, in;
1605   ssize_t len = 0;
1606
1607   p_delete(dst);
1608
1609   if (DTYPE(src->type) == DT_STR ||
1610       DTYPE(src->type) == DT_PATH) {
1611     /* only expand for string as it's the only place where
1612      * we want to expand vars right now */
1613     if (src->init && *src->init) {
1614       p_clear(&token, 1);
1615       p_clear(&in, 1);
1616       len = m_strlen(src->init) + 2;
1617       in.data = p_new(char, len + 1);
1618       snprintf (in.data, len, "\"%s\"", src->init);
1619       in.dptr = in.data;
1620       in.dsize = len;
1621       mutt_extract_token (&token, &in, 0);
1622       if (token.data && *token.data)
1623         *dst = m_strdup(token.data);
1624       else
1625         *dst = m_strdup("");
1626       p_delete(&in.data);
1627       p_delete(&token.data);
1628     } else
1629       *dst = m_strdup("");
1630   } else
1631     /* for non-string: take value as is */
1632     *dst = m_strdup(src->init);
1633   return (1);
1634 }
1635
1636 /* if additional data more == 1, we want to resolve synonyms */
1637 static void mutt_restore_default (const char* name __attribute__ ((unused)),
1638                                   void* p, unsigned long more) {
1639   char errbuf[STRING];
1640   struct option_t* ptr = (struct option_t*) p;
1641   char* init = NULL;
1642
1643   if (DTYPE (ptr->type) == DT_SYN) {
1644     if (!more)
1645       return;
1646     ptr = hash_find (ConfigOptions, (char*) ptr->data);
1647   }
1648   if (!ptr)
1649     return;
1650   if (FuncTable[DTYPE (ptr->type)].opt_from_string) {
1651     init_expand (&init, ptr);
1652     if (!FuncTable[DTYPE (ptr->type)].opt_from_string (ptr, init, errbuf,
1653                                                        sizeof(errbuf))) {
1654       if (!option (OPTNOCURSES))
1655         mutt_endwin (NULL);
1656       fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
1657                          "Please report this error: \"%s\"\n"),
1658                ptr->option, NONULL (init), errbuf);
1659       exit (1);
1660     }
1661     p_delete(&init);
1662   }
1663
1664   if (ptr->flags & R_INDEX)
1665     set_option (OPTFORCEREDRAWINDEX);
1666   if (ptr->flags & R_PAGER)
1667     set_option (OPTFORCEREDRAWPAGER);
1668   if (ptr->flags & R_RESORT_SUB)
1669     set_option (OPTSORTSUBTHREADS);
1670   if (ptr->flags & R_RESORT)
1671     set_option (OPTNEEDRESORT);
1672   if (ptr->flags & R_RESORT_INIT)
1673     set_option (OPTRESORTINIT);
1674   if (ptr->flags & R_TREE)
1675     set_option (OPTREDRAWTREE);
1676 }
1677
1678 /* check whether value for $dsn_return would be valid */
1679 static int check_dsn_return (const char* option __attribute__ ((unused)), unsigned long p,
1680                              char* errbuf, ssize_t errlen) {
1681   char* val = (char*) p;
1682   if (val && *val && m_strncmp(val, "hdrs", 4) != 0 &&
1683       m_strncmp(val, "full", 4) != 0) {
1684     if (errbuf)
1685       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), val, "dsn_return");
1686     return (0);
1687   }
1688   return (1);
1689 }
1690
1691 /* check whether value for $dsn_notify would be valid */
1692 static int check_dsn_notify (const char* option __attribute__ ((unused)), unsigned long p,
1693                              char* errbuf, ssize_t errlen) {
1694   list2_t* list = NULL;
1695   ssize_t i = 0;
1696   int rc = 1;
1697   char* val = (char*) p;
1698
1699   if (!val || !*val)
1700     return (1);
1701   list = list_from_str (val, ",");
1702   if (list_empty (list))
1703     return (1);
1704
1705   for (i = 0; i < list->length; i++)
1706     if (m_strncmp(list->data[i], "never", 5) != 0 &&
1707         m_strncmp(list->data[i], "failure", 7) != 0 &&
1708         m_strncmp(list->data[i], "delay", 5) != 0 &&
1709         m_strncmp(list->data[i], "success", 7) != 0) {
1710       if (errbuf)
1711         snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
1712                   (char*) list->data[i], "dsn_notify");
1713       rc = 0;
1714       break;
1715     }
1716   list_del (&list, (list_del_t*)xmemfree);
1717   return (rc);
1718 }
1719
1720 static int check_num (const char* option, unsigned long p,
1721                       char* errbuf, ssize_t errlen) {
1722   if ((int) p < 0) {
1723     if (errbuf)
1724       snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
1725     return (0);
1726   }
1727   return (1);
1728 }
1729
1730 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
1731                           char* errbuf, ssize_t errlen) {
1732   if (!check_num ("history", p, errbuf, errlen))
1733     return (0);
1734   mutt_init_history ();
1735   return (1);
1736 }
1737
1738 static int check_special (const char* name, unsigned long val,
1739                           char* errbuf, ssize_t errlen) {
1740   int i = 0;
1741
1742   for (i = 0; SpecialVars[i].name; i++) {
1743     if (m_strcmp(SpecialVars[i].name, name) == 0) {
1744       return (SpecialVars[i].check (SpecialVars[i].name,
1745                                     val, errbuf, errlen));
1746     }
1747   }
1748   return (1);
1749 }
1750
1751 static const struct mapping_t* get_sortmap (struct option_t* option) {
1752   const struct mapping_t* map = NULL;
1753
1754   switch (option->type & DT_SUBTYPE_MASK) {
1755   case DT_SORT_ALIAS:
1756     map = SortAliasMethods;
1757     break;
1758   case DT_SORT_BROWSER:
1759     map = SortBrowserMethods;
1760     break;
1761   case DT_SORT_KEYS:
1762     map = SortKeyMethods;
1763     break;
1764   case DT_SORT_AUX:
1765     map = SortAuxMethods;
1766     break;
1767   default:
1768     map = SortMethods;
1769     break;
1770   }
1771   return (map);
1772 }
1773
1774 #define CHECK_PAGER \
1775   if ((CurrentMenu == MENU_PAGER) && \
1776       (!option || (option->flags & R_RESORT))) \
1777   { \
1778     snprintf (err->data, err->dsize, \
1779               _("Not available in this menu.")); \
1780     return (-1); \
1781   }
1782
1783 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1784                       BUFFER * err)
1785 {
1786   int query, unset, inv, reset, r = 0;
1787   struct option_t* option = NULL;
1788
1789   while (MoreArgs (s)) {
1790     /* reset state variables */
1791     query = 0;
1792     unset = data & M_SET_UNSET;
1793     inv = data & M_SET_INV;
1794     reset = data & M_SET_RESET;
1795
1796     if (*s->dptr == '?') {
1797       query = 1;
1798       s->dptr++;
1799     }
1800     else if (m_strncmp("no", s->dptr, 2) == 0) {
1801       s->dptr += 2;
1802       unset = !unset;
1803     }
1804     else if (m_strncmp("inv", s->dptr, 3) == 0) {
1805       s->dptr += 3;
1806       inv = !inv;
1807     }
1808     else if (*s->dptr == '&') {
1809       reset = 1;
1810       s->dptr++;
1811     }
1812
1813     /* get the variable name */
1814     mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1815
1816     /* resolve synonyms */
1817     if ((option = hash_find (ConfigOptions, tmp->data)) != NULL &&
1818         DTYPE (option->type == DT_SYN)) {
1819       struct option_t* newopt = hash_find (ConfigOptions, (char*) option->data);
1820       syn_add (newopt, option);
1821       option = newopt;
1822     }
1823
1824     /* see if we need to add $user_ var */
1825     if (!option && ascii_strncmp ("user_", tmp->data, 5) == 0) {
1826       /* there's no option named like this yet so only add one
1827        * if the action isn't any of: reset, unset, query */
1828       if (!(reset || unset || query || *s->dptr != '=')) {
1829         option = add_user_option (tmp->data);
1830         hash_insert (ConfigOptions, option->option, option, 0);
1831       }
1832     }
1833
1834     if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1835       snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1836       return (-1);
1837     }
1838     s->dptr = vskipspaces(s->dptr);
1839
1840     if (reset) {
1841       if (query || unset || inv) {
1842         snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1843         return (-1);
1844       }
1845
1846       if (s && *s->dptr == '=') {
1847         snprintf (err->data, err->dsize, _("value is illegal with reset"));
1848         return (-1);
1849       }
1850
1851       if (!m_strcmp("all", tmp->data)) {
1852         if (CurrentMenu == MENU_PAGER) {
1853           snprintf (err->data, err->dsize, _("Not available in this menu."));
1854           return (-1);
1855         }
1856         hash_map (ConfigOptions, mutt_restore_default, 1);
1857         set_option (OPTFORCEREDRAWINDEX);
1858         set_option (OPTFORCEREDRAWPAGER);
1859         set_option (OPTSORTSUBTHREADS);
1860         set_option (OPTNEEDRESORT);
1861         set_option (OPTRESORTINIT);
1862         set_option (OPTREDRAWTREE);
1863         return (0);
1864       }
1865       else if (!FuncTable[DTYPE (option->type)].opt_from_string) {
1866         snprintf (err->data, err->dsize, _("$%s is read-only"), option->option);
1867         r = -1;
1868         break;
1869       } else {
1870         CHECK_PAGER;
1871         mutt_restore_default (NULL, option, 1);
1872       }
1873     }
1874     else if (DTYPE (option->type) == DT_BOOL) {
1875       /* XXX this currently ignores the function table
1876        * as we don't get invert and stuff into it */
1877       if (s && *s->dptr == '=') {
1878         if (unset || inv || query) {
1879           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1880           return (-1);
1881         }
1882
1883         s->dptr++;
1884         mutt_extract_token (tmp, s, 0);
1885         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1886           unset = inv = 0;
1887         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1888           unset = 1;
1889         else {
1890           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1891           return (-1);
1892         }
1893       }
1894
1895       if (query) {
1896         bool_to_string (err->data, err->dsize, option);
1897         return 0;
1898       }
1899
1900       CHECK_PAGER;
1901       if (unset)
1902         unset_option (option->data);
1903       else if (inv)
1904         toggle_option (option->data);
1905       else
1906         set_option (option->data);
1907     }
1908     else if (DTYPE (option->type) == DT_STR ||
1909              DTYPE (option->type) == DT_PATH ||
1910              DTYPE (option->type) == DT_ADDR ||
1911              DTYPE (option->type) == DT_MAGIC ||
1912              DTYPE (option->type) == DT_NUM ||
1913              DTYPE (option->type) == DT_SORT ||
1914              DTYPE (option->type) == DT_RX ||
1915              DTYPE (option->type) == DT_USER ||
1916              DTYPE (option->type) == DT_SYS) {
1917
1918       /* XXX maybe we need to get unset into handlers? */
1919       if (DTYPE (option->type) == DT_STR ||
1920           DTYPE (option->type) == DT_PATH ||
1921           DTYPE (option->type) == DT_ADDR ||
1922           DTYPE (option->type) == DT_USER ||
1923           DTYPE (option->type) == DT_SYS) {
1924         if (unset) {
1925           CHECK_PAGER;
1926           if (!FuncTable[DTYPE (option->type)].opt_from_string) {
1927             snprintf (err->data, err->dsize, _("$%s is read-only"),
1928                       option->option);
1929             r = -1;
1930             break;
1931           } else if (DTYPE (option->type) == DT_ADDR)
1932             address_list_wipe((address_t **) option->data);
1933           else if (DTYPE (option->type) == DT_USER)
1934             /* to unset $user_ means remove */
1935             hash_delete (ConfigOptions, option->option,
1936                          option, del_option);
1937           else
1938             p_delete((void **)(void *)&option->data);
1939           break;
1940         }
1941       }
1942
1943       if (query || *s->dptr != '=') {
1944         FuncTable[DTYPE (option->type)].opt_to_string
1945           (err->data, err->dsize, option);
1946         break;
1947       }
1948
1949       /* the $madmutt_ variables are read-only */
1950       if (!FuncTable[DTYPE (option->type)].opt_from_string) {
1951         snprintf (err->data, err->dsize, _("$%s is read-only"),
1952                   option->option);
1953         r = -1;
1954         break;
1955       } else {
1956         CHECK_PAGER;
1957         s->dptr++;
1958         mutt_extract_token (tmp, s, 0);
1959         if (!FuncTable[DTYPE (option->type)].opt_from_string
1960             (option, tmp->data, err->data, err->dsize))
1961           r = -1;
1962       }
1963     }
1964     else if (DTYPE (option->type) == DT_QUAD) {
1965
1966       if (query) {
1967         quad_to_string (err->data, err->dsize, option);
1968         break;
1969       }
1970
1971       if (*s->dptr == '=') {
1972         CHECK_PAGER;
1973         s->dptr++;
1974         mutt_extract_token (tmp, s, 0);
1975         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1976           set_quadoption (option->data, M_YES);
1977         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1978           set_quadoption (option->data, M_NO);
1979         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1980           set_quadoption (option->data, M_ASKYES);
1981         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1982           set_quadoption (option->data, M_ASKNO);
1983         else {
1984           snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1985                     tmp->data, option->option);
1986           r = -1;
1987           break;
1988         }
1989       }
1990       else {
1991         if (inv)
1992           toggle_quadoption (option->data);
1993         else if (unset)
1994           set_quadoption (option->data, M_NO);
1995         else
1996           set_quadoption (option->data, M_YES);
1997       }
1998     }
1999     else {
2000       snprintf (err->data, err->dsize, _("%s: unknown type"),
2001                 option->option);
2002       r = -1;
2003       break;
2004     }
2005
2006     if (option->flags & R_INDEX)
2007       set_option (OPTFORCEREDRAWINDEX);
2008     if (option->flags & R_PAGER)
2009       set_option (OPTFORCEREDRAWPAGER);
2010     if (option->flags & R_RESORT_SUB)
2011       set_option (OPTSORTSUBTHREADS);
2012     if (option->flags & R_RESORT)
2013       set_option (OPTNEEDRESORT);
2014     if (option->flags & R_RESORT_INIT)
2015       set_option (OPTRESORTINIT);
2016     if (option->flags & R_TREE)
2017       set_option (OPTREDRAWTREE);
2018   }
2019   return (r);
2020 }
2021
2022 #define MAXERRS 128
2023
2024 /* reads the specified initialization file.  returns -1 if errors were found
2025    so that we can pause to let the user know...  */
2026 static int source_rc (const char *rcfile, BUFFER * err)
2027 {
2028   FILE *f;
2029   int line = 0, rc = 0, conv = 0;
2030   BUFFER token;
2031   char *linebuf = NULL;
2032   char *currentline = NULL;
2033   ssize_t buflen;
2034   pid_t pid;
2035
2036   if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
2037     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
2038     return (-1);
2039   }
2040
2041   p_clear(&token, 1);
2042   while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
2043     conv = ConfigCharset && (*ConfigCharset) && Charset;
2044     if (conv) {
2045       currentline = m_strdup(linebuf);
2046       if (!currentline)
2047         continue;
2048       mutt_convert_string (&currentline, ConfigCharset, Charset, 0);
2049     }
2050     else
2051       currentline = linebuf;
2052
2053     CurRCLine = line;
2054     CurRCFile = rcfile;
2055
2056     if (mutt_parse_rc_line (currentline, &token, err) == -1) {
2057       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
2058       if (--rc < -MAXERRS) {
2059         if (conv)
2060           p_delete(&currentline);
2061         break;
2062       }
2063     }
2064     else {
2065       if (rc < 0)
2066         rc = -1;
2067     }
2068     if (conv)
2069       p_delete(&currentline);
2070   }
2071   p_delete(&token.data);
2072   p_delete(&linebuf);
2073   fclose (f);
2074   if (pid != -1)
2075     mutt_wait_filter (pid);
2076   if (rc) {
2077     /* the muttrc source keyword */
2078     snprintf (err->data, err->dsize,
2079               rc >= -MAXERRS ? _("source: errors in %s")
2080               : _("source: reading aborted due too many errors in %s"),
2081               rcfile);
2082     rc = -1;
2083   }
2084   return (rc);
2085 }
2086
2087 #undef MAXERRS
2088
2089 static int parse_source (BUFFER * tmp, BUFFER * s,
2090                          unsigned long data __attribute__ ((unused)),
2091                          BUFFER * err)
2092 {
2093   char path[_POSIX_PATH_MAX];
2094   int rc = 0;
2095
2096   do {
2097     if (mutt_extract_token (tmp, s, 0) != 0) {
2098       snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
2099       return (-1);
2100     }
2101
2102     m_strcpy(path, sizeof(path), tmp->data);
2103     mutt_expand_path (path, sizeof(path));
2104
2105     rc += source_rc (path, err);
2106   }
2107   while (MoreArgs (s));
2108
2109   return ((rc < 0) ? -1 : 0);
2110 }
2111
2112 /* line         command to execute
2113
2114    token        scratch buffer to be used by parser.  caller should free
2115                 token->data when finished.  the reason for this variable is
2116                 to avoid having to allocate and deallocate a lot of memory
2117                 if we are parsing many lines.  the caller can pass in the
2118                 memory to use, which avoids having to create new space for
2119                 every call to this function.
2120
2121    err          where to write error messages */
2122 int mutt_parse_rc_line ( /* const */ char *line, BUFFER * token, BUFFER * err)
2123 {
2124   int i, r = -1;
2125   BUFFER expn;
2126
2127   p_clear(&expn, 1);
2128   expn.data = expn.dptr = line;
2129   expn.dsize = m_strlen(line);
2130
2131   *err->data = 0;
2132
2133   expn.dptr = vskipspaces(expn.dptr);
2134   while (*expn.dptr) {
2135     if (*expn.dptr == '#')
2136       break;                    /* rest of line is a comment */
2137     if (*expn.dptr == ';') {
2138       expn.dptr++;
2139       continue;
2140     }
2141     mutt_extract_token (token, &expn, 0);
2142     for (i = 0; Commands[i].name; i++) {
2143       if (!m_strcmp(token->data, Commands[i].name)) {
2144         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
2145           goto finish;
2146         break;
2147       }
2148     }
2149     if (!Commands[i].name) {
2150       snprintf (err->data, err->dsize, _("%s: unknown command"),
2151                 NONULL (token->data));
2152       goto finish;
2153     }
2154   }
2155   r = 0;
2156 finish:
2157   if (expn.destroy)
2158     p_delete(&expn.data);
2159   return (r);
2160 }
2161
2162
2163 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
2164 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
2165 /* initial string that starts completion. No telling how much crap
2166  * the user has typed so far. Allocate LONG_STRING just to be sure! */
2167 char User_typed[LONG_STRING] = { 0 };
2168
2169 int Num_matched = 0;            /* Number of matches for completion */
2170 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
2171 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1];  /* all the matches + User_typed */
2172
2173 /* helper function for completion.  Changes the dest buffer if
2174    necessary/possible to aid completion.
2175         dest == completion result gets here.
2176         src == candidate for completion.
2177         try == user entered data for completion.
2178         len == length of dest buffer.
2179 */
2180 static void candidate (char *dest, char *try, const char *src, int len)
2181 {
2182   int l;
2183
2184   if (strstr (src, try) == src) {
2185     Matches[Num_matched++] = src;
2186     if (dest[0] == 0)
2187       m_strcpy(dest, len, src);
2188     else {
2189       for (l = 0; src[l] && src[l] == dest[l]; l++);
2190       dest[l] = 0;
2191     }
2192   }
2193 }
2194
2195 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
2196 {
2197   char *pt = buffer;
2198   int num;
2199   int spaces;                   /* keep track of the number of leading spaces on the line */
2200
2201   buffer = vskipspaces(buffer);
2202   spaces = buffer - pt;
2203
2204   pt = buffer + pos - spaces;
2205   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2206     pt--;
2207
2208   if (pt == buffer) {           /* complete cmd */
2209     /* first TAB. Collect all the matches */
2210     if (numtabs == 1) {
2211       Num_matched = 0;
2212       m_strcpy(User_typed, sizeof(User_typed), pt);
2213       p_clear(Matches, countof(Matches));
2214       p_clear(Completed, countof(Completed));
2215       for (num = 0; Commands[num].name; num++)
2216         candidate (Completed, User_typed, Commands[num].name,
2217                    sizeof(Completed));
2218       Matches[Num_matched++] = User_typed;
2219
2220       /* All matches are stored. Longest non-ambiguous string is ""
2221        * i.e. dont change 'buffer'. Fake successful return this time */
2222       if (User_typed[0] == 0)
2223         return 1;
2224     }
2225
2226     if (Completed[0] == 0 && User_typed[0])
2227       return 0;
2228
2229     /* Num_matched will _always_ be atleast 1 since the initial
2230      * user-typed string is always stored */
2231     if (numtabs == 1 && Num_matched == 2)
2232       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2233     else if (numtabs > 1 && Num_matched > 2)
2234       /* cycle thru all the matches */
2235       snprintf (Completed, sizeof(Completed), "%s",
2236                 Matches[(numtabs - 2) % Num_matched]);
2237
2238     /* return the completed command */
2239     m_strcpy(buffer, len - spaces, Completed);
2240   }
2241   else if (!m_strncmp(buffer, "set", 3)
2242            || !m_strncmp(buffer, "unset", 5)
2243            || !m_strncmp(buffer, "reset", 5)
2244            || !m_strncmp(buffer, "toggle", 6)) {    /* complete variables */
2245     const char *prefixes[] = { "no", "inv", "?", "&", NULL };
2246
2247     pt++;
2248     /* loop through all the possible prefixes (no, inv, ...) */
2249     if (!m_strncmp(buffer, "set", 3)) {
2250       for (num = 0; prefixes[num]; num++) {
2251         if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
2252           pt += m_strlen(prefixes[num]);
2253           break;
2254         }
2255       }
2256     }
2257
2258     /* first TAB. Collect all the matches */
2259     if (numtabs == 1) {
2260       Num_matched = 0;
2261       m_strcpy(User_typed, sizeof(User_typed), pt);
2262       p_clear(Matches, countof(Matches));
2263       p_clear(Completed, countof(Completed));
2264       for (num = 0; MuttVars[num].option; num++)
2265         candidate(Completed, User_typed, MuttVars[num].option,
2266                   sizeof(Completed));
2267       Matches[Num_matched++] = User_typed;
2268
2269       /* All matches are stored. Longest non-ambiguous string is ""
2270        * i.e. dont change 'buffer'. Fake successful return this time */
2271       if (User_typed[0] == 0)
2272         return 1;
2273     }
2274
2275     if (Completed[0] == 0 && User_typed[0])
2276       return 0;
2277
2278     /* Num_matched will _always_ be atleast 1 since the initial
2279      * user-typed string is always stored */
2280     if (numtabs == 1 && Num_matched == 2)
2281       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2282     else if (numtabs > 1 && Num_matched > 2)
2283       /* cycle thru all the matches */
2284       snprintf (Completed, sizeof(Completed), "%s",
2285                 Matches[(numtabs - 2) % Num_matched]);
2286
2287     m_strcpy(pt, buffer + len - pt - spaces, Completed);
2288   }
2289   else if (!m_strncmp(buffer, "exec", 4)) {
2290     struct binding_t *menu = km_get_table (CurrentMenu);
2291
2292     if (!menu && CurrentMenu != MENU_PAGER)
2293       menu = OpGeneric;
2294
2295     pt++;
2296     /* first TAB. Collect all the matches */
2297     if (numtabs == 1) {
2298       Num_matched = 0;
2299       m_strcpy(User_typed, sizeof(User_typed), pt);
2300       p_clear(Matches, countof(Matches));
2301       p_clear(Completed, countof(Completed));
2302       for (num = 0; menu[num].name; num++)
2303         candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
2304       /* try the generic menu */
2305       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
2306         menu = OpGeneric;
2307         for (num = 0; menu[num].name; num++)
2308           candidate (Completed, User_typed, menu[num].name,
2309                      sizeof(Completed));
2310       }
2311       Matches[Num_matched++] = User_typed;
2312
2313       /* All matches are stored. Longest non-ambiguous string is ""
2314        * i.e. dont change 'buffer'. Fake successful return this time */
2315       if (User_typed[0] == 0)
2316         return 1;
2317     }
2318
2319     if (Completed[0] == 0 && User_typed[0])
2320       return 0;
2321
2322     /* Num_matched will _always_ be atleast 1 since the initial
2323      * user-typed string is always stored */
2324     if (numtabs == 1 && Num_matched == 2)
2325       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2326     else if (numtabs > 1 && Num_matched > 2)
2327       /* cycle thru all the matches */
2328       snprintf (Completed, sizeof(Completed), "%s",
2329                 Matches[(numtabs - 2) % Num_matched]);
2330
2331     m_strcpy(pt, buffer + len - pt - spaces, Completed);
2332   }
2333   else
2334     return 0;
2335
2336   return 1;
2337 }
2338
2339 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
2340 {
2341   char var[STRING], *pt = buffer;
2342   int spaces;
2343   struct option_t* option = NULL;
2344
2345   if (buffer[0] == 0)
2346     return 0;
2347
2348   buffer = vskipspaces(buffer);
2349   spaces = buffer - pt;
2350
2351   pt = buffer + pos - spaces;
2352   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2353     pt--;
2354   pt++;                         /* move past the space */
2355   if (*pt == '=')               /* abort if no var before the '=' */
2356     return 0;
2357
2358   if (m_strncmp(buffer, "set", 3) == 0) {
2359     m_strcpy(var, sizeof(var), pt);
2360     /* ignore the trailing '=' when comparing */
2361     var[m_strlen(var) - 1] = 0;
2362     if (!(option = hash_find (ConfigOptions, var)))
2363       return 0;                 /* no such variable. */
2364     else {
2365       char tmp[LONG_STRING], tmp2[LONG_STRING];
2366       char *s, *d;
2367       ssize_t dlen = buffer + len - pt - spaces;
2368       const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
2369
2370       tmp[0] = '\0';
2371
2372       if ((DTYPE (option->type) == DT_STR) ||
2373           (DTYPE (option->type) == DT_PATH) ||
2374           (DTYPE (option->type) == DT_RX)) {
2375         m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
2376         if (DTYPE (option->type) == DT_PATH)
2377           mutt_pretty_mailbox (tmp);
2378       }
2379       else if (DTYPE (option->type) == DT_ADDR) {
2380         rfc822_write_address (tmp, sizeof(tmp),
2381                               *((address_t **) option->data), 0);
2382       }
2383       else if (DTYPE (option->type) == DT_QUAD)
2384         m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
2385       else if (DTYPE (option->type) == DT_NUM)
2386         snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
2387       else if (DTYPE (option->type) == DT_SORT) {
2388         const struct mapping_t *map;
2389         const char *p;
2390
2391         switch (option->type & DT_SUBTYPE_MASK) {
2392         case DT_SORT_ALIAS:
2393           map = SortAliasMethods;
2394           break;
2395         case DT_SORT_BROWSER:
2396           map = SortBrowserMethods;
2397           break;
2398         case DT_SORT_KEYS:
2399           map = SortKeyMethods;
2400           break;
2401         default:
2402           map = SortMethods;
2403           break;
2404         }
2405         p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
2406         snprintf(tmp, sizeof(tmp), "%s%s%s",
2407                  (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
2408                  (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
2409       }
2410       else if (DTYPE (option->type) == DT_MAGIC) {
2411         const char *p;
2412         switch (DefaultMagic) {
2413           case M_MBOX:
2414             p = "mbox";
2415             break;
2416           case M_MMDF:
2417             p = "MMDF";
2418             break;
2419           case M_MH:
2420             p = "MH";
2421           break;
2422           case M_MAILDIR:
2423             p = "Maildir";
2424             break;
2425           default:
2426             p = "unknown";
2427         }
2428         m_strcpy(tmp, sizeof(tmp), p);
2429       }
2430       else if (DTYPE (option->type) == DT_BOOL)
2431         m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
2432       else
2433         return 0;
2434
2435       for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
2436         if (*s == '\\' || *s == '"')
2437           *d++ = '\\';
2438         *d++ = *s++;
2439       }
2440       *d = '\0';
2441
2442       m_strcpy(tmp, sizeof(tmp), pt);
2443       snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
2444
2445       return 1;
2446     }
2447   }
2448   return 0;
2449 }
2450
2451 /* Implement the -Q command line flag */
2452 int mutt_query_variables (string_list_t * queries)
2453 {
2454   string_list_t *p;
2455
2456   char errbuff[STRING];
2457   char command[STRING];
2458
2459   BUFFER err, token;
2460
2461   p_clear(&err, 1);
2462   p_clear(&token, 1);
2463
2464   err.data = errbuff;
2465   err.dsize = sizeof(errbuff);
2466
2467   for (p = queries; p; p = p->next) {
2468     snprintf (command, sizeof(command), "set ?%s\n", p->data);
2469     if (mutt_parse_rc_line (command, &token, &err) == -1) {
2470       fprintf (stderr, "%s\n", err.data);
2471       p_delete(&token.data);
2472       return 1;
2473     }
2474     printf ("%s\n", err.data);
2475   }
2476
2477   p_delete(&token.data);
2478   return 0;
2479 }
2480
2481 static int mutt_execute_commands (string_list_t * p)
2482 {
2483   BUFFER err, token;
2484   char errstr[SHORT_STRING];
2485
2486   p_clear(&err, 1);
2487   err.data = errstr;
2488   err.dsize = sizeof(errstr);
2489   p_clear(&token, 1);
2490   for (; p; p = p->next) {
2491     if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
2492       fprintf (stderr, _("Error in command line: %s\n"), err.data);
2493       p_delete(&token.data);
2494       return (-1);
2495     }
2496   }
2497   p_delete(&token.data);
2498   return 0;
2499 }
2500
2501 void mutt_init (int skip_sys_rc, string_list_t * commands)
2502 {
2503   struct passwd *pw;
2504   struct utsname utsname;
2505   const char *p;
2506   char buffer[STRING], error[STRING];
2507   int default_rc = 0, need_pause = 0;
2508   int i;
2509   BUFFER err;
2510
2511   p_clear(&err, 1);
2512   err.data = error;
2513   err.dsize = sizeof(error);
2514
2515   /* use 3*sizeof(muttvars) instead of 2*sizeof()
2516    * to have some room for $user_ vars */
2517   ConfigOptions = hash_create (sizeof(MuttVars) * 3);
2518   for (i = 0; MuttVars[i].option; i++) {
2519     if (DTYPE (MuttVars[i].type) != DT_SYS)
2520       hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i], 0);
2521     else
2522       hash_insert (ConfigOptions, MuttVars[i].option,
2523                    add_option (MuttVars[i].option, MuttVars[i].init,
2524                                DT_SYS, 0), 0);
2525   }
2526
2527   /*
2528    * XXX - use something even more difficult to predict?
2529    */
2530   snprintf (AttachmentMarker, sizeof(AttachmentMarker),
2531             "\033]9;%ld\a", (long) time (NULL));
2532
2533   /* on one of the systems I use, getcwd() does not return the same prefix
2534      as is listed in the passwd file */
2535   if ((p = getenv ("HOME")))
2536     Homedir = m_strdup(p);
2537
2538   /* Get some information about the user */
2539   if ((pw = getpwuid (getuid ()))) {
2540     char rnbuf[STRING];
2541
2542     Username = m_strdup(pw->pw_name);
2543     if (!Homedir)
2544       Homedir = m_strdup(pw->pw_dir);
2545
2546     mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, GecosMask.rx);
2547     Realname = m_strdup(rnbuf);
2548     Shell = m_strdup(pw->pw_shell);
2549     endpwent ();
2550   }
2551   else {
2552     if (!Homedir) {
2553       mutt_endwin (NULL);
2554       fputs (_("unable to determine home directory"), stderr);
2555       exit (1);
2556     }
2557     if ((p = getenv ("USER")))
2558       Username = m_strdup(p);
2559     else {
2560       mutt_endwin (NULL);
2561       fputs (_("unable to determine username"), stderr);
2562       exit (1);
2563     }
2564     Shell = m_strdup((p = getenv ("SHELL")) ? p : "/bin/sh");
2565   }
2566
2567   /* And about the host... */
2568   uname (&utsname);
2569   /* some systems report the FQDN instead of just the hostname */
2570   if ((p = strchr (utsname.nodename, '.'))) {
2571     Hostname = p_dupstr(utsname.nodename, p - utsname.nodename);
2572     p++;
2573     m_strcpy(buffer, sizeof(buffer), p);       /* save the domain for below */
2574   }
2575   else
2576     Hostname = m_strdup(utsname.nodename);
2577
2578   if (!p && getdnsdomainname(buffer, sizeof(buffer)) == -1)
2579     Fqdn = m_strdup("@");
2580   else
2581   if (*buffer != '@') {
2582     Fqdn = p_new(char, m_strlen(buffer) + m_strlen(Hostname) + 2);
2583     sprintf (Fqdn, "%s.%s", NONULL(Hostname), buffer); /* __SPRINTF_CHECKED__ */
2584   }
2585   else
2586     Fqdn = m_strdup(NONULL (Hostname));
2587
2588 #ifdef USE_NNTP
2589   {
2590     FILE *f;
2591     char *q;
2592
2593     if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
2594       buffer[0] = '\0';
2595       fgets (buffer, sizeof(buffer), f);
2596       p = vskipspaces(buffer);
2597       q = (char*)p;
2598       while (*q && !isspace(*q))
2599         q++;
2600       *q = '\0';
2601       NewsServer = m_strdup(p);
2602       fclose (f);
2603     }
2604   }
2605   if ((p = getenv ("NNTPSERVER")))
2606     NewsServer = m_strdup(p);
2607 #endif
2608
2609   if ((p = getenv ("MAIL")))
2610     Spoolfile = m_strdup(p);
2611   else if ((p = getenv ("MAILDIR")))
2612     Spoolfile = m_strdup(p);
2613   else {
2614 #ifdef HOMESPOOL
2615     mutt_concat_path(buffer, sizeof(buffer), NONULL(Homedir), MAILPATH);
2616 #else
2617     mutt_concat_path(buffer, sizeof(buffer), MAILPATH, NONULL(Username));
2618 #endif
2619     Spoolfile = m_strdup(buffer);
2620   }
2621
2622   if ((p = getenv ("MAILCAPS")))
2623     MailcapPath = m_strdup(p);
2624   else {
2625     /* Default search path from RFC1524 */
2626     MailcapPath =
2627       m_strdup("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR
2628                    "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap");
2629   }
2630
2631   Tempdir = m_strdup((p = getenv ("TMPDIR")) ? p : "/tmp");
2632
2633   p = getenv ("VISUAL");
2634   if (!p) {
2635     p = getenv ("EDITOR");
2636     if (!p)
2637       p = "vi";
2638   }
2639   Editor = m_strdup(p);
2640
2641   if ((p = getenv ("REPLYTO")) != NULL) {
2642     BUFFER buf, token;
2643
2644     snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
2645
2646     p_clear(&buf, 1);
2647     buf.data = buf.dptr = buffer;
2648     buf.dsize = m_strlen(buffer);
2649
2650     p_clear(&token, 1);
2651     parse_my_hdr (&token, &buf, 0, &err);
2652     p_delete(&token.data);
2653   }
2654
2655   if ((p = getenv ("EMAIL")) != NULL)
2656     From = rfc822_parse_adrlist (NULL, p);
2657
2658   charset_initialize();
2659
2660   /* Set standard defaults */
2661   hash_map (ConfigOptions, mutt_set_default, 0);
2662   hash_map (ConfigOptions, mutt_restore_default, 0);
2663
2664   CurrentMenu = MENU_MAIN;
2665
2666 #ifdef HAVE_GETSID
2667   /* Unset suspend by default if we're the session leader */
2668   if (getsid (0) == getpid ())
2669     unset_option (OPTSUSPEND);
2670 #endif
2671
2672   mutt_init_history ();
2673
2674   if (!Muttrc) {
2675 #if 0
2676     snprintf (buffer, sizeof(buffer), "%s/.madmuttrc-%s", NONULL (Homedir),
2677               MUTT_VERSION);
2678     if (access (buffer, F_OK) == -1)
2679 #endif
2680       snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL (Homedir));
2681     if (access (buffer, F_OK) == -1)
2682 #if 0
2683       snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc-%s",
2684                 NONULL (Homedir), MUTT_VERSION);
2685     if (access (buffer, F_OK) == -1)
2686 #endif
2687       snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
2688                 NONULL (Homedir));
2689
2690     default_rc = 1;
2691     Muttrc = m_strdup(buffer);
2692   }
2693   else {
2694     m_strcpy(buffer, sizeof(buffer), Muttrc);
2695     p_delete(&Muttrc);
2696     mutt_expand_path (buffer, sizeof(buffer));
2697     Muttrc = m_strdup(buffer);
2698   }
2699   p_delete(&AliasFile);
2700   AliasFile = m_strdup(NONULL (Muttrc));
2701
2702   /* Process the global rc file if it exists and the user hasn't explicity
2703      requested not to via "-n".  */
2704   if (!skip_sys_rc) {
2705     snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
2706               MUTT_VERSION);
2707     if (access (buffer, F_OK) == -1)
2708       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
2709     if (access (buffer, F_OK) == -1)
2710       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
2711                 MUTT_VERSION);
2712     if (access (buffer, F_OK) == -1)
2713       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
2714     if (access (buffer, F_OK) != -1) {
2715       if (source_rc (buffer, &err) != 0) {
2716         fputs (err.data, stderr);
2717         fputc ('\n', stderr);
2718         need_pause = 1;
2719       }
2720     }
2721   }
2722
2723   /* Read the user's initialization file.  */
2724   if (access (Muttrc, F_OK) != -1) {
2725     if (!option (OPTNOCURSES))
2726       mutt_endwin (NULL);
2727     if (source_rc (Muttrc, &err) != 0) {
2728       fputs (err.data, stderr);
2729       fputc ('\n', stderr);
2730       need_pause = 1;
2731     }
2732   }
2733   else if (!default_rc) {
2734     /* file specified by -F does not exist */
2735     snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
2736     mutt_endwin (buffer);
2737     exit (1);
2738   }
2739
2740   if (mutt_execute_commands (commands) != 0)
2741     need_pause = 1;
2742
2743   /* warn about synonym variables */
2744   if (!list_empty(Synonyms)) {
2745     i = 0;
2746     fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
2747     for (i = 0; i < Synonyms->length; i++) {
2748       struct option_t* newopt = NULL, *oldopt = NULL;
2749       newopt = (struct option_t*) ((syn_t*) Synonyms->data[i])->n;
2750       oldopt = (struct option_t*) ((syn_t*) Synonyms->data[i])->o;
2751       fprintf (stderr, "$%s ($%s should be used) (%s:%d)\n",
2752                oldopt ? NONULL (oldopt->option) : "",
2753                newopt ? NONULL (newopt->option) : "",
2754                NONULL(((syn_t*) Synonyms->data[i])->f),
2755                ((syn_t*) Synonyms->data[i])->l);
2756     }
2757     fprintf (stderr, _("Warning: synonym variables are scheduled"
2758                        " for removal.\n"));
2759     list_del (&Synonyms, syn_del);
2760     need_pause = 1;
2761   }
2762
2763   if (need_pause && !option (OPTNOCURSES)) {
2764     if (mutt_any_key_to_continue (NULL) == -1)
2765       mutt_exit (1);
2766   }
2767
2768 #if 0
2769   set_option (OPTWEED);         /* turn weeding on by default */
2770 #endif
2771 }
2772
2773 int mutt_get_hook_type (const char *name)
2774 {
2775   struct command_t *c;
2776
2777   for (c = Commands; c->name; c++)
2778     if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
2779       return c->data;
2780   return 0;
2781 }
2782
2783 /* compare two option_t*'s for sorting -t/-T output */
2784 static int opt_cmp (const void* a, const void* b) {
2785   return (m_strcmp((*(struct option_t**) a)->option,
2786                        (*(struct option_t**) b)->option));
2787 }
2788
2789 /* callback for hash_map() to put all non-synonym vars into list */
2790 static void opt_sel_full (const char* key __attribute__ ((unused)),
2791                           void* data,
2792                           unsigned long more) {
2793   list2_t** l = (list2_t**) more;
2794   struct option_t* option = (struct option_t*) data;
2795
2796   if (DTYPE (option->type) == DT_SYN)
2797     return;
2798   list_push_back (l, option);
2799 }
2800
2801 /* callback for hash_map() to put all changed non-synonym vars into list */
2802 static void opt_sel_diff (const char* key __attribute__ ((unused)),
2803                           void* data,
2804                           unsigned long more) {
2805   list2_t** l = (list2_t**) more;
2806   struct option_t* option = (struct option_t*) data;
2807   char buf[LONG_STRING];
2808
2809   if (DTYPE (option->type) == DT_SYN)
2810     return;
2811
2812   mutt_option_value (option->option, buf, sizeof(buf));
2813   if (m_strcmp(buf, option->init) != 0)
2814     list_push_back (l, option);
2815 }
2816
2817 /* dump out the value of all the variables we have */
2818 int mutt_dump_variables (int full) {
2819   ssize_t i = 0;
2820   char outbuf[STRING];
2821   list2_t* tmp = NULL;
2822   struct option_t* option = NULL;
2823
2824   /* get all non-synonyms into list... */
2825   hash_map (ConfigOptions, full ? opt_sel_full : opt_sel_diff,
2826             (unsigned long) &tmp);
2827
2828   if (!list_empty(tmp)) {
2829     /* ...and dump list sorted */
2830     qsort (tmp->data, tmp->length, sizeof(void*), opt_cmp);
2831     for (i = 0; i < tmp->length; i++) {
2832       option = (struct option_t*) tmp->data[i];
2833       FuncTable[DTYPE (option->type)].opt_to_string
2834         (outbuf, sizeof(outbuf), option);
2835       printf ("%s\n", outbuf);
2836     }
2837   }
2838   list_del (&tmp, NULL);
2839   return 0;
2840 }