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