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