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