Rocco Rutte:
[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  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #if HAVE_CONFIG_H
11 # include "config.h"
12 #endif
13
14 #include "mutt.h"
15 #include "buffer.h"
16 #include "ascii.h"
17 #include "mapping.h"
18 #include "mutt_curses.h"
19 #include "history.h"
20 #include "keymap.h"
21 #include "mbyte.h"
22 #include "charset.h"
23 #include "mutt_crypt.h"
24 #include "mutt_idna.h"
25
26 #if defined(USE_SSL) || defined(USE_GNUTLS)
27 #include "mutt_ssl.h"
28 #endif
29
30 #include "mx.h"
31 #include "init.h"
32
33 #include "lib/mem.h"
34 #include "lib/intl.h"
35 #include "lib/str.h"
36 #include "lib/rx.h"
37 #include "lib/list.h"
38 #include "lib/debug.h"
39
40 #include <ctype.h>
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <string.h>
44 #include <sys/utsname.h>
45 #include <errno.h>
46 #include <sys/wait.h>
47
48 /* for synonym warning reports: synonym found during parsing */
49 typedef struct {
50   char* f;      /* file */
51   int l;        /* line */
52   int n;        /* new name (index) */
53   int o;        /* old name (index) */
54 } syn_t;
55
56 /* for synonym warning reports: list of synonyms found */
57 list2_t* Synonyms;
58 /* for synonym warning reports: current rc file */
59 char* CurRCFile = NULL;
60 /* for synonym warning reports: current rc line */
61 int CurRCLine = 0;
62
63 /* for synonym warning reports: adds synonym to end of list */
64 static void syn_add (int n, int o) {
65   syn_t* tmp = mem_malloc (sizeof (syn_t));
66   tmp->f = str_dup (CurRCFile);
67   tmp->l = CurRCLine;
68   tmp->n = n;
69   tmp->o = o;
70   list_push_back (&Synonyms, tmp);
71 }
72
73 /* for synonym warning reports: free single item (for list_del()) */
74 static void syn_del (void** p) {
75   mem_free(&(*(syn_t**) p)->f);
76   mem_free(p);
77 }
78
79 void toggle_quadoption (int opt)
80 {
81   int n = opt / 4;
82   int b = (opt % 4) * 2;
83
84   QuadOptions[n] ^= (1 << b);
85 }
86
87 void set_quadoption (int opt, int flag)
88 {
89   int n = opt / 4;
90   int b = (opt % 4) * 2;
91
92   QuadOptions[n] &= ~(0x3 << b);
93   QuadOptions[n] |= (flag & 0x3) << b;
94 }
95
96 int quadoption (int opt)
97 {
98   int n = opt / 4;
99   int b = (opt % 4) * 2;
100
101   return (QuadOptions[n] >> b) & 0x3;
102 }
103
104 int query_quadoption (int opt, const char *prompt)
105 {
106   int v = quadoption (opt);
107
108   switch (v) {
109   case M_YES:
110   case M_NO:
111     return (v);
112
113   default:
114     v = mutt_yesorno (prompt, (v == M_ASKYES));
115     CLEARLINE (LINES - 1);
116     return (v);
117   }
118
119   /* not reached */
120 }
121
122 /* given the variable ``s'', return the index into the rc_vars array which
123    matches, or -1 if the variable is not found.  */
124 int mutt_option_index (char *s)
125 {
126   int i;
127
128   for (i = 0; MuttVars[i].option; i++)
129     if (str_cmp (s, MuttVars[i].option) == 0) {
130       if (MuttVars[i].type == DT_SYN)
131         syn_add (mutt_option_index ((char *) MuttVars[i].data), i);
132       return (MuttVars[i].type ==
133               DT_SYN ? mutt_option_index ((char *) MuttVars[i].data) : i);
134     }
135   return (-1);
136 }
137
138 static void add_to_list (LIST ** list, const char *str)
139 {
140   LIST *t, *last = NULL;
141
142   /* don't add a NULL or empty string to the list */
143   if (!str || *str == '\0')
144     return;
145
146   /* check to make sure the item is not already on this list */
147   for (last = *list; last; last = last->next) {
148     if (ascii_strcasecmp (str, last->data) == 0) {
149       /* already on the list, so just ignore it */
150       last = NULL;
151       break;
152     }
153     if (!last->next)
154       break;
155   }
156
157   if (!*list || last) {
158     t = (LIST *) mem_calloc (1, sizeof (LIST));
159     t->data = str_dup (str);
160     if (last) {
161       last->next = t;
162       last = last->next;
163     }
164     else
165       *list = last = t;
166   }
167 }
168
169 static int add_to_rx_list (list2_t** list, const char *s, int flags,
170                            BUFFER * err)
171 {
172   rx_t* rx;
173   int i = 0;
174
175   if (!s || !*s)
176     return 0;
177
178   if (!(rx = rx_compile (s, flags))) {
179     snprintf (err->data, err->dsize, "Bad regexp: %s\n", s);
180     return -1;
181   }
182
183   i = rx_lookup ((*list), rx->pattern);
184   if (i >= 0)
185     rx_free (&rx);
186   else
187     list_push_back (list, rx);
188   return 0;
189 }
190
191 static int add_to_spam_list (SPAM_LIST ** list, const char *pat,
192                              const char *templ, BUFFER * err)
193 {
194   SPAM_LIST *t = NULL, *last = NULL;
195   rx_t* rx;
196   int n;
197   const char *p;
198
199   if (!pat || !*pat || !templ)
200     return 0;
201
202   if (!(rx = rx_compile (pat, REG_ICASE))) {
203     snprintf (err->data, err->dsize, _("Bad regexp: %s"), pat);
204     return -1;
205   }
206
207   /* check to make sure the item is not already on this list */
208   for (last = *list; last; last = last->next) {
209     if (ascii_strcasecmp (rx->pattern, last->rx->pattern) == 0) {
210       /* Already on the list. Formerly we just skipped this case, but
211        * now we're supporting removals, which means we're supporting
212        * re-adds conceptually. So we probably want this to imply a
213        * removal, then do an add. We can achieve the removal by freeing
214        * the template, and leaving t pointed at the current item.
215        */
216       t = last;
217       mem_free(t->template);
218       break;
219     }
220     if (!last->next)
221       break;
222   }
223
224   /* If t is set, it's pointing into an extant SPAM_LIST* that we want to
225    * update. Otherwise we want to make a new one to link at the list's end.
226    */
227   if (!t) {
228     t = mutt_new_spam_list ();
229     t->rx = rx;
230     if (last)
231       last->next = t;
232     else
233       *list = t;
234   }
235
236   /* Now t is the SPAM_LIST* that we want to modify. It is prepared. */
237   t->template = str_dup (templ);
238
239   /* Find highest match number in template string */
240   t->nmatch = 0;
241   for (p = templ; *p;) {
242     if (*p == '%') {
243       n = atoi (++p);
244       if (n > t->nmatch)
245         t->nmatch = n;
246       while (*p && isdigit ((int) *p))
247         ++p;
248     }
249     else
250       ++p;
251   }
252   t->nmatch++;                  /* match 0 is always the whole expr */
253
254   return 0;
255 }
256
257 static int remove_from_spam_list (SPAM_LIST ** list, const char *pat)
258 {
259   SPAM_LIST *spam, *prev;
260   int nremoved = 0;
261
262   /* Being first is a special case. */
263   spam = *list;
264   if (!spam)
265     return 0;
266   if (spam->rx && !str_cmp (spam->rx->pattern, pat)) {
267     *list = spam->next;
268     rx_free (&spam->rx);
269     mem_free(&spam->template);
270     mem_free(&spam);
271     return 1;
272   }
273
274   prev = spam;
275   for (spam = prev->next; spam;) {
276     if (!str_cmp (spam->rx->pattern, pat)) {
277       prev->next = spam->next;
278       rx_free (&spam->rx);
279       mem_free(spam->template);
280       mem_free(spam);
281       spam = prev->next;
282       ++nremoved;
283     }
284     else
285       spam = spam->next;
286   }
287
288   return nremoved;
289 }
290
291
292 static void remove_from_list (LIST ** l, const char *str)
293 {
294   LIST *p, *last = NULL;
295
296   if (str_cmp ("*", str) == 0)
297     mutt_free_list (l);         /* ``unCMD *'' means delete all current entries */
298   else {
299     p = *l;
300     last = NULL;
301     while (p) {
302       if (ascii_strcasecmp (str, p->data) == 0) {
303         mem_free (&p->data);
304         if (last)
305           last->next = p->next;
306         else
307           (*l) = p->next;
308         mem_free (&p);
309       }
310       else {
311         last = p;
312         p = p->next;
313       }
314     }
315   }
316 }
317
318 static int remove_from_rx_list (list2_t** l, const char *str)
319 {
320   int i = 0;
321
322   if (str_cmp ("*", str) == 0) {
323     list_del (l, (list_del_t*) rx_free);
324     return (0);
325   }
326   else {
327     i = rx_lookup ((*l), str);
328     if (i >= 0) {
329       rx_t* r = list_pop_idx ((*l), i);
330       rx_free (&r);
331       return (0);
332     }
333   }
334   return (-1);
335 }
336
337 static int parse_ifdef (BUFFER * tmp, BUFFER * s, unsigned long data,
338                         BUFFER * err)
339 {
340   int i, j, res = 0;
341   BUFFER token;
342
343   memset (&token, 0, sizeof (token));
344   mutt_extract_token (tmp, s, 0);
345
346   /* is the item defined as a variable or a function? */
347   if (!(res = (mutt_option_index (tmp->data) != -1)))
348     for (i = 0; !res && i < MENU_MAX; i++) {
349       struct binding_t *b = km_get_table (Menus[i].value);
350
351       if (!b)
352         continue;
353
354       for (j = 0; b[j].name; j++)
355         if (!ascii_strncasecmp (tmp->data, b[j].name, str_len (tmp->data))
356             && (str_len (b[j].name) == str_len (tmp->data))) {
357           res = 1;
358           break;
359         }
360     }
361   /* check for feature_* */
362   if (!res) {
363     char *p = NULL;
364
365     i = 0;
366     j = str_len (tmp->data);
367     /* need at least input of 'feature_X' */
368     if (j >= 7) {
369       p = tmp->data + 7;
370       j -= 7;
371       while (Features[i].name) {
372         if (str_len (Features[i].name) == j &&
373             ascii_strncasecmp (Features[i].name, p, j)) {
374           res = 1;
375           break;
376         }
377         i++;
378       }
379     }
380   }
381
382   if (!MoreArgs (s)) {
383     if (data)
384       snprintf (err->data, err->dsize, _("ifdef: too few arguments"));
385     else
386       snprintf (err->data, err->dsize, _("ifndef: too few arguments"));
387     return (-1);
388   }
389   mutt_extract_token (tmp, s, M_TOKEN_SPACE);
390
391   if ((data && res) || (!data && !res)) {
392     if (mutt_parse_rc_line (tmp->data, &token, err) == -1) {
393       mutt_error ("Error: %s", err->data);
394       mem_free (&token.data);
395       return (-1);
396     }
397     mem_free (&token.data);
398   }
399   return 0;
400 }
401
402 static int parse_unignore (BUFFER * buf, BUFFER * s, unsigned long data,
403                            BUFFER * err)
404 {
405   do {
406     mutt_extract_token (buf, s, 0);
407
408     /* don't add "*" to the unignore list */
409     if (strcmp (buf->data, "*"))
410       add_to_list (&UnIgnore, buf->data);
411
412     remove_from_list (&Ignore, buf->data);
413   }
414   while (MoreArgs (s));
415
416   return 0;
417 }
418
419 static int parse_ignore (BUFFER * buf, BUFFER * s, unsigned long data,
420                          BUFFER * err)
421 {
422   do {
423     mutt_extract_token (buf, s, 0);
424     remove_from_list (&UnIgnore, buf->data);
425     add_to_list (&Ignore, buf->data);
426   }
427   while (MoreArgs (s));
428
429   return 0;
430 }
431
432 static int parse_list (BUFFER * buf, BUFFER * s, unsigned long data,
433                        BUFFER * err)
434 {
435   do {
436     mutt_extract_token (buf, s, 0);
437     add_to_list ((LIST **) data, buf->data);
438   }
439   while (MoreArgs (s));
440
441   return 0;
442 }
443
444 static void _alternates_clean (void)
445 {
446   int i;
447
448   if (Context && Context->msgcount) {
449     for (i = 0; i < Context->msgcount; i++)
450       Context->hdrs[i]->recip_valid = 0;
451   }
452 }
453
454 static int parse_alternates (BUFFER * buf, BUFFER * s, unsigned long data,
455                              BUFFER * err)
456 {
457   _alternates_clean ();
458   do {
459     mutt_extract_token (buf, s, 0);
460     remove_from_rx_list (&UnAlternates, buf->data);
461
462     if (add_to_rx_list (&Alternates, buf->data, REG_ICASE, err) != 0)
463       return -1;
464   }
465   while (MoreArgs (s));
466
467   return 0;
468 }
469
470 static int parse_unalternates (BUFFER * buf, BUFFER * s, unsigned long data,
471                                BUFFER * err)
472 {
473   _alternates_clean ();
474   do {
475     mutt_extract_token (buf, s, 0);
476     remove_from_rx_list (&Alternates, buf->data);
477
478     if (str_cmp (buf->data, "*") &&
479         add_to_rx_list (&UnAlternates, buf->data, REG_ICASE, err) != 0)
480       return -1;
481
482   }
483   while (MoreArgs (s));
484
485   return 0;
486 }
487
488 static int parse_spam_list (BUFFER * buf, BUFFER * s, unsigned long data,
489                             BUFFER * err)
490 {
491   BUFFER templ;
492
493   memset (&templ, 0, sizeof (templ));
494
495   /* Insist on at least one parameter */
496   if (!MoreArgs (s)) {
497     if (data == M_SPAM)
498       strfcpy (err->data, _("spam: no matching pattern"), err->dsize);
499     else
500       strfcpy (err->data, _("nospam: no matching pattern"), err->dsize);
501     return -1;
502   }
503
504   /* Extract the first token, a regexp */
505   mutt_extract_token (buf, s, 0);
506
507   /* data should be either M_SPAM or M_NOSPAM. M_SPAM is for spam commands. */
508   if (data == M_SPAM) {
509     /* If there's a second parameter, it's a template for the spam tag. */
510     if (MoreArgs (s)) {
511       mutt_extract_token (&templ, s, 0);
512
513       /* Add to the spam list. */
514       if (add_to_spam_list (&SpamList, buf->data, templ.data, err) != 0) {
515         mem_free (&templ.data);
516         return -1;
517       }
518       mem_free (&templ.data);
519     }
520
521     /* If not, try to remove from the nospam list. */
522     else {
523       remove_from_rx_list (&NoSpamList, buf->data);
524     }
525
526     return 0;
527   }
528
529   /* M_NOSPAM is for nospam commands. */
530   else if (data == M_NOSPAM) {
531     /* nospam only ever has one parameter. */
532
533     /* "*" is a special case. */
534     if (!str_cmp (buf->data, "*")) {
535       mutt_free_spam_list (&SpamList);
536       list_del (&NoSpamList, (list_del_t*) rx_free);
537       return 0;
538     }
539
540     /* If it's on the spam list, just remove it. */
541     if (remove_from_spam_list (&SpamList, buf->data) != 0)
542       return 0;
543
544     /* Otherwise, add it to the nospam list. */
545     if (add_to_rx_list (&NoSpamList, buf->data, REG_ICASE, err) != 0)
546       return -1;
547
548     return 0;
549   }
550
551   /* This should not happen. */
552   strfcpy (err->data, "This is no good at all.", err->dsize);
553   return -1;
554 }
555
556 static int parse_unlist (BUFFER * buf, BUFFER * s, unsigned long data,
557                          BUFFER * err)
558 {
559   do {
560     mutt_extract_token (buf, s, 0);
561     /*
562      * Check for deletion of entire list
563      */
564     if (str_cmp (buf->data, "*") == 0) {
565       mutt_free_list ((LIST **) data);
566       break;
567     }
568     remove_from_list ((LIST **) data, buf->data);
569   }
570   while (MoreArgs (s));
571
572   return 0;
573 }
574
575 static int parse_lists (BUFFER * buf, BUFFER * s, unsigned long data,
576                         BUFFER * err)
577 {
578   do {
579     mutt_extract_token (buf, s, 0);
580     remove_from_rx_list (&UnMailLists, buf->data);
581
582     if (add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
583       return -1;
584   }
585   while (MoreArgs (s));
586
587   return 0;
588 }
589
590 static int parse_unlists (BUFFER * buf, BUFFER * s, unsigned long data,
591                           BUFFER * err)
592 {
593   do {
594     mutt_extract_token (buf, s, 0);
595     remove_from_rx_list (&SubscribedLists, buf->data);
596     remove_from_rx_list (&MailLists, buf->data);
597
598     if (str_cmp (buf->data, "*") &&
599         add_to_rx_list (&UnMailLists, buf->data, REG_ICASE, err) != 0)
600       return -1;
601   }
602   while (MoreArgs (s));
603
604   return 0;
605 }
606
607 static int parse_subscribe (BUFFER * buf, BUFFER * s, unsigned long data,
608                             BUFFER * err)
609 {
610   do {
611     mutt_extract_token (buf, s, 0);
612     remove_from_rx_list (&UnMailLists, buf->data);
613     remove_from_rx_list (&UnSubscribedLists, buf->data);
614
615     if (add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
616       return -1;
617     if (add_to_rx_list (&SubscribedLists, buf->data, REG_ICASE, err) != 0)
618       return -1;
619   }
620   while (MoreArgs (s));
621
622   return 0;
623 }
624
625 static int parse_unsubscribe (BUFFER * buf, BUFFER * s, unsigned long data,
626                               BUFFER * err)
627 {
628   do {
629     mutt_extract_token (buf, s, 0);
630     remove_from_rx_list (&SubscribedLists, buf->data);
631
632     if (str_cmp (buf->data, "*") &&
633         add_to_rx_list (&UnSubscribedLists, buf->data, REG_ICASE, err) != 0)
634       return -1;
635   }
636   while (MoreArgs (s));
637
638   return 0;
639 }
640
641 static int parse_unalias (BUFFER * buf, BUFFER * s, unsigned long data,
642                           BUFFER * err)
643 {
644   ALIAS *tmp, *last = NULL;
645
646   do {
647     mutt_extract_token (buf, s, 0);
648
649     if (str_cmp ("*", buf->data) == 0) {
650       if (CurrentMenu == MENU_ALIAS) {
651         for (tmp = Aliases; tmp; tmp = tmp->next)
652           tmp->del = 1;
653         set_option (OPTFORCEREDRAWINDEX);
654       }
655       else
656         mutt_free_alias (&Aliases);
657       break;
658     }
659     else
660       for (tmp = Aliases; tmp; tmp = tmp->next) {
661         if (str_casecmp (buf->data, tmp->name) == 0) {
662           if (CurrentMenu == MENU_ALIAS) {
663             tmp->del = 1;
664             set_option (OPTFORCEREDRAWINDEX);
665             break;
666           }
667
668           if (last)
669             last->next = tmp->next;
670           else
671             Aliases = tmp->next;
672           tmp->next = NULL;
673           mutt_free_alias (&tmp);
674           break;
675         }
676         last = tmp;
677       }
678   }
679   while (MoreArgs (s));
680   return 0;
681 }
682
683 static int parse_alias (BUFFER * buf, BUFFER * s, unsigned long data,
684                         BUFFER * err)
685 {
686   ALIAS *tmp = Aliases;
687   ALIAS *last = NULL;
688   char *estr = NULL;
689
690   if (!MoreArgs (s)) {
691     strfcpy (err->data, _("alias: no address"), err->dsize);
692     return (-1);
693   }
694
695   mutt_extract_token (buf, s, 0);
696
697   debug_print (2, ("first token is '%s'.\n", buf->data));
698
699   /* check to see if an alias with this name already exists */
700   for (; tmp; tmp = tmp->next) {
701     if (!str_casecmp (tmp->name, buf->data))
702       break;
703     last = tmp;
704   }
705
706   if (!tmp) {
707     /* create a new alias */
708     tmp = (ALIAS *) mem_calloc (1, sizeof (ALIAS));
709     tmp->self = tmp;
710     tmp->name = str_dup (buf->data);
711     /* give the main addressbook code a chance */
712     if (CurrentMenu == MENU_ALIAS)
713       set_option (OPTMENUCALLER);
714   }
715   else {
716     /* override the previous value */
717     rfc822_free_address (&tmp->addr);
718     if (CurrentMenu == MENU_ALIAS)
719       set_option (OPTFORCEREDRAWINDEX);
720   }
721
722   mutt_extract_token (buf, s,
723                       M_TOKEN_QUOTE | M_TOKEN_SPACE | M_TOKEN_SEMICOLON);
724   debug_print (2, ("second token is '%s'.\n", buf->data));
725   tmp->addr = mutt_parse_adrlist (tmp->addr, buf->data);
726   if (last)
727     last->next = tmp;
728   else
729     Aliases = tmp;
730   if (mutt_addrlist_to_idna (tmp->addr, &estr)) {
731     snprintf (err->data, err->dsize,
732               _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, tmp->name);
733     return -1;
734   }
735 #ifdef DEBUG
736   if (DebugLevel >= 2) {
737     ADDRESS *a;
738
739     for (a = tmp->addr; a; a = a->next) {
740       if (!a->group)
741         debug_print (2, ("%s\n", a->mailbox));
742       else
743         debug_print (2, ("group %s\n", a->mailbox));
744     }
745   }
746 #endif
747   return 0;
748 }
749
750 static int
751 parse_unmy_hdr (BUFFER * buf, BUFFER * s, unsigned long data, BUFFER * err)
752 {
753   LIST *last = NULL;
754   LIST *tmp = UserHeader;
755   LIST *ptr;
756   size_t l;
757
758   do {
759     mutt_extract_token (buf, s, 0);
760     if (str_cmp ("*", buf->data) == 0)
761       mutt_free_list (&UserHeader);
762     else {
763       tmp = UserHeader;
764       last = NULL;
765
766       l = str_len (buf->data);
767       if (buf->data[l - 1] == ':')
768         l--;
769
770       while (tmp) {
771         if (ascii_strncasecmp (buf->data, tmp->data, l) == 0
772             && tmp->data[l] == ':') {
773           ptr = tmp;
774           if (last)
775             last->next = tmp->next;
776           else
777             UserHeader = tmp->next;
778           tmp = tmp->next;
779           ptr->next = NULL;
780           mutt_free_list (&ptr);
781         }
782         else {
783           last = tmp;
784           tmp = tmp->next;
785         }
786       }
787     }
788   }
789   while (MoreArgs (s));
790   return 0;
791 }
792
793 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data,
794                          BUFFER * err)
795 {
796   LIST *tmp;
797   size_t keylen;
798   char *p;
799
800   mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
801   if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
802     strfcpy (err->data, _("invalid header field"), err->dsize);
803     return (-1);
804   }
805   keylen = p - buf->data + 1;
806
807   if (UserHeader) {
808     for (tmp = UserHeader;; tmp = tmp->next) {
809       /* see if there is already a field by this name */
810       if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
811         /* replace the old value */
812         mem_free (&tmp->data);
813         tmp->data = buf->data;
814         memset (buf, 0, sizeof (BUFFER));
815         return 0;
816       }
817       if (!tmp->next)
818         break;
819     }
820     tmp->next = mutt_new_list ();
821     tmp = tmp->next;
822   }
823   else {
824     tmp = mutt_new_list ();
825     UserHeader = tmp;
826   }
827   tmp->data = buf->data;
828   memset (buf, 0, sizeof (BUFFER));
829   return 0;
830 }
831
832 static int
833 parse_sort (short *val, const char *s, const struct mapping_t *map,
834             BUFFER * err)
835 {
836   int i, flags = 0;
837
838   if (str_ncmp ("reverse-", s, 8) == 0) {
839     s += 8;
840     flags = SORT_REVERSE;
841   }
842
843   if (str_ncmp ("last-", s, 5) == 0) {
844     s += 5;
845     flags |= SORT_LAST;
846   }
847
848   if ((i = mutt_getvaluebyname (s, map)) == -1) {
849     snprintf (err->data, err->dsize, _("%s: unknown sorting method"), s);
850     return (-1);
851   }
852
853   *val = i | flags;
854
855   return 0;
856 }
857
858 static void mutt_set_default (struct option_t *p)
859 {
860   switch (p->type & DT_MASK) {
861   case DT_STR:
862     if (!p->init && *((char **) p->data))
863       p->init = (unsigned long) str_dup (*((char **) p->data));
864     break;
865   case DT_PATH:
866     if (!p->init && *((char **) p->data)) {
867       char *cp = str_dup (*((char **) p->data));
868
869       /* mutt_pretty_mailbox (cp); */
870       p->init = (unsigned long) cp;
871     }
872     break;
873   case DT_ADDR:
874     if (!p->init && *((ADDRESS **) p->data)) {
875       char tmp[HUGE_STRING];
876
877       *tmp = '\0';
878       rfc822_write_address (tmp, sizeof (tmp), *((ADDRESS **) p->data), 0);
879       p->init = (unsigned long) str_dup (tmp);
880     }
881     break;
882   case DT_RX:
883     {
884       rx_t* pp = (rx_t*) p->data;
885
886       if (!p->init && pp->pattern)
887         p->init = (unsigned long) str_dup (pp->pattern);
888       break;
889     }
890   }
891 }
892
893 static void mutt_restore_default (struct option_t *p)
894 {
895   switch (p->type & DT_MASK) {
896   case DT_STR:
897     if (p->init)
898       str_replace ((char **) p->data, (char *) p->init);
899     break;
900   case DT_PATH:
901     if (p->init) {
902       char path[_POSIX_PATH_MAX];
903
904       strfcpy (path, (char *) p->init, sizeof (path));
905       mutt_expand_path (path, sizeof (path));
906       str_replace ((char **) p->data, path);
907     }
908     break;
909   case DT_ADDR:
910     if (p->init) {
911       rfc822_free_address ((ADDRESS **) p->data);
912       *((ADDRESS **) p->data) = rfc822_parse_adrlist (NULL, (char *) p->init);
913     }
914     break;
915   case DT_BOOL:
916     if (p->init)
917       set_option (p->data);
918     else
919       unset_option (p->data);
920     break;
921   case DT_QUAD:
922     set_quadoption (p->data, p->init);
923     break;
924   case DT_NUM:
925   case DT_SORT:
926   case DT_MAGIC:
927     *((short *) p->data) = p->init;
928     break;
929   case DT_RX:
930     {
931       rx_t *pp = (rx_t *) p->data;
932       int flags = 0;
933
934       mem_free (&pp->pattern);
935       if (pp->rx) {
936         regfree (pp->rx);
937         mem_free (&pp->rx);
938       }
939
940       if (p->init) {
941         char *s = (char *) p->init;
942
943         pp->rx = mem_calloc (1, sizeof (regex_t));
944         if (str_cmp (p->option, "mask") != 0)
945           flags |= mutt_which_case ((const char *) p->init);
946         if (str_cmp (p->option, "mask") == 0 && *s == '!') {
947           s++;
948           pp->not = 1;
949         }
950         if (REGCOMP (pp->rx, s, flags) != 0) {
951           fprintf (stderr,
952                    _("mutt_restore_default(%s): error in regexp: %s\n"),
953                    p->option, pp->pattern);
954           mem_free (&pp->pattern);
955           regfree (pp->rx);
956           mem_free (&pp->rx);
957         }
958         else
959           str_replace (&pp->pattern, (char *) p->init);
960       }
961     }
962     break;
963   }
964
965   if (p->flags & R_INDEX)
966     set_option (OPTFORCEREDRAWINDEX);
967   if (p->flags & R_PAGER)
968     set_option (OPTFORCEREDRAWPAGER);
969   if (p->flags & R_RESORT_SUB)
970     set_option (OPTSORTSUBTHREADS);
971   if (p->flags & R_RESORT)
972     set_option (OPTNEEDRESORT);
973   if (p->flags & R_RESORT_INIT)
974     set_option (OPTRESORTINIT);
975   if (p->flags & R_TREE)
976     set_option (OPTREDRAWTREE);
977 }
978
979 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
980                       BUFFER * err)
981 {
982   int idx, query, unset, inv, reset, r = 0;
983   char *p, scratch[_POSIX_PATH_MAX];
984
985   while (MoreArgs (s)) {
986     /* reset state variables */
987     query = 0;
988     unset = data & M_SET_UNSET;
989     inv = data & M_SET_INV;
990     reset = data & M_SET_RESET;
991
992     if (*s->dptr == '?') {
993       query = 1;
994       s->dptr++;
995     }
996     else if (str_ncmp ("no", s->dptr, 2) == 0) {
997       s->dptr += 2;
998       unset = !unset;
999     }
1000     else if (str_ncmp ("inv", s->dptr, 3) == 0) {
1001       s->dptr += 3;
1002       inv = !inv;
1003     }
1004     else if (*s->dptr == '&') {
1005       reset = 1;
1006       s->dptr++;
1007     }
1008
1009     /* get the variable name */
1010     mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1011
1012     if ((idx = mutt_option_index (tmp->data)) == -1 &&
1013         !(reset && !str_cmp ("all", tmp->data))) {
1014       snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1015       return (-1);
1016     }
1017     SKIPWS (s->dptr);
1018
1019     if (reset) {
1020       if (query || unset || inv) {
1021         snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1022         return (-1);
1023       }
1024
1025       if (s && *s->dptr == '=') {
1026         snprintf (err->data, err->dsize, _("value is illegal with reset"));
1027         return (-1);
1028       }
1029
1030       if (!str_cmp ("all", tmp->data)) {
1031         for (idx = 0; MuttVars[idx].option; idx++)
1032           mutt_restore_default (&MuttVars[idx]);
1033         return 0;
1034       }
1035       else
1036         mutt_restore_default (&MuttVars[idx]);
1037     }
1038     else if (DTYPE (MuttVars[idx].type) == DT_BOOL) {
1039       if (s && *s->dptr == '=') {
1040         if (unset || inv || query) {
1041           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1042           return (-1);
1043         }
1044
1045         s->dptr++;
1046         mutt_extract_token (tmp, s, 0);
1047         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1048           unset = inv = 0;
1049         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1050           unset = 1;
1051         else {
1052           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1053           return (-1);
1054         }
1055       }
1056
1057       if (query) {
1058         snprintf (err->data, err->dsize, option (MuttVars[idx].data)
1059                   ? _("%s is set") : _("%s is unset"), tmp->data);
1060         return 0;
1061       }
1062
1063       if (unset)
1064         unset_option (MuttVars[idx].data);
1065       else if (inv)
1066         toggle_option (MuttVars[idx].data);
1067       else
1068         set_option (MuttVars[idx].data);
1069     }
1070     else if (DTYPE (MuttVars[idx].type) == DT_STR ||
1071              DTYPE (MuttVars[idx].type) == DT_PATH ||
1072              DTYPE (MuttVars[idx].type) == DT_ADDR) {
1073       if (unset) {
1074         if (DTYPE (MuttVars[idx].type) == DT_ADDR)
1075           rfc822_free_address ((ADDRESS **) MuttVars[idx].data);
1076         else
1077           mem_free ((void *) MuttVars[idx].data);
1078       }
1079       else if (query || *s->dptr != '=') {
1080         char _tmp[STRING];
1081         char *val = NULL;
1082
1083         if (DTYPE (MuttVars[idx].type) == DT_ADDR) {
1084           _tmp[0] = '\0';
1085           rfc822_write_address (_tmp, sizeof (_tmp),
1086                                 *((ADDRESS **) MuttVars[idx].data), 0);
1087           val = _tmp;
1088         }
1089         else
1090           val = *((char **) MuttVars[idx].data);
1091
1092         /* user requested the value of this variable */
1093         snprintf (err->data, err->dsize, "%s=\"%s\"", MuttVars[idx].option,
1094                   NONULL (val));
1095         break;
1096       }
1097       else {
1098         s->dptr++;
1099
1100         /* copy the value of the string */
1101         if (DTYPE (MuttVars[idx].type) == DT_ADDR)
1102           rfc822_free_address ((ADDRESS **) MuttVars[idx].data);
1103         else
1104           mem_free ((void *) MuttVars[idx].data);
1105
1106         mutt_extract_token (tmp, s, 0);
1107         if (DTYPE (MuttVars[idx].type) == DT_PATH) {
1108           strfcpy (scratch, tmp->data, sizeof (scratch));
1109           mutt_expand_path (scratch, sizeof (scratch));
1110           *((char **) MuttVars[idx].data) = str_dup (scratch);
1111         }
1112         else if (DTYPE (MuttVars[idx].type) == DT_STR) {
1113           *((char **) MuttVars[idx].data) = str_dup (tmp->data);
1114           if (str_cmp (MuttVars[idx].option, "charset") == 0)
1115             mutt_set_charset (Charset);
1116         }
1117         else {
1118           *((ADDRESS **) MuttVars[idx].data) =
1119             rfc822_parse_adrlist (NULL, tmp->data);
1120         }
1121       }
1122     }
1123     else if (DTYPE (MuttVars[idx].type) == DT_RX) {
1124       rx_t *ptr = (rx_t *) MuttVars[idx].data;
1125       regex_t *rx;
1126       int e, flags = 0;
1127
1128       if (query || *s->dptr != '=') {
1129         /* user requested the value of this variable */
1130         snprintf (err->data, err->dsize, "%s=\"%s\"", MuttVars[idx].option,
1131                   NONULL (ptr->pattern));
1132         break;
1133       }
1134
1135       if (option (OPTATTACHMSG)
1136           && !str_cmp (MuttVars[idx].option, "reply_regexp")) {
1137         snprintf (err->data, err->dsize,
1138                   "Operation not permitted when in attach-message mode.");
1139         r = -1;
1140         break;
1141       }
1142
1143       s->dptr++;
1144
1145       /* copy the value of the string */
1146       mutt_extract_token (tmp, s, 0);
1147
1148       if (!ptr->pattern || str_cmp (ptr->pattern, tmp->data) != 0) {
1149         int not = 0;
1150
1151         /* $mask is case-sensitive */
1152         if (str_cmp (MuttVars[idx].option, "mask") != 0)
1153           flags |= mutt_which_case (tmp->data);
1154
1155         p = tmp->data;
1156         if (str_cmp (MuttVars[idx].option, "mask") == 0) {
1157           if (*p == '!') {
1158             not = 1;
1159             p++;
1160           }
1161         }
1162
1163         rx = (regex_t *) mem_malloc (sizeof (regex_t));
1164         if ((e = REGCOMP (rx, p, flags)) != 0) {
1165           regerror (e, rx, err->data, err->dsize);
1166           regfree (rx);
1167           mem_free (&rx);
1168           break;
1169         }
1170
1171         /* get here only if everything went smootly */
1172         if (ptr->pattern) {
1173           mem_free (&ptr->pattern);
1174           regfree ((regex_t *) ptr->rx);
1175           mem_free (&ptr->rx);
1176         }
1177
1178         ptr->pattern = str_dup (tmp->data);
1179         ptr->rx = rx;
1180         ptr->not = not;
1181
1182         /* $reply_regexp and $alterantes require special treatment */
1183
1184         if (Context && Context->msgcount &&
1185             str_cmp (MuttVars[idx].option, "reply_regexp") == 0) {
1186           regmatch_t pmatch[1];
1187           int i;
1188
1189 #define CUR_ENV Context->hdrs[i]->env
1190           for (i = 0; i < Context->msgcount; i++) {
1191             if (CUR_ENV && CUR_ENV->subject) {
1192               CUR_ENV->real_subj = (regexec (ReplyRegexp.rx,
1193                                              CUR_ENV->subject, 1, pmatch,
1194                                              0)) ? CUR_ENV->
1195                 subject : CUR_ENV->subject + pmatch[0].rm_eo;
1196             }
1197           }
1198 #undef CUR_ENV
1199         }
1200       }
1201     }
1202     else if (DTYPE (MuttVars[idx].type) == DT_MAGIC) {
1203       if (query || *s->dptr != '=') {
1204         switch (DefaultMagic) {
1205         case M_MBOX:
1206           p = "mbox";
1207           break;
1208         case M_MMDF:
1209           p = "MMDF";
1210           break;
1211         case M_MH:
1212           p = "MH";
1213           break;
1214         case M_MAILDIR:
1215           p = "Maildir";
1216           break;
1217         default:
1218           p = "unknown";
1219           break;
1220         }
1221         snprintf (err->data, err->dsize, "%s=%s", MuttVars[idx].option, p);
1222         break;
1223       }
1224
1225       s->dptr++;
1226
1227       /* copy the value of the string */
1228       mutt_extract_token (tmp, s, 0);
1229       if (mx_set_magic (tmp->data)) {
1230         snprintf (err->data, err->dsize, _("%s: invalid mailbox type"),
1231                   tmp->data);
1232         r = -1;
1233         break;
1234       }
1235     }
1236     else if (DTYPE (MuttVars[idx].type) == DT_NUM) {
1237       short *ptr = (short *) MuttVars[idx].data;
1238       int val;
1239       char *t;
1240
1241       if (query || *s->dptr != '=') {
1242         /* user requested the value of this variable */
1243         snprintf (err->data, err->dsize, "%s=%d", MuttVars[idx].option, *ptr);
1244         break;
1245       }
1246
1247       s->dptr++;
1248
1249       mutt_extract_token (tmp, s, 0);
1250       val = strtol (tmp->data, &t, 0);
1251
1252       if (!*tmp->data || *t || (short) val != val) {
1253         snprintf (err->data, err->dsize, _("%s: invalid value"), tmp->data);
1254         r = -1;
1255         break;
1256       }
1257       else
1258         *ptr = (short) val;
1259
1260       /* these ones need a sanity check */
1261       if (str_cmp (MuttVars[idx].option, "history") == 0) {
1262         if (*ptr < 0)
1263           *ptr = 0;
1264         mutt_init_history ();
1265       }
1266       else if (str_cmp (MuttVars[idx].option, "pager_index_lines") == 0) {
1267         if (*ptr < 0)
1268           *ptr = 0;
1269       }
1270     }
1271     else if (DTYPE (MuttVars[idx].type) == DT_QUAD) {
1272       if (query) {
1273         char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1274
1275         snprintf (err->data, err->dsize, "%s=%s", MuttVars[idx].option,
1276                   vals[quadoption (MuttVars[idx].data)]);
1277         break;
1278       }
1279
1280       if (*s->dptr == '=') {
1281         s->dptr++;
1282         mutt_extract_token (tmp, s, 0);
1283         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1284           set_quadoption (MuttVars[idx].data, M_YES);
1285         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1286           set_quadoption (MuttVars[idx].data, M_NO);
1287         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1288           set_quadoption (MuttVars[idx].data, M_ASKYES);
1289         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1290           set_quadoption (MuttVars[idx].data, M_ASKNO);
1291         else {
1292           snprintf (err->data, err->dsize, _("%s: invalid value"), tmp->data);
1293           r = -1;
1294           break;
1295         }
1296       }
1297       else {
1298         if (inv)
1299           toggle_quadoption (MuttVars[idx].data);
1300         else if (unset)
1301           set_quadoption (MuttVars[idx].data, M_NO);
1302         else
1303           set_quadoption (MuttVars[idx].data, M_YES);
1304       }
1305     }
1306     else if (DTYPE (MuttVars[idx].type) == DT_SORT) {
1307       const struct mapping_t *map = NULL;
1308
1309       switch (MuttVars[idx].type & DT_SUBTYPE_MASK) {
1310       case DT_SORT_ALIAS:
1311         map = SortAliasMethods;
1312         break;
1313       case DT_SORT_BROWSER:
1314         map = SortBrowserMethods;
1315         break;
1316       case DT_SORT_KEYS:
1317         if ((WithCrypto & APPLICATION_PGP))
1318           map = SortKeyMethods;
1319         break;
1320       case DT_SORT_AUX:
1321         map = SortAuxMethods;
1322         break;
1323       default:
1324         map = SortMethods;
1325         break;
1326       }
1327
1328       if (!map) {
1329         snprintf (err->data, err->dsize, _("%s: Unknown type."),
1330                   MuttVars[idx].option);
1331         r = -1;
1332         break;
1333       }
1334
1335       if (query || *s->dptr != '=') {
1336         p =
1337           mutt_getnamebyvalue (*((short *) MuttVars[idx].data) & SORT_MASK,
1338                                map);
1339
1340         snprintf (err->data, err->dsize, "%s=%s%s%s", MuttVars[idx].option,
1341                   (*((short *) MuttVars[idx].data) & SORT_REVERSE) ?
1342                   "reverse-" : "",
1343                   (*((short *) MuttVars[idx].data) & SORT_LAST) ? "last-" :
1344                   "", p);
1345         return 0;
1346       }
1347       s->dptr++;
1348       mutt_extract_token (tmp, s, 0);
1349
1350       if (parse_sort ((short *) MuttVars[idx].data, tmp->data, map, err) ==
1351           -1) {
1352         r = -1;
1353         break;
1354       }
1355     }
1356     else {
1357       snprintf (err->data, err->dsize, _("%s: unknown type"),
1358                 MuttVars[idx].option);
1359       r = -1;
1360       break;
1361     }
1362
1363     if (MuttVars[idx].flags & R_INDEX)
1364       set_option (OPTFORCEREDRAWINDEX);
1365     if (MuttVars[idx].flags & R_PAGER)
1366       set_option (OPTFORCEREDRAWPAGER);
1367     if (MuttVars[idx].flags & R_RESORT_SUB)
1368       set_option (OPTSORTSUBTHREADS);
1369     if (MuttVars[idx].flags & R_RESORT)
1370       set_option (OPTNEEDRESORT);
1371     if (MuttVars[idx].flags & R_RESORT_INIT)
1372       set_option (OPTRESORTINIT);
1373     if (MuttVars[idx].flags & R_TREE)
1374       set_option (OPTREDRAWTREE);
1375   }
1376   return (r);
1377 }
1378
1379 #define MAXERRS 128
1380
1381 /* reads the specified initialization file.  returns -1 if errors were found
1382    so that we can pause to let the user know...  */
1383 static int source_rc (const char *rcfile, BUFFER * err)
1384 {
1385   FILE *f;
1386   int line = 0, rc = 0, conv = 0;
1387   BUFFER token;
1388   char *linebuf = NULL;
1389   char *currentline = NULL;
1390   size_t buflen;
1391   pid_t pid;
1392
1393   debug_print (2, ("reading configuration file '%s'.\n", rcfile));
1394   str_replace (&CurRCFile, rcfile);
1395   CurRCLine = 0;
1396
1397   if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1398     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1399     return (-1);
1400   }
1401
1402   memset (&token, 0, sizeof (token));
1403   while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line)) != NULL) {
1404     CurRCLine++;
1405     conv = ConfigCharset && (*ConfigCharset) && Charset;
1406     if (conv) {
1407       currentline = str_dup (linebuf);
1408       if (!currentline)
1409         continue;
1410       mutt_convert_string (&currentline, ConfigCharset, Charset, 0);
1411     }
1412     else
1413       currentline = linebuf;
1414
1415     if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1416       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1417       if (--rc < -MAXERRS) {
1418         if (conv)
1419           mem_free (&currentline);
1420         break;
1421       }
1422     }
1423     else {
1424       if (rc < 0)
1425         rc = -1;
1426     }
1427     if (conv)
1428       mem_free (&currentline);
1429   }
1430   mem_free (&token.data);
1431   mem_free (&linebuf);
1432   fclose (f);
1433   if (pid != -1)
1434     mutt_wait_filter (pid);
1435   if (rc) {
1436     /* the muttrc source keyword */
1437     snprintf (err->data, err->dsize,
1438               rc >= -MAXERRS ? _("source: errors in %s")
1439               : _("source: reading aborted due too many errors in %s"),
1440               rcfile);
1441     rc = -1;
1442   }
1443   return (rc);
1444 }
1445
1446 #undef MAXERRS
1447
1448 static int parse_source (BUFFER * tmp, BUFFER * s, unsigned long data,
1449                          BUFFER * err)
1450 {
1451   char path[_POSIX_PATH_MAX];
1452   int rc = 0;
1453
1454   do {
1455     if (mutt_extract_token (tmp, s, 0) != 0) {
1456       snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1457       return (-1);
1458     }
1459
1460     strfcpy (path, tmp->data, sizeof (path));
1461     mutt_expand_path (path, sizeof (path));
1462
1463     rc += source_rc (path, err);
1464   }
1465   while (MoreArgs (s));
1466
1467   return ((rc < 0) ? -1 : 0);
1468 }
1469
1470 /* line         command to execute
1471
1472    token        scratch buffer to be used by parser.  caller should free
1473                 token->data when finished.  the reason for this variable is
1474                 to avoid having to allocate and deallocate a lot of memory
1475                 if we are parsing many lines.  the caller can pass in the
1476                 memory to use, which avoids having to create new space for
1477                 every call to this function.
1478
1479    err          where to write error messages */
1480 int mutt_parse_rc_line ( /* const */ char *line, BUFFER * token, BUFFER * err)
1481 {
1482   int i, r = -1;
1483   BUFFER expn;
1484
1485   memset (&expn, 0, sizeof (expn));
1486   expn.data = expn.dptr = line;
1487   expn.dsize = str_len (line);
1488
1489   *err->data = 0;
1490
1491   SKIPWS (expn.dptr);
1492   while (*expn.dptr) {
1493     if (*expn.dptr == '#')
1494       break;                    /* rest of line is a comment */
1495     if (*expn.dptr == ';') {
1496       expn.dptr++;
1497       continue;
1498     }
1499     mutt_extract_token (token, &expn, 0);
1500     for (i = 0; Commands[i].name; i++) {
1501       if (!str_cmp (token->data, Commands[i].name)) {
1502         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1503           goto finish;
1504         break;
1505       }
1506     }
1507     if (!Commands[i].name) {
1508       snprintf (err->data, err->dsize, _("%s: unknown command"),
1509                 NONULL (token->data));
1510       goto finish;
1511     }
1512   }
1513   r = 0;
1514 finish:
1515   if (expn.destroy)
1516     mem_free (&expn.data);
1517   return (r);
1518 }
1519
1520
1521 #define NUMVARS (sizeof (MuttVars)/sizeof (MuttVars[0]))
1522 #define NUMCOMMANDS (sizeof (Commands)/sizeof (Commands[0]))
1523 /* initial string that starts completion. No telling how much crap 
1524  * the user has typed so far. Allocate LONG_STRING just to be sure! */
1525 char User_typed[LONG_STRING] = { 0 };
1526
1527 int Num_matched = 0;            /* Number of matches for completion */
1528 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
1529 char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1];  /* all the matches + User_typed */
1530
1531 /* helper function for completion.  Changes the dest buffer if
1532    necessary/possible to aid completion.
1533         dest == completion result gets here.
1534         src == candidate for completion.
1535         try == user entered data for completion.
1536         len == length of dest buffer.
1537 */
1538 static void candidate (char *dest, char *try, char *src, int len)
1539 {
1540   int l;
1541
1542   if (strstr (src, try) == src) {
1543     Matches[Num_matched++] = src;
1544     if (dest[0] == 0)
1545       strfcpy (dest, src, len);
1546     else {
1547       for (l = 0; src[l] && src[l] == dest[l]; l++);
1548       dest[l] = 0;
1549     }
1550   }
1551 }
1552
1553 int mutt_command_complete (char *buffer, size_t len, int pos, int numtabs)
1554 {
1555   char *pt = buffer;
1556   int num;
1557   int spaces;                   /* keep track of the number of leading spaces on the line */
1558
1559   SKIPWS (buffer);
1560   spaces = buffer - pt;
1561
1562   pt = buffer + pos - spaces;
1563   while ((pt > buffer) && !isspace ((unsigned char) *pt))
1564     pt--;
1565
1566   if (pt == buffer) {           /* complete cmd */
1567     /* first TAB. Collect all the matches */
1568     if (numtabs == 1) {
1569       Num_matched = 0;
1570       strfcpy (User_typed, pt, sizeof (User_typed));
1571       memset (Matches, 0, sizeof (Matches));
1572       memset (Completed, 0, sizeof (Completed));
1573       for (num = 0; Commands[num].name; num++)
1574         candidate (Completed, User_typed, Commands[num].name,
1575                    sizeof (Completed));
1576       Matches[Num_matched++] = User_typed;
1577
1578       /* All matches are stored. Longest non-ambiguous string is ""
1579        * i.e. dont change 'buffer'. Fake successful return this time */
1580       if (User_typed[0] == 0)
1581         return 1;
1582     }
1583
1584     if (Completed[0] == 0 && User_typed[0])
1585       return 0;
1586
1587     /* Num_matched will _always_ be atleast 1 since the initial
1588      * user-typed string is always stored */
1589     if (numtabs == 1 && Num_matched == 2)
1590       snprintf (Completed, sizeof (Completed), "%s", Matches[0]);
1591     else if (numtabs > 1 && Num_matched > 2)
1592       /* cycle thru all the matches */
1593       snprintf (Completed, sizeof (Completed), "%s",
1594                 Matches[(numtabs - 2) % Num_matched]);
1595
1596     /* return the completed command */
1597     strncpy (buffer, Completed, len - spaces);
1598   }
1599   else if (!str_ncmp (buffer, "set", 3)
1600            || !str_ncmp (buffer, "unset", 5)
1601            || !str_ncmp (buffer, "reset", 5)
1602            || !str_ncmp (buffer, "toggle", 6)) {    /* complete variables */
1603     char *prefixes[] = { "no", "inv", "?", "&", 0 };
1604
1605     pt++;
1606     /* loop through all the possible prefixes (no, inv, ...) */
1607     if (!str_ncmp (buffer, "set", 3)) {
1608       for (num = 0; prefixes[num]; num++) {
1609         if (!str_ncmp (pt, prefixes[num], str_len (prefixes[num]))) {
1610           pt += str_len (prefixes[num]);
1611           break;
1612         }
1613       }
1614     }
1615
1616     /* first TAB. Collect all the matches */
1617     if (numtabs == 1) {
1618       Num_matched = 0;
1619       strfcpy (User_typed, pt, sizeof (User_typed));
1620       memset (Matches, 0, sizeof (Matches));
1621       memset (Completed, 0, sizeof (Completed));
1622       for (num = 0; MuttVars[num].option; num++)
1623         candidate (Completed, User_typed, MuttVars[num].option,
1624                    sizeof (Completed));
1625       Matches[Num_matched++] = User_typed;
1626
1627       /* All matches are stored. Longest non-ambiguous string is ""
1628        * i.e. dont change 'buffer'. Fake successful return this time */
1629       if (User_typed[0] == 0)
1630         return 1;
1631     }
1632
1633     if (Completed[0] == 0 && User_typed[0])
1634       return 0;
1635
1636     /* Num_matched will _always_ be atleast 1 since the initial
1637      * user-typed string is always stored */
1638     if (numtabs == 1 && Num_matched == 2)
1639       snprintf (Completed, sizeof (Completed), "%s", Matches[0]);
1640     else if (numtabs > 1 && Num_matched > 2)
1641       /* cycle thru all the matches */
1642       snprintf (Completed, sizeof (Completed), "%s",
1643                 Matches[(numtabs - 2) % Num_matched]);
1644
1645     strncpy (pt, Completed, buffer + len - pt - spaces);
1646   }
1647   else if (!str_ncmp (buffer, "exec", 4)) {
1648     struct binding_t *menu = km_get_table (CurrentMenu);
1649
1650     if (!menu && CurrentMenu != MENU_PAGER)
1651       menu = OpGeneric;
1652
1653     pt++;
1654     /* first TAB. Collect all the matches */
1655     if (numtabs == 1) {
1656       Num_matched = 0;
1657       strfcpy (User_typed, pt, sizeof (User_typed));
1658       memset (Matches, 0, sizeof (Matches));
1659       memset (Completed, 0, sizeof (Completed));
1660       for (num = 0; menu[num].name; num++)
1661         candidate (Completed, User_typed, menu[num].name, sizeof (Completed));
1662       /* try the generic menu */
1663       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
1664         menu = OpGeneric;
1665         for (num = 0; menu[num].name; num++)
1666           candidate (Completed, User_typed, menu[num].name,
1667                      sizeof (Completed));
1668       }
1669       Matches[Num_matched++] = User_typed;
1670
1671       /* All matches are stored. Longest non-ambiguous string is ""
1672        * i.e. dont change 'buffer'. Fake successful return this time */
1673       if (User_typed[0] == 0)
1674         return 1;
1675     }
1676
1677     if (Completed[0] == 0 && User_typed[0])
1678       return 0;
1679
1680     /* Num_matched will _always_ be atleast 1 since the initial
1681      * user-typed string is always stored */
1682     if (numtabs == 1 && Num_matched == 2)
1683       snprintf (Completed, sizeof (Completed), "%s", Matches[0]);
1684     else if (numtabs > 1 && Num_matched > 2)
1685       /* cycle thru all the matches */
1686       snprintf (Completed, sizeof (Completed), "%s",
1687                 Matches[(numtabs - 2) % Num_matched]);
1688
1689     strncpy (pt, Completed, buffer + len - pt - spaces);
1690   }
1691   else
1692     return 0;
1693
1694   return 1;
1695 }
1696
1697 int mutt_var_value_complete (char *buffer, size_t len, int pos)
1698 {
1699   char var[STRING], *pt = buffer;
1700   int spaces;
1701
1702   if (buffer[0] == 0)
1703     return 0;
1704
1705   SKIPWS (buffer);
1706   spaces = buffer - pt;
1707
1708   pt = buffer + pos - spaces;
1709   while ((pt > buffer) && !isspace ((unsigned char) *pt))
1710     pt--;
1711   pt++;                         /* move past the space */
1712   if (*pt == '=')               /* abort if no var before the '=' */
1713     return 0;
1714
1715   if (str_ncmp (buffer, "set", 3) == 0) {
1716     int idx;
1717
1718     strfcpy (var, pt, sizeof (var));
1719     /* ignore the trailing '=' when comparing */
1720     var[str_len (var) - 1] = 0;
1721     if ((idx = mutt_option_index (var)) == -1)
1722       return 0;                 /* no such variable. */
1723     else {
1724       char tmp[LONG_STRING], tmp2[LONG_STRING];
1725       char *s, *d;
1726       size_t dlen = buffer + len - pt - spaces;
1727       char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1728
1729       tmp[0] = '\0';
1730
1731       if ((DTYPE (MuttVars[idx].type) == DT_STR) ||
1732           (DTYPE (MuttVars[idx].type) == DT_PATH) ||
1733           (DTYPE (MuttVars[idx].type) == DT_RX)) {
1734         strfcpy (tmp, NONULL (*((char **) MuttVars[idx].data)), sizeof (tmp));
1735         if (DTYPE (MuttVars[idx].type) == DT_PATH)
1736           mutt_pretty_mailbox (tmp);
1737       }
1738       else if (DTYPE (MuttVars[idx].type) == DT_ADDR) {
1739         rfc822_write_address (tmp, sizeof (tmp),
1740                               *((ADDRESS **) MuttVars[idx].data), 0);
1741       }
1742       else if (DTYPE (MuttVars[idx].type) == DT_QUAD)
1743         strfcpy (tmp, vals[quadoption (MuttVars[idx].data)], sizeof (tmp));
1744       else if (DTYPE (MuttVars[idx].type) == DT_NUM)
1745         snprintf (tmp, sizeof (tmp), "%d", (*((short *) MuttVars[idx].data)));
1746       else if (DTYPE (MuttVars[idx].type) == DT_SORT) {
1747         const struct mapping_t *map;
1748         char *p;
1749
1750         switch (MuttVars[idx].type & DT_SUBTYPE_MASK) {
1751         case DT_SORT_ALIAS:
1752           map = SortAliasMethods;
1753           break;
1754         case DT_SORT_BROWSER:
1755           map = SortBrowserMethods;
1756           break;
1757         case DT_SORT_KEYS:
1758           if ((WithCrypto & APPLICATION_PGP))
1759             map = SortKeyMethods;
1760           else
1761             map = SortMethods;
1762           break;
1763         default:
1764           map = SortMethods;
1765           break;
1766         }
1767         p =
1768           mutt_getnamebyvalue (*((short *) MuttVars[idx].data) & SORT_MASK,
1769                                map);
1770         snprintf (tmp, sizeof (tmp), "%s%s%s",
1771                   (*((short *) MuttVars[idx].data) & SORT_REVERSE) ?
1772                   "reverse-" : "",
1773                   (*((short *) MuttVars[idx].data) & SORT_LAST) ? "last-" :
1774                   "", p);
1775       } 
1776       else if (DTYPE (MuttVars[idx].type) == DT_MAGIC) {
1777         char *p;
1778         switch (DefaultMagic) {
1779           case M_MBOX:
1780             p = "mbox";
1781             break;
1782           case M_MMDF:
1783             p = "MMDF";
1784             break;
1785           case M_MH:
1786             p = "MH";
1787           break;
1788           case M_MAILDIR:
1789             p = "Maildir";
1790             break;
1791           default:
1792             p = "unknown";
1793         }
1794         strfcpy (tmp, p, sizeof (tmp));
1795       }
1796       else if (DTYPE (MuttVars[idx].type) == DT_BOOL)
1797         strfcpy (tmp, option (MuttVars[idx].data) ? "yes" : "no",
1798                  sizeof (tmp));
1799       else
1800         return 0;
1801
1802       for (s = tmp, d = tmp2; *s && (d - tmp2) < sizeof (tmp2) - 2;) {
1803         if (*s == '\\' || *s == '"')
1804           *d++ = '\\';
1805         *d++ = *s++;
1806       }
1807       *d = '\0';
1808
1809       strfcpy (tmp, pt, sizeof (tmp));
1810       snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
1811
1812       return 1;
1813     }
1814   }
1815   return 0;
1816 }
1817
1818 /* Implement the -Q command line flag */
1819 int mutt_query_variables (LIST * queries)
1820 {
1821   LIST *p;
1822
1823   char errbuff[STRING];
1824   char command[STRING];
1825
1826   BUFFER err, token;
1827
1828   memset (&err, 0, sizeof (err));
1829   memset (&token, 0, sizeof (token));
1830
1831   err.data = errbuff;
1832   err.dsize = sizeof (errbuff);
1833
1834   for (p = queries; p; p = p->next) {
1835     snprintf (command, sizeof (command), "set ?%s\n", p->data);
1836     if (mutt_parse_rc_line (command, &token, &err) == -1) {
1837       fprintf (stderr, "%s\n", err.data);
1838       mem_free (&token.data);
1839       return 1;
1840     }
1841     printf ("%s\n", err.data);
1842   }
1843
1844   mem_free (&token.data);
1845   return 0;
1846 }
1847
1848 char *mutt_getnamebyvalue (int val, const struct mapping_t *map)
1849 {
1850   int i;
1851
1852   for (i = 0; map[i].name; i++)
1853     if (map[i].value == val)
1854       return (map[i].name);
1855   return NULL;
1856 }
1857
1858 int mutt_getvaluebyname (const char *name, const struct mapping_t *map)
1859 {
1860   int i;
1861
1862   for (i = 0; map[i].name; i++)
1863     if (ascii_strcasecmp (map[i].name, name) == 0)
1864       return (map[i].value);
1865   return (-1);
1866 }
1867
1868 static int mutt_execute_commands (LIST * p)
1869 {
1870   BUFFER err, token;
1871   char errstr[SHORT_STRING];
1872
1873   memset (&err, 0, sizeof (err));
1874   err.data = errstr;
1875   err.dsize = sizeof (errstr);
1876   memset (&token, 0, sizeof (token));
1877   for (; p; p = p->next) {
1878     if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
1879       fprintf (stderr, _("Error in command line: %s\n"), err.data);
1880       mem_free (&token.data);
1881       return (-1);
1882     }
1883   }
1884   mem_free (&token.data);
1885   return 0;
1886 }
1887
1888 void mutt_init (int skip_sys_rc, LIST * commands)
1889 {
1890   struct passwd *pw;
1891   struct utsname utsname;
1892   char *p, buffer[STRING], error[STRING];
1893   int i, default_rc = 0, need_pause = 0;
1894   BUFFER err;
1895
1896   memset (&err, 0, sizeof (err));
1897   err.data = error;
1898   err.dsize = sizeof (error);
1899
1900   /* 
1901    * XXX - use something even more difficult to predict?
1902    */
1903   snprintf (AttachmentMarker, sizeof (AttachmentMarker),
1904             "\033]9;%ld\a", (long) time (NULL));
1905
1906   /* on one of the systems I use, getcwd() does not return the same prefix
1907      as is listed in the passwd file */
1908   if ((p = getenv ("HOME")))
1909     Homedir = str_dup (p);
1910
1911   /* Get some information about the user */
1912   if ((pw = getpwuid (getuid ()))) {
1913     char rnbuf[STRING];
1914
1915     Username = str_dup (pw->pw_name);
1916     if (!Homedir)
1917       Homedir = str_dup (pw->pw_dir);
1918
1919     Realname = str_dup (mutt_gecos_name (rnbuf, sizeof (rnbuf), pw));
1920     Shell = str_dup (pw->pw_shell);
1921   }
1922   else {
1923     if (!Homedir) {
1924       mutt_endwin (NULL);
1925       fputs (_("unable to determine home directory"), stderr);
1926       exit (1);
1927     }
1928     if ((p = getenv ("USER")))
1929       Username = str_dup (p);
1930     else {
1931       mutt_endwin (NULL);
1932       fputs (_("unable to determine username"), stderr);
1933       exit (1);
1934     }
1935     Shell = str_dup ((p = getenv ("SHELL")) ? p : "/bin/sh");
1936   }
1937
1938   debug_start(Homedir);
1939
1940   /* And about the host... */
1941   uname (&utsname);
1942   /* some systems report the FQDN instead of just the hostname */
1943   if ((p = strchr (utsname.nodename, '.'))) {
1944     Hostname = str_substrdup (utsname.nodename, p);
1945     p++;
1946     strfcpy (buffer, p, sizeof (buffer));       /* save the domain for below */
1947   }
1948   else
1949     Hostname = str_dup (utsname.nodename);
1950
1951 #ifndef DOMAIN
1952 #define DOMAIN buffer
1953   if (!p && getdnsdomainname (buffer, sizeof (buffer)) == -1)
1954     Fqdn = str_dup ("@");
1955   else
1956 #endif /* DOMAIN */
1957   if (*DOMAIN != '@') {
1958     Fqdn = mem_malloc (str_len (DOMAIN) + str_len (Hostname) + 2);
1959     sprintf (Fqdn, "%s.%s", NONULL (Hostname), DOMAIN); /* __SPRINTF_CHECKED__ */
1960   }
1961   else
1962     Fqdn = str_dup (NONULL (Hostname));
1963
1964 #ifdef USE_NNTP
1965   {
1966     FILE *f;
1967     char *i;
1968
1969     if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
1970       buffer[0] = '\0';
1971       fgets (buffer, sizeof (buffer), f);
1972       p = (char*) &buffer;
1973       SKIPWS (p);
1974       i = p;
1975       while (*i && (*i != ' ') && (*i != '\t') && (*i != '\r')
1976              && (*i != '\n'))
1977         i++;
1978       *i = '\0';
1979       NewsServer = str_dup (p);
1980       fclose (f);
1981     }
1982   }
1983   if ((p = getenv ("NNTPSERVER")))
1984     NewsServer = str_dup (p);
1985 #endif
1986
1987   if ((p = getenv ("MAIL")))
1988     Spoolfile = str_dup (p);
1989   else if ((p = getenv ("MAILDIR")))
1990     Spoolfile = str_dup (p);
1991   else {
1992 #ifdef HOMESPOOL
1993     mutt_concat_path (buffer, NONULL (Homedir), MAILPATH, sizeof (buffer));
1994 #else
1995     mutt_concat_path (buffer, MAILPATH, NONULL (Username), sizeof (buffer));
1996 #endif
1997     Spoolfile = str_dup (buffer);
1998   }
1999
2000   if ((p = getenv ("MAILCAPS")))
2001     MailcapPath = str_dup (p);
2002   else {
2003     /* Default search path from RFC1524 */
2004     MailcapPath =
2005       str_dup ("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR
2006                    "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap");
2007   }
2008
2009   Tempdir = str_dup ((p = getenv ("TMPDIR")) ? p : "/tmp");
2010
2011   p = getenv ("VISUAL");
2012   if (!p) {
2013     p = getenv ("EDITOR");
2014     if (!p)
2015       p = "vi";
2016   }
2017   Editor = str_dup (p);
2018   Visual = str_dup (p);
2019
2020   if ((p = getenv ("REPLYTO")) != NULL) {
2021     BUFFER buf, token;
2022
2023     snprintf (buffer, sizeof (buffer), "Reply-To: %s", p);
2024
2025     memset (&buf, 0, sizeof (buf));
2026     buf.data = buf.dptr = buffer;
2027     buf.dsize = str_len (buffer);
2028
2029     memset (&token, 0, sizeof (token));
2030     parse_my_hdr (&token, &buf, 0, &err);
2031     mem_free (&token.data);
2032   }
2033
2034   if ((p = getenv ("EMAIL")) != NULL)
2035     From = rfc822_parse_adrlist (NULL, p);
2036
2037   mutt_set_langinfo_charset ();
2038   mutt_set_charset (Charset);
2039
2040
2041   /* Set standard defaults */
2042   for (i = 0; MuttVars[i].option; i++) {
2043     mutt_set_default (&MuttVars[i]);
2044     mutt_restore_default (&MuttVars[i]);
2045   }
2046
2047   CurrentMenu = MENU_MAIN;
2048
2049
2050 #ifndef LOCALES_HACK
2051   /* Do we have a locale definition? */
2052   if (((p = getenv ("LC_ALL")) != NULL && p[0]) ||
2053       ((p = getenv ("LANG")) != NULL && p[0]) ||
2054       ((p = getenv ("LC_CTYPE")) != NULL && p[0]))
2055     set_option (OPTLOCALES);
2056 #endif
2057
2058 #ifdef HAVE_GETSID
2059   /* Unset suspend by default if we're the session leader */
2060   if (getsid (0) == getpid ())
2061     unset_option (OPTSUSPEND);
2062 #endif
2063
2064   mutt_init_history ();
2065
2066
2067
2068
2069   /*
2070    * 
2071    *                       BIG FAT WARNING
2072    * 
2073    * When changing the code which looks for a configuration file,
2074    * please also change the corresponding code in muttbug.sh.in.
2075    * 
2076    * 
2077    */
2078
2079
2080
2081
2082   if (!Muttrc) {
2083     snprintf (buffer, sizeof (buffer), "%s/.muttngrc-%s", NONULL (Homedir),
2084               MUTT_VERSION);
2085     if (access (buffer, F_OK) == -1)
2086       snprintf (buffer, sizeof (buffer), "%s/.muttngrc", NONULL (Homedir));
2087     if (access (buffer, F_OK) == -1)
2088       snprintf (buffer, sizeof (buffer), "%s/.muttng/muttngrc-%s",
2089                 NONULL (Homedir), MUTT_VERSION);
2090     if (access (buffer, F_OK) == -1)
2091       snprintf (buffer, sizeof (buffer), "%s/.muttng/muttngrc",
2092                 NONULL (Homedir));
2093
2094     default_rc = 1;
2095     Muttrc = str_dup (buffer);
2096   }
2097   else {
2098     strfcpy (buffer, Muttrc, sizeof (buffer));
2099     mem_free (&Muttrc);
2100     mutt_expand_path (buffer, sizeof (buffer));
2101     Muttrc = str_dup (buffer);
2102   }
2103   mem_free (&AliasFile);
2104   AliasFile = str_dup (NONULL (Muttrc));
2105
2106   /* Process the global rc file if it exists and the user hasn't explicity
2107      requested not to via "-n".  */
2108   if (!skip_sys_rc) {
2109     snprintf (buffer, sizeof (buffer), "%s/Muttngrc-%s", SYSCONFDIR,
2110               MUTT_VERSION);
2111     if (access (buffer, F_OK) == -1)
2112       snprintf (buffer, sizeof (buffer), "%s/Muttngrc", SYSCONFDIR);
2113     if (access (buffer, F_OK) == -1)
2114       snprintf (buffer, sizeof (buffer), "%s/Muttngrc-%s", PKGDATADIR,
2115                 MUTT_VERSION);
2116     if (access (buffer, F_OK) == -1)
2117       snprintf (buffer, sizeof (buffer), "%s/Muttngrc", PKGDATADIR);
2118     if (access (buffer, F_OK) != -1) {
2119       if (source_rc (buffer, &err) != 0) {
2120         fputs (err.data, stderr);
2121         fputc ('\n', stderr);
2122         need_pause = 1;
2123       }
2124     }
2125   }
2126
2127   /* Read the user's initialization file.  */
2128   if (access (Muttrc, F_OK) != -1) {
2129     if (!option (OPTNOCURSES))
2130       endwin ();
2131     if (source_rc (Muttrc, &err) != 0) {
2132       fputs (err.data, stderr);
2133       fputc ('\n', stderr);
2134       need_pause = 1;
2135     }
2136   }
2137   else if (!default_rc) {
2138     /* file specified by -F does not exist */
2139     snprintf (buffer, sizeof (buffer), "%s: %s", Muttrc, strerror (errno));
2140     mutt_endwin (buffer);
2141     exit (1);
2142   }
2143
2144   if (mutt_execute_commands (commands) != 0)
2145     need_pause = 1;
2146
2147   /* warn about synonym variables */
2148   if (!list_empty(Synonyms)) {
2149     int i = 0;
2150     fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
2151     for (i = 0; i < Synonyms->length; i++)
2152       fprintf (stderr, "$%s ($%s should be used) (%s:%d)\n",
2153                MuttVars[((syn_t*) Synonyms->data[i])->o].option,
2154                MuttVars[((syn_t*) Synonyms->data[i])->n].option,
2155                NONULL(((syn_t*) Synonyms->data[i])->f),
2156                ((syn_t*) Synonyms->data[i])->l);
2157     fprintf (stderr, _("Warning: Synonym variables are scheduled for removal.\n"));
2158     list_del (&Synonyms, syn_del);
2159     need_pause = 1;
2160   }
2161   /* this is not needed during runtime */
2162   mem_free(&CurRCFile);
2163
2164   if (need_pause && !option (OPTNOCURSES)) {
2165     if (mutt_any_key_to_continue (NULL) == -1)
2166       mutt_exit (1);
2167   }
2168
2169 #if 0
2170   set_option (OPTWEED);         /* turn weeding on by default */
2171 #endif
2172 }
2173
2174 int mutt_get_hook_type (const char *name)
2175 {
2176   struct command_t *c;
2177
2178   for (c = Commands; c->name; c++)
2179     if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
2180       return c->data;
2181   return 0;
2182 }
2183
2184 static int opt_cmp (const void* a, const void* b) {
2185   return (str_cmp ((*(struct option_t**) a)->option,
2186                        (*(struct option_t**) b)->option));
2187 }
2188
2189 /* dump out the value of all the variables we have */
2190 int mutt_dump_variables (void) {
2191   int i;
2192
2193   char errbuff[STRING];
2194   char command[STRING];
2195   list2_t* tmp = NULL;
2196
2197   BUFFER err, token;
2198
2199   memset (&err, 0, sizeof (err));
2200   memset (&token, 0, sizeof (token));
2201
2202   err.data = errbuff;
2203   err.dsize = sizeof (errbuff);
2204
2205   /* get all non-synonyms into list... */
2206   for (i = 0; MuttVars[i].option; i++) {
2207     if (MuttVars[i].type == DT_SYN)
2208       continue;
2209     list_push_back (&tmp, &MuttVars[i]);
2210   }
2211   if (!list_empty(tmp)) {
2212     /* ...and dump list sorted */
2213     qsort (tmp->data, tmp->length, sizeof (void*), opt_cmp);
2214     for (i = 0; i < tmp->length; i++) {
2215       snprintf (command, sizeof (command), "set ?%s\n",
2216                 ((struct option_t*) tmp->data[i])->option);
2217       if (mutt_parse_rc_line (command, &token, &err) == -1) {
2218         fprintf (stderr, "%s\n", err.data);
2219         mem_free (&token.data);
2220         list_del (&tmp, NULL);
2221         return 1;
2222       }
2223       printf("%s\n", err.data);
2224     }
2225   }
2226   mem_free (&token.data);
2227   list_del (&tmp, NULL);
2228   return 0;
2229 }