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