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