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