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