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