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