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