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