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