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