well this makes things fail for people ...
[apps/madmutt.git] / init.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  *
5  * Parts were written/modified by:
6  * Rocco Rutte <pdmef@cs.tu-berlin.de>
7  *
8  * This file is part of mutt-ng, see http://www.muttng.org/.
9  * It's licensed under the GNU General Public License,
10  * please see the file GPL in the top level source directory.
11  */
12
13 #include <lib-lib/lib-lib.h>
14
15 #include <lib-lua/lib-lua.h>
16 #include <lib-sys/unix.h>
17 #include <lib-ui/lib-ui.h>
18 #include <lib-ui/history.h>
19 #include <lib-mx/mx.h>
20
21 #include "mutt.h"
22 #include "keymap.h"
23 #include "crypt.h"
24 #include "charset.h"
25 #include "thread.h"
26 #include "mutt_idna.h"
27 #include "alias.h"
28 #include "init.h"
29
30 /*
31  * prototypes
32  */
33 static const struct mapping_t* get_sortmap (struct option_t* option);
34 static int parse_sort (struct option_t* dst, const char *s,
35                        const struct mapping_t *map,
36                        char* errbuf, ssize_t errlen);
37
38 static hash_t *ConfigOptions = NULL;
39
40 /* for synonym warning reports: synonym found during parsing */
41 typedef struct syn_t {
42   struct syn_t *next;
43   char* f;              /* file */
44   int l;                /* line */
45   struct option_t* n;   /* new */
46   struct option_t* o;   /* old */
47 } syn_t;
48
49 DO_INIT(syn_t, syn);
50 static void syn_wipe(syn_t *syn) {
51     p_delete(&syn->f);
52 }
53 DO_NEW(syn_t, syn);
54 DO_DELETE(syn_t, syn);
55 DO_SLIST(syn_t, syn, syn_delete);
56
57 /* for synonym warning reports: list of synonyms found */
58 static syn_t *Synonyms = NULL;
59 /* for synonym warning reports: current rc file */
60 static const char* CurRCFile = NULL;
61 /* for synonym warning reports: current rc line */
62 static int CurRCLine = 0;
63
64 /* prototypes for checking for special vars */
65 static int check_history    (const char* option, unsigned long val,
66                              char* errbuf, ssize_t errlen);
67 /* this checks that numbers are >= 0 */
68 static int check_num        (const char* option, unsigned long val,
69                              char* errbuf, ssize_t errlen);
70
71 /* use this to check only */
72 static int check_special (const char* option, unsigned long val,
73                           char* errbuf, ssize_t errlen);
74
75 /* variable <-> sanity check function mappings
76  * when changing these, make sure the proper _from_string handler
77  * does this checking!
78  */
79 static struct {
80   const char* name;
81   int (*check) (const char* option, unsigned long val,
82                 char* errbuf, ssize_t errlen);
83 } SpecialVars[] = {
84   { "history",                  check_history },
85   { "pager_index_lines",        check_num },
86   /* last */
87   { NULL,         NULL }
88 };
89
90 static void bool_to_string (char* dst, ssize_t dstlen,
91                             struct option_t* option) {
92   snprintf (dst, dstlen, "%s=%s", option->option,
93             option (option->data) ? "yes" : "no");
94 }
95
96 static int bool_from_string (struct option_t* dst, const char* val,
97                              char* errbuf __attribute__ ((unused)),
98                              ssize_t errlen __attribute__ ((unused))) {
99   int flag = -1;
100
101   if (!dst)
102     return 0;
103   if (ascii_strncasecmp (val, "yes", 3) == 0)
104     flag = 1;
105   else if (ascii_strncasecmp (val, "no", 2) == 0)
106     flag = 0;
107
108   if (flag < 0)
109     return 0;
110   if (flag)
111     set_option (dst->data);
112   else
113     unset_option (dst->data);
114   return 1;
115 }
116
117 static void num_to_string (char* dst, ssize_t dstlen,
118                            struct option_t* option) {
119   /* XXX puke */
120   const char* fmt = (m_strcmp(option->option, "umask") == 0) ?
121                     "%s=%04o" : "%s=%d";
122   snprintf (dst, dstlen, fmt, option->option,
123             *((short*) option->data));
124 }
125
126 static int num_from_string (struct option_t* dst, const char* val,
127                             char* errbuf, ssize_t errlen) {
128   int num = 0, old = 0;
129   char* t = NULL;
130
131   if (!dst)
132     return 0;
133
134   num = strtol (val, &t, 0);
135
136   if (m_strisempty(val) || *t || (short) num != num) {
137     if (errbuf) {
138       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
139                 val, dst->option);
140     }
141     return 0;
142   }
143
144   /* just temporarily accept new val so that check_special for
145    * $history already has it when doing history's init() */
146   old = *((short*) dst->data);
147   *((short*) dst->data) = (short) num;
148
149   if (!check_special (dst->option, (unsigned long) num, errbuf, errlen)) {
150     *((short*) dst->data) = old;
151     return 0;
152   }
153
154   return 1;
155 }
156
157 static void str_to_string (char* dst, ssize_t dstlen,
158                            struct option_t* option) {
159   snprintf (dst, dstlen, "%s=\"%s\"", option->option,
160             NONULL (*((char**) option->data)));
161 }
162
163 static int path_from_string (struct option_t* dst, const char* val,
164                              char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
165   char path[_POSIX_PATH_MAX];
166
167   if (!dst)
168     return 0;
169
170   if (m_strisempty(val)) {
171     p_delete((char**) dst->data);
172     return 1;
173   }
174
175   path[0] = '\0';
176   m_strcpy(path, sizeof(path), val);
177   mutt_expand_path (path, sizeof(path));
178   m_strreplace((char **) dst->data, path);
179   return 1;
180 }
181
182 static int str_from_string (struct option_t* dst, const char* val,
183                             char* errbuf, ssize_t errlen) {
184   if (!dst)
185     return 0;
186
187   if (!check_special (dst->option, (unsigned long) val, errbuf, errlen))
188     return 0;
189
190   m_strreplace((char**) dst->data, val);
191   return 1;
192 }
193
194 static void quad_to_string (char* dst, ssize_t dstlen,
195                             struct option_t* option) {
196   const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
197   snprintf (dst, dstlen, "%s=%s", option->option,
198             vals[quadoption (option->data)]);
199 }
200
201 static int quad_from_string (struct option_t* dst, const char* val,
202                              char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
203   int flag = -1;
204
205   if (!dst)
206     return 0;
207   if (ascii_strncasecmp (val, "yes", 3) == 0)
208     flag = M_YES;
209   else if (ascii_strncasecmp (val, "no", 2) == 0)
210     flag = M_NO;
211   else if (ascii_strncasecmp (val, "ask-yes", 7) == 0)
212     flag = M_ASKYES;
213   else if (ascii_strncasecmp (val, "ask-no", 6) == 0)
214     flag = M_ASKNO;
215
216   if (flag < 0)
217     return 0;
218
219   set_quadoption (dst->data, flag);
220   return 1;
221 }
222
223 static void sort_to_string (char* dst, ssize_t dstlen,
224                             struct option_t* option) {
225   const struct mapping_t *map = get_sortmap (option);
226   const char *p = NULL;
227
228   if (!map) {
229     snprintf (dst, sizeof(dst), "%s=unknown", option->option);
230     return;
231   }
232
233   p = mutt_getnamebyvalue(*((short *)option->data) & SORT_MASK, map);
234
235   snprintf (dst, dstlen, "%s=%s%s%s", option->option,
236             (*((short *) option->data) & SORT_REVERSE) ?
237             "reverse-" : "",
238             (*((short *) option->data) & SORT_LAST) ? "last-" :
239             "", NONULL (p));
240 }
241
242 static int sort_from_string (struct option_t* dst, const char* val,
243                              char* errbuf, ssize_t errlen) {
244   const struct mapping_t *map = NULL;
245   if (!(map = get_sortmap (dst))) {
246     if (errbuf)
247       snprintf (errbuf, errlen, _("%s: Unknown type."),
248                 dst->option);
249     return 0;
250   }
251   if (parse_sort (dst, val, map, errbuf, errlen) == -1)
252     return 0;
253   return 1;
254 }
255
256 static void rx_to_string (char* dst, ssize_t dstlen,
257                           struct option_t* option) {
258   rx_t* p = (rx_t*) option->data;
259   snprintf (dst, dstlen, "%s=\"%s\"", option->option,
260             NONULL (p->pattern));
261 }
262
263 static int rx_from_string (struct option_t* dst, const char* val,
264                            char* errbuf, ssize_t errlen) {
265   rx_t* p = NULL;
266   regex_t* rx = NULL;
267   int flags = 0, e = 0, neg = 0;
268   char* s = NULL;
269
270   if (!dst)
271     return 0;
272
273   if (option (OPTATTACHMSG) && !m_strcmp(dst->option, "reply_regexp")) {
274     if (errbuf)
275       snprintf (errbuf, errlen,
276                 "Operation not permitted when in attach-message mode.");
277     return 0;
278   }
279
280   if (!((rx_t*) dst->data))
281     *((rx_t**) dst->data) = p_new(rx_t, 1);
282
283   p = (rx_t*) dst->data;
284
285   /* something to do? */
286   if (m_strisempty(val) || (p->pattern && m_strcmp(p->pattern, val) == 0))
287     return 1;
288
289   if (m_strcmp(dst->option, "mask") != 0)
290     flags |= mutt_which_case (val);
291
292   s = (char*) val;
293   if (m_strcmp(dst->option, "mask") == 0 && *s == '!') {
294     neg = 1;
295     s++;
296   }
297
298   rx = p_new(regex_t, 1);
299
300   if ((e = REGCOMP (rx, s, flags)) != 0) {
301     regerror (e, rx, errbuf, errlen);
302     regfree (rx);
303     p_delete(&rx);
304     return 0;
305   }
306
307   if (p->rx) {
308     regfree (p->rx);
309     p_delete(&p->rx);
310   }
311
312   m_strreplace(&p->pattern, val);
313   p->rx = rx;
314   p->neg = neg;
315
316   if (m_strcmp(dst->option, "reply_regexp") == 0)
317     mutt_adjust_all_subjects ();
318
319   return 1;
320 }
321
322 static struct {
323   unsigned short type;
324   void (*opt_tostr) (char* dst, ssize_t dstlen, struct option_t* option);
325   int (*opt_fromstr) (struct option_t* dst, const char* val,
326                           char* errbuf, ssize_t errlen);
327 } FuncTable[] = {
328   { 0,          NULL,             NULL }, /* there's no DT_ type with 0 */
329   { DT_BOOL,    bool_to_string,   bool_from_string },
330   { DT_NUM,     num_to_string,    num_from_string },
331   { DT_STR,     str_to_string,    str_from_string },
332   { DT_PATH,    str_to_string,    path_from_string },
333   { DT_QUAD,    quad_to_string,   quad_from_string },
334   { DT_SORT,    sort_to_string,   sort_from_string },
335   { DT_RX,      rx_to_string,     rx_from_string },
336 };
337
338
339 int mutt_option_value (const char* val, char* dst, ssize_t dstlen) {
340   struct option_t* option = NULL;
341   char* tmp = NULL, *t = NULL;
342   ssize_t l = 0;
343
344   if (!(option = hash_find (ConfigOptions, val))) {
345     *dst = '\0';
346     return 0;
347   }
348   tmp = p_new(char, dstlen+1);
349   FuncTable[DTYPE(option->type)].opt_tostr (tmp, dstlen, option);
350
351   /* as we get things of type $var=value and don't want to bloat the
352    * above "just" for expansion, we do the stripping here */
353   t = strchr (tmp, '=');
354   t++;
355   l = m_strlen(t);
356   if (l >= 2) {
357     if (t[l-1] == '"' && *t == '"') {
358       t[l-1] = '\0';
359       t++;
360     }
361   }
362   memcpy (dst, t, l+1);
363   p_delete(&tmp);
364
365   return 1;
366 }
367
368 static void toggle_quadoption (int opt)
369 {
370   int n = opt / 4;
371   int b = (opt % 4) * 2;
372
373   QuadOptions[n] ^= (1 << b);
374 }
375
376 void set_quadoption (int opt, int flag)
377 {
378   int n = opt / 4;
379   int b = (opt % 4) * 2;
380
381   QuadOptions[n] &= ~(0x3 << b);
382   QuadOptions[n] |= (flag & 0x3) << b;
383 }
384
385 int quadoption (int opt)
386 {
387   int n = opt / 4;
388   int b = (opt % 4) * 2;
389
390   return (QuadOptions[n] >> b) & 0x3;
391 }
392
393 int query_quadoption2(int v, const char *prompt)
394 {
395   switch (v) {
396   case M_YES:
397   case M_NO:
398     return v;
399
400   default:
401     return mutt_yesorno(prompt, (v == M_ASKYES));
402   }
403 }
404
405 int query_quadoption (int opt, const char *prompt)
406 {
407   int v = quadoption (opt);
408
409   switch (v) {
410   case M_YES:
411   case M_NO:
412     return v;
413
414   default:
415     return mutt_yesorno(prompt, (v == M_ASKYES));
416   }
417 }
418
419 /* always wise to do what someone else did before */
420 static void _attachments_clean (void) {
421   int i;
422   if (Context && Context->msgcount) {
423     for (i = 0; i < Context->msgcount; i++)
424       Context->hdrs[i]->attach_valid = 0;
425   }
426 }
427
428 static int parse_attach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
429                               BUFFER *err __attribute__ ((unused))) {
430   ATTACH_MATCH *a;
431   string_list_t *listp, *lastp;
432   char *p;
433   char *tmpminor;
434   int len;
435
436   /* Find the last item in the list that data points to. */
437   lastp = NULL;
438   for (listp = *ldata; listp; listp = listp->next) {
439     a = (ATTACH_MATCH *)listp->data;
440     lastp = listp;
441   }
442
443   do {
444     mutt_extract_token (buf, s, 0);
445
446     if (!buf->data || *buf->data == '\0')
447       continue;
448
449     a = p_new(ATTACH_MATCH, 1);
450
451     /* some cheap hacks that I expect to remove */
452     if (!m_strcasecmp(buf->data, "any"))
453       a->major = m_strdup("*/.*");
454     else if (!m_strcasecmp(buf->data, "none"))
455       a->major = m_strdup("cheap_hack/this_should_never_match");
456     else
457       a->major = m_strdup(buf->data);
458
459     if ((p = strchr(a->major, '/'))) {
460       *p = '\0';
461       ++p;
462       a->minor = p;
463     } else {
464       a->minor = "unknown";
465     }
466
467     len = m_strlen(a->minor);
468     tmpminor = p_new(char, len + 3);
469     m_strcpy(&tmpminor[1], len + 3, a->minor);
470     tmpminor[0] = '^';
471     tmpminor[len+1] = '$';
472     tmpminor[len+2] = '\0';
473
474     a->major_int = mutt_check_mime_type(a->major);
475     regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
476
477     p_delete(&tmpminor);
478
479     listp = p_new(string_list_t, 1);
480     listp->data = (char *)a;
481     listp->next = NULL;
482     if (lastp) {
483       lastp->next = listp;
484     } else {
485       *ldata = listp;
486     }
487     lastp = listp;
488   }
489   while (MoreArgs (s));
490
491   _attachments_clean();
492   return 0;
493 }
494
495 static int parse_unattach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
496                                 BUFFER *err __attribute__ ((unused))) {
497   ATTACH_MATCH *a;
498   string_list_t *lp, *lastp, *newlp;
499   char *tmp;
500   int major;
501   char *minor;
502
503   do {
504     mutt_extract_token (buf, s, 0);
505
506     if (!m_strcasecmp(buf->data, "any"))
507       tmp = m_strdup("*/.*");
508     else if (!m_strcasecmp(buf->data, "none"))
509       tmp = m_strdup("cheap_hack/this_should_never_match");
510     else
511       tmp = m_strdup(buf->data);
512
513     if ((minor = strchr(tmp, '/'))) {
514       *minor = '\0';
515       ++minor;
516     } else {
517       minor = m_strdup("unknown");
518     }
519     major = mutt_check_mime_type(tmp);
520
521     /* We must do our own walk here because string_list_remove() will only
522      * remove the string_list_t->data, not anything pointed to by the string_list_t->data. */
523     lastp = NULL;
524     for(lp = *ldata; lp; ) {
525       a = (ATTACH_MATCH *)lp->data;
526       if (a->major_int == major && !m_strcasecmp(minor, a->minor)) {
527         regfree(&a->minor_rx);
528         p_delete(&a->major);
529
530         /* Relink backward */
531         if (lastp)
532           lastp->next = lp->next;
533         else
534           *ldata = lp->next;
535
536         newlp = lp->next;
537         p_delete(&lp->data); /* same as a */
538         p_delete(&lp);
539         lp = newlp;
540         continue;
541       }
542
543       lastp = lp;
544       lp = lp->next;
545     }
546   }
547   while (MoreArgs (s));
548
549   p_delete(&tmp);
550   _attachments_clean();
551   return 0;
552 }
553
554 static int print_attach_list (string_list_t *lp, char op, const char *name) {
555   while (lp) {
556     printf("attachments %c%s %s/%s\n", op, name,
557            ((ATTACH_MATCH *)lp->data)->major,
558            ((ATTACH_MATCH *)lp->data)->minor);
559     lp = lp->next;
560   }
561
562   return 0;
563 }
564
565 static int parse_attachments (BUFFER *buf, BUFFER *s,
566                               unsigned long data __attribute__ ((unused)),
567                               BUFFER *err) {
568   char op, *category;
569   string_list_t **listp;
570
571   mutt_extract_token(buf, s, 0);
572   if (!buf->data || *buf->data == '\0') {
573     m_strcpy(err->data, err->dsize, _("attachments: no disposition"));
574     return -1;
575   }
576
577   category = buf->data;
578   op = *category++;
579
580   if (op == '?') {
581     mutt_endwin (NULL);
582     fflush (stdout);
583     printf("\nCurrent attachments settings:\n\n");
584     print_attach_list(AttachAllow, '+', "A");
585     print_attach_list(AttachExclude, '-', "A");
586     print_attach_list(InlineAllow, '+', "I");
587     print_attach_list(InlineExclude, '-', "I");
588     set_option (OPTFORCEREDRAWINDEX);
589     set_option (OPTFORCEREDRAWPAGER);
590     mutt_any_key_to_continue (NULL);
591     return 0;
592   }
593
594   if (op != '+' && op != '-') {
595     op = '+';
596     category--;
597   }
598   if (!m_strncasecmp(category, "attachment", strlen(category))) {
599     if (op == '+')
600       listp = &AttachAllow;
601     else
602       listp = &AttachExclude;
603   }
604   else if (!m_strncasecmp(category, "inline", strlen(category))) {
605     if (op == '+')
606       listp = &InlineAllow;
607     else
608       listp = &InlineExclude;
609   } else {
610     m_strcpy(err->data, err->dsize, _("attachments: invalid disposition"));
611     return -1;
612   }
613
614   return parse_attach_list(buf, s, listp, err);
615 }
616
617 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data __attribute__ ((unused)), BUFFER *err) {
618   char op, *p;
619   string_list_t **listp;
620
621   mutt_extract_token(buf, s, 0);
622   if (!buf->data || *buf->data == '\0') {
623     m_strcpy(err->data, err->dsize, _("unattachments: no disposition"));
624     return -1;
625   }
626
627   p = buf->data;
628   op = *p++;
629   if (op != '+' && op != '-') {
630     op = '+';
631     p--;
632   }
633   if (!m_strncasecmp(p, "attachment", strlen(p))) {
634     if (op == '+')
635       listp = &AttachAllow;
636     else
637       listp = &AttachExclude;
638   }
639   else if (!m_strncasecmp(p, "inline", strlen(p))) {
640     if (op == '+')
641       listp = &InlineAllow;
642     else
643       listp = &InlineExclude;
644   }
645   else {
646     m_strcpy(err->data, err->dsize, _("unattachments: invalid disposition"));
647     return -1;
648   }
649
650   return parse_unattach_list(buf, s, listp, err);
651 }
652
653 static int parse_unalias (BUFFER * buf, BUFFER * s,
654                           unsigned long data __attribute__ ((unused)),
655                           BUFFER * err __attribute__ ((unused)))
656 {
657     alias_t *tmp, **last;
658
659     do {
660         mutt_extract_token (buf, s, 0);
661
662         if (!m_strcmp("*", buf->data) == 0) {
663             if (CurrentMenu == MENU_ALIAS) {
664                 for (tmp = Aliases; tmp; tmp = tmp->next)
665                     tmp->del = 1;
666                 set_option(OPTFORCEREDRAWINDEX);
667             } else {
668                 alias_list_wipe(&Aliases);
669             }
670             break;
671         }
672
673         last = &Aliases;
674         for (last = &Aliases; *last; last = &(*last)->next) {
675             if (!m_strcasecmp(buf->data, (*last)->name)) {
676                 if (CurrentMenu == MENU_ALIAS) {
677                     (*last)->del = 1;
678                     set_option (OPTFORCEREDRAWINDEX);
679                 } else {
680                     tmp = alias_list_pop(last);
681                     alias_delete(&tmp);
682                 }
683                 break;
684             }
685         }
686     } while (MoreArgs(s));
687
688     return 0;
689 }
690
691 static int parse_alias (BUFFER * buf, BUFFER * s,
692                         unsigned long data __attribute__ ((unused)),
693                         BUFFER * err)
694 {
695     alias_t **last;
696     char *estr = NULL;
697
698     if (!MoreArgs (s)) {
699         m_strcpy(err->data, err->dsize, _("alias: no address"));
700         return -1;
701     }
702
703     mutt_extract_token (buf, s, 0);
704
705     /* check to see if an alias with this name already exists */
706     for (last = &Aliases; *last; last = &(*last)->next) {
707         if (!m_strcasecmp((*last)->name, buf->data))
708             break;
709     }
710
711     if (!*last) {
712         /* create a new alias */
713         *last = alias_new();
714         (*last)->name = m_strdup(buf->data);
715         /* give the main addressbook code a chance */
716         if (CurrentMenu == MENU_ALIAS)
717             set_option (OPTMENUCALLER);
718     } else {
719         /* override the previous value */
720         address_list_wipe(&(*last)->addr);
721         if (CurrentMenu == MENU_ALIAS)
722             set_option (OPTFORCEREDRAWINDEX);
723     }
724
725     mutt_extract_token(buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE);
726     (*last)->addr = mutt_parse_adrlist((*last)->addr, buf->data);
727     if (mutt_addrlist_to_idna((*last)->addr, &estr)) {
728         snprintf (err->data, err->dsize,
729                   _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, (*last)->name);
730         p_delete(&estr);
731         return -1;
732     }
733
734     return 0;
735 }
736
737 static int
738 parse_unmy_hdr(BUFFER * buf, BUFFER * s,
739                unsigned long data __attribute__ ((unused)),
740                BUFFER * err __attribute__ ((unused)))
741 {
742     do {
743         mutt_extract_token (buf, s, 0);
744
745         if (!m_strcmp("*", buf->data)) {
746             string_list_wipe(&UserHeader);
747         } else {
748             string_list_t **last = &UserHeader;
749             ssize_t l = m_strlen(buf->data);
750
751             if (buf->data[l - 1] == ':')
752                 l--;
753
754             while (*last) {
755                 if (!ascii_strncasecmp(buf->data, (*last)->data, l)
756                 && (*last)->data[l] == ':')
757                 {
758                     string_list_t *tmp = string_list_pop(last);
759                     string_item_delete(&tmp);
760                 } else {
761                     last = &(*last)->next;
762                 }
763             }
764         }
765     } while (MoreArgs(s));
766
767     return 0;
768 }
769
770 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
771                          BUFFER * err)
772 {
773   string_list_t *tmp;
774   ssize_t keylen;
775   char *p;
776
777   mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
778   if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
779     m_strcpy(err->data, err->dsize, _("invalid header field"));
780     return -1;
781   }
782   keylen = p - buf->data + 1;
783
784   if (UserHeader) {
785     for (tmp = UserHeader;; tmp = tmp->next) {
786       /* see if there is already a field by this name */
787       if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
788         /* replace the old value */
789         p_delete(&tmp->data);
790         tmp->data = buf->data;
791         p_clear(buf, 1);
792         return 0;
793       }
794       if (!tmp->next)
795         break;
796     }
797     tmp->next = string_item_new();
798     tmp = tmp->next;
799   }
800   else {
801     tmp = string_item_new();
802     UserHeader = tmp;
803   }
804   tmp->data = buf->data;
805   p_clear(buf, 1);
806   return 0;
807 }
808
809 static int
810 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
811             char* errbuf, ssize_t errlen) {
812   int i, flags = 0;
813
814   if (m_strncmp("reverse-", s, 8) == 0) {
815     s += 8;
816     flags = SORT_REVERSE;
817   }
818
819   if (m_strncmp("last-", s, 5) == 0) {
820     s += 5;
821     flags |= SORT_LAST;
822   }
823
824   if ((i = mutt_getvaluebyname (s, map)) == -1) {
825     if (errbuf)
826       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
827     return -1;
828   }
829
830   *((short*) dst->data) = i | flags;
831   return 0;
832 }
833
834 /* if additional data more == 1, we want to resolve synonyms */
835 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
836 {
837     char buf[LONG_STRING];
838     struct option_t *ptr = p;
839
840     if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_fromstr)
841         return;
842
843     mutt_option_value(ptr->option, buf, sizeof(buf));
844     if (m_strlen(ptr->init) == 0 && *buf)
845         ptr->init = m_strdup(buf);
846 }
847
848 static int init_expand (char** dst, struct option_t* src) {
849   BUFFER token, in;
850   ssize_t len = 0;
851
852   p_delete(dst);
853
854   if (DTYPE(src->type) == DT_STR || DTYPE(src->type) == DT_PATH) {
855     /* only expand for string as it's the only place where
856      * we want to expand vars right now */
857     if (src->init && *src->init) {
858       p_clear(&token, 1);
859       p_clear(&in, 1);
860       len = m_strlen(src->init) + 2;
861       in.data = p_new(char, len + 1);
862       snprintf (in.data, len, "\"%s\"", src->init);
863       in.dptr = in.data;
864       in.dsize = len;
865       mutt_extract_token (&token, &in, 0);
866       if (token.data && *token.data)
867         *dst = m_strdup(token.data);
868       else
869         *dst = m_strdup("");
870       p_delete(&in.data);
871       p_delete(&token.data);
872     } else
873       *dst = m_strdup("");
874   } else
875     /* for non-string: take value as is */
876     *dst = m_strdup(src->init);
877   return 1;
878 }
879
880 /* if additional data more == 1, we want to resolve synonyms */
881 static void mutt_restore_default (const char* name __attribute__ ((unused)),
882                                   void* p, unsigned long more) {
883   char errbuf[STRING];
884   struct option_t* ptr = (struct option_t*) p;
885   char* init = NULL;
886
887   if (!ptr)
888     return;
889   if (FuncTable[DTYPE (ptr->type)].opt_fromstr) {
890     init_expand (&init, ptr);
891     if (!FuncTable[DTYPE (ptr->type)].opt_fromstr (ptr, init, errbuf,
892                                                        sizeof(errbuf))) {
893       if (!option (OPTNOCURSES))
894         mutt_endwin (NULL);
895       fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
896                          "Please report this error: \"%s\"\n"),
897                ptr->option, NONULL (init), errbuf);
898       exit (1);
899     }
900     p_delete(&init);
901   }
902
903   set_option (OPTFORCEREDRAWINDEX);
904   set_option (OPTFORCEREDRAWPAGER);
905   set_option (OPTSORTSUBTHREADS);
906   set_option (OPTNEEDRESORT);
907   set_option (OPTRESORTINIT);
908   set_option (OPTREDRAWTREE);
909 }
910
911 static int check_num (const char* option, unsigned long p,
912                       char* errbuf, ssize_t errlen) {
913   if ((int) p < 0) {
914     if (errbuf)
915       snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
916     return 0;
917   }
918   return 1;
919 }
920
921 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
922                           char* errbuf, ssize_t errlen) {
923   if (!check_num ("history", p, errbuf, errlen))
924     return 0;
925   mutt_init_history ();
926   return 1;
927 }
928
929 static int check_special (const char* name, unsigned long val,
930                           char* errbuf, ssize_t errlen) {
931   int i = 0;
932
933   for (i = 0; SpecialVars[i].name; i++) {
934     if (m_strcmp(SpecialVars[i].name, name) == 0) {
935       return (SpecialVars[i].check (SpecialVars[i].name,
936                                     val, errbuf, errlen));
937     }
938   }
939   return 1;
940 }
941
942 static const struct mapping_t* get_sortmap (struct option_t* option) {
943   const struct mapping_t* map = NULL;
944
945   switch (option->type & DT_SUBTYPE_MASK) {
946   case DT_SORT_ALIAS:
947     map = SortAliasMethods;
948     break;
949   case DT_SORT_BROWSER:
950     map = SortBrowserMethods;
951     break;
952   case DT_SORT_KEYS:
953     map = SortKeyMethods;
954     break;
955   case DT_SORT_AUX:
956     map = SortAuxMethods;
957     break;
958   default:
959     map = SortMethods;
960     break;
961   }
962   return map;
963 }
964
965 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
966                       BUFFER * err)
967 {
968   int query, unset, inv, reset, r = 0;
969   struct option_t* option = NULL;
970
971   while (MoreArgs (s)) {
972     /* reset state variables */
973     query = 0;
974     unset = data & M_SET_UNSET;
975     inv = data & M_SET_INV;
976     reset = data & M_SET_RESET;
977
978     if (*s->dptr == '?') {
979       query = 1;
980       s->dptr++;
981     }
982     else if (m_strncmp("no", s->dptr, 2) == 0) {
983       s->dptr += 2;
984       unset = !unset;
985     }
986     else if (m_strncmp("inv", s->dptr, 3) == 0) {
987       s->dptr += 3;
988       inv = !inv;
989     }
990     else if (*s->dptr == '&') {
991       reset = 1;
992       s->dptr++;
993     }
994
995     /* get the variable name */
996     mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
997     option = hash_find(ConfigOptions, tmp->data);
998     if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
999       snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1000       return -1;
1001     }
1002     s->dptr = vskipspaces(s->dptr);
1003
1004     if (reset) {
1005       if (query || unset || inv) {
1006         snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1007         return -1;
1008       }
1009
1010       if (s && *s->dptr == '=') {
1011         snprintf (err->data, err->dsize, _("value is illegal with reset"));
1012         return -1;
1013       }
1014
1015       if (!m_strcmp("all", tmp->data)) {
1016         if (CurrentMenu == MENU_PAGER) {
1017           snprintf (err->data, err->dsize, _("Not available in this menu."));
1018           return -1;
1019         }
1020         hash_map (ConfigOptions, mutt_restore_default, 1);
1021         set_option (OPTFORCEREDRAWINDEX);
1022         set_option (OPTFORCEREDRAWPAGER);
1023         set_option (OPTSORTSUBTHREADS);
1024         set_option (OPTNEEDRESORT);
1025         set_option (OPTRESORTINIT);
1026         set_option (OPTREDRAWTREE);
1027         return 0;
1028       } else {
1029         mutt_restore_default (NULL, option, 1);
1030       }
1031     }
1032     else if (DTYPE (option->type) == DT_BOOL) {
1033       /* XXX this currently ignores the function table
1034        * as we don't get invert and stuff into it */
1035       if (s && *s->dptr == '=') {
1036         if (unset || inv || query) {
1037           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1038           return -1;
1039         }
1040
1041         s->dptr++;
1042         mutt_extract_token (tmp, s, 0);
1043         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1044           unset = inv = 0;
1045         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1046           unset = 1;
1047         else {
1048           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1049           return -1;
1050         }
1051       }
1052
1053       if (query) {
1054         bool_to_string (err->data, err->dsize, option);
1055         return 0;
1056       }
1057
1058       if (unset)
1059         unset_option (option->data);
1060       else if (inv)
1061         toggle_option (option->data);
1062       else
1063         set_option (option->data);
1064     }
1065     else if (DTYPE (option->type) == DT_STR ||
1066              DTYPE (option->type) == DT_PATH ||
1067              DTYPE (option->type) == DT_NUM ||
1068              DTYPE (option->type) == DT_SORT ||
1069              DTYPE (option->type) == DT_RX)
1070     {
1071       /* XXX maybe we need to get unset into handlers? */
1072       if (DTYPE (option->type) == DT_STR || DTYPE (option->type) == DT_PATH) {
1073         if (unset) {
1074           p_delete((void **)(void *)&option->data);
1075           break;
1076         }
1077       }
1078
1079       if (query || *s->dptr != '=') {
1080         FuncTable[DTYPE (option->type)].opt_tostr
1081           (err->data, err->dsize, option);
1082         break;
1083       }
1084
1085       s->dptr++;
1086       mutt_extract_token (tmp, s, 0);
1087       if (!FuncTable[DTYPE (option->type)].opt_fromstr
1088           (option, tmp->data, err->data, err->dsize))
1089         r = -1;
1090     }
1091     else if (DTYPE (option->type) == DT_QUAD) {
1092
1093       if (query) {
1094         quad_to_string (err->data, err->dsize, option);
1095         break;
1096       }
1097
1098       if (*s->dptr == '=') {
1099         s->dptr++;
1100         mutt_extract_token (tmp, s, 0);
1101         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1102           set_quadoption (option->data, M_YES);
1103         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1104           set_quadoption (option->data, M_NO);
1105         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1106           set_quadoption (option->data, M_ASKYES);
1107         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1108           set_quadoption (option->data, M_ASKNO);
1109         else {
1110           snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1111                     tmp->data, option->option);
1112           r = -1;
1113           break;
1114         }
1115       }
1116       else {
1117         if (inv)
1118           toggle_quadoption (option->data);
1119         else if (unset)
1120           set_quadoption (option->data, M_NO);
1121         else
1122           set_quadoption (option->data, M_YES);
1123       }
1124     }
1125     else {
1126       snprintf (err->data, err->dsize, _("%s: unknown type"),
1127                 option->option);
1128       r = -1;
1129       break;
1130     }
1131
1132     set_option (OPTFORCEREDRAWINDEX);
1133     set_option (OPTFORCEREDRAWPAGER);
1134     set_option (OPTSORTSUBTHREADS);
1135     set_option (OPTNEEDRESORT);
1136     set_option (OPTRESORTINIT);
1137     set_option (OPTREDRAWTREE);
1138   }
1139   return r;
1140 }
1141
1142 #define MAXERRS 128
1143
1144 /* reads the specified initialization file.  returns -1 if errors were found
1145    so that we can pause to let the user know...  */
1146 static int source_rc (const char *rcfile, BUFFER * err)
1147 {
1148   FILE *f;
1149   int line = 0, rc = 0, conv = 0;
1150   BUFFER token;
1151   char *linebuf = NULL;
1152   char *currentline = NULL;
1153   ssize_t buflen;
1154   pid_t pid;
1155
1156   if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1157     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1158     return -1;
1159   }
1160
1161   p_clear(&token, 1);
1162   while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
1163     conv = ConfigCharset && (*ConfigCharset) && mod_cset.charset;
1164     if (conv) {
1165       currentline = m_strdup(linebuf);
1166       if (!currentline)
1167         continue;
1168       mutt_convert_string (&currentline, ConfigCharset, mod_cset.charset, 0);
1169     }
1170     else
1171       currentline = linebuf;
1172
1173     CurRCLine = line;
1174     CurRCFile = rcfile;
1175
1176     if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1177       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1178       if (--rc < -MAXERRS) {
1179         if (conv)
1180           p_delete(&currentline);
1181         break;
1182       }
1183     }
1184     else {
1185       if (rc < 0)
1186         rc = -1;
1187     }
1188     if (conv)
1189       p_delete(&currentline);
1190   }
1191   p_delete(&token.data);
1192   p_delete(&linebuf);
1193   m_fclose(&f);
1194   if (pid != -1)
1195     mutt_wait_filter (pid);
1196   if (rc) {
1197     /* the muttrc source keyword */
1198     snprintf (err->data, err->dsize,
1199               rc >= -MAXERRS ? _("source: errors in %s")
1200               : _("source: reading aborted due too many errors in %s"),
1201               rcfile);
1202     rc = -1;
1203   }
1204   return rc;
1205 }
1206
1207 #undef MAXERRS
1208
1209 static int parse_source (BUFFER * tmp, BUFFER * s,
1210                          unsigned long data __attribute__ ((unused)),
1211                          BUFFER * err)
1212 {
1213   char path[_POSIX_PATH_MAX];
1214   int rc = 0;
1215
1216   do {
1217     if (mutt_extract_token (tmp, s, 0) != 0) {
1218       snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1219       return -1;
1220     }
1221
1222     m_strcpy(path, sizeof(path), tmp->data);
1223     mutt_expand_path (path, sizeof(path));
1224
1225     rc += source_rc (path, err);
1226   }
1227   while (MoreArgs (s));
1228
1229   return (rc < 0) ? -1 : 0;
1230 }
1231
1232 /* line         command to execute
1233
1234    token        scratch buffer to be used by parser.  caller should free
1235                 token->data when finished.  the reason for this variable is
1236                 to avoid having to allocate and deallocate a lot of memory
1237                 if we are parsing many lines.  the caller can pass in the
1238                 memory to use, which avoids having to create new space for
1239                 every call to this function.
1240
1241    err          where to write error messages */
1242 int mutt_parse_rc_line (const char *line, BUFFER * token, BUFFER * err)
1243 {
1244   int i, r = -1;
1245   BUFFER expn;
1246
1247   p_clear(&expn, 1);
1248   expn.data = expn.dptr = line;
1249   expn.dsize = m_strlen(line);
1250
1251   *err->data = 0;
1252
1253   expn.dptr = vskipspaces(expn.dptr);
1254   while (*expn.dptr) {
1255     if (*expn.dptr == '#')
1256       break;                    /* rest of line is a comment */
1257     if (*expn.dptr == ';') {
1258       expn.dptr++;
1259       continue;
1260     }
1261     mutt_extract_token (token, &expn, 0);
1262     for (i = 0; Commands[i].name; i++) {
1263       if (!m_strcmp(token->data, Commands[i].name)) {
1264         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1265           goto finish;
1266         break;
1267       }
1268     }
1269     if (!Commands[i].name) {
1270       snprintf (err->data, err->dsize, _("%s: unknown command"),
1271                 NONULL (token->data));
1272       goto finish;
1273     }
1274   }
1275   r = 0;
1276 finish:
1277   if (expn.destroy)
1278     p_delete(&expn.data);
1279   return r;
1280 }
1281
1282
1283 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
1284 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
1285 /* initial string that starts completion. No telling how much crap
1286  * the user has typed so far. Allocate LONG_STRING just to be sure! */
1287 char User_typed[LONG_STRING] = { 0 };
1288
1289 int Num_matched = 0;            /* Number of matches for completion */
1290 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
1291 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1];  /* all the matches + User_typed */
1292
1293 /* helper function for completion.  Changes the dest buffer if
1294    necessary/possible to aid completion.
1295         dest == completion result gets here.
1296         src == candidate for completion.
1297         try == user entered data for completion.
1298         len == length of dest buffer.
1299 */
1300 static void candidate (char *dest, char *try, const char *src, int len)
1301 {
1302   int l;
1303
1304   if (strstr (src, try) == src) {
1305     Matches[Num_matched++] = src;
1306     if (dest[0] == 0)
1307       m_strcpy(dest, len, src);
1308     else {
1309       for (l = 0; src[l] && src[l] == dest[l]; l++);
1310       dest[l] = 0;
1311     }
1312   }
1313 }
1314
1315 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
1316 {
1317   char *pt = buffer;
1318   int num;
1319   int spaces;                   /* keep track of the number of leading spaces on the line */
1320
1321   buffer = vskipspaces(buffer);
1322   spaces = buffer - pt;
1323
1324   pt = buffer + pos - spaces;
1325   while ((pt > buffer) && !isspace ((unsigned char) *pt))
1326     pt--;
1327
1328   if (pt == buffer) {           /* complete cmd */
1329     /* first TAB. Collect all the matches */
1330     if (numtabs == 1) {
1331       Num_matched = 0;
1332       m_strcpy(User_typed, sizeof(User_typed), pt);
1333       p_clear(Matches, countof(Matches));
1334       p_clear(Completed, countof(Completed));
1335       for (num = 0; Commands[num].name; num++)
1336         candidate (Completed, User_typed, Commands[num].name,
1337                    sizeof(Completed));
1338       Matches[Num_matched++] = User_typed;
1339
1340       /* All matches are stored. Longest non-ambiguous string is ""
1341        * i.e. dont change 'buffer'. Fake successful return this time */
1342       if (User_typed[0] == 0)
1343         return 1;
1344     }
1345
1346     if (Completed[0] == 0 && User_typed[0])
1347       return 0;
1348
1349     /* Num_matched will _always_ be atleast 1 since the initial
1350      * user-typed string is always stored */
1351     if (numtabs == 1 && Num_matched == 2)
1352       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1353     else if (numtabs > 1 && Num_matched > 2)
1354       /* cycle thru all the matches */
1355       snprintf (Completed, sizeof(Completed), "%s",
1356                 Matches[(numtabs - 2) % Num_matched]);
1357
1358     /* return the completed command */
1359     m_strcpy(buffer, len - spaces, Completed);
1360   }
1361   else if (!m_strncmp(buffer, "set", 3)
1362            || !m_strncmp(buffer, "unset", 5)
1363            || !m_strncmp(buffer, "reset", 5)
1364            || !m_strncmp(buffer, "toggle", 6)) {    /* complete variables */
1365     const char *prefixes[] = { "no", "inv", "?", "&", NULL };
1366
1367     pt++;
1368     /* loop through all the possible prefixes (no, inv, ...) */
1369     if (!m_strncmp(buffer, "set", 3)) {
1370       for (num = 0; prefixes[num]; num++) {
1371         if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
1372           pt += m_strlen(prefixes[num]);
1373           break;
1374         }
1375       }
1376     }
1377
1378     /* first TAB. Collect all the matches */
1379     if (numtabs == 1) {
1380       Num_matched = 0;
1381       m_strcpy(User_typed, sizeof(User_typed), pt);
1382       p_clear(Matches, countof(Matches));
1383       p_clear(Completed, countof(Completed));
1384       for (num = 0; MuttVars[num].option; num++)
1385         candidate(Completed, User_typed, MuttVars[num].option,
1386                   sizeof(Completed));
1387       Matches[Num_matched++] = User_typed;
1388
1389       /* All matches are stored. Longest non-ambiguous string is ""
1390        * i.e. dont change 'buffer'. Fake successful return this time */
1391       if (User_typed[0] == 0)
1392         return 1;
1393     }
1394
1395     if (Completed[0] == 0 && User_typed[0])
1396       return 0;
1397
1398     /* Num_matched will _always_ be atleast 1 since the initial
1399      * user-typed string is always stored */
1400     if (numtabs == 1 && Num_matched == 2)
1401       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1402     else if (numtabs > 1 && Num_matched > 2)
1403       /* cycle thru all the matches */
1404       snprintf (Completed, sizeof(Completed), "%s",
1405                 Matches[(numtabs - 2) % Num_matched]);
1406
1407     m_strcpy(pt, buffer + len - pt - spaces, Completed);
1408   }
1409   else if (!m_strncmp(buffer, "exec", 4)) {
1410     struct binding_t *menu = km_get_table (CurrentMenu);
1411
1412     if (!menu && CurrentMenu != MENU_PAGER)
1413       menu = OpGeneric;
1414
1415     pt++;
1416     /* first TAB. Collect all the matches */
1417     if (numtabs == 1) {
1418       Num_matched = 0;
1419       m_strcpy(User_typed, sizeof(User_typed), pt);
1420       p_clear(Matches, countof(Matches));
1421       p_clear(Completed, countof(Completed));
1422       for (num = 0; menu[num].name; num++)
1423         candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
1424       /* try the generic menu */
1425       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
1426         menu = OpGeneric;
1427         for (num = 0; menu[num].name; num++)
1428           candidate (Completed, User_typed, menu[num].name,
1429                      sizeof(Completed));
1430       }
1431       Matches[Num_matched++] = User_typed;
1432
1433       /* All matches are stored. Longest non-ambiguous string is ""
1434        * i.e. dont change 'buffer'. Fake successful return this time */
1435       if (User_typed[0] == 0)
1436         return 1;
1437     }
1438
1439     if (Completed[0] == 0 && User_typed[0])
1440       return 0;
1441
1442     /* Num_matched will _always_ be atleast 1 since the initial
1443      * user-typed string is always stored */
1444     if (numtabs == 1 && Num_matched == 2)
1445       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1446     else if (numtabs > 1 && Num_matched > 2)
1447       /* cycle thru all the matches */
1448       snprintf (Completed, sizeof(Completed), "%s",
1449                 Matches[(numtabs - 2) % Num_matched]);
1450
1451     m_strcpy(pt, buffer + len - pt - spaces, Completed);
1452   }
1453   else
1454     return 0;
1455
1456   return 1;
1457 }
1458
1459 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
1460 {
1461   char var[STRING], *pt = buffer;
1462   int spaces;
1463   struct option_t* option = NULL;
1464
1465   if (buffer[0] == 0)
1466     return 0;
1467
1468   buffer = vskipspaces(buffer);
1469   spaces = buffer - pt;
1470
1471   pt = buffer + pos - spaces;
1472   while ((pt > buffer) && !isspace ((unsigned char) *pt))
1473     pt--;
1474   pt++;                         /* move past the space */
1475   if (*pt == '=')               /* abort if no var before the '=' */
1476     return 0;
1477
1478   if (m_strncmp(buffer, "set", 3) == 0) {
1479     m_strcpy(var, sizeof(var), pt);
1480     /* ignore the trailing '=' when comparing */
1481     var[m_strlen(var) - 1] = 0;
1482     if (!(option = hash_find (ConfigOptions, var)))
1483       return 0;                 /* no such variable. */
1484     else {
1485       char tmp[LONG_STRING], tmp2[LONG_STRING];
1486       char *s, *d;
1487       ssize_t dlen = buffer + len - pt - spaces;
1488       const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1489
1490       tmp[0] = '\0';
1491
1492       if ((DTYPE (option->type) == DT_STR) ||
1493           (DTYPE (option->type) == DT_PATH) ||
1494           (DTYPE (option->type) == DT_RX)) {
1495         m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
1496         if (DTYPE (option->type) == DT_PATH)
1497           mutt_pretty_mailbox (tmp);
1498       }
1499       else if (DTYPE (option->type) == DT_QUAD)
1500         m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
1501       else if (DTYPE (option->type) == DT_NUM)
1502         snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
1503       else if (DTYPE (option->type) == DT_SORT) {
1504         const struct mapping_t *map;
1505         const char *p;
1506
1507         switch (option->type & DT_SUBTYPE_MASK) {
1508         case DT_SORT_ALIAS:
1509           map = SortAliasMethods;
1510           break;
1511         case DT_SORT_BROWSER:
1512           map = SortBrowserMethods;
1513           break;
1514         case DT_SORT_KEYS:
1515           map = SortKeyMethods;
1516           break;
1517         default:
1518           map = SortMethods;
1519           break;
1520         }
1521         p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
1522         snprintf(tmp, sizeof(tmp), "%s%s%s",
1523                  (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
1524                  (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
1525       }
1526       else if (DTYPE (option->type) == DT_BOOL)
1527         m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
1528       else
1529         return 0;
1530
1531       for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
1532         if (*s == '\\' || *s == '"')
1533           *d++ = '\\';
1534         *d++ = *s++;
1535       }
1536       *d = '\0';
1537
1538       m_strcpy(tmp, sizeof(tmp), pt);
1539       snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
1540
1541       return 1;
1542     }
1543   }
1544   return 0;
1545 }
1546
1547 static int mutt_execute_commands (string_list_t * p)
1548 {
1549   BUFFER err, token;
1550   char errstr[STRING];
1551
1552   p_clear(&err, 1);
1553   err.data = errstr;
1554   err.dsize = sizeof(errstr);
1555   p_clear(&token, 1);
1556   for (; p; p = p->next) {
1557     if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
1558       fprintf (stderr, _("Error in command line: %s\n"), err.data);
1559       p_delete(&token.data);
1560       return -1;
1561     }
1562   }
1563   p_delete(&token.data);
1564   return 0;
1565 }
1566
1567 void mutt_init (int skip_sys_rc, string_list_t * commands)
1568 {
1569   struct passwd *pw;
1570   const char *p;
1571   char buffer[STRING], error[STRING];
1572   int default_rc = 0, need_pause = 0;
1573   int i;
1574   BUFFER err;
1575
1576   p_clear(&err, 1);
1577   err.data = error;
1578   err.dsize = sizeof(error);
1579
1580   ConfigOptions = hash_new (sizeof(MuttVars) * 2, 0);
1581   for (i = 0; MuttVars[i].option; i++) {
1582     hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i]);
1583   }
1584
1585   /*
1586    * XXX - use something even more difficult to predict?
1587    */
1588   snprintf (AttachmentMarker, sizeof(AttachmentMarker),
1589             "\033]9;%ld\a", (long) time (NULL));
1590
1591   luaM_initialize();
1592   /* Get some information about the user */
1593   if ((pw = getpwuid (getuid ()))) {
1594     char rnbuf[STRING];
1595     mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, mod_core.gecos_mask);
1596     Realname = m_strdup(rnbuf);
1597   }
1598
1599   if ((p = getenv("MAIL") ?: getenv("MAILDIR"))) {
1600     Spoolfile = m_strdup(p);
1601   } else {
1602     mutt_concat_path(buffer, sizeof(buffer), NONULL(mod_core.homedir), MAILPATH);
1603     Spoolfile = m_strdup(buffer);
1604   }
1605
1606   if ((p = getenv ("REPLYTO")) != NULL) {
1607     BUFFER buf, token;
1608
1609     snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
1610
1611     p_clear(&buf, 1);
1612     buf.data = buf.dptr = buffer;
1613     buf.dsize = m_strlen(buffer);
1614
1615     p_clear(&token, 1);
1616     parse_my_hdr (&token, &buf, 0, &err);
1617     p_delete(&token.data);
1618   }
1619
1620   /* Set standard defaults */
1621   hash_map (ConfigOptions, mutt_set_default, 0);
1622   hash_map (ConfigOptions, mutt_restore_default, 0);
1623
1624   CurrentMenu = MENU_MAIN;
1625   mutt_init_history ();
1626
1627   if (!Muttrc) {
1628       snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL(mod_core.homedir));
1629     if (access (buffer, F_OK) == -1)
1630       snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
1631                 NONULL(mod_core.homedir));
1632
1633     default_rc = 1;
1634     Muttrc = m_strdup(buffer);
1635   }
1636   else {
1637     m_strcpy(buffer, sizeof(buffer), Muttrc);
1638     p_delete(&Muttrc);
1639     mutt_expand_path (buffer, sizeof(buffer));
1640     Muttrc = m_strdup(buffer);
1641   }
1642
1643   /* Process the global rc file if it exists and the user hasn't explicity
1644      requested not to via "-n".  */
1645   if (!skip_sys_rc) {
1646     snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
1647               MUTT_VERSION);
1648     if (access (buffer, F_OK) == -1)
1649       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
1650     if (access (buffer, F_OK) == -1)
1651       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
1652                 MUTT_VERSION);
1653     if (access (buffer, F_OK) == -1)
1654       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
1655     if (access (buffer, F_OK) != -1) {
1656       if (source_rc (buffer, &err) != 0) {
1657         fputs (err.data, stderr);
1658         fputc ('\n', stderr);
1659         need_pause = 1;
1660       }
1661     }
1662   }
1663
1664   /* Read the user's initialization file.  */
1665   if (access (Muttrc, F_OK) != -1) {
1666     if (!option (OPTNOCURSES))
1667       mutt_endwin (NULL);
1668     if (source_rc (Muttrc, &err) != 0) {
1669       fputs (err.data, stderr);
1670       fputc ('\n', stderr);
1671       need_pause = 1;
1672     }
1673   }
1674   else if (!default_rc) {
1675     /* file specified by -F does not exist */
1676     snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
1677     mutt_endwin (buffer);
1678     exit (1);
1679   }
1680
1681   /* LUA {{{ */
1682   snprintf(buffer, sizeof(buffer), "%s/.madmutt.lua", NONULL(mod_core.homedir));
1683   if (access(buffer, F_OK) < 0)
1684       snprintf(buffer, sizeof(buffer), "%s/.madmutt/cfg.lua", NONULL(mod_core.homedir));
1685   if (!access(buffer, F_OK)) {
1686       need_pause = luaM_wrap(mutt_error, luaM_dofile(buffer));
1687   }
1688   /* }}} */
1689
1690   if (mutt_execute_commands (commands) != 0)
1691     need_pause = 1;
1692
1693   /* warn about synonym variables */
1694   if (Synonyms) {
1695     syn_t *syn;
1696
1697     fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
1698
1699     for (syn = Synonyms; syn; syn = syn->next) {
1700       fprintf(stderr, "$%s ($%s should be used) (%s:%d)\n",
1701               syn->o ? NONULL(syn->o->option) : "",
1702               syn->n ? NONULL(syn->n->option) : "",
1703               NONULL(syn->f), syn->l);
1704     }
1705     fprintf (stderr, _("Warning: synonym variables are scheduled"
1706                        " for removal.\n"));
1707     syn_list_wipe(&Synonyms);
1708     need_pause = 1;
1709   }
1710
1711   if (need_pause && !option (OPTNOCURSES)) {
1712     if (mutt_any_key_to_continue (NULL) == -1)
1713       mutt_exit (1);
1714   }
1715 }
1716