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