]> Pileus Git - ~andy/fetchmail/blob - unmime.c
Note Earl's regression fix for SSL_CTX_clear_options() on older OpenSSL.
[~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 "config.h"
17 #include <string.h>
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <ctype.h>
21 #include "fetchmail.h"
22 #include "i18n.h"
23
24 static unsigned char unhex(unsigned char c)
25 {
26   if ((c >= '0') && (c <= '9'))
27     return (c - '0');
28   else if ((c >= 'A') && (c <= 'F'))
29     return (c - 'A' + 10);
30   else if ((c >= 'a') && (c <= 'f'))
31     return (c - 'a' + 10);
32   else
33       return 16;        /* invalid hex character */
34 }
35
36 static int qp_char(unsigned char c1, unsigned char c2, char *c_out)
37 {
38   c1 = unhex(c1);
39   c2 = unhex(c2);
40
41   if ((c1 > 15) || (c2 > 15)) 
42     return 1;
43   else {
44     *c_out = 16*c1+c2;
45     return 0;
46   }
47 }
48
49
50 /*
51  * Routines to decode MIME QP-encoded headers, as per RFC 2047.
52  */
53
54 /* States of the decoding state machine */
55 #define S_COPY_PLAIN        0   /* Just copy, but watch for the QP flag */
56 #define S_SKIP_MIMEINIT     1   /* Get the encoding, and skip header */
57 #define S_COPY_MIME         2   /* Decode a sequence of coded characters */
58
59 static const char MIMEHDR_INIT[]  = "=?";       /* Start of coded sequence */
60 static const char MIMEHDR_END[]   = "?=";       /* End of coded sequence */
61
62 void UnMimeHeader(char *hdr)
63 {
64   /* Decode a buffer containing data encoded according to RFC
65    * 2047. This only handles content-transfer-encoding; conversion
66    * between character sets is not implemented.  In other words: We
67    * assume the charsets used can be displayed by your mail program
68    * without problems. 
69    */
70
71   /* Note: Decoding is done "in-situ", i.e. without using an
72    * additional buffer for temp. storage. This is possible, since the
73    * decoded string will always be shorter than the encoded string,
74    * due to the encoding scheme.
75    */
76
77   int  state = S_COPY_PLAIN;
78   char *p_in, *p_out, *p;
79   char enc = '\0';              /* initialization pacifies -Wall */
80   int  i;
81
82   /* Speed up in case this is not a MIME-encoded header */
83   p = strstr(hdr, MIMEHDR_INIT);
84   if (p == NULL)
85     return;   /* No MIME header */
86
87   /* Loop through the buffer.
88    *  p_in : Next char to be processed.
89    *  p_out: Where to put the next processed char
90    *  enc  : Encoding used (usually, 'q' = quoted-printable)
91    */
92   for (p_out = p_in = hdr; (*p_in); ) {
93     switch (state) {
94     case S_COPY_PLAIN:
95       p = strstr(p_in, MIMEHDR_INIT);
96       if (p == NULL) {
97         /* 
98          * No more coded data in buffer, 
99          * just move remainder into place. 
100          */
101         i = strlen(p_in);   /* How much left */
102         memmove(p_out, p_in, i);
103         p_in += i; p_out += i;
104       }
105       else {
106         /* MIME header init found at location p */
107         if (p > p_in) {
108           /* There are some uncoded chars at the beginning. */
109           i = (p - p_in);
110           memmove(p_out, p_in, i);
111           p_out += i;
112         }
113         p_in = (p + 2);
114         state = S_SKIP_MIMEINIT;
115       }
116       break;
117
118     case S_SKIP_MIMEINIT:
119       /* Mime type definition: "charset?encoding?" */
120       p = strchr(p_in, '?');
121       if (p != NULL) {
122         /* p_in .. (p-1) holds the charset */
123
124         /* *(p+1) is the transfer encoding, *(p+2) must be a '?' */
125         if (*(p+2) == '?') {
126           enc = tolower((unsigned char)*(p+1));
127           p_in = p+3;
128           state = S_COPY_MIME;
129         }
130         else
131           state = S_COPY_PLAIN;
132       }
133       else
134         state = S_COPY_PLAIN;   /* Invalid data */
135       break;
136
137     case S_COPY_MIME:
138       p = strstr(p_in, MIMEHDR_END);  /* Find end of coded data */
139       if (p == NULL) p = p_in + strlen(p_in);
140       for (; (p_in < p); ) {
141         /* Decode all encoded data */
142         if (enc == 'q') {
143           if (*p_in == '=') {
144             /* Decode one char qp-coded at (p_in+1) and (p_in+2) */
145             if (qp_char(*(p_in+1), *(p_in+2), p_out) == 0)
146               p_in += 3;
147             else {
148               /* Invalid QP data - pass through unchanged. */
149               *p_out = *p_in;
150               p_in++;
151             }
152           }
153           else if (*p_in == '_') {
154             /* 
155              * RFC 2047: '_' inside encoded word represents 0x20.
156              * NOT a space - always the value 0x20.
157              */
158             *p_out = 0x20;
159             p_in++;
160           }
161           else {
162             /* Copy unchanged */
163             *p_out = *p_in;
164             p_in++;
165           }
166           p_out++;
167         }
168         else if (enc == 'b') {
169           /* Decode base64 encoded data */
170           char delimsave;
171           int decoded_count;
172
173           delimsave = *p; *p = '\r';
174           decoded_count = from64tobits(p_out, p_in, 0);
175           *p = delimsave;
176           if (decoded_count > 0) 
177             p_out += decoded_count;            
178           p_in = p;
179         }
180         else {
181           /* Copy unchanged */
182           *p_out = *p_in;
183           p_in++;
184           p_out++;
185         }
186       }
187       if (*p_in)
188         p_in += 2;   /* Skip the MIMEHDR_END delimiter */
189
190       /* 
191        * We've completed decoding one encoded sequence. But another
192        * may follow immediately, in which case whitespace before the
193        * new MIMEHDR_INIT delimiter must be discarded.
194        * See if that is the case 
195        */
196       p = strstr(p_in, MIMEHDR_INIT);
197       state = S_COPY_PLAIN;
198       if (p != NULL) {
199         /*
200          * There is more MIME data later on. Is there
201          * whitespace  only before the delimiter? 
202          */
203         char *q;
204         int  wsp_only = 1;
205
206         for (q=p_in; (wsp_only && (q < p)); q++)
207           wsp_only = isspace((unsigned char)*q);
208
209         if (wsp_only) {
210           /* 
211            * Whitespace-only before the MIME delimiter. OK,
212            * just advance p_in to past the new MIMEHDR_INIT,
213            * and prepare to process the new MIME charset/encoding
214            * header.
215            */
216           p_in = p + sizeof(MIMEHDR_INIT) - 1;
217           state = S_SKIP_MIMEINIT;
218         }
219       }
220       break;
221     }
222   }
223
224   *p_out = '\0';
225 }
226
227
228
229 /*
230  * Routines for decoding body-parts of a message.
231  *
232  * Since the "fetch" part of fetchmail gets a message body
233  * one line at a time, we need to maintain some state variables
234  * across multiple invokations of the UnMimeBodyline() routine.
235  * The driver routine should call MimeBodyType() when all
236  * headers have been received, and then UnMimeBodyline() for
237  * every line in the message body.
238  *
239  */
240 #define S_BODY_DATA 0
241 #define S_BODY_HDR  1
242
243 /* 
244  * Flag indicating if we are currently processing 
245  * the headers or the body of a (multipart) message.
246  */
247 static int  BodyState = S_BODY_DATA;
248
249 /* 
250  * Flag indicating if we are in the process of decoding
251  * a quoted-printable body part.
252  */
253 static int  CurrEncodingIsQP = 0;
254 static int  CurrTypeNeedsDecode = 0;
255
256 /* 
257  * Delimiter for multipart messages. RFC 2046 states that this must
258  * NEVER be longer than 70 characters. Add 3 for the two hyphens
259  * at the beginning, and a terminating null.
260  */
261 #define MAX_DELIM_LEN 70
262 static char MultipartDelimiter[MAX_DELIM_LEN+3];
263
264
265 /* This string replaces the "Content-Transfer-Encoding: quoted-printable"
266  * string in all headers, including those in body-parts. The replacement
267  * must be no longer than the original string.
268  */
269 static const char ENC8BIT[] = "Content-Transfer-Encoding: 8bit";
270 static void SetEncoding8bit(char *XferEncOfs)
271 {
272   char *p;
273
274   if (XferEncOfs != NULL) {
275      memcpy(XferEncOfs, ENC8BIT, sizeof(ENC8BIT) - 1);
276
277      /* If anything left, in this header, replace with whitespace */
278      for (p=XferEncOfs+sizeof(ENC8BIT)-1; ((unsigned char)*p >= ' '); p++)
279        *p=' ';
280   }
281 }
282
283 static char *GetBoundary(char *CntType)
284 {
285   char *p1, *p2;
286   int flag;
287
288   /* Find the "boundary" delimiter. It must be preceded with a ';'
289    * and optionally some whitespace.
290    */
291   p1 = CntType;
292   do {
293     p2 = strchr(p1, ';'); 
294     if (p2)
295       for (p2++; isspace((unsigned char)*p2); p2++) { }
296
297     p1 = p2;
298   } while ((p1) && (strncasecmp(p1, "boundary", 8) != 0));
299
300   if (p1 == NULL)
301     /* No boundary delimiter */
302     return NULL;
303
304   /* Skip "boundary", whitespace and '='; check that we do have a '=' */
305   for (p1+=8, flag=0; (isspace((unsigned char)*p1) || (*p1 == '=')); p1++)
306     flag |= (*p1 == '=');
307   if (!flag)
308     return NULL;
309
310   /* Find end of boundary delimiter string */
311   if (*p1 == '\"') {
312     /* The delimiter is inside quotes */
313     p1++;
314     p2 = strchr(p1, '\"');
315     if (p2 == NULL)
316       return NULL;  /* No closing '"' !?! */
317   }
318   else {
319     /* There might be more text after the "boundary" string. */
320     p2 = strchr(p1, ';');  /* Safe - delimiter with ';' must be in quotes */
321   }
322
323   /* Zero-terminate the boundary string */
324   if (p2 != NULL)
325     *p2 = '\0';
326
327   return (p1 && strlen(p1)) ? p1 : NULL;
328 }
329
330
331 static int CheckContentType(char *CntType)
332 {
333   /*
334    * Static array of Content-Type's for which we will do
335    * quoted-printable decoding, if requested. 
336    * It is probably wise to do this only on known text-only types;
337    * be really careful if you change this.
338    */
339
340   static const char *DecodedTypes[] = {
341     "text/",        /* Will match ALL content-type's starting with 'text/' */
342     "message/rfc822", 
343     NULL
344   };
345
346   char *p = CntType;
347   int i;
348
349   /* If no Content-Type header, it isn't MIME - don't touch it */
350   if (CntType == NULL) return 0;
351
352   /* Skip whitespace, if any */
353   for (; isspace((unsigned char)*p); p++) ;
354
355   for (i=0; 
356        (DecodedTypes[i] && 
357         (strncasecmp(p, DecodedTypes[i], strlen(DecodedTypes[i])))); 
358        i++) ;
359
360   return (DecodedTypes[i] != NULL);
361 }
362
363
364 /*
365  * This routine does three things:
366  * 1) It determines - based on the message headers - whether the
367  *    message body is a MIME message that may hold 8 bit data.
368  *    - A message that has a "quoted-printable" or "8bit" transfer 
369  *      encoding is assumed to contain 8-bit data (when decoded).
370  *    - A multipart message is assumed to contain 8-bit data
371  *      when decoded (there might be quoted-printable body-parts).
372  *    - All other messages are assumed NOT to include 8-bit data.
373  * 2) It determines the delimiter-string used in multi-part message
374  *    bodies.
375  * 3) It sets the initial values of the CurrEncodingIsQP, 
376  *    CurrTypeNeedsDecode, and BodyState variables, from the header 
377  *    contents.
378  *
379  * The return value is a bitmask.
380  */
381 int MimeBodyType(char *hdrs, int WantDecode)
382 {
383     char *NxtHdr = hdrs;
384     char *XferEnc, *XferEncOfs, *CntType, *MimeVer, *p;
385     int  HdrsFound = 0;     /* We only look for three headers */
386     int  BodyType;          /* Return value */ 
387
388     /* Setup for a standard (no MIME, no QP, 7-bit US-ASCII) message */
389     MultipartDelimiter[0] = '\0';
390     CurrEncodingIsQP = CurrTypeNeedsDecode = 0;
391     BodyState = S_BODY_DATA;
392     BodyType = 0;
393
394     /* Just in case ... */
395     if (hdrs == NULL)
396         return BodyType;
397
398     XferEnc = XferEncOfs = CntType = MimeVer = NULL;
399
400     do {
401         if (strncasecmp("Content-Transfer-Encoding:", NxtHdr, 26) == 0) {
402             XferEncOfs = NxtHdr;
403             p = nxtaddr(NxtHdr);
404             if (p != NULL) {
405                 xfree(XferEnc);
406                 XferEnc = xstrdup(p);
407                 HdrsFound++;
408             }
409         }
410         else if (strncasecmp("Content-Type:", NxtHdr, 13) == 0) {
411             /*
412              * This one is difficult. We cannot use the standard
413              * nxtaddr() routine, since the boundary-delimiter is
414              * (probably) enclosed in quotes - and thus appears
415              * as an rfc822 comment, and nxtaddr() "eats" up any
416              * spaces in the delimiter. So, we have to do this
417              * by hand.
418              */
419
420             /* Skip the "Content-Type:" part and whitespace after it */
421             for (NxtHdr += 13; ((*NxtHdr == ' ') || (*NxtHdr == '\t')); NxtHdr++) { }
422
423             /* 
424              * Get the full value of the Content-Type header;
425              * it might span multiple lines. So search for
426              * a newline char, but ignore those that have a
427              * have a TAB or space just after the NL (continued
428              * lines).
429              */
430             p = NxtHdr-1;
431             do {
432                 p=strchr((p+1),'\n'); 
433             } while ( (p != NULL) && ((*(p+1) == '\t') || (*(p+1) == ' ')) );
434             if (p == NULL) p = NxtHdr + strlen(NxtHdr);
435
436             xfree(CntType);
437             CntType = (char *)xmalloc(p-NxtHdr+1);
438             strlcpy(CntType, NxtHdr, p-NxtHdr+1);
439             HdrsFound++;
440         }
441         else if (strncasecmp("MIME-Version:", NxtHdr, 13) == 0) {
442             p = nxtaddr(NxtHdr);
443             if (p != NULL) {
444                 xfree(MimeVer);
445                 MimeVer = xstrdup(p);
446                 HdrsFound++;
447             }
448         }
449
450         NxtHdr = (strchr(NxtHdr, '\n'));
451         if (NxtHdr != NULL) NxtHdr++;
452     } while ((NxtHdr != NULL) && (*NxtHdr) && (HdrsFound != 3));
453
454
455     /* Done looking through the headers, now check what they say */
456     if ((MimeVer != NULL) && (strcmp(MimeVer, "1.0") == 0)) {
457
458         CurrTypeNeedsDecode = CheckContentType(CntType);
459
460         /* Check Content-Type to see if this is a multipart message */
461         if ( (CntType != NULL) &&
462                 ((strncasecmp(CntType, "multipart/mixed", 16) == 0) ||
463                  (strncasecmp(CntType, "message/", 8) == 0)) ) {
464
465             char *p1 = GetBoundary(CntType);
466
467             if (p1 != NULL) {
468                 /* The actual delimiter is "--" followed by 
469                    the boundary string */
470                 strcpy(MultipartDelimiter, "--");
471                 strlcat(MultipartDelimiter, p1, sizeof(MultipartDelimiter));
472                 MultipartDelimiter[sizeof(MultipartDelimiter)-1] = '\0';
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