]> git.llucax.com Git - software/mutt-debian.git/blob - rfc822.c
Merge commit 'upstream/1.5.20'
[software/mutt-debian.git] / rfc822.c
1 /*
2  * Copyright (C) 1996-2000 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */ 
18
19 #if HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 #include <string.h>
24 #include <ctype.h>
25 #include <stdlib.h>
26
27 #ifndef TESTING
28 #include "mutt.h"
29 #else
30 #define safe_strdup strdup
31 #define safe_malloc malloc
32 #define SKIPWS(x) while(isspace(*x))x++
33 #define FREE(x) safe_free(x)
34 #define ISSPACE isspace
35 #define strfcpy(a,b,c) {if (c) {strncpy(a,b,c);a[c-1]=0;}}
36 #define LONG_STRING 1024
37 #include "rfc822.h"
38 #endif
39
40 #include "mutt_idna.h"
41
42 #define terminate_string(a, b, c) do { if ((b) < (c)) a[(b)] = 0; else \
43         a[(c)] = 0; } while (0)
44
45 #define terminate_buffer(a, b) terminate_string(a, b, sizeof (a) - 1)
46
47
48 const char RFC822Specials[] = "@.,:;<>[]\\\"()";
49 #define is_special(x) strchr(RFC822Specials,x)
50
51 int RFC822Error = 0;
52
53 /* these must defined in the same order as the numerated errors given in rfc822.h */
54 const char *RFC822Errors[] = {
55   "out of memory",
56   "mismatched parenthesis",
57   "mismatched quotes",
58   "bad route in <>",
59   "bad address in <>",
60   "bad address spec"
61 };
62
63 static void rfc822_dequote_comment (char *s)
64 {
65   char *w = s;
66
67   for (; *s; s++)
68   {
69     if (*s == '\\')
70     {
71       if (!*++s)
72         break; /* error? */
73       *w++ = *s;
74     }
75     else if (*s != '\"')
76     {
77       if (w != s)
78         *w = *s;
79       w++;
80     }
81   }
82   *w = 0;
83 }
84
85 void rfc822_free_address (ADDRESS **p)
86 {
87   ADDRESS *t;
88
89   while (*p)
90   {
91     t = *p;
92     *p = (*p)->next;
93 #ifdef EXACT_ADDRESS
94     FREE (&t->val);
95 #endif
96     FREE (&t->personal);
97     FREE (&t->mailbox);
98     FREE (&t);
99   }
100 }
101
102 static const char *
103 parse_comment (const char *s,
104                char *comment, size_t *commentlen, size_t commentmax)
105 {
106   int level = 1;
107   
108   while (*s && level)
109   {
110     if (*s == '(')
111       level++;
112     else if (*s == ')')
113     {
114       if (--level == 0)
115       {
116         s++;
117         break;
118       }
119     }
120     else if (*s == '\\')
121     {
122       if (!*++s)
123         break;
124     }
125     if (*commentlen < commentmax)
126       comment[(*commentlen)++] = *s;
127     s++;
128   }
129   if (level)
130   {
131     RFC822Error = ERR_MISMATCH_PAREN;
132     return NULL;
133   }
134   return s;
135 }
136
137 static const char *
138 parse_quote (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
139 {
140   if (*tokenlen < tokenmax)
141     token[(*tokenlen)++] = '"';
142   while (*s)
143   {
144     if (*tokenlen < tokenmax)
145       token[*tokenlen] = *s;
146     if (*s == '"')
147     {
148       (*tokenlen)++;
149       return (s + 1);
150     }
151     if (*s == '\\')
152     {
153       if (!*++s)
154         break;
155
156       if (*tokenlen < tokenmax)
157         token[*tokenlen] = *s;
158     }
159     (*tokenlen)++;
160     s++;
161   }
162   RFC822Error = ERR_MISMATCH_QUOTE;
163   return NULL;
164 }
165
166 static const char *
167 next_token (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
168 {
169   if (*s == '(')
170     return (parse_comment (s + 1, token, tokenlen, tokenmax));
171   if (*s == '"')
172     return (parse_quote (s + 1, token, tokenlen, tokenmax));
173   if (is_special (*s))
174   {
175     if (*tokenlen < tokenmax)
176       token[(*tokenlen)++] = *s;
177     return (s + 1);
178   }
179   while (*s)
180   {
181     if (ISSPACE ((unsigned char) *s) || is_special (*s))
182       break;
183     if (*tokenlen < tokenmax)
184       token[(*tokenlen)++] = *s;
185     s++;
186   }
187   return s;
188 }
189
190 static const char *
191 parse_mailboxdomain (const char *s, const char *nonspecial,
192                      char *mailbox, size_t *mailboxlen, size_t mailboxmax,
193                      char *comment, size_t *commentlen, size_t commentmax)
194 {
195   const char *ps;
196
197   while (*s)
198   {
199     SKIPWS (s);
200     if (strchr (nonspecial, *s) == NULL && is_special (*s))
201       return s;
202
203     if (*s == '(')
204     {
205       if (*commentlen && *commentlen < commentmax)
206         comment[(*commentlen)++] = ' ';
207       ps = next_token (s, comment, commentlen, commentmax);
208     }
209     else
210       ps = next_token (s, mailbox, mailboxlen, mailboxmax);
211     if (!ps)
212       return NULL;
213     s = ps;
214   }
215
216   return s;
217 }
218
219 static const char *
220 parse_address (const char *s,
221                char *token, size_t *tokenlen, size_t tokenmax,
222                char *comment, size_t *commentlen, size_t commentmax,
223                ADDRESS *addr)
224 {
225   s = parse_mailboxdomain (s, ".\"(\\",
226                            token, tokenlen, tokenmax,
227                            comment, commentlen, commentmax);
228   if (!s)
229     return NULL;
230
231   if (*s == '@')
232   {
233     if (*tokenlen < tokenmax)
234       token[(*tokenlen)++] = '@';
235     s = parse_mailboxdomain (s + 1, ".([]\\",
236                              token, tokenlen, tokenmax,
237                              comment, commentlen, commentmax);
238     if (!s)
239       return NULL;
240   }
241
242   terminate_string (token, *tokenlen, tokenmax);
243   addr->mailbox = safe_strdup (token);
244
245   if (*commentlen && !addr->personal)
246   {
247     terminate_string (comment, *commentlen, commentmax);
248     addr->personal = safe_strdup (comment);
249   }
250
251   return s;
252 }
253
254 static const char *
255 parse_route_addr (const char *s,
256                   char *comment, size_t *commentlen, size_t commentmax,
257                   ADDRESS *addr)
258 {
259   char token[LONG_STRING];
260   size_t tokenlen = 0;
261
262   SKIPWS (s);
263
264   /* find the end of the route */
265   if (*s == '@')
266   {
267     while (s && *s == '@')
268     {
269       if (tokenlen < sizeof (token) - 1)
270         token[tokenlen++] = '@';
271       s = parse_mailboxdomain (s + 1, ",.\\[](", token,
272                                &tokenlen, sizeof (token) - 1,
273                                comment, commentlen, commentmax);
274     }
275     if (!s || *s != ':')
276     {
277       RFC822Error = ERR_BAD_ROUTE;
278       return NULL; /* invalid route */
279     }
280
281     if (tokenlen < sizeof (token) - 1)
282       token[tokenlen++] = ':';
283     s++;
284   }
285
286   if ((s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr)) == NULL)
287     return NULL;
288
289   if (*s != '>')
290   {
291     RFC822Error = ERR_BAD_ROUTE_ADDR;
292     return NULL;
293   }
294
295   if (!addr->mailbox)
296     addr->mailbox = safe_strdup ("@");
297
298   s++;
299   return s;
300 }
301
302 static const char *
303 parse_addr_spec (const char *s,
304                  char *comment, size_t *commentlen, size_t commentmax,
305                  ADDRESS *addr)
306 {
307   char token[LONG_STRING];
308   size_t tokenlen = 0;
309
310   s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr);
311   if (s && *s && *s != ',' && *s != ';')
312   {
313     RFC822Error = ERR_BAD_ADDR_SPEC;
314     return NULL;
315   }
316   return s;
317 }
318
319 static void
320 add_addrspec (ADDRESS **top, ADDRESS **last, const char *phrase,
321               char *comment, size_t *commentlen, size_t commentmax)
322 {
323   ADDRESS *cur = rfc822_new_address ();
324   
325   if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL)
326   {
327     rfc822_free_address (&cur);
328     return;
329   }
330
331   if (*last)
332     (*last)->next = cur;
333   else
334     *top = cur;
335   *last = cur;
336 }
337
338 ADDRESS *rfc822_parse_adrlist (ADDRESS *top, const char *s)
339 {
340   int ws_pending, nl;
341   const char *begin, *ps;
342   char comment[LONG_STRING], phrase[LONG_STRING];
343   size_t phraselen = 0, commentlen = 0;
344   ADDRESS *cur, *last = NULL;
345   
346   RFC822Error = 0;
347
348   last = top;
349   while (last && last->next)
350     last = last->next;
351
352   ws_pending = isspace ((unsigned char) *s);
353   if ((nl = mutt_strlen (s)))
354     nl = s[nl - 1] == '\n';
355   
356   SKIPWS (s);
357   begin = s;
358   while (*s)
359   {
360     if (*s == ',')
361     {
362       if (phraselen)
363       {
364         terminate_buffer (phrase, phraselen);
365         add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
366       }
367       else if (commentlen && last && !last->personal)
368       {
369         terminate_buffer (comment, commentlen);
370         last->personal = safe_strdup (comment);
371       }
372
373 #ifdef EXACT_ADDRESS
374       if (last && !last->val)
375         last->val = mutt_substrdup (begin, s);
376 #endif
377       commentlen = 0;
378       phraselen = 0;
379       s++;
380       begin = s;
381       SKIPWS (begin);
382     }
383     else if (*s == '(')
384     {
385       if (commentlen && commentlen < sizeof (comment) - 1)
386         comment[commentlen++] = ' ';
387       if ((ps = next_token (s, comment, &commentlen, sizeof (comment) - 1)) == NULL)
388       {
389         rfc822_free_address (&top);
390         return NULL;
391       }
392       s = ps;
393     }
394     else if (*s == ':')
395     {
396       cur = rfc822_new_address ();
397       terminate_buffer (phrase, phraselen);
398       cur->mailbox = safe_strdup (phrase);
399       cur->group = 1;
400
401       if (last)
402         last->next = cur;
403       else
404         top = cur;
405       last = cur;
406
407 #ifdef EXACT_ADDRESS
408       last->val = mutt_substrdup (begin, s);
409 #endif
410
411       phraselen = 0;
412       commentlen = 0;
413       s++;
414       begin = s;
415       SKIPWS (begin);
416     }
417     else if (*s == ';')
418     {
419       if (phraselen)
420       {
421         terminate_buffer (phrase, phraselen);
422         add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
423       }
424       else if (commentlen && last && !last->personal)
425       {
426         terminate_buffer (comment, commentlen);
427         last->personal = safe_strdup (comment);
428       }
429 #ifdef EXACT_ADDRESS
430       if (last && !last->val)
431         last->val = mutt_substrdup (begin, s);
432 #endif
433
434       /* add group terminator */
435       cur = rfc822_new_address ();
436       if (last)
437       {
438         last->next = cur;
439         last = cur;
440       }
441
442       phraselen = 0;
443       commentlen = 0;
444       s++;
445       begin = s;
446       SKIPWS (begin);
447     }
448     else if (*s == '<')
449     {
450       terminate_buffer (phrase, phraselen);
451       cur = rfc822_new_address ();
452       if (phraselen)
453       {
454         if (cur->personal)
455           FREE (&cur->personal);
456         /* if we get something like "Michael R. Elkins" remove the quotes */
457         rfc822_dequote_comment (phrase);
458         cur->personal = safe_strdup (phrase);
459       }
460       if ((ps = parse_route_addr (s + 1, comment, &commentlen, sizeof (comment) - 1, cur)) == NULL)
461       {
462         rfc822_free_address (&top);
463         rfc822_free_address (&cur);
464         return NULL;
465       }
466
467       if (last)
468         last->next = cur;
469       else
470         top = cur;
471       last = cur;
472
473       phraselen = 0;
474       commentlen = 0;
475       s = ps;
476     }
477     else
478     {
479       if (phraselen && phraselen < sizeof (phrase) - 1 && ws_pending)
480         phrase[phraselen++] = ' ';
481       if ((ps = next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL)
482       {
483         rfc822_free_address (&top);
484         return NULL;
485       }
486       s = ps;
487     }
488     ws_pending = isspace ((unsigned char) *s);
489     SKIPWS (s);
490   }
491   
492   if (phraselen)
493   {
494     terminate_buffer (phrase, phraselen);
495     terminate_buffer (comment, commentlen);
496     add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
497   }
498   else if (commentlen && last && !last->personal)
499   {
500     terminate_buffer (comment, commentlen);
501     last->personal = safe_strdup (comment);
502   }
503 #ifdef EXACT_ADDRESS
504   if (last)
505     last->val = mutt_substrdup (begin, s - nl < begin ? begin : s - nl);
506 #endif
507
508   return top;
509 }
510
511 void rfc822_qualify (ADDRESS *addr, const char *host)
512 {
513   char *p;
514
515   for (; addr; addr = addr->next)
516     if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL)
517     {
518       p = safe_malloc (mutt_strlen (addr->mailbox) + mutt_strlen (host) + 2);
519       sprintf (p, "%s@%s", addr->mailbox, host);        /* __SPRINTF_CHECKED__ */
520       FREE (&addr->mailbox);
521       addr->mailbox = p;
522     }
523 }
524
525 void
526 rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials)
527 {
528   if (strpbrk (value, specials))
529   {
530     char tmp[256], *pc = tmp;
531     size_t tmplen = sizeof (tmp) - 3;
532
533     *pc++ = '"';
534     for (; *value && tmplen > 1; value++)
535     {
536       if (*value == '\\' || *value == '"')
537       {
538         *pc++ = '\\';
539         tmplen--;
540       }
541       *pc++ = *value;
542       tmplen--;
543     }
544     *pc++ = '"';
545     *pc = 0;
546     strfcpy (buf, tmp, buflen);
547   }
548   else
549     strfcpy (buf, value, buflen);
550 }
551
552 void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS *addr,
553                                   int display)
554 {
555   size_t len;
556   char *pbuf = buf;
557   char *pc;
558   
559   if (!addr)
560     return;
561
562   buflen--; /* save room for the terminal nul */
563
564 #ifdef EXACT_ADDRESS
565   if (addr->val)
566   {
567     if (!buflen)
568       goto done;
569     strfcpy (pbuf, addr->val, buflen);
570     len = mutt_strlen (pbuf);
571     pbuf += len;
572     buflen -= len;
573     if (addr->group)
574     {
575       if (!buflen)
576         goto done;
577       *pbuf++ = ':';
578       buflen--;
579       *pbuf = 0;
580     }
581     return;
582   }
583 #endif
584
585   if (addr->personal)
586   {
587     if (strpbrk (addr->personal, RFC822Specials))
588     {
589       if (!buflen)
590         goto done;
591       *pbuf++ = '"';
592       buflen--;
593       for (pc = addr->personal; *pc && buflen > 0; pc++)
594       {
595         if (*pc == '"' || *pc == '\\')
596         {
597           *pbuf++ = '\\';
598           buflen--;
599         }
600         if (!buflen)
601           goto done;
602         *pbuf++ = *pc;
603         buflen--;
604       }
605       if (!buflen)
606         goto done;
607       *pbuf++ = '"';
608       buflen--;
609     }
610     else
611     {
612       if (!buflen)
613         goto done;
614       strfcpy (pbuf, addr->personal, buflen);
615       len = mutt_strlen (pbuf);
616       pbuf += len;
617       buflen -= len;
618     }
619
620     if (!buflen)
621       goto done;
622     *pbuf++ = ' ';
623     buflen--;
624   }
625
626   if (addr->personal || (addr->mailbox && *addr->mailbox == '@'))
627   {
628     if (!buflen)
629       goto done;
630     *pbuf++ = '<';
631     buflen--;
632   }
633
634   if (addr->mailbox)
635   {
636     if (!buflen)
637       goto done;
638     if (ascii_strcmp (addr->mailbox, "@") && !display)
639     {
640       strfcpy (pbuf, addr->mailbox, buflen);
641       len = mutt_strlen (pbuf);
642     }
643     else if (ascii_strcmp (addr->mailbox, "@") && display)
644     {
645       strfcpy (pbuf, mutt_addr_for_display (addr), buflen);
646       len = mutt_strlen (pbuf);
647     }
648     else
649     {
650       *pbuf = '\0';
651       len = 0;
652     }
653     pbuf += len;
654     buflen -= len;
655
656     if (addr->personal || (addr->mailbox && *addr->mailbox == '@'))
657     {
658       if (!buflen)
659         goto done;
660       *pbuf++ = '>';
661       buflen--;
662     }
663
664     if (addr->group)
665     {
666       if (!buflen)
667         goto done;
668       *pbuf++ = ':';
669       buflen--;
670       if (!buflen)
671         goto done;
672       *pbuf++ = ' ';
673       buflen--;
674     }
675   }
676   else
677   {
678     if (!buflen)
679       goto done;
680     *pbuf++ = ';';
681     buflen--;
682   }
683 done:
684   /* no need to check for length here since we already save space at the
685      beginning of this routine */
686   *pbuf = 0;
687 }
688
689 /* note: it is assumed that `buf' is nul terminated! */
690 int rfc822_write_address (char *buf, size_t buflen, ADDRESS *addr, int display)
691 {
692   char *pbuf = buf;
693   size_t len = mutt_strlen (buf);
694   
695   buflen--; /* save room for the terminal nul */
696
697   if (len > 0)
698   {
699     if (len > buflen)
700       return pbuf - buf; /* safety check for bogus arguments */
701
702     pbuf += len;
703     buflen -= len;
704     if (!buflen)
705       goto done;
706     *pbuf++ = ',';
707     buflen--;
708     if (!buflen)
709       goto done;
710     *pbuf++ = ' ';
711     buflen--;
712   }
713
714   for (; addr && buflen > 0; addr = addr->next)
715   {
716     /* use buflen+1 here because we already saved space for the trailing
717        nul char, and the subroutine can make use of it */
718     rfc822_write_address_single (pbuf, buflen + 1, addr, display);
719
720     /* this should be safe since we always have at least 1 char passed into
721        the above call, which means `pbuf' should always be nul terminated */
722     len = mutt_strlen (pbuf);
723     pbuf += len;
724     buflen -= len;
725
726     /* if there is another address, and its not a group mailbox name or
727        group terminator, add a comma to separate the addresses */
728     if (addr->next && addr->next->mailbox && !addr->group)
729     {
730       if (!buflen)
731         goto done;
732       *pbuf++ = ',';
733       buflen--;
734       if (!buflen)
735         goto done;
736       *pbuf++ = ' ';
737       buflen--;
738     }
739   }
740 done:
741   *pbuf = 0;
742   return pbuf - buf;
743 }
744
745 /* this should be rfc822_cpy_adr */
746 ADDRESS *rfc822_cpy_adr_real (ADDRESS *addr)
747 {
748   ADDRESS *p = rfc822_new_address ();
749
750 #ifdef EXACT_ADDRESS
751   p->val = safe_strdup (addr->val);
752 #endif
753   p->personal = safe_strdup (addr->personal);
754   p->mailbox = safe_strdup (addr->mailbox);
755   p->group = addr->group;
756   return p;
757 }
758
759 /* this should be rfc822_cpy_adrlist */
760 ADDRESS *rfc822_cpy_adr (ADDRESS *addr, int prune)
761 {
762   ADDRESS *top = NULL, *last = NULL;
763   
764   for (; addr; addr = addr->next)
765   {
766     if (prune && addr->group && (!addr->next || !addr->next->mailbox))
767     {
768       addr = addr->next;
769       continue;
770     }
771     if (last)
772     {
773       last->next = rfc822_cpy_adr_real (addr);
774       last = last->next;
775     }
776     else
777       top = last = rfc822_cpy_adr_real (addr);
778   }
779   return top;
780 }
781
782 /* append list 'b' to list 'a' and return the last element in the new list */
783 ADDRESS *rfc822_append (ADDRESS **a, ADDRESS *b, int prune)
784 {
785   ADDRESS *tmp = *a;
786
787   while (tmp && tmp->next)
788     tmp = tmp->next;
789   if (!b)
790     return tmp;
791   if (tmp)
792     tmp->next = rfc822_cpy_adr (b, prune);
793   else
794     tmp = *a = rfc822_cpy_adr (b, prune);
795   while (tmp && tmp->next)
796     tmp = tmp->next;
797   return tmp;
798 }
799
800 /* incomplete. Only used to thwart the APOP MD5 attack (#2846). */
801 int rfc822_valid_msgid (const char *msgid)
802 {
803   /* msg-id         = "<" addr-spec ">"
804    * addr-spec      = local-part "@" domain
805    * local-part     = word *("." word)
806    * word           = atom / quoted-string
807    * atom           = 1*<any CHAR except specials, SPACE and CTLs>
808    * CHAR           = ( 0.-127. )
809    * specials       = "(" / ")" / "<" / ">" / "@"
810                     / "," / ";" / ":" / "\" / <">
811                     / "." / "[" / "]"
812    * SPACE          = ( 32. )
813    * CTLS           = ( 0.-31., 127.)
814    * quoted-string  = <"> *(qtext/quoted-pair) <">
815    * qtext          = <any CHAR except <">, "\" and CR>
816    * CR             = ( 13. )
817    * quoted-pair    = "\" CHAR
818    * domain         = sub-domain *("." sub-domain)
819    * sub-domain     = domain-ref / domain-literal
820    * domain-ref     = atom
821    * domain-literal = "[" *(dtext / quoted-pair) "]"
822    */
823
824   unsigned int l, i;
825
826   if (!msgid || !*msgid)
827     return -1;
828
829   l = mutt_strlen (msgid);
830   if (l < 5) /* <atom@atom> */
831     return -1;
832   if (msgid[0] != '<' || msgid[l-1] != '>')
833     return -1;
834   if (!(strrchr (msgid, '@')))
835     return -1;
836
837   /* TODO: complete parser */
838   for (i = 0; i < l; i++)
839     if ((unsigned char)msgid[i] > 127)
840       return -1;
841
842   return 0;
843 }
844
845 #ifdef TESTING
846 int safe_free (void **p)        /* __SAFE_FREE_CHECKED__ */
847 {
848   free(*p);             /* __MEM_CHECKED__ */
849   *p = 0;
850 }
851
852 int main (int argc, char **argv)
853 {
854   ADDRESS *list;
855   char buf[256];
856 # if 0
857   char *str = "michael, Michael Elkins <me@mutt.org>, testing a really complex address: this example <@contains.a.source.route,@with.multiple.hosts:address@example.com>;, lothar@of.the.hillpeople (lothar)";
858 # else
859   char *str = "a b c ";
860 # endif
861   
862   list = rfc822_parse_adrlist (NULL, str);
863   buf[0] = 0;
864   rfc822_write_address (buf, sizeof (buf), list);
865   rfc822_free_address (&list);
866   puts (buf);
867   exit (0);
868 }
869 #endif