move more things in the lib-ui.
[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   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     p_delete(&estr);
1450     return -1;
1451   }
1452
1453   return 0;
1454 }
1455
1456 static int
1457 parse_unmy_hdr (BUFFER * buf, BUFFER * s,
1458                 unsigned long data __attribute__ ((unused)),
1459                 BUFFER * err __attribute__ ((unused)))
1460 {
1461   string_list_t *last = NULL;
1462   string_list_t *tmp = UserHeader;
1463   string_list_t *ptr;
1464   ssize_t l;
1465
1466   do {
1467     mutt_extract_token (buf, s, 0);
1468     if (m_strcmp("*", buf->data) == 0)
1469       string_list_wipe(&UserHeader);
1470     else {
1471       tmp = UserHeader;
1472       last = NULL;
1473
1474       l = m_strlen(buf->data);
1475       if (buf->data[l - 1] == ':')
1476         l--;
1477
1478       while (tmp) {
1479         if (ascii_strncasecmp (buf->data, tmp->data, l) == 0
1480             && tmp->data[l] == ':') {
1481           ptr = tmp;
1482           if (last)
1483             last->next = tmp->next;
1484           else
1485             UserHeader = tmp->next;
1486           tmp = tmp->next;
1487           ptr->next = NULL;
1488           string_list_wipe(&ptr);
1489         }
1490         else {
1491           last = tmp;
1492           tmp = tmp->next;
1493         }
1494       }
1495     }
1496   }
1497   while (MoreArgs (s));
1498   return 0;
1499 }
1500
1501 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
1502                          BUFFER * err)
1503 {
1504   string_list_t *tmp;
1505   ssize_t keylen;
1506   char *p;
1507
1508   mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
1509   if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
1510     m_strcpy(err->data, err->dsize, _("invalid header field"));
1511     return (-1);
1512   }
1513   keylen = p - buf->data + 1;
1514
1515   if (UserHeader) {
1516     for (tmp = UserHeader;; tmp = tmp->next) {
1517       /* see if there is already a field by this name */
1518       if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
1519         /* replace the old value */
1520         p_delete(&tmp->data);
1521         tmp->data = buf->data;
1522         p_clear(buf, 1);
1523         return 0;
1524       }
1525       if (!tmp->next)
1526         break;
1527     }
1528     tmp->next = string_item_new();
1529     tmp = tmp->next;
1530   }
1531   else {
1532     tmp = string_item_new();
1533     UserHeader = tmp;
1534   }
1535   tmp->data = buf->data;
1536   p_clear(buf, 1);
1537   return 0;
1538 }
1539
1540 static int
1541 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
1542             char* errbuf, ssize_t errlen) {
1543   int i, flags = 0;
1544
1545   if (m_strncmp("reverse-", s, 8) == 0) {
1546     s += 8;
1547     flags = SORT_REVERSE;
1548   }
1549
1550   if (m_strncmp("last-", s, 5) == 0) {
1551     s += 5;
1552     flags |= SORT_LAST;
1553   }
1554
1555   if ((i = mutt_getvaluebyname (s, map)) == -1) {
1556     if (errbuf)
1557       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
1558     return (-1);
1559   }
1560
1561   *((short*) dst->data) = i | flags;
1562   return 0;
1563 }
1564
1565 /* if additional data more == 1, we want to resolve synonyms */
1566 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
1567 {
1568     char buf[LONG_STRING];
1569     struct option_t *ptr = p;
1570
1571     if (DTYPE(ptr->type) == DT_SYN) {
1572         if (!more)
1573             return;
1574         ptr = hash_find(ConfigOptions, (const char *)ptr->data);
1575     }
1576     if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_from_string)
1577         return;
1578
1579     mutt_option_value(ptr->option, buf, sizeof(buf));
1580     if (m_strlen(ptr->init) == 0 && buf && *buf)
1581         ptr->init = m_strdup(buf);
1582 }
1583
1584 static struct option_t* add_option (const char* name, const char* init,
1585                                     short type, short dodup) {
1586   struct option_t* option = p_new(struct option_t, 1);
1587
1588   option->option = m_strdup(name);
1589   option->type = type;
1590   if (init)
1591     option->init = dodup ? m_strdup(init) : (char*) init;
1592   return (option);
1593 }
1594
1595 /* creates new option_t* of type DT_USER for $user_ var */
1596 static struct option_t* add_user_option (const char* name) {
1597   return (add_option (name, NULL, DT_USER, 1));
1598 }
1599
1600 /* free()'s option_t* */
1601 static void del_option (void* p) {
1602   struct option_t *ptr = (struct option_t*) p;
1603   char* s = (char*) ptr->data;
1604   p_delete(&ptr->option);
1605   p_delete(&s);
1606   p_delete(&ptr->init);
1607   p_delete(&ptr);
1608 }
1609
1610 static int init_expand (char** dst, struct option_t* src) {
1611   BUFFER token, in;
1612   ssize_t len = 0;
1613
1614   p_delete(dst);
1615
1616   if (DTYPE(src->type) == DT_STR ||
1617       DTYPE(src->type) == DT_PATH) {
1618     /* only expand for string as it's the only place where
1619      * we want to expand vars right now */
1620     if (src->init && *src->init) {
1621       p_clear(&token, 1);
1622       p_clear(&in, 1);
1623       len = m_strlen(src->init) + 2;
1624       in.data = p_new(char, len + 1);
1625       snprintf (in.data, len, "\"%s\"", src->init);
1626       in.dptr = in.data;
1627       in.dsize = len;
1628       mutt_extract_token (&token, &in, 0);
1629       if (token.data && *token.data)
1630         *dst = m_strdup(token.data);
1631       else
1632         *dst = m_strdup("");
1633       p_delete(&in.data);
1634       p_delete(&token.data);
1635     } else
1636       *dst = m_strdup("");
1637   } else
1638     /* for non-string: take value as is */
1639     *dst = m_strdup(src->init);
1640   return (1);
1641 }
1642
1643 /* if additional data more == 1, we want to resolve synonyms */
1644 static void mutt_restore_default (const char* name __attribute__ ((unused)),
1645                                   void* p, unsigned long more) {
1646   char errbuf[STRING];
1647   struct option_t* ptr = (struct option_t*) p;
1648   char* init = NULL;
1649
1650   if (DTYPE (ptr->type) == DT_SYN) {
1651     if (!more)
1652       return;
1653     ptr = hash_find (ConfigOptions, (char*) ptr->data);
1654   }
1655   if (!ptr)
1656     return;
1657   if (FuncTable[DTYPE (ptr->type)].opt_from_string) {
1658     init_expand (&init, ptr);
1659     if (!FuncTable[DTYPE (ptr->type)].opt_from_string (ptr, init, errbuf,
1660                                                        sizeof(errbuf))) {
1661       if (!option (OPTNOCURSES))
1662         mutt_endwin (NULL);
1663       fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
1664                          "Please report this error: \"%s\"\n"),
1665                ptr->option, NONULL (init), errbuf);
1666       exit (1);
1667     }
1668     p_delete(&init);
1669   }
1670
1671   if (ptr->flags & R_INDEX)
1672     set_option (OPTFORCEREDRAWINDEX);
1673   if (ptr->flags & R_PAGER)
1674     set_option (OPTFORCEREDRAWPAGER);
1675   if (ptr->flags & R_RESORT_SUB)
1676     set_option (OPTSORTSUBTHREADS);
1677   if (ptr->flags & R_RESORT)
1678     set_option (OPTNEEDRESORT);
1679   if (ptr->flags & R_RESORT_INIT)
1680     set_option (OPTRESORTINIT);
1681   if (ptr->flags & R_TREE)
1682     set_option (OPTREDRAWTREE);
1683 }
1684
1685 /* check whether value for $dsn_return would be valid */
1686 static int check_dsn_return (const char* option __attribute__ ((unused)), unsigned long p,
1687                              char* errbuf, ssize_t errlen) {
1688   char* val = (char*) p;
1689   if (val && *val && m_strncmp(val, "hdrs", 4) != 0 &&
1690       m_strncmp(val, "full", 4) != 0) {
1691     if (errbuf)
1692       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), val, "dsn_return");
1693     return (0);
1694   }
1695   return (1);
1696 }
1697
1698 /* check whether value for $dsn_notify would be valid */
1699 static int check_dsn_notify (const char* option __attribute__ ((unused)), unsigned long p,
1700                              char* errbuf, ssize_t errlen) {
1701   list2_t* list = NULL;
1702   ssize_t i = 0;
1703   int rc = 1;
1704   char* val = (char*) p;
1705
1706   if (!val || !*val)
1707     return (1);
1708   list = list_from_str (val, ",");
1709   if (list_empty (list))
1710     return (1);
1711
1712   for (i = 0; i < list->length; i++)
1713     if (m_strncmp(list->data[i], "never", 5) != 0 &&
1714         m_strncmp(list->data[i], "failure", 7) != 0 &&
1715         m_strncmp(list->data[i], "delay", 5) != 0 &&
1716         m_strncmp(list->data[i], "success", 7) != 0) {
1717       if (errbuf)
1718         snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
1719                   (char*) list->data[i], "dsn_notify");
1720       rc = 0;
1721       break;
1722     }
1723   list_del (&list, (list_del_t*)xmemfree);
1724   return (rc);
1725 }
1726
1727 static int check_num (const char* option, unsigned long p,
1728                       char* errbuf, ssize_t errlen) {
1729   if ((int) p < 0) {
1730     if (errbuf)
1731       snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
1732     return (0);
1733   }
1734   return (1);
1735 }
1736
1737 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
1738                           char* errbuf, ssize_t errlen) {
1739   if (!check_num ("history", p, errbuf, errlen))
1740     return (0);
1741   mutt_init_history ();
1742   return (1);
1743 }
1744
1745 static int check_special (const char* name, unsigned long val,
1746                           char* errbuf, ssize_t errlen) {
1747   int i = 0;
1748
1749   for (i = 0; SpecialVars[i].name; i++) {
1750     if (m_strcmp(SpecialVars[i].name, name) == 0) {
1751       return (SpecialVars[i].check (SpecialVars[i].name,
1752                                     val, errbuf, errlen));
1753     }
1754   }
1755   return (1);
1756 }
1757
1758 static const struct mapping_t* get_sortmap (struct option_t* option) {
1759   const struct mapping_t* map = NULL;
1760
1761   switch (option->type & DT_SUBTYPE_MASK) {
1762   case DT_SORT_ALIAS:
1763     map = SortAliasMethods;
1764     break;
1765   case DT_SORT_BROWSER:
1766     map = SortBrowserMethods;
1767     break;
1768   case DT_SORT_KEYS:
1769     map = SortKeyMethods;
1770     break;
1771   case DT_SORT_AUX:
1772     map = SortAuxMethods;
1773     break;
1774   default:
1775     map = SortMethods;
1776     break;
1777   }
1778   return (map);
1779 }
1780
1781 #define CHECK_PAGER \
1782   if ((CurrentMenu == MENU_PAGER) && \
1783       (!option || (option->flags & R_RESORT))) \
1784   { \
1785     snprintf (err->data, err->dsize, \
1786               _("Not available in this menu.")); \
1787     return (-1); \
1788   }
1789
1790 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1791                       BUFFER * err)
1792 {
1793   int query, unset, inv, reset, r = 0;
1794   struct option_t* option = NULL;
1795
1796   while (MoreArgs (s)) {
1797     /* reset state variables */
1798     query = 0;
1799     unset = data & M_SET_UNSET;
1800     inv = data & M_SET_INV;
1801     reset = data & M_SET_RESET;
1802
1803     if (*s->dptr == '?') {
1804       query = 1;
1805       s->dptr++;
1806     }
1807     else if (m_strncmp("no", s->dptr, 2) == 0) {
1808       s->dptr += 2;
1809       unset = !unset;
1810     }
1811     else if (m_strncmp("inv", s->dptr, 3) == 0) {
1812       s->dptr += 3;
1813       inv = !inv;
1814     }
1815     else if (*s->dptr == '&') {
1816       reset = 1;
1817       s->dptr++;
1818     }
1819
1820     /* get the variable name */
1821     mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1822
1823     /* resolve synonyms */
1824     if ((option = hash_find (ConfigOptions, tmp->data)) != NULL &&
1825         DTYPE (option->type == DT_SYN)) {
1826       struct option_t* newopt = hash_find (ConfigOptions, (char*) option->data);
1827       syn_add (newopt, option);
1828       option = newopt;
1829     }
1830
1831     /* see if we need to add $user_ var */
1832     if (!option && ascii_strncmp ("user_", tmp->data, 5) == 0) {
1833       /* there's no option named like this yet so only add one
1834        * if the action isn't any of: reset, unset, query */
1835       if (!(reset || unset || query || *s->dptr != '=')) {
1836         option = add_user_option (tmp->data);
1837         hash_insert (ConfigOptions, option->option, option, 0);
1838       }
1839     }
1840
1841     if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1842       snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1843       return (-1);
1844     }
1845     s->dptr = vskipspaces(s->dptr);
1846
1847     if (reset) {
1848       if (query || unset || inv) {
1849         snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1850         return (-1);
1851       }
1852
1853       if (s && *s->dptr == '=') {
1854         snprintf (err->data, err->dsize, _("value is illegal with reset"));
1855         return (-1);
1856       }
1857
1858       if (!m_strcmp("all", tmp->data)) {
1859         if (CurrentMenu == MENU_PAGER) {
1860           snprintf (err->data, err->dsize, _("Not available in this menu."));
1861           return (-1);
1862         }
1863         hash_map (ConfigOptions, mutt_restore_default, 1);
1864         set_option (OPTFORCEREDRAWINDEX);
1865         set_option (OPTFORCEREDRAWPAGER);
1866         set_option (OPTSORTSUBTHREADS);
1867         set_option (OPTNEEDRESORT);
1868         set_option (OPTRESORTINIT);
1869         set_option (OPTREDRAWTREE);
1870         return (0);
1871       }
1872       else if (!FuncTable[DTYPE (option->type)].opt_from_string) {
1873         snprintf (err->data, err->dsize, _("$%s is read-only"), option->option);
1874         r = -1;
1875         break;
1876       } else {
1877         CHECK_PAGER;
1878         mutt_restore_default (NULL, option, 1);
1879       }
1880     }
1881     else if (DTYPE (option->type) == DT_BOOL) {
1882       /* XXX this currently ignores the function table
1883        * as we don't get invert and stuff into it */
1884       if (s && *s->dptr == '=') {
1885         if (unset || inv || query) {
1886           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1887           return (-1);
1888         }
1889
1890         s->dptr++;
1891         mutt_extract_token (tmp, s, 0);
1892         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1893           unset = inv = 0;
1894         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1895           unset = 1;
1896         else {
1897           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1898           return (-1);
1899         }
1900       }
1901
1902       if (query) {
1903         bool_to_string (err->data, err->dsize, option);
1904         return 0;
1905       }
1906
1907       CHECK_PAGER;
1908       if (unset)
1909         unset_option (option->data);
1910       else if (inv)
1911         toggle_option (option->data);
1912       else
1913         set_option (option->data);
1914     }
1915     else if (DTYPE (option->type) == DT_STR ||
1916              DTYPE (option->type) == DT_PATH ||
1917              DTYPE (option->type) == DT_ADDR ||
1918              DTYPE (option->type) == DT_MAGIC ||
1919              DTYPE (option->type) == DT_NUM ||
1920              DTYPE (option->type) == DT_SORT ||
1921              DTYPE (option->type) == DT_RX ||
1922              DTYPE (option->type) == DT_USER ||
1923              DTYPE (option->type) == DT_SYS) {
1924
1925       /* XXX maybe we need to get unset into handlers? */
1926       if (DTYPE (option->type) == DT_STR ||
1927           DTYPE (option->type) == DT_PATH ||
1928           DTYPE (option->type) == DT_ADDR ||
1929           DTYPE (option->type) == DT_USER ||
1930           DTYPE (option->type) == DT_SYS) {
1931         if (unset) {
1932           CHECK_PAGER;
1933           if (!FuncTable[DTYPE (option->type)].opt_from_string) {
1934             snprintf (err->data, err->dsize, _("$%s is read-only"),
1935                       option->option);
1936             r = -1;
1937             break;
1938           } else if (DTYPE (option->type) == DT_ADDR)
1939             address_list_wipe((address_t **) option->data);
1940           else if (DTYPE (option->type) == DT_USER)
1941             /* to unset $user_ means remove */
1942             hash_delete (ConfigOptions, option->option,
1943                          option, del_option);
1944           else
1945             p_delete((void **)(void *)&option->data);
1946           break;
1947         }
1948       }
1949
1950       if (query || *s->dptr != '=') {
1951         FuncTable[DTYPE (option->type)].opt_to_string
1952           (err->data, err->dsize, option);
1953         break;
1954       }
1955
1956       /* the $madmutt_ variables are read-only */
1957       if (!FuncTable[DTYPE (option->type)].opt_from_string) {
1958         snprintf (err->data, err->dsize, _("$%s is read-only"),
1959                   option->option);
1960         r = -1;
1961         break;
1962       } else {
1963         CHECK_PAGER;
1964         s->dptr++;
1965         mutt_extract_token (tmp, s, 0);
1966         if (!FuncTable[DTYPE (option->type)].opt_from_string
1967             (option, tmp->data, err->data, err->dsize))
1968           r = -1;
1969       }
1970     }
1971     else if (DTYPE (option->type) == DT_QUAD) {
1972
1973       if (query) {
1974         quad_to_string (err->data, err->dsize, option);
1975         break;
1976       }
1977
1978       if (*s->dptr == '=') {
1979         CHECK_PAGER;
1980         s->dptr++;
1981         mutt_extract_token (tmp, s, 0);
1982         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1983           set_quadoption (option->data, M_YES);
1984         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1985           set_quadoption (option->data, M_NO);
1986         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1987           set_quadoption (option->data, M_ASKYES);
1988         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1989           set_quadoption (option->data, M_ASKNO);
1990         else {
1991           snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1992                     tmp->data, option->option);
1993           r = -1;
1994           break;
1995         }
1996       }
1997       else {
1998         if (inv)
1999           toggle_quadoption (option->data);
2000         else if (unset)
2001           set_quadoption (option->data, M_NO);
2002         else
2003           set_quadoption (option->data, M_YES);
2004       }
2005     }
2006     else {
2007       snprintf (err->data, err->dsize, _("%s: unknown type"),
2008                 option->option);
2009       r = -1;
2010       break;
2011     }
2012
2013     if (option->flags & R_INDEX)
2014       set_option (OPTFORCEREDRAWINDEX);
2015     if (option->flags & R_PAGER)
2016       set_option (OPTFORCEREDRAWPAGER);
2017     if (option->flags & R_RESORT_SUB)
2018       set_option (OPTSORTSUBTHREADS);
2019     if (option->flags & R_RESORT)
2020       set_option (OPTNEEDRESORT);
2021     if (option->flags & R_RESORT_INIT)
2022       set_option (OPTRESORTINIT);
2023     if (option->flags & R_TREE)
2024       set_option (OPTREDRAWTREE);
2025   }
2026   return (r);
2027 }
2028
2029 #define MAXERRS 128
2030
2031 /* reads the specified initialization file.  returns -1 if errors were found
2032    so that we can pause to let the user know...  */
2033 static int source_rc (const char *rcfile, BUFFER * err)
2034 {
2035   FILE *f;
2036   int line = 0, rc = 0, conv = 0;
2037   BUFFER token;
2038   char *linebuf = NULL;
2039   char *currentline = NULL;
2040   ssize_t buflen;
2041   pid_t pid;
2042
2043   if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
2044     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
2045     return (-1);
2046   }
2047
2048   p_clear(&token, 1);
2049   while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
2050     conv = ConfigCharset && (*ConfigCharset) && Charset;
2051     if (conv) {
2052       currentline = m_strdup(linebuf);
2053       if (!currentline)
2054         continue;
2055       mutt_convert_string (&currentline, ConfigCharset, Charset, 0);
2056     }
2057     else
2058       currentline = linebuf;
2059
2060     CurRCLine = line;
2061     CurRCFile = rcfile;
2062
2063     if (mutt_parse_rc_line (currentline, &token, err) == -1) {
2064       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
2065       if (--rc < -MAXERRS) {
2066         if (conv)
2067           p_delete(&currentline);
2068         break;
2069       }
2070     }
2071     else {
2072       if (rc < 0)
2073         rc = -1;
2074     }
2075     if (conv)
2076       p_delete(&currentline);
2077   }
2078   p_delete(&token.data);
2079   p_delete(&linebuf);
2080   fclose (f);
2081   if (pid != -1)
2082     mutt_wait_filter (pid);
2083   if (rc) {
2084     /* the muttrc source keyword */
2085     snprintf (err->data, err->dsize,
2086               rc >= -MAXERRS ? _("source: errors in %s")
2087               : _("source: reading aborted due too many errors in %s"),
2088               rcfile);
2089     rc = -1;
2090   }
2091   return (rc);
2092 }
2093
2094 #undef MAXERRS
2095
2096 static int parse_source (BUFFER * tmp, BUFFER * s,
2097                          unsigned long data __attribute__ ((unused)),
2098                          BUFFER * err)
2099 {
2100   char path[_POSIX_PATH_MAX];
2101   int rc = 0;
2102
2103   do {
2104     if (mutt_extract_token (tmp, s, 0) != 0) {
2105       snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
2106       return (-1);
2107     }
2108
2109     m_strcpy(path, sizeof(path), tmp->data);
2110     mutt_expand_path (path, sizeof(path));
2111
2112     rc += source_rc (path, err);
2113   }
2114   while (MoreArgs (s));
2115
2116   return ((rc < 0) ? -1 : 0);
2117 }
2118
2119 /* line         command to execute
2120
2121    token        scratch buffer to be used by parser.  caller should free
2122                 token->data when finished.  the reason for this variable is
2123                 to avoid having to allocate and deallocate a lot of memory
2124                 if we are parsing many lines.  the caller can pass in the
2125                 memory to use, which avoids having to create new space for
2126                 every call to this function.
2127
2128    err          where to write error messages */
2129 int mutt_parse_rc_line ( /* const */ char *line, BUFFER * token, BUFFER * err)
2130 {
2131   int i, r = -1;
2132   BUFFER expn;
2133
2134   p_clear(&expn, 1);
2135   expn.data = expn.dptr = line;
2136   expn.dsize = m_strlen(line);
2137
2138   *err->data = 0;
2139
2140   expn.dptr = vskipspaces(expn.dptr);
2141   while (*expn.dptr) {
2142     if (*expn.dptr == '#')
2143       break;                    /* rest of line is a comment */
2144     if (*expn.dptr == ';') {
2145       expn.dptr++;
2146       continue;
2147     }
2148     mutt_extract_token (token, &expn, 0);
2149     for (i = 0; Commands[i].name; i++) {
2150       if (!m_strcmp(token->data, Commands[i].name)) {
2151         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
2152           goto finish;
2153         break;
2154       }
2155     }
2156     if (!Commands[i].name) {
2157       snprintf (err->data, err->dsize, _("%s: unknown command"),
2158                 NONULL (token->data));
2159       goto finish;
2160     }
2161   }
2162   r = 0;
2163 finish:
2164   if (expn.destroy)
2165     p_delete(&expn.data);
2166   return (r);
2167 }
2168
2169
2170 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
2171 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
2172 /* initial string that starts completion. No telling how much crap
2173  * the user has typed so far. Allocate LONG_STRING just to be sure! */
2174 char User_typed[LONG_STRING] = { 0 };
2175
2176 int Num_matched = 0;            /* Number of matches for completion */
2177 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
2178 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1];  /* all the matches + User_typed */
2179
2180 /* helper function for completion.  Changes the dest buffer if
2181    necessary/possible to aid completion.
2182         dest == completion result gets here.
2183         src == candidate for completion.
2184         try == user entered data for completion.
2185         len == length of dest buffer.
2186 */
2187 static void candidate (char *dest, char *try, const char *src, int len)
2188 {
2189   int l;
2190
2191   if (strstr (src, try) == src) {
2192     Matches[Num_matched++] = src;
2193     if (dest[0] == 0)
2194       m_strcpy(dest, len, src);
2195     else {
2196       for (l = 0; src[l] && src[l] == dest[l]; l++);
2197       dest[l] = 0;
2198     }
2199   }
2200 }
2201
2202 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
2203 {
2204   char *pt = buffer;
2205   int num;
2206   int spaces;                   /* keep track of the number of leading spaces on the line */
2207
2208   buffer = vskipspaces(buffer);
2209   spaces = buffer - pt;
2210
2211   pt = buffer + pos - spaces;
2212   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2213     pt--;
2214
2215   if (pt == buffer) {           /* complete cmd */
2216     /* first TAB. Collect all the matches */
2217     if (numtabs == 1) {
2218       Num_matched = 0;
2219       m_strcpy(User_typed, sizeof(User_typed), pt);
2220       p_clear(Matches, countof(Matches));
2221       p_clear(Completed, countof(Completed));
2222       for (num = 0; Commands[num].name; num++)
2223         candidate (Completed, User_typed, Commands[num].name,
2224                    sizeof(Completed));
2225       Matches[Num_matched++] = User_typed;
2226
2227       /* All matches are stored. Longest non-ambiguous string is ""
2228        * i.e. dont change 'buffer'. Fake successful return this time */
2229       if (User_typed[0] == 0)
2230         return 1;
2231     }
2232
2233     if (Completed[0] == 0 && User_typed[0])
2234       return 0;
2235
2236     /* Num_matched will _always_ be atleast 1 since the initial
2237      * user-typed string is always stored */
2238     if (numtabs == 1 && Num_matched == 2)
2239       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2240     else if (numtabs > 1 && Num_matched > 2)
2241       /* cycle thru all the matches */
2242       snprintf (Completed, sizeof(Completed), "%s",
2243                 Matches[(numtabs - 2) % Num_matched]);
2244
2245     /* return the completed command */
2246     m_strcpy(buffer, len - spaces, Completed);
2247   }
2248   else if (!m_strncmp(buffer, "set", 3)
2249            || !m_strncmp(buffer, "unset", 5)
2250            || !m_strncmp(buffer, "reset", 5)
2251            || !m_strncmp(buffer, "toggle", 6)) {    /* complete variables */
2252     const char *prefixes[] = { "no", "inv", "?", "&", NULL };
2253
2254     pt++;
2255     /* loop through all the possible prefixes (no, inv, ...) */
2256     if (!m_strncmp(buffer, "set", 3)) {
2257       for (num = 0; prefixes[num]; num++) {
2258         if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
2259           pt += m_strlen(prefixes[num]);
2260           break;
2261         }
2262       }
2263     }
2264
2265     /* first TAB. Collect all the matches */
2266     if (numtabs == 1) {
2267       Num_matched = 0;
2268       m_strcpy(User_typed, sizeof(User_typed), pt);
2269       p_clear(Matches, countof(Matches));
2270       p_clear(Completed, countof(Completed));
2271       for (num = 0; MuttVars[num].option; num++)
2272         candidate(Completed, User_typed, MuttVars[num].option,
2273                   sizeof(Completed));
2274       Matches[Num_matched++] = User_typed;
2275
2276       /* All matches are stored. Longest non-ambiguous string is ""
2277        * i.e. dont change 'buffer'. Fake successful return this time */
2278       if (User_typed[0] == 0)
2279         return 1;
2280     }
2281
2282     if (Completed[0] == 0 && User_typed[0])
2283       return 0;
2284
2285     /* Num_matched will _always_ be atleast 1 since the initial
2286      * user-typed string is always stored */
2287     if (numtabs == 1 && Num_matched == 2)
2288       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2289     else if (numtabs > 1 && Num_matched > 2)
2290       /* cycle thru all the matches */
2291       snprintf (Completed, sizeof(Completed), "%s",
2292                 Matches[(numtabs - 2) % Num_matched]);
2293
2294     m_strcpy(pt, buffer + len - pt - spaces, Completed);
2295   }
2296   else if (!m_strncmp(buffer, "exec", 4)) {
2297     struct binding_t *menu = km_get_table (CurrentMenu);
2298
2299     if (!menu && CurrentMenu != MENU_PAGER)
2300       menu = OpGeneric;
2301
2302     pt++;
2303     /* first TAB. Collect all the matches */
2304     if (numtabs == 1) {
2305       Num_matched = 0;
2306       m_strcpy(User_typed, sizeof(User_typed), pt);
2307       p_clear(Matches, countof(Matches));
2308       p_clear(Completed, countof(Completed));
2309       for (num = 0; menu[num].name; num++)
2310         candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
2311       /* try the generic menu */
2312       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
2313         menu = OpGeneric;
2314         for (num = 0; menu[num].name; num++)
2315           candidate (Completed, User_typed, menu[num].name,
2316                      sizeof(Completed));
2317       }
2318       Matches[Num_matched++] = User_typed;
2319
2320       /* All matches are stored. Longest non-ambiguous string is ""
2321        * i.e. dont change 'buffer'. Fake successful return this time */
2322       if (User_typed[0] == 0)
2323         return 1;
2324     }
2325
2326     if (Completed[0] == 0 && User_typed[0])
2327       return 0;
2328
2329     /* Num_matched will _always_ be atleast 1 since the initial
2330      * user-typed string is always stored */
2331     if (numtabs == 1 && Num_matched == 2)
2332       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
2333     else if (numtabs > 1 && Num_matched > 2)
2334       /* cycle thru all the matches */
2335       snprintf (Completed, sizeof(Completed), "%s",
2336                 Matches[(numtabs - 2) % Num_matched]);
2337
2338     m_strcpy(pt, buffer + len - pt - spaces, Completed);
2339   }
2340   else
2341     return 0;
2342
2343   return 1;
2344 }
2345
2346 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
2347 {
2348   char var[STRING], *pt = buffer;
2349   int spaces;
2350   struct option_t* option = NULL;
2351
2352   if (buffer[0] == 0)
2353     return 0;
2354
2355   buffer = vskipspaces(buffer);
2356   spaces = buffer - pt;
2357
2358   pt = buffer + pos - spaces;
2359   while ((pt > buffer) && !isspace ((unsigned char) *pt))
2360     pt--;
2361   pt++;                         /* move past the space */
2362   if (*pt == '=')               /* abort if no var before the '=' */
2363     return 0;
2364
2365   if (m_strncmp(buffer, "set", 3) == 0) {
2366     m_strcpy(var, sizeof(var), pt);
2367     /* ignore the trailing '=' when comparing */
2368     var[m_strlen(var) - 1] = 0;
2369     if (!(option = hash_find (ConfigOptions, var)))
2370       return 0;                 /* no such variable. */
2371     else {
2372       char tmp[LONG_STRING], tmp2[LONG_STRING];
2373       char *s, *d;
2374       ssize_t dlen = buffer + len - pt - spaces;
2375       const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
2376
2377       tmp[0] = '\0';
2378
2379       if ((DTYPE (option->type) == DT_STR) ||
2380           (DTYPE (option->type) == DT_PATH) ||
2381           (DTYPE (option->type) == DT_RX)) {
2382         m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
2383         if (DTYPE (option->type) == DT_PATH)
2384           mutt_pretty_mailbox (tmp);
2385       }
2386       else if (DTYPE (option->type) == DT_ADDR) {
2387         rfc822_write_address (tmp, sizeof(tmp),
2388                               *((address_t **) option->data), 0);
2389       }
2390       else if (DTYPE (option->type) == DT_QUAD)
2391         m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
2392       else if (DTYPE (option->type) == DT_NUM)
2393         snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
2394       else if (DTYPE (option->type) == DT_SORT) {
2395         const struct mapping_t *map;
2396         const char *p;
2397
2398         switch (option->type & DT_SUBTYPE_MASK) {
2399         case DT_SORT_ALIAS:
2400           map = SortAliasMethods;
2401           break;
2402         case DT_SORT_BROWSER:
2403           map = SortBrowserMethods;
2404           break;
2405         case DT_SORT_KEYS:
2406           map = SortKeyMethods;
2407           break;
2408         default:
2409           map = SortMethods;
2410           break;
2411         }
2412         p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
2413         snprintf(tmp, sizeof(tmp), "%s%s%s",
2414                  (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
2415                  (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
2416       }
2417       else if (DTYPE (option->type) == DT_MAGIC) {
2418         const char *p;
2419         switch (DefaultMagic) {
2420           case M_MBOX:
2421             p = "mbox";
2422             break;
2423           case M_MMDF:
2424             p = "MMDF";
2425             break;
2426           case M_MH:
2427             p = "MH";
2428           break;
2429           case M_MAILDIR:
2430             p = "Maildir";
2431             break;
2432           default:
2433             p = "unknown";
2434         }
2435         m_strcpy(tmp, sizeof(tmp), p);
2436       }
2437       else if (DTYPE (option->type) == DT_BOOL)
2438         m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
2439       else
2440         return 0;
2441
2442       for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
2443         if (*s == '\\' || *s == '"')
2444           *d++ = '\\';
2445         *d++ = *s++;
2446       }
2447       *d = '\0';
2448
2449       m_strcpy(tmp, sizeof(tmp), pt);
2450       snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
2451
2452       return 1;
2453     }
2454   }
2455   return 0;
2456 }
2457
2458 /* Implement the -Q command line flag */
2459 int mutt_query_variables (string_list_t * queries)
2460 {
2461   string_list_t *p;
2462
2463   char errbuff[STRING];
2464   char command[STRING];
2465
2466   BUFFER err, token;
2467
2468   p_clear(&err, 1);
2469   p_clear(&token, 1);
2470
2471   err.data = errbuff;
2472   err.dsize = sizeof(errbuff);
2473
2474   for (p = queries; p; p = p->next) {
2475     snprintf (command, sizeof(command), "set ?%s\n", p->data);
2476     if (mutt_parse_rc_line (command, &token, &err) == -1) {
2477       fprintf (stderr, "%s\n", err.data);
2478       p_delete(&token.data);
2479       return 1;
2480     }
2481     printf ("%s\n", err.data);
2482   }
2483
2484   p_delete(&token.data);
2485   return 0;
2486 }
2487
2488 static int mutt_execute_commands (string_list_t * p)
2489 {
2490   BUFFER err, token;
2491   char errstr[SHORT_STRING];
2492
2493   p_clear(&err, 1);
2494   err.data = errstr;
2495   err.dsize = sizeof(errstr);
2496   p_clear(&token, 1);
2497   for (; p; p = p->next) {
2498     if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
2499       fprintf (stderr, _("Error in command line: %s\n"), err.data);
2500       p_delete(&token.data);
2501       return (-1);
2502     }
2503   }
2504   p_delete(&token.data);
2505   return 0;
2506 }
2507
2508 void mutt_init (int skip_sys_rc, string_list_t * commands)
2509 {
2510   struct passwd *pw;
2511   struct utsname utsname;
2512   const char *p;
2513   char buffer[STRING], error[STRING];
2514   int default_rc = 0, need_pause = 0;
2515   int i;
2516   BUFFER err;
2517
2518   p_clear(&err, 1);
2519   err.data = error;
2520   err.dsize = sizeof(error);
2521
2522   /* use 3*sizeof(muttvars) instead of 2*sizeof()
2523    * to have some room for $user_ vars */
2524   ConfigOptions = hash_create (sizeof(MuttVars) * 3);
2525   for (i = 0; MuttVars[i].option; i++) {
2526     if (DTYPE (MuttVars[i].type) != DT_SYS)
2527       hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i], 0);
2528     else
2529       hash_insert (ConfigOptions, MuttVars[i].option,
2530                    add_option (MuttVars[i].option, MuttVars[i].init,
2531                                DT_SYS, 0), 0);
2532   }
2533
2534   /*
2535    * XXX - use something even more difficult to predict?
2536    */
2537   snprintf (AttachmentMarker, sizeof(AttachmentMarker),
2538             "\033]9;%ld\a", (long) time (NULL));
2539
2540   /* on one of the systems I use, getcwd() does not return the same prefix
2541      as is listed in the passwd file */
2542   if ((p = getenv ("HOME")))
2543     Homedir = m_strdup(p);
2544
2545   /* Get some information about the user */
2546   if ((pw = getpwuid (getuid ()))) {
2547     char rnbuf[STRING];
2548
2549     Username = m_strdup(pw->pw_name);
2550     if (!Homedir)
2551       Homedir = m_strdup(pw->pw_dir);
2552
2553     mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, GecosMask.rx);
2554     Realname = m_strdup(rnbuf);
2555     Shell = m_strdup(pw->pw_shell);
2556     endpwent ();
2557   }
2558   else {
2559     if (!Homedir) {
2560       mutt_endwin (NULL);
2561       fputs (_("unable to determine home directory"), stderr);
2562       exit (1);
2563     }
2564     if ((p = getenv ("USER")))
2565       Username = m_strdup(p);
2566     else {
2567       mutt_endwin (NULL);
2568       fputs (_("unable to determine username"), stderr);
2569       exit (1);
2570     }
2571     Shell = m_strdup((p = getenv ("SHELL")) ? p : "/bin/sh");
2572   }
2573
2574   /* And about the host... */
2575   uname (&utsname);
2576   /* some systems report the FQDN instead of just the hostname */
2577   if ((p = strchr (utsname.nodename, '.'))) {
2578     Hostname = p_dupstr(utsname.nodename, p - utsname.nodename);
2579     p++;
2580     m_strcpy(buffer, sizeof(buffer), p);       /* save the domain for below */
2581   }
2582   else
2583     Hostname = m_strdup(utsname.nodename);
2584
2585   if (!p && getdnsdomainname(buffer, sizeof(buffer)) == -1)
2586     Fqdn = m_strdup("@");
2587   else
2588   if (*buffer != '@') {
2589     Fqdn = p_new(char, m_strlen(buffer) + m_strlen(Hostname) + 2);
2590     sprintf (Fqdn, "%s.%s", NONULL(Hostname), buffer); /* __SPRINTF_CHECKED__ */
2591   }
2592   else
2593     Fqdn = m_strdup(NONULL (Hostname));
2594
2595 #ifdef USE_NNTP
2596   {
2597     FILE *f;
2598     char *q;
2599
2600     if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
2601       buffer[0] = '\0';
2602       fgets (buffer, sizeof(buffer), f);
2603       p = vskipspaces(buffer);
2604       q = (char*)p;
2605       while (*q && !isspace(*q))
2606         q++;
2607       *q = '\0';
2608       NewsServer = m_strdup(p);
2609       fclose (f);
2610     }
2611   }
2612   if ((p = getenv ("NNTPSERVER")))
2613     NewsServer = m_strdup(p);
2614 #endif
2615
2616   if ((p = getenv ("MAIL")))
2617     Spoolfile = m_strdup(p);
2618   else if ((p = getenv ("MAILDIR")))
2619     Spoolfile = m_strdup(p);
2620   else {
2621 #ifdef HOMESPOOL
2622     mutt_concat_path(buffer, sizeof(buffer), NONULL(Homedir), MAILPATH);
2623 #else
2624     mutt_concat_path(buffer, sizeof(buffer), MAILPATH, NONULL(Username));
2625 #endif
2626     Spoolfile = m_strdup(buffer);
2627   }
2628
2629   if ((p = getenv ("MAILCAPS")))
2630     MailcapPath = m_strdup(p);
2631   else {
2632     /* Default search path from RFC1524 */
2633     MailcapPath =
2634       m_strdup("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR
2635                    "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap");
2636   }
2637
2638   Tempdir = m_strdup((p = getenv ("TMPDIR")) ? p : "/tmp");
2639
2640   p = getenv ("VISUAL");
2641   if (!p) {
2642     p = getenv ("EDITOR");
2643     if (!p)
2644       p = "vi";
2645   }
2646   Editor = m_strdup(p);
2647
2648   if ((p = getenv ("REPLYTO")) != NULL) {
2649     BUFFER buf, token;
2650
2651     snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
2652
2653     p_clear(&buf, 1);
2654     buf.data = buf.dptr = buffer;
2655     buf.dsize = m_strlen(buffer);
2656
2657     p_clear(&token, 1);
2658     parse_my_hdr (&token, &buf, 0, &err);
2659     p_delete(&token.data);
2660   }
2661
2662   if ((p = getenv ("EMAIL")) != NULL)
2663     From = rfc822_parse_adrlist (NULL, p);
2664
2665   charset_initialize();
2666
2667   /* Set standard defaults */
2668   hash_map (ConfigOptions, mutt_set_default, 0);
2669   hash_map (ConfigOptions, mutt_restore_default, 0);
2670
2671   CurrentMenu = MENU_MAIN;
2672
2673   /* Do we have a locale definition? */
2674   if (((p = getenv ("LC_ALL")) != NULL && p[0]) ||
2675       ((p = getenv ("LANG")) != NULL && p[0]) ||
2676       ((p = getenv ("LC_CTYPE")) != NULL && p[0]))
2677     set_option (OPTLOCALES);
2678
2679 #ifdef HAVE_GETSID
2680   /* Unset suspend by default if we're the session leader */
2681   if (getsid (0) == getpid ())
2682     unset_option (OPTSUSPEND);
2683 #endif
2684
2685   mutt_init_history ();
2686
2687
2688
2689
2690   /*
2691    *
2692    *                       BIG FAT WARNING
2693    *
2694    * When changing the code which looks for a configuration file,
2695    * please also change the corresponding code in muttbug.sh.in.
2696    *
2697    *
2698    */
2699
2700
2701
2702
2703   if (!Muttrc) {
2704 #if 0
2705     snprintf (buffer, sizeof(buffer), "%s/.madmuttrc-%s", NONULL (Homedir),
2706               MUTT_VERSION);
2707     if (access (buffer, F_OK) == -1)
2708 #endif
2709       snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL (Homedir));
2710     if (access (buffer, F_OK) == -1)
2711 #if 0
2712       snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc-%s",
2713                 NONULL (Homedir), MUTT_VERSION);
2714     if (access (buffer, F_OK) == -1)
2715 #endif
2716       snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
2717                 NONULL (Homedir));
2718
2719     default_rc = 1;
2720     Muttrc = m_strdup(buffer);
2721   }
2722   else {
2723     m_strcpy(buffer, sizeof(buffer), Muttrc);
2724     p_delete(&Muttrc);
2725     mutt_expand_path (buffer, sizeof(buffer));
2726     Muttrc = m_strdup(buffer);
2727   }
2728   p_delete(&AliasFile);
2729   AliasFile = m_strdup(NONULL (Muttrc));
2730
2731   /* Process the global rc file if it exists and the user hasn't explicity
2732      requested not to via "-n".  */
2733   if (!skip_sys_rc) {
2734     snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
2735               MUTT_VERSION);
2736     if (access (buffer, F_OK) == -1)
2737       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
2738     if (access (buffer, F_OK) == -1)
2739       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
2740                 MUTT_VERSION);
2741     if (access (buffer, F_OK) == -1)
2742       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
2743     if (access (buffer, F_OK) != -1) {
2744       if (source_rc (buffer, &err) != 0) {
2745         fputs (err.data, stderr);
2746         fputc ('\n', stderr);
2747         need_pause = 1;
2748       }
2749     }
2750   }
2751
2752   /* Read the user's initialization file.  */
2753   if (access (Muttrc, F_OK) != -1) {
2754     if (!option (OPTNOCURSES))
2755       mutt_endwin (NULL);
2756     if (source_rc (Muttrc, &err) != 0) {
2757       fputs (err.data, stderr);
2758       fputc ('\n', stderr);
2759       need_pause = 1;
2760     }
2761   }
2762   else if (!default_rc) {
2763     /* file specified by -F does not exist */
2764     snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
2765     mutt_endwin (buffer);
2766     exit (1);
2767   }
2768
2769   if (mutt_execute_commands (commands) != 0)
2770     need_pause = 1;
2771
2772   /* warn about synonym variables */
2773   if (!list_empty(Synonyms)) {
2774     i = 0;
2775     fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
2776     for (i = 0; i < Synonyms->length; i++) {
2777       struct option_t* newopt = NULL, *oldopt = NULL;
2778       newopt = (struct option_t*) ((syn_t*) Synonyms->data[i])->n;
2779       oldopt = (struct option_t*) ((syn_t*) Synonyms->data[i])->o;
2780       fprintf (stderr, "$%s ($%s should be used) (%s:%d)\n",
2781                oldopt ? NONULL (oldopt->option) : "",
2782                newopt ? NONULL (newopt->option) : "",
2783                NONULL(((syn_t*) Synonyms->data[i])->f),
2784                ((syn_t*) Synonyms->data[i])->l);
2785     }
2786     fprintf (stderr, _("Warning: synonym variables are scheduled"
2787                        " for removal.\n"));
2788     list_del (&Synonyms, syn_del);
2789     need_pause = 1;
2790   }
2791
2792   if (need_pause && !option (OPTNOCURSES)) {
2793     if (mutt_any_key_to_continue (NULL) == -1)
2794       mutt_exit (1);
2795   }
2796
2797 #if 0
2798   set_option (OPTWEED);         /* turn weeding on by default */
2799 #endif
2800 }
2801
2802 int mutt_get_hook_type (const char *name)
2803 {
2804   struct command_t *c;
2805
2806   for (c = Commands; c->name; c++)
2807     if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
2808       return c->data;
2809   return 0;
2810 }
2811
2812 /* compare two option_t*'s for sorting -t/-T output */
2813 static int opt_cmp (const void* a, const void* b) {
2814   return (m_strcmp((*(struct option_t**) a)->option,
2815                        (*(struct option_t**) b)->option));
2816 }
2817
2818 /* callback for hash_map() to put all non-synonym vars into list */
2819 static void opt_sel_full (const char* key __attribute__ ((unused)),
2820                           void* data,
2821                           unsigned long more) {
2822   list2_t** l = (list2_t**) more;
2823   struct option_t* option = (struct option_t*) data;
2824
2825   if (DTYPE (option->type) == DT_SYN)
2826     return;
2827   list_push_back (l, option);
2828 }
2829
2830 /* callback for hash_map() to put all changed non-synonym vars into list */
2831 static void opt_sel_diff (const char* key __attribute__ ((unused)),
2832                           void* data,
2833                           unsigned long more) {
2834   list2_t** l = (list2_t**) more;
2835   struct option_t* option = (struct option_t*) data;
2836   char buf[LONG_STRING];
2837
2838   if (DTYPE (option->type) == DT_SYN)
2839     return;
2840
2841   mutt_option_value (option->option, buf, sizeof(buf));
2842   if (m_strcmp(buf, option->init) != 0)
2843     list_push_back (l, option);
2844 }
2845
2846 /* dump out the value of all the variables we have */
2847 int mutt_dump_variables (int full) {
2848   ssize_t i = 0;
2849   char outbuf[STRING];
2850   list2_t* tmp = NULL;
2851   struct option_t* option = NULL;
2852
2853   /* get all non-synonyms into list... */
2854   hash_map (ConfigOptions, full ? opt_sel_full : opt_sel_diff,
2855             (unsigned long) &tmp);
2856
2857   if (!list_empty(tmp)) {
2858     /* ...and dump list sorted */
2859     qsort (tmp->data, tmp->length, sizeof(void*), opt_cmp);
2860     for (i = 0; i < tmp->length; i++) {
2861       option = (struct option_t*) tmp->data[i];
2862       FuncTable[DTYPE (option->type)].opt_to_string
2863         (outbuf, sizeof(outbuf), option);
2864       printf ("%s\n", outbuf);
2865     }
2866   }
2867   list_del (&tmp, NULL);
2868   return 0;
2869 }