]> Pileus Git - ~andy/fetchmail/blob - unmime.c
Prepare 6.5.0.beta1.
[~andy/fetchmail] / unmime.c
1 /*
2  * MIME mail decoding.
3  *
4  * This module contains decoding routines for converting
5  * quoted-printable data into pure 8-bit data, in MIME
6  * formatted messages.
7  *
8  * By Henrik Storner <storner@image.dk>
9  *
10  * Configuration file support for fetchmail 4.3.8 by 
11  * Frank Damgaard <frda@post3.tele.dk>
12  * 
13  * For license terms, see the file COPYING in this directory.
14  */
15
16 #include "fetchmail.h"
17 #include "config.h"
18 #include <string.h>
19 #include <strings.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <ctype.h>
23 #include "i18n.h"
24
25 static unsigned char unhex(unsigned char c)
26 {
27   if ((c >= '0') && (c <= '9'))
28     return (c - '0');
29   else if ((c >= 'A') && (c <= 'F'))
30     return (c - 'A' + 10);
31   else if ((c >= 'a') && (c <= 'f'))
32     return (c - 'a' + 10);
33   else
34       return 16;        /* invalid hex character */
35 }
36
37 static int qp_char(unsigned char c1, unsigned char c2, char *c_out)
38 {
39   c1 = unhex(c1);
40   c2 = unhex(c2);
41
42   if ((c1 > 15) || (c2 > 15)) 
43     return 1;
44   else {
45     *c_out = 16*c1+c2;
46     return 0;
47   }
48 }
49
50
51 /*
52  * Routines to decode MIME QP-encoded headers, as per RFC 2047.
53  */
54
55 /* States of the decoding state machine */
56 #define S_COPY_PLAIN        0   /* Just copy, but watch for the QP flag */
57 #define S_SKIP_MIMEINIT     1   /* Get the encoding, and skip header */
58 #define S_COPY_MIME         2   /* Decode a sequence of coded characters */
59
60 static const char MIMEHDR_INIT[]  = "=?";       /* Start of coded sequence */
61 static const char MIMEHDR_END[]   = "?=";       /* End of coded sequence */
62
63 void UnMimeHeader(char *hdr)
64 {
65   /* Decode a buffer containing data encoded according to RFC
66    * 2047. This only handles content-transfer-encoding; conversion
67    * between character sets is not implemented.  In other words: We
68    * assume the charsets used can be displayed by your mail program
69    * without problems. 
70    */
71
72   /* Note: Decoding is done "in-situ", i.e. without using an
73    * additional buffer for temp. storage. This is possible, since the
74    * decoded string will always be shorter than the encoded string,
75    * due to the encoding scheme.
76    */
77
78   int  state = S_COPY_PLAIN;
79   char *p_in, *p_out, *p;
80   char enc = '\0';              /* initialization pacifies -Wall */
81   int  i;
82
83   /* Speed up in case this is not a MIME-encoded header */
84   p = strstr(hdr, MIMEHDR_INIT);
85   if (p == NULL)
86     return;   /* No MIME header */
87
88   /* Loop through the buffer.
89    *  p_in : Next char to be processed.
90    *  p_out: Where to put the next processed char
91    *  enc  : Encoding used (usually, 'q' = quoted-printable)
92    */
93   for (p_out = p_in = hdr; (*p_in); ) {
94     switch (state) {
95     case S_COPY_PLAIN:
96       p = strstr(p_in, MIMEHDR_INIT);
97       if (p == NULL) {
98         /* 
99          * No more coded data in buffer, 
100          * just move remainder into place. 
101          */
102         i = strlen(p_in);   /* How much left */
103         memmove(p_out, p_in, i);
104         p_in += i; p_out += i;
105       }
106       else {
107         /* MIME header init found at location p */
108         if (p > p_in) {
109           /* There are some uncoded chars at the beginning. */
110           i = (p - p_in);
111           memmove(p_out, p_in, i);
112           p_out += i;
113         }
114         p_in = (p + 2);
115         state = S_SKIP_MIMEINIT;
116       }
117       break;
118
119     case S_SKIP_MIMEINIT:
120       /* Mime type definition: "charset?encoding?" */
121       p = strchr(p_in, '?');
122       if (p != NULL) {
123         /* p_in .. (p-1) holds the charset */
124
125         /* *(p+1) is the transfer encoding, *(p+2) must be a '?' */
126         if (*(p+2) == '?') {
127           enc = tolower((unsigned char)*(p+1));
128           p_in = p+3;
129           state = S_COPY_MIME;
130         }
131         else
132           state = S_COPY_PLAIN;
133       }
134       else
135         state = S_COPY_PLAIN;   /* Invalid data */
136       break;
137
138     case S_COPY_MIME:
139       p = strstr(p_in, MIMEHDR_END);  /* Find end of coded data */
140       if (p == NULL) p = p_in + strlen(p_in);
141       for (; (p_in < p); ) {
142         /* Decode all encoded data */
143         if (enc == 'q') {
144           if (*p_in == '=') {
145             /* Decode one char qp-coded at (p_in+1) and (p_in+2) */
146             if (qp_char(*(p_in+1), *(p_in+2), p_out) == 0)
147               p_in += 3;
148             else {
149               /* Invalid QP data - pass through unchanged. */
150               *p_out = *p_in;
151               p_in++;
152             }
153           }
154           else if (*p_in == '_') {
155             /* 
156              * RFC 2047: '_' inside encoded word represents 0x20.
157              * NOT a space - always the value 0x20.
158              */
159             *p_out = 0x20;
160             p_in++;
161           }
162           else {
163             /* Copy unchanged */
164             *p_out = *p_in;
165             p_in++;
166           }
167           p_out++;
168         }
169         else if (enc == 'b') {
170           /* Decode base64 encoded data */
171           char delimsave;
172           int decoded_count;
173
174           delimsave = *p; *p = '\r';
175           decoded_count = from64tobits(p_out, p_in, 0);
176           *p = delimsave;
177           if (decoded_count > 0) 
178             p_out += decoded_count;            
179           p_in = p;
180         }
181         else {
182           /* Copy unchanged */
183           *p_out = *p_in;
184           p_in++;
185           p_out++;
186         }
187       }
188       if (*p_in)
189         p_in += 2;   /* Skip the MIMEHDR_END delimiter */
190
191       /* 
192        * We've completed decoding one encoded sequence. But another
193        * may follow immediately, in which case whitespace before the
194        * new MIMEHDR_INIT delimiter must be discarded.
195        * See if that is the case 
196        */
197       p = strstr(p_in, MIMEHDR_INIT);
198       state = S_COPY_PLAIN;
199       if (p != NULL) {
200         /*
201          * There is more MIME data later on. Is there
202          * whitespace  only before the delimiter? 
203          */
204         char *q;
205         int  wsp_only = 1;
206
207         for (q=p_in; (wsp_only && (q < p)); q++)
208           wsp_only = isspace((unsigned char)*q);
209
210         if (wsp_only) {
211           /* 
212            * Whitespace-only before the MIME delimiter. OK,
213            * just advance p_in to past the new MIMEHDR_INIT,
214            * and prepare to process the new MIME charset/encoding
215            * header.
216            */
217           p_in = p + sizeof(MIMEHDR_INIT) - 1;
218           state = S_SKIP_MIMEINIT;
219         }
220       }
221       break;
222     }
223   }
224
225   *p_out = '\0';
226 }
227
228
229
230 /*
231  * Routines for decoding body-parts of a message.
232  *
233  * Since the "fetch" part of fetchmail gets a message body
234  * one line at a time, we need to maintain some state variables
235  * across multiple invokations of the UnMimeBodyline() routine.
236  * The driver routine should call MimeBodyType() when all
237  * headers have been received, and then UnMimeBodyline() for
238  * every line in the message body.
239  *
240  */
241 #define S_BODY_DATA 0
242 #define S_BODY_HDR  1
243
244 /* 
245  * Flag indicating if we are currently processing 
246  * the headers or the body of a (multipart) message.
247  */
248 static int  BodyState = S_BODY_DATA;
249
250 /* 
251  * Flag indicating if we are in the process of decoding
252  * a quoted-printable body part.
253  */
254 static int  CurrEncodingIsQP = 0;
255 static int  CurrTypeNeedsDecode = 0;
256
257 /* 
258  * Delimiter for multipart messages. RFC 2046 states that this must
259  * NEVER be longer than 70 characters. Add 3 for the two hyphens
260  * at the beginning, and a terminating null.
261  */
262 #define MAX_DELIM_LEN 70
263 static char MultipartDelimiter[MAX_DELIM_LEN+3];
264
265
266 /* This string replaces the "Content-Transfer-Encoding: quoted-printable"
267  * string in all headers, including those in body-parts. The replacement
268  * must be no longer than the original string.
269  */
270 static const char ENC8BIT[] = "Content-Transfer-Encoding: 8bit";
271 static void SetEncoding8bit(char *XferEncOfs)
272 {
273   char *p;
274
275   if (XferEncOfs != NULL) {
276      memcpy(XferEncOfs, ENC8BIT, sizeof(ENC8BIT) - 1);
277
278      /* If anything left, in this header, replace with whitespace */
279      for (p=XferEncOfs+sizeof(ENC8BIT)-1; ((unsigned char)*p >= ' '); p++)
280        *p=' ';
281   }
282 }
283
284 static char *GetBoundary(char *CntType)
285 {
286   char *p1, *p2;
287   int flag;
288
289   /* Find the "boundary" delimiter. It must be preceded with a ';'
290    * and optionally some whitespace.
291    */
292   p1 = CntType;
293   do {
294     p2 = strchr(p1, ';'); 
295     if (p2)
296       for (p2++; isspace((unsigned char)*p2); p2++) { }
297
298     p1 = p2;
299   } while ((p1) && (strncasecmp(p1, "boundary", 8) != 0));
300
301   if (p1 == NULL)
302     /* No boundary delimiter */
303     return NULL;
304
305   /* Skip "boundary", whitespace and '='; check that we do have a '=' */
306   for (p1+=8, flag=0; (isspace((unsigned char)*p1) || (*p1 == '=')); p1++)
307     flag |= (*p1 == '=');
308   if (!flag)
309     return NULL;
310
311   /* Find end of boundary delimiter string */
312   if (*p1 == '\"') {
313     /* The delimiter is inside quotes */
314     p1++;
315     p2 = strchr(p1, '\"');
316     if (p2 == NULL)
317       return NULL;  /* No closing '"' !?! */
318   }
319   else {
320     /* There might be more text after the "boundary" string. */
321     p2 = strchr(p1, ';');  /* Safe - delimiter with ';' must be in quotes */
322   }
323
324   /* Zero-terminate the boundary string */
325   if (p2 != NULL)
326     *p2 = '\0';
327
328   return (p1 && strlen(p1)) ? p1 : NULL;
329 }
330
331
332 static int CheckContentType(char *CntType)
333 {
334   /*
335    * Static array of Content-Type's for which we will do
336    * quoted-printable decoding, if requested. 
337    * It is probably wise to do this only on known text-only types;
338    * be really careful if you change this.
339    */
340
341   static const char *DecodedTypes[] = {
342     "text/",        /* Will match ALL content-type's starting with 'text/' */
343     "message/rfc822", 
344     NULL
345   };
346
347   char *p = CntType;
348   int i;
349
350   /* If no Content-Type header, it isn't MIME - don't touch it */
351   if (CntType == NULL) return 0;
352
353   /* Skip whitespace, if any */
354   for (; isspace((unsigned char)*p); p++) ;
355
356   for (i=0; 
357        (DecodedTypes[i] && 
358         (strncasecmp(p, DecodedTypes[i], strlen(DecodedTypes[i])))); 
359        i++) ;
360
361   return (DecodedTypes[i] != NULL);
362 }
363
364
365 /*
366  * This routine does three things:
367  * 1) It determines - based on the message headers - whether the
368  *    message body is a MIME message that may hold 8 bit data.
369  *    - A message that has a "quoted-printable" or "8bit" transfer 
370  *      encoding is assumed to contain 8-bit data (when decoded).
371  *    - A multipart message is assumed to contain 8-bit data
372  *      when decoded (there might be quoted-printable body-parts).
373  *    - All other messages are assumed NOT to include 8-bit data.
374  * 2) It determines the delimiter-string used in multi-part message
375  *    bodies.
376  * 3) It sets the initial values of the CurrEncodingIsQP, 
377  *    CurrTypeNeedsDecode, and BodyState variables, from the header 
378  *    contents.
379  *
380  * The return value is a bitmask.
381  */
382 int MimeBodyType(char *hdrs, int WantDecode)
383 {
384     char *NxtHdr = hdrs;
385     char *XferEnc, *XferEncOfs, *CntType, *MimeVer, *p;
386     int  HdrsFound = 0;     /* We only look for three headers */
387     int  BodyType;          /* Return value */ 
388
389     /* Setup for a standard (no MIME, no QP, 7-bit US-ASCII) message */
390     MultipartDelimiter[0] = '\0';
391     CurrEncodingIsQP = CurrTypeNeedsDecode = 0;
392     BodyState = S_BODY_DATA;
393     BodyType = 0;
394
395     /* Just in case ... */
396     if (hdrs == NULL)
397         return BodyType;
398
399     XferEnc = XferEncOfs = CntType = MimeVer = NULL;
400
401     do {
402         if (strncasecmp("Content-Transfer-Encoding:", NxtHdr, 26) == 0) {
403             XferEncOfs = NxtHdr;
404             p = nxtaddr(NxtHdr);
405             if (p != NULL) {
406                 xfree(XferEnc);
407                 XferEnc = xstrdup(p);
408                 HdrsFound++;
409             }
410         }
411         else if (strncasecmp("Content-Type:", NxtHdr, 13) == 0) {
412             /*
413              * This one is difficult. We cannot use the standard
414              * nxtaddr() routine, since the boundary-delimiter is
415              * (probably) enclosed in quotes - and thus appears
416              * as an rfc822 comment, and nxtaddr() "eats" up any
417              * spaces in the delimiter. So, we have to do this
418              * by hand.
419              */
420
421             /* Skip the "Content-Type:" part and whitespace after it */
422             for (NxtHdr += 13; ((*NxtHdr == ' ') || (*NxtHdr == '\t')); NxtHdr++) { }
423
424             /* 
425              * Get the full value of the Content-Type header;
426              * it might span multiple lines. So search for
427              * a newline char, but ignore those that have a
428              * have a TAB or space just after the NL (continued
429              * lines).
430              */
431             p = NxtHdr-1;
432             do {
433                 p=strchr((p+1),'\n'); 
434             } while ( (p != NULL) && ((*(p+1) == '\t') || (*(p+1) == ' ')) );
435             if (p == NULL) p = NxtHdr + strlen(NxtHdr);
436
437             xfree(CntType);
438             CntType = (char *)xmalloc(p-NxtHdr+1);
439             strlcpy(CntType, NxtHdr, p-NxtHdr+1);
440             HdrsFound++;
441         }
442         else if (strncasecmp("MIME-Version:", NxtHdr, 13) == 0) {
443             p = nxtaddr(NxtHdr);
444             if (p != NULL) {
445                 xfree(MimeVer);
446                 MimeVer = xstrdup(p);
447                 HdrsFound++;
448             }
449         }
450
451         NxtHdr = (strchr(NxtHdr, '\n'));
452         if (NxtHdr != NULL) NxtHdr++;
453     } while ((NxtHdr != NULL) && (*NxtHdr) && (HdrsFound != 3));
454
455
456     /* Done looking through the headers, now check what they say */
457     if ((MimeVer != NULL) && (strcmp(MimeVer, "1.0") == 0)) {
458
459         CurrTypeNeedsDecode = CheckContentType(CntType);
460
461         /* Check Content-Type to see if this is a multipart message */
462         if ( (CntType != NULL) &&
463                 ((strncasecmp(CntType, "multipart/mixed", 15) == 0) ||
464                  (strncasecmp(CntType, "message/", 8) == 0)) ) {
465
466             char *p1 = GetBoundary(CntType);
467
468             if (p1 != NULL) {
469                 /* The actual delimiter is "--" followed by 
470                    the boundary string */
471                 strcpy(MultipartDelimiter, "--");
472                 strlcat(MultipartDelimiter, p1, sizeof(MultipartDelimiter));
473                 BodyType = (MSG_IS_8BIT | MSG_NEEDS_DECODE);
474             }
475         }
476
477         /* 
478          * Check Content-Transfer-Encoding, but
479          * ONLY for non-multipart messages (BodyType == 0).
480          */
481         if ((XferEnc != NULL) && (BodyType == 0)) {
482             if (strcasecmp(XferEnc, "quoted-printable") == 0) {
483                 CurrEncodingIsQP = 1;
484                 BodyType = (MSG_IS_8BIT | MSG_NEEDS_DECODE);
485                 if (WantDecode && CurrTypeNeedsDecode) {
486                     SetEncoding8bit(XferEncOfs);
487                 }
488             }
489             else if (strcasecmp(XferEnc, "7bit") == 0) {
490                 CurrEncodingIsQP = 0;
491                 BodyType = (MSG_IS_7BIT);
492             }
493             else if (strcasecmp(XferEnc, "8bit") == 0) {
494                 CurrEncodingIsQP = 0;
495                 BodyType = (MSG_IS_8BIT);
496             }
497         }
498
499     }
500
501     xfree(XferEnc);
502     xfree(CntType);
503     xfree(MimeVer);
504
505     return BodyType;
506 }
507
508
509 /*
510  * Decode one line of data containing QP data.
511  * Return flag set if this line ends with a soft line-break.
512  * 'bufp' is modified to point to the end of the output buffer.
513  */
514 static int DoOneQPLine(char **bufp, flag delimited, flag issoftline)
515 {
516   char *buf = *bufp;
517   char *p_in, *p_out, *p;
518   int n;
519   int ret = 0;
520
521   /*
522    * Special case: line consists of a single =2E and messages are 
523    * dot-terminated.  Line has to be dot-stuffed after decoding.
524    */
525   if (delimited && !issoftline && buf[0]=='=' && !strncmp(*bufp, "=2E\r\n", 5))
526   {
527       strcpy(buf, "..\r\n");
528       *bufp += 5;
529       return(FALSE);
530   }
531
532   p_in = buf;
533   if (delimited && issoftline && (strncmp(buf, "..", 2) == 0))
534     p_in++;
535
536   for (p_out = buf; (*p_in); ) {
537     p = strchr(p_in, '=');
538     if (p == NULL) {
539       /* No more QP data, just move remainder into place */
540       n = strlen(p_in);
541       memmove(p_out, p_in, n);
542       p_in += n; p_out += n;
543     }
544     else {
545       if (p > p_in) {
546         /* There are some uncoded chars at the beginning. */
547         n = (p - p_in);
548         memmove(p_out, p_in, n);
549         p_out += n;
550       }
551               
552       switch (*(p+1)) {
553       case '\0': case '\r': case '\n':
554         /* Soft line break, skip '=' */
555         p_in = p+1; 
556         if (*p_in == '\r') p_in++;
557         if (*p_in == '\n') p_in++;
558         ret = 1;
559         break;
560
561       default:
562         /* There is a QP encoded byte */
563         if (qp_char(*(p+1), *(p+2), p_out) == 0) {
564           p_in = p+3;
565         }
566         else {
567           /* Invalid QP data - pass through unchanged. */
568           *p_out = '=';
569           p_in = p+1;
570         }
571         p_out++;
572         break;
573       }
574     }
575   }
576
577   *p_out = '\0';
578   *bufp = p_out;
579   return ret;
580 }
581
582
583 /* This is called once per line in the message body.  We need to scan
584  * all lines in the message body for the multipart delimiter string,
585  * and handle any body-part headers in such messages (these can toggle
586  * qp-decoding on and off).
587  *
588  * Note: Messages that are NOT multipart-messages go through this
589  * routine quickly, since BodyState will always be S_BODY_DATA,
590  * and MultipartDelimiter is NULL.
591  *
592  * Return flag set if this line ends with a soft line-break.
593  * 'bufp' is modified to point to the end of the output buffer.
594  */
595
596 int UnMimeBodyline(char **bufp, flag delimited, flag softline)
597 {
598   char *buf = *bufp;
599   int ret = 0;
600
601   switch (BodyState) {
602   case S_BODY_HDR:
603     UnMimeHeader(buf);   /* Headers in body-parts can be encoded, too! */
604     if ((*buf == '\0') || (*buf == '\n') || (strcmp(buf, "\r\n") == 0)) {
605       BodyState = S_BODY_DATA;
606     } 
607     else if (strncasecmp("Content-Transfer-Encoding:", buf, 26) == 0) {
608       char *XferEnc;
609
610       XferEnc = nxtaddr(buf);
611       if ((XferEnc != NULL) && (strcasecmp(XferEnc, "quoted-printable") == 0)) {
612         CurrEncodingIsQP = 1;
613
614         /*
615          * Hmm ... we cannot be really sure that CurrTypeNeedsDecode
616          * has been set - we may not have seen the Content-Type header
617          * yet. But *usually* the Content-Type header comes first, so
618          * this will work. And there is really no way of doing it 
619          * "right" as long as we stick with the line-by-line processing.
620          */
621         if (CurrTypeNeedsDecode)
622             SetEncoding8bit(buf);
623       }
624     }
625     else if (strncasecmp("Content-Type:", buf, 13) == 0) {
626       CurrTypeNeedsDecode = CheckContentType(nxtaddr(buf));
627     }
628
629     *bufp = (buf + strlen(buf));
630     break;
631
632   case S_BODY_DATA:
633     if ((*MultipartDelimiter) && 
634         (strncmp(buf, MultipartDelimiter, strlen(MultipartDelimiter)) == 0)) {
635       BodyState = S_BODY_HDR;
636       CurrEncodingIsQP = CurrTypeNeedsDecode = 0;
637     }
638
639     if (CurrEncodingIsQP && CurrTypeNeedsDecode) 
640       ret = DoOneQPLine(bufp, delimited, softline);
641     else
642      *bufp = (buf + strlen(buf));
643     break;
644   }
645
646   return ret;
647 }
648
649
650 #ifdef STANDALONE
651 #include <stdio.h>
652 #include <unistd.h>
653
654 const char *program_name = "unmime";
655 int outlevel = 0;
656
657 #define BUFSIZE_INCREMENT 4096
658
659 #ifdef DEBUG
660 #define DBG_FWRITE(B,L,BS,FD) do { if (fwrite((B), (L), (BS), (FD))) { } } while(0)
661 #else
662 #define DBG_FWRITE(B,L,BS,FD)
663 #endif
664
665 int main(int argc, char *argv[])
666 {
667   unsigned int BufSize;
668   char *buffer, *buf_p;
669   int nl_count, i, bodytype;
670
671   /* quench warnings about unused arguments */
672   (void)argc;
673   (void)argv;
674
675 #ifdef DEBUG
676   pid_t pid;
677   FILE *fd_orig, *fd_conv;
678   char fnam[100];
679
680   /* we don't need snprintf here, but for consistency, we'll use it */
681   pid = getpid();
682   snprintf(fnam, sizeof(fnam), "/tmp/i_unmime.%lx", (long)pid);
683   fd_orig = fopen(fnam, "w");
684   snprintf(fnam, sizeof(fnam), "/tmp/o_unmime.%lx", (long)pid);
685   fd_conv = fopen(fnam, "w");
686 #endif
687
688   BufSize = BUFSIZE_INCREMENT;    /* Initial size of buffer */
689   buf_p = buffer = (char *) xmalloc(BufSize);
690   nl_count = 0;
691
692   do {
693     i = fread(buf_p, 1, 1, stdin);
694     switch (*buf_p) {
695      case '\n':
696        nl_count++;
697        break;
698
699      case '\r':
700        break;
701
702      default:
703        nl_count = 0;
704        break;
705     }
706
707     buf_p++;
708     if ((unsigned)(buf_p - buffer) == BufSize) {
709        /* Buffer is full! Get more room. */
710        buffer = (char *)xrealloc(buffer, BufSize+BUFSIZE_INCREMENT);
711        buf_p = buffer + BufSize;
712        BufSize += BUFSIZE_INCREMENT;
713     }
714   } while ((i > 0) && (nl_count < 2));
715
716   *buf_p = '\0';
717   DBG_FWRITE(buffer, strlen(buffer), 1, fd_orig);
718
719   UnMimeHeader(buffer);
720   bodytype = MimeBodyType(buffer, 1);
721
722   i = strlen(buffer);
723   DBG_FWRITE(buffer, i, 1, fd_conv);
724   if (fwrite(buffer, i, 1, stdout) < 1) {
725       perror("fwrite");
726       goto barf;
727   }
728   
729   do {
730      buf_p = (buffer - 1);
731      do {
732         buf_p++;
733         i = fread(buf_p, 1, 1, stdin);
734      } while ((i == 1) && (*buf_p != '\n'));
735      if (i == 1) buf_p++;
736      *buf_p = '\0';
737      DBG_FWRITE(buf, (buf_p - buffer), 1, fd_orig);
738
739      if (buf_p > buffer) {
740         if (bodytype & MSG_NEEDS_DECODE) {
741            buf_p = buffer;
742            UnMimeBodyline(&buf_p, 0, 0);
743         }
744         DBG_FWRITE(buffer, (buf_p - buffer), 1, fd_conv);
745         if (fwrite(buffer, (buf_p - buffer), 1, stdout) < 1) {
746             perror("fwrite");
747             goto barf;
748         }
749      }
750   } while (buf_p > buffer);
751
752 barf:
753   free(buffer);
754   if (EOF == fflush(stdout)) perror("fflush");
755
756 #ifdef DEBUG
757   fclose(fd_orig);
758   fclose(fd_conv);
759 #endif
760
761   return 0;
762 }
763 #endif