source: trunk/third/sendmail/sendmail/mime.c @ 22421

Revision 22421, 30.1 KB checked in by zacheiss, 18 years ago (diff)
Apply patches from 3-22-06 CERT advisory.
Line 
1/*
2 * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
3 *      All rights reserved.
4 * Copyright (c) 1994, 1996-1997 Eric P. Allman.  All rights reserved.
5 * Copyright (c) 1994
6 *      The Regents of the University of California.  All rights reserved.
7 *
8 * By using this file, you agree to the terms and conditions set
9 * forth in the LICENSE file which can be found at the top level of
10 * the sendmail distribution.
11 *
12 */
13
14#include <sendmail.h>
15#include <string.h>
16
17SM_RCSID("@(#)$Id: mime.c,v 1.2 2006-03-23 21:02:46 zacheiss Exp $")
18
19/*
20**  MIME support.
21**
22**      I am indebted to John Beck of Hewlett-Packard, who contributed
23**      his code to me for inclusion.  As it turns out, I did not use
24**      his code since he used a "minimum change" approach that used
25**      several temp files, and I wanted a "minimum impact" approach
26**      that would avoid copying.  However, looking over his code
27**      helped me cement my understanding of the problem.
28**
29**      I also looked at, but did not directly use, Nathaniel
30**      Borenstein's "code.c" module.  Again, it functioned as
31**      a file-to-file translator, which did not fit within my
32**      design bounds, but it was a useful base for understanding
33**      the problem.
34*/
35
36#if MIME8TO7
37static int      isboundary __P((char *, char **));
38static int      mimeboundary __P((char *, char **));
39static int      mime_getchar __P((SM_FILE_T *, char **, int *));
40static int      mime_getchar_crlf __P((SM_FILE_T *, char **, int *));
41
42/* character set for hex and base64 encoding */
43static char     Base16Code[] =  "0123456789ABCDEF";
44static char     Base64Code[] =  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
45
46/* types of MIME boundaries */
47# define MBT_SYNTAX     0       /* syntax error */
48# define MBT_NOTSEP     1       /* not a boundary */
49# define MBT_INTERMED   2       /* intermediate boundary (no trailing --) */
50# define MBT_FINAL      3       /* final boundary (trailing -- included) */
51
52static char     *MimeBoundaryNames[] =
53{
54        "SYNTAX",       "NOTSEP",       "INTERMED",     "FINAL"
55};
56
57static bool     MapNLtoCRLF;
58
59/*
60**  MIME8TO7 -- output 8 bit body in 7 bit format
61**
62**      The header has already been output -- this has to do the
63**      8 to 7 bit conversion.  It would be easy if we didn't have
64**      to deal with nested formats (multipart/xxx and message/rfc822).
65**
66**      We won't be called if we don't have to do a conversion, and
67**      appropriate MIME-Version: and Content-Type: fields have been
68**      output.  Any Content-Transfer-Encoding: field has not been
69**      output, and we can add it here.
70**
71**      Parameters:
72**              mci -- mailer connection information.
73**              header -- the header for this body part.
74**              e -- envelope.
75**              boundaries -- the currently pending message boundaries.
76**                      NULL if we are processing the outer portion.
77**              flags -- to tweak processing.
78**
79**      Returns:
80**              An indicator of what terminated the message part:
81**                MBT_FINAL -- the final boundary
82**                MBT_INTERMED -- an intermediate boundary
83**                MBT_NOTSEP -- an end of file
84**                SM_IO_EOF -- I/O error occurred
85*/
86
87struct args
88{
89        char    *a_field;       /* name of field */
90        char    *a_value;       /* value of that field */
91};
92
93int
94mime8to7(mci, header, e, boundaries, flags)
95        register MCI *mci;
96        HDR *header;
97        register ENVELOPE *e;
98        char **boundaries;
99        int flags;
100{
101        register char *p;
102        int linelen;
103        int bt;
104        off_t offset;
105        size_t sectionsize, sectionhighbits;
106        int i;
107        char *type;
108        char *subtype;
109        char *cte;
110        char **pvp;
111        int argc = 0;
112        char *bp;
113        bool use_qp = false;
114        struct args argv[MAXMIMEARGS];
115        char bbuf[128];
116        char buf[MAXLINE];
117        char pvpbuf[MAXLINE];
118        extern unsigned char MimeTokenTab[256];
119
120        if (tTd(43, 1))
121        {
122                sm_dprintf("mime8to7: flags = %x, boundaries =", flags);
123                if (boundaries[0] == NULL)
124                        sm_dprintf(" <none>");
125                else
126                {
127                        for (i = 0; boundaries[i] != NULL; i++)
128                                sm_dprintf(" %s", boundaries[i]);
129                }
130                sm_dprintf("\n");
131        }
132        MapNLtoCRLF = true;
133        p = hvalue("Content-Transfer-Encoding", header);
134        if (p == NULL ||
135            (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
136                           MimeTokenTab)) == NULL ||
137            pvp[0] == NULL)
138        {
139                cte = NULL;
140        }
141        else
142        {
143                cataddr(pvp, NULL, buf, sizeof buf, '\0');
144                cte = sm_rpool_strdup_x(e->e_rpool, buf);
145        }
146
147        type = subtype = NULL;
148        p = hvalue("Content-Type", header);
149        if (p == NULL)
150        {
151                if (bitset(M87F_DIGEST, flags))
152                        p = "message/rfc822";
153                else
154                        p = "text/plain";
155        }
156        if (p != NULL &&
157            (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
158                           MimeTokenTab)) != NULL &&
159            pvp[0] != NULL)
160        {
161                if (tTd(43, 40))
162                {
163                        for (i = 0; pvp[i] != NULL; i++)
164                                sm_dprintf("pvp[%d] = \"%s\"\n", i, pvp[i]);
165                }
166                type = *pvp++;
167                if (*pvp != NULL && strcmp(*pvp, "/") == 0 &&
168                    *++pvp != NULL)
169                {
170                        subtype = *pvp++;
171                }
172
173                /* break out parameters */
174                while (*pvp != NULL && argc < MAXMIMEARGS)
175                {
176                        /* skip to semicolon separator */
177                        while (*pvp != NULL && strcmp(*pvp, ";") != 0)
178                                pvp++;
179                        if (*pvp++ == NULL || *pvp == NULL)
180                                break;
181
182                        /* complain about empty values */
183                        if (strcmp(*pvp, ";") == 0)
184                        {
185                                usrerr("mime8to7: Empty parameter in Content-Type header");
186
187                                /* avoid bounce loops */
188                                e->e_flags |= EF_DONT_MIME;
189                                continue;
190                        }
191
192                        /* extract field name */
193                        argv[argc].a_field = *pvp++;
194
195                        /* see if there is a value */
196                        if (*pvp != NULL && strcmp(*pvp, "=") == 0 &&
197                            (*++pvp == NULL || strcmp(*pvp, ";") != 0))
198                        {
199                                argv[argc].a_value = *pvp;
200                                argc++;
201                        }
202                }
203        }
204
205        /* check for disaster cases */
206        if (type == NULL)
207                type = "-none-";
208        if (subtype == NULL)
209                subtype = "-none-";
210
211        /* don't propogate some flags more than one level into the message */
212        flags &= ~M87F_DIGEST;
213
214        /*
215        **  Check for cases that can not be encoded.
216        **
217        **      For example, you can't encode certain kinds of types
218        **      or already-encoded messages.  If we find this case,
219        **      just copy it through.
220        */
221
222        (void) sm_snprintf(buf, sizeof buf, "%.100s/%.100s", type, subtype);
223        if (wordinclass(buf, 'n') || (cte != NULL && !wordinclass(cte, 'e')))
224                flags |= M87F_NO8BIT;
225
226# ifdef USE_B_CLASS
227        if (wordinclass(buf, 'b') || wordinclass(type, 'b'))
228                MapNLtoCRLF = false;
229# endif /* USE_B_CLASS */
230        if (wordinclass(buf, 'q') || wordinclass(type, 'q'))
231                use_qp = true;
232
233        /*
234        **  Multipart requires special processing.
235        **
236        **      Do a recursive descent into the message.
237        */
238
239        if (sm_strcasecmp(type, "multipart") == 0 &&
240            (!bitset(M87F_NO8BIT, flags) || bitset(M87F_NO8TO7, flags)))
241        {
242
243                if (sm_strcasecmp(subtype, "digest") == 0)
244                        flags |= M87F_DIGEST;
245
246                for (i = 0; i < argc; i++)
247                {
248                        if (sm_strcasecmp(argv[i].a_field, "boundary") == 0)
249                                break;
250                }
251                if (i >= argc || argv[i].a_value == NULL)
252                {
253                        usrerr("mime8to7: Content-Type: \"%s\": %s boundary",
254                                i >= argc ? "missing" : "bogus", p);
255                        p = "---";
256
257                        /* avoid bounce loops */
258                        e->e_flags |= EF_DONT_MIME;
259                }
260                else
261                {
262                        p = argv[i].a_value;
263                        stripquotes(p);
264                }
265                if (sm_strlcpy(bbuf, p, sizeof bbuf) >= sizeof bbuf)
266                {
267                        usrerr("mime8to7: multipart boundary \"%s\" too long",
268                                p);
269
270                        /* avoid bounce loops */
271                        e->e_flags |= EF_DONT_MIME;
272                }
273
274                if (tTd(43, 1))
275                        sm_dprintf("mime8to7: multipart boundary \"%s\"\n",
276                                bbuf);
277                for (i = 0; i < MAXMIMENESTING; i++)
278                {
279                        if (boundaries[i] == NULL)
280                                break;
281                }
282                if (i >= MAXMIMENESTING)
283                {
284                        usrerr("mime8to7: multipart nesting boundary too deep");
285
286                        /* avoid bounce loops */
287                        e->e_flags |= EF_DONT_MIME;
288                }
289                else
290                {
291                        boundaries[i] = bbuf;
292                        boundaries[i + 1] = NULL;
293                }
294                mci->mci_flags |= MCIF_INMIME;
295
296                /* skip the early "comment" prologue */
297                if (!putline("", mci))
298                        goto writeerr;
299                mci->mci_flags &= ~MCIF_INHEADER;
300                bt = MBT_FINAL;
301                while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
302                        != NULL)
303                {
304                        bt = mimeboundary(buf, boundaries);
305                        if (bt != MBT_NOTSEP)
306                                break;
307                        if (!putxline(buf, strlen(buf), mci,
308                                        PXLF_MAPFROM|PXLF_STRIP8BIT))
309                                goto writeerr;
310                        if (tTd(43, 99))
311                                sm_dprintf("  ...%s", buf);
312                }
313                if (sm_io_eof(e->e_dfp))
314                        bt = MBT_FINAL;
315                while (bt != MBT_FINAL)
316                {
317                        auto HDR *hdr = NULL;
318
319                        (void) sm_strlcpyn(buf, sizeof buf, 2, "--", bbuf);
320                        if (!putline(buf, mci))
321                                goto writeerr;
322                        if (tTd(43, 35))
323                                sm_dprintf("  ...%s\n", buf);
324                        collect(e->e_dfp, false, &hdr, e);
325                        if (tTd(43, 101))
326                                putline("+++after collect", mci);
327                        if (!putheader(mci, hdr, e, flags))
328                                goto writeerr;
329                        if (tTd(43, 101))
330                                putline("+++after putheader", mci);
331                        bt = mime8to7(mci, hdr, e, boundaries, flags);
332                        if (bt == SM_IO_EOF)
333                                goto writeerr;
334                }
335                (void) sm_strlcpyn(buf, sizeof buf, 3, "--", bbuf, "--");
336                if (!putline(buf, mci))
337                        goto writeerr;
338                if (tTd(43, 35))
339                        sm_dprintf("  ...%s\n", buf);
340                boundaries[i] = NULL;
341                mci->mci_flags &= ~MCIF_INMIME;
342
343                /* skip the late "comment" epilogue */
344                while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
345                        != NULL)
346                {
347                        bt = mimeboundary(buf, boundaries);
348                        if (bt != MBT_NOTSEP)
349                                break;
350                        if (!putxline(buf, strlen(buf), mci,
351                                        PXLF_MAPFROM|PXLF_STRIP8BIT))
352                                goto writeerr;
353                        if (tTd(43, 99))
354                                sm_dprintf("  ...%s", buf);
355                }
356                if (sm_io_eof(e->e_dfp))
357                        bt = MBT_FINAL;
358                if (tTd(43, 3))
359                        sm_dprintf("\t\t\tmime8to7=>%s (multipart)\n",
360                                MimeBoundaryNames[bt]);
361                return bt;
362        }
363
364        /*
365        **  Message/xxx types -- recurse exactly once.
366        **
367        **      Class 's' is predefined to have "rfc822" only.
368        */
369
370        if (sm_strcasecmp(type, "message") == 0)
371        {
372                if (!wordinclass(subtype, 's'))
373                {
374                        flags |= M87F_NO8BIT;
375                }
376                else
377                {
378                        auto HDR *hdr = NULL;
379
380                        if (!putline("", mci))
381                                goto writeerr;
382
383                        mci->mci_flags |= MCIF_INMIME;
384                        collect(e->e_dfp, false, &hdr, e);
385                        if (tTd(43, 101))
386                                putline("+++after collect", mci);
387                        if (!putheader(mci, hdr, e, flags))
388                                goto writeerr;
389                        if (tTd(43, 101))
390                                putline("+++after putheader", mci);
391                        if (hvalue("MIME-Version", hdr) == NULL &&
392                            !bitset(M87F_NO8TO7, flags) &&
393                            !putline("MIME-Version: 1.0", mci))
394                                goto writeerr;
395                        bt = mime8to7(mci, hdr, e, boundaries, flags);
396                        mci->mci_flags &= ~MCIF_INMIME;
397                        return bt;
398                }
399        }
400
401        /*
402        **  Non-compound body type
403        **
404        **      Compute the ratio of seven to eight bit characters;
405        **      use that as a heuristic to decide how to do the
406        **      encoding.
407        */
408
409        sectionsize = sectionhighbits = 0;
410        if (!bitset(M87F_NO8BIT|M87F_NO8TO7, flags))
411        {
412                /* remember where we were */
413                offset = sm_io_tell(e->e_dfp, SM_TIME_DEFAULT);
414                if (offset == -1)
415                        syserr("mime8to7: cannot sm_io_tell on %cf%s",
416                               DATAFL_LETTER, e->e_id);
417
418                /* do a scan of this body type to count character types */
419                while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
420                        != NULL)
421                {
422                        if (mimeboundary(buf, boundaries) != MBT_NOTSEP)
423                                break;
424                        for (p = buf; *p != '\0'; p++)
425                        {
426                                /* count bytes with the high bit set */
427                                sectionsize++;
428                                if (bitset(0200, *p))
429                                        sectionhighbits++;
430                        }
431
432                        /*
433                        **  Heuristic: if 1/4 of the first 4K bytes are 8-bit,
434                        **  assume base64.  This heuristic avoids double-reading
435                        **  large graphics or video files.
436                        */
437
438                        if (sectionsize >= 4096 &&
439                            sectionhighbits > sectionsize / 4)
440                                break;
441                }
442
443                /* return to the original offset for processing */
444                /* XXX use relative seeks to handle >31 bit file sizes? */
445                if (sm_io_seek(e->e_dfp, SM_TIME_DEFAULT, offset, SEEK_SET) < 0)
446                        syserr("mime8to7: cannot sm_io_fseek on %cf%s",
447                               DATAFL_LETTER, e->e_id);
448                else
449                        sm_io_clearerr(e->e_dfp);
450        }
451
452        /*
453        **  Heuristically determine encoding method.
454        **      If more than 1/8 of the total characters have the
455        **      eighth bit set, use base64; else use quoted-printable.
456        **      However, only encode binary encoded data as base64,
457        **      since otherwise the NL=>CRLF mapping will be a problem.
458        */
459
460        if (tTd(43, 8))
461        {
462                sm_dprintf("mime8to7: %ld high bit(s) in %ld byte(s), cte=%s, type=%s/%s\n",
463                        (long) sectionhighbits, (long) sectionsize,
464                        cte == NULL ? "[none]" : cte,
465                        type == NULL ? "[none]" : type,
466                        subtype == NULL ? "[none]" : subtype);
467        }
468        if (cte != NULL && sm_strcasecmp(cte, "binary") == 0)
469                sectionsize = sectionhighbits;
470        linelen = 0;
471        bp = buf;
472        if (sectionhighbits == 0)
473        {
474                /* no encoding necessary */
475                if (cte != NULL &&
476                    bitset(MCIF_CVT8TO7|MCIF_CVT7TO8|MCIF_INMIME,
477                           mci->mci_flags) &&
478                    !bitset(M87F_NO8TO7, flags))
479                {
480                        /*
481                        **  Skip _unless_ in MIME mode and potentially
482                        **  converting from 8 bit to 7 bit MIME.  See
483                        **  putheader() for the counterpart where the
484                        **  CTE header is skipped in the opposite
485                        **  situation.
486                        */
487
488                        (void) sm_snprintf(buf, sizeof buf,
489                                "Content-Transfer-Encoding: %.200s", cte);
490                        if (!putline(buf, mci))
491                                goto writeerr;
492                        if (tTd(43, 36))
493                                sm_dprintf("  ...%s\n", buf);
494                }
495                if (!putline("", mci))
496                        goto writeerr;
497                mci->mci_flags &= ~MCIF_INHEADER;
498                while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
499                        != NULL)
500                {
501                        bt = mimeboundary(buf, boundaries);
502                        if (bt != MBT_NOTSEP)
503                                break;
504                        if (!putline(buf, mci))
505                                goto writeerr;
506                }
507                if (sm_io_eof(e->e_dfp))
508                        bt = MBT_FINAL;
509        }
510        else if (!MapNLtoCRLF ||
511                 (sectionsize / 8 < sectionhighbits && !use_qp))
512        {
513                /* use base64 encoding */
514                int c1, c2;
515
516                if (tTd(43, 36))
517                        sm_dprintf("  ...Content-Transfer-Encoding: base64\n");
518                if (!putline("Content-Transfer-Encoding: base64", mci))
519                        goto writeerr;
520                (void) sm_snprintf(buf, sizeof buf,
521                        "X-MIME-Autoconverted: from 8bit to base64 by %s id %s",
522                        MyHostName, e->e_id);
523                if (!putline(buf, mci) || !putline("", mci))
524                        goto writeerr;
525                mci->mci_flags &= ~MCIF_INHEADER;
526                while ((c1 = mime_getchar_crlf(e->e_dfp, boundaries, &bt)) !=
527                        SM_IO_EOF)
528                {
529                        if (linelen > 71)
530                        {
531                                *bp = '\0';
532                                if (!putline(buf, mci))
533                                        goto writeerr;
534                                linelen = 0;
535                                bp = buf;
536                        }
537                        linelen += 4;
538                        *bp++ = Base64Code[(c1 >> 2)];
539                        c1 = (c1 & 0x03) << 4;
540                        c2 = mime_getchar_crlf(e->e_dfp, boundaries, &bt);
541                        if (c2 == SM_IO_EOF)
542                        {
543                                *bp++ = Base64Code[c1];
544                                *bp++ = '=';
545                                *bp++ = '=';
546                                break;
547                        }
548                        c1 |= (c2 >> 4) & 0x0f;
549                        *bp++ = Base64Code[c1];
550                        c1 = (c2 & 0x0f) << 2;
551                        c2 = mime_getchar_crlf(e->e_dfp, boundaries, &bt);
552                        if (c2 == SM_IO_EOF)
553                        {
554                                *bp++ = Base64Code[c1];
555                                *bp++ = '=';
556                                break;
557                        }
558                        c1 |= (c2 >> 6) & 0x03;
559                        *bp++ = Base64Code[c1];
560                        *bp++ = Base64Code[c2 & 0x3f];
561                }
562                *bp = '\0';
563                if (!putline(buf, mci))
564                        goto writeerr;
565        }
566        else
567        {
568                /* use quoted-printable encoding */
569                int c1, c2;
570                int fromstate;
571                BITMAP256 badchars;
572
573                /* set up map of characters that must be mapped */
574                clrbitmap(badchars);
575                for (c1 = 0x00; c1 < 0x20; c1++)
576                        setbitn(c1, badchars);
577                clrbitn('\t', badchars);
578                for (c1 = 0x7f; c1 < 0x100; c1++)
579                        setbitn(c1, badchars);
580                setbitn('=', badchars);
581                if (bitnset(M_EBCDIC, mci->mci_mailer->m_flags))
582                        for (p = "!\"#$@[\\]^`{|}~"; *p != '\0'; p++)
583                                setbitn(*p, badchars);
584
585                if (tTd(43, 36))
586                        sm_dprintf("  ...Content-Transfer-Encoding: quoted-printable\n");
587                if (!putline("Content-Transfer-Encoding: quoted-printable",
588                                mci))
589                        goto writeerr;
590                (void) sm_snprintf(buf, sizeof buf,
591                        "X-MIME-Autoconverted: from 8bit to quoted-printable by %s id %s",
592                        MyHostName, e->e_id);
593                if (!putline(buf, mci) || !putline("", mci))
594                        goto writeerr;
595                mci->mci_flags &= ~MCIF_INHEADER;
596                fromstate = 0;
597                c2 = '\n';
598                while ((c1 = mime_getchar(e->e_dfp, boundaries, &bt)) !=
599                        SM_IO_EOF)
600                {
601                        if (c1 == '\n')
602                        {
603                                if (c2 == ' ' || c2 == '\t')
604                                {
605                                        *bp++ = '=';
606                                        *bp++ = Base16Code[(c2 >> 4) & 0x0f];
607                                        *bp++ = Base16Code[c2 & 0x0f];
608                                }
609                                if (buf[0] == '.' && bp == &buf[1])
610                                {
611                                        buf[0] = '=';
612                                        *bp++ = Base16Code[('.' >> 4) & 0x0f];
613                                        *bp++ = Base16Code['.' & 0x0f];
614                                }
615                                *bp = '\0';
616                                if (!putline(buf, mci))
617                                        goto writeerr;
618                                linelen = fromstate = 0;
619                                bp = buf;
620                                c2 = c1;
621                                continue;
622                        }
623                        if (c2 == ' ' && linelen == 4 && fromstate == 4 &&
624                            bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
625                        {
626                                *bp++ = '=';
627                                *bp++ = '2';
628                                *bp++ = '0';
629                                linelen += 3;
630                        }
631                        else if (c2 == ' ' || c2 == '\t')
632                        {
633                                *bp++ = c2;
634                                linelen++;
635                        }
636                        if (linelen > 72 &&
637                            (linelen > 75 || c1 != '.' ||
638                             (linelen > 73 && c2 == '.')))
639                        {
640                                if (linelen > 73 && c2 == '.')
641                                        bp--;
642                                else
643                                        c2 = '\n';
644                                *bp++ = '=';
645                                *bp = '\0';
646                                if (!putline(buf, mci))
647                                        goto writeerr;
648                                linelen = fromstate = 0;
649                                bp = buf;
650                                if (c2 == '.')
651                                {
652                                        *bp++ = '.';
653                                        linelen++;
654                                }
655                        }
656                        if (bitnset(bitidx(c1), badchars))
657                        {
658                                *bp++ = '=';
659                                *bp++ = Base16Code[(c1 >> 4) & 0x0f];
660                                *bp++ = Base16Code[c1 & 0x0f];
661                                linelen += 3;
662                        }
663                        else if (c1 != ' ' && c1 != '\t')
664                        {
665                                if (linelen < 4 && c1 == "From"[linelen])
666                                        fromstate++;
667                                *bp++ = c1;
668                                linelen++;
669                        }
670                        c2 = c1;
671                }
672
673                /* output any saved character */
674                if (c2 == ' ' || c2 == '\t')
675                {
676                        *bp++ = '=';
677                        *bp++ = Base16Code[(c2 >> 4) & 0x0f];
678                        *bp++ = Base16Code[c2 & 0x0f];
679                        linelen += 3;
680                }
681
682                if (linelen > 0 || boundaries[0] != NULL)
683                {
684                        *bp = '\0';
685                        if (!putline(buf, mci))
686                                goto writeerr;
687                }
688
689        }
690        if (tTd(43, 3))
691                sm_dprintf("\t\t\tmime8to7=>%s (basic)\n", MimeBoundaryNames[bt]);
692        return bt;
693
694  writeerr:
695        return SM_IO_EOF;
696}
697/*
698**  MIME_GETCHAR -- get a character for MIME processing
699**
700**      Treats boundaries as SM_IO_EOF.
701**
702**      Parameters:
703**              fp -- the input file.
704**              boundaries -- the current MIME boundaries.
705**              btp -- if the return value is SM_IO_EOF, *btp is set to
706**                      the type of the boundary.
707**
708**      Returns:
709**              The next character in the input stream.
710*/
711
712static int
713mime_getchar(fp, boundaries, btp)
714        register SM_FILE_T *fp;
715        char **boundaries;
716        int *btp;
717{
718        int c;
719        static unsigned char *bp = NULL;
720        static int buflen = 0;
721        static bool atbol = true;       /* at beginning of line */
722        static int bt = MBT_SYNTAX;     /* boundary type of next SM_IO_EOF */
723        static unsigned char buf[128];  /* need not be a full line */
724        int start = 0;                  /* indicates position of - in buffer */
725
726        if (buflen == 1 && *bp == '\n')
727        {
728                /* last \n in buffer may be part of next MIME boundary */
729                c = *bp;
730        }
731        else if (buflen > 0)
732        {
733                buflen--;
734                return *bp++;
735        }
736        else
737                c = sm_io_getc(fp, SM_TIME_DEFAULT);
738        bp = buf;
739        buflen = 0;
740        if (c == '\n')
741        {
742                /* might be part of a MIME boundary */
743                *bp++ = c;
744                atbol = true;
745                c = sm_io_getc(fp, SM_TIME_DEFAULT);
746                if (c == '\n')
747                {
748                        (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c);
749                        return c;
750                }
751                start = 1;
752        }
753        if (c != SM_IO_EOF)
754                *bp++ = c;
755        else
756                bt = MBT_FINAL;
757        if (atbol && c == '-')
758        {
759                /* check for a message boundary */
760                c = sm_io_getc(fp, SM_TIME_DEFAULT);
761                if (c != '-')
762                {
763                        if (c != SM_IO_EOF)
764                                *bp++ = c;
765                        else
766                                bt = MBT_FINAL;
767                        buflen = bp - buf - 1;
768                        bp = buf;
769                        return *bp++;
770                }
771
772                /* got "--", now check for rest of separator */
773                *bp++ = '-';
774                while (bp < &buf[sizeof buf - 2] &&
775                       (c = sm_io_getc(fp, SM_TIME_DEFAULT)) != SM_IO_EOF &&
776                       c != '\n')
777                {
778                        *bp++ = c;
779                }
780                *bp = '\0';     /* XXX simply cut off? */
781                bt = mimeboundary((char *) &buf[start], boundaries);
782                switch (bt)
783                {
784                  case MBT_FINAL:
785                  case MBT_INTERMED:
786                        /* we have a message boundary */
787                        buflen = 0;
788                        *btp = bt;
789                        return SM_IO_EOF;
790                }
791
792                atbol = c == '\n';
793                if (c != SM_IO_EOF)
794                        *bp++ = c;
795        }
796
797        buflen = bp - buf - 1;
798        if (buflen < 0)
799        {
800                *btp = bt;
801                return SM_IO_EOF;
802        }
803        bp = buf;
804        return *bp++;
805}
806/*
807**  MIME_GETCHAR_CRLF -- do mime_getchar, but translate NL => CRLF
808**
809**      Parameters:
810**              fp -- the input file.
811**              boundaries -- the current MIME boundaries.
812**              btp -- if the return value is SM_IO_EOF, *btp is set to
813**                      the type of the boundary.
814**
815**      Returns:
816**              The next character in the input stream.
817*/
818
819static int
820mime_getchar_crlf(fp, boundaries, btp)
821        register SM_FILE_T *fp;
822        char **boundaries;
823        int *btp;
824{
825        static bool sendlf = false;
826        int c;
827
828        if (sendlf)
829        {
830                sendlf = false;
831                return '\n';
832        }
833        c = mime_getchar(fp, boundaries, btp);
834        if (c == '\n' && MapNLtoCRLF)
835        {
836                sendlf = true;
837                return '\r';
838        }
839        return c;
840}
841/*
842**  MIMEBOUNDARY -- determine if this line is a MIME boundary & its type
843**
844**      Parameters:
845**              line -- the input line.
846**              boundaries -- the set of currently pending boundaries.
847**
848**      Returns:
849**              MBT_NOTSEP -- if this is not a separator line
850**              MBT_INTERMED -- if this is an intermediate separator
851**              MBT_FINAL -- if this is a final boundary
852**              MBT_SYNTAX -- if this is a boundary for the wrong
853**                      enclosure -- i.e., a syntax error.
854*/
855
856static int
857mimeboundary(line, boundaries)
858        register char *line;
859        char **boundaries;
860{
861        int type = MBT_NOTSEP;
862        int i;
863        int savec;
864
865        if (line[0] != '-' || line[1] != '-' || boundaries == NULL)
866                return MBT_NOTSEP;
867        i = strlen(line);
868        if (i > 0 && line[i - 1] == '\n')
869                i--;
870
871        /* strip off trailing whitespace */
872        while (i > 0 && (line[i - 1] == ' ' || line[i - 1] == '\t'))
873                i--;
874        savec = line[i];
875        line[i] = '\0';
876
877        if (tTd(43, 5))
878                sm_dprintf("mimeboundary: line=\"%s\"... ", line);
879
880        /* check for this as an intermediate boundary */
881        if (isboundary(&line[2], boundaries) >= 0)
882                type = MBT_INTERMED;
883        else if (i > 2 && strncmp(&line[i - 2], "--", 2) == 0)
884        {
885                /* check for a final boundary */
886                line[i - 2] = '\0';
887                if (isboundary(&line[2], boundaries) >= 0)
888                        type = MBT_FINAL;
889                line[i - 2] = '-';
890        }
891
892        line[i] = savec;
893        if (tTd(43, 5))
894                sm_dprintf("%s\n", MimeBoundaryNames[type]);
895        return type;
896}
897/*
898**  DEFCHARSET -- return default character set for message
899**
900**      The first choice for character set is for the mailer
901**      corresponding to the envelope sender.  If neither that
902**      nor the global configuration file has a default character
903**      set defined, return "unknown-8bit" as recommended by
904**      RFC 1428 section 3.
905**
906**      Parameters:
907**              e -- the envelope for this message.
908**
909**      Returns:
910**              The default character set for that mailer.
911*/
912
913char *
914defcharset(e)
915        register ENVELOPE *e;
916{
917        if (e != NULL && e->e_from.q_mailer != NULL &&
918            e->e_from.q_mailer->m_defcharset != NULL)
919                return e->e_from.q_mailer->m_defcharset;
920        if (DefaultCharSet != NULL)
921                return DefaultCharSet;
922        return "unknown-8bit";
923}
924/*
925**  ISBOUNDARY -- is a given string a currently valid boundary?
926**
927**      Parameters:
928**              line -- the current input line.
929**              boundaries -- the list of valid boundaries.
930**
931**      Returns:
932**              The index number in boundaries if the line is found.
933**              -1 -- otherwise.
934**
935*/
936
937static int
938isboundary(line, boundaries)
939        char *line;
940        char **boundaries;
941{
942        register int i;
943
944        for (i = 0; i <= MAXMIMENESTING && boundaries[i] != NULL; i++)
945        {
946                if (strcmp(line, boundaries[i]) == 0)
947                        return i;
948        }
949        return -1;
950}
951#endif /* MIME8TO7 */
952
953#if MIME7TO8
954static int      mime_fromqp __P((unsigned char *, unsigned char **, int));
955
956/*
957**  MIME7TO8 -- output 7 bit encoded MIME body in 8 bit format
958**
959**  This is a hack. Supports translating the two 7-bit body-encodings
960**  (quoted-printable and base64) to 8-bit coded bodies.
961**
962**  There is not much point in supporting multipart here, as the UA
963**  will be able to deal with encoded MIME bodies if it can parse MIME
964**  multipart messages.
965**
966**  Note also that we won't be called unless it is a text/plain MIME
967**  message, encoded base64 or QP and mailer flag '9' has been defined
968**  on mailer.
969**
970**  Contributed by Marius Olaffson <marius@rhi.hi.is>.
971**
972**      Parameters:
973**              mci -- mailer connection information.
974**              header -- the header for this body part.
975**              e -- envelope.
976**
977**      Returns:
978**              true iff body was written successfully
979*/
980
981static char index_64[128] =
982{
983        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
984        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
985        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
986        52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
987        -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
988        15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
989        -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
990        41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
991};
992
993# define CHAR64(c)  (((c) < 0 || (c) > 127) ? -1 : index_64[(c)])
994
995bool
996mime7to8(mci, header, e)
997        register MCI *mci;
998        HDR *header;
999        register ENVELOPE *e;
1000{
1001        int pxflags;
1002        register char *p;
1003        char *cte;
1004        char **pvp;
1005        unsigned char *fbufp;
1006        char buf[MAXLINE];
1007        unsigned char fbuf[MAXLINE + 1];
1008        char pvpbuf[MAXLINE];
1009        extern unsigned char MimeTokenTab[256];
1010
1011        p = hvalue("Content-Transfer-Encoding", header);
1012        if (p == NULL ||
1013            (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
1014                           MimeTokenTab)) == NULL ||
1015            pvp[0] == NULL)
1016        {
1017                /* "can't happen" -- upper level should have caught this */
1018                syserr("mime7to8: unparsable CTE %s", p == NULL ? "<NULL>" : p);
1019
1020                /* avoid bounce loops */
1021                e->e_flags |= EF_DONT_MIME;
1022
1023                /* cheap failsafe algorithm -- should work on text/plain */
1024                if (p != NULL)
1025                {
1026                        (void) sm_snprintf(buf, sizeof buf,
1027                                "Content-Transfer-Encoding: %s", p);
1028                        if (!putline(buf, mci))
1029                                goto writeerr;
1030                }
1031                if (!putline("", mci))
1032                        goto writeerr;
1033                mci->mci_flags &= ~MCIF_INHEADER;
1034                while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
1035                        != NULL)
1036                {
1037                        if (!putline(buf, mci))
1038                                goto writeerr;
1039                }
1040                return true;
1041        }
1042        cataddr(pvp, NULL, buf, sizeof buf, '\0');
1043        cte = sm_rpool_strdup_x(e->e_rpool, buf);
1044
1045        mci->mci_flags |= MCIF_INHEADER;
1046        if (!putline("Content-Transfer-Encoding: 8bit", mci))
1047                goto writeerr;
1048        (void) sm_snprintf(buf, sizeof buf,
1049                "X-MIME-Autoconverted: from %.200s to 8bit by %s id %s",
1050                cte, MyHostName, e->e_id);
1051        if (!putline(buf, mci) || !putline("", mci))
1052                goto writeerr;
1053        mci->mci_flags &= ~MCIF_INHEADER;
1054
1055        /*
1056        **  Translate body encoding to 8-bit.  Supports two types of
1057        **  encodings; "base64" and "quoted-printable". Assume qp if
1058        **  it is not base64.
1059        */
1060
1061        pxflags = PXLF_MAPFROM;
1062        if (sm_strcasecmp(cte, "base64") == 0)
1063        {
1064                int c1, c2, c3, c4;
1065
1066                fbufp = fbuf;
1067                while ((c1 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT)) !=
1068                        SM_IO_EOF)
1069                {
1070                        if (isascii(c1) && isspace(c1))
1071                                continue;
1072
1073                        do
1074                        {
1075                                c2 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT);
1076                        } while (isascii(c2) && isspace(c2));
1077                        if (c2 == SM_IO_EOF)
1078                                break;
1079
1080                        do
1081                        {
1082                                c3 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT);
1083                        } while (isascii(c3) && isspace(c3));
1084                        if (c3 == SM_IO_EOF)
1085                                break;
1086
1087                        do
1088                        {
1089                                c4 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT);
1090                        } while (isascii(c4) && isspace(c4));
1091                        if (c4 == SM_IO_EOF)
1092                                break;
1093
1094                        if (c1 == '=' || c2 == '=')
1095                                continue;
1096                        c1 = CHAR64(c1);
1097                        c2 = CHAR64(c2);
1098
1099                        *fbufp = (c1 << 2) | ((c2 & 0x30) >> 4);
1100                        if (*fbufp++ == '\n' || fbufp >= &fbuf[MAXLINE])
1101                        {
1102                                if (*--fbufp != '\n' ||
1103                                    (fbufp > fbuf && *--fbufp != '\r'))
1104                                {
1105                                        pxflags |= PXLF_NOADDEOL;
1106                                        fbufp++;
1107                                }
1108                                if (!putxline((char *) fbuf, fbufp - fbuf,
1109                                              mci, pxflags))
1110                                        goto writeerr;
1111                                pxflags &= ~PXLF_NOADDEOL;
1112                                fbufp = fbuf;
1113                        }
1114                        if (c3 == '=')
1115                                continue;
1116                        c3 = CHAR64(c3);
1117                        *fbufp = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2);
1118                        if (*fbufp++ == '\n' || fbufp >= &fbuf[MAXLINE])
1119                        {
1120                                if (*--fbufp != '\n' ||
1121                                    (fbufp > fbuf && *--fbufp != '\r'))
1122                                {
1123                                        pxflags |= PXLF_NOADDEOL;
1124                                        fbufp++;
1125                                }
1126                                if (!putxline((char *) fbuf, fbufp - fbuf,
1127                                              mci, pxflags))
1128                                        goto writeerr;
1129                                pxflags &= ~PXLF_NOADDEOL;
1130                                fbufp = fbuf;
1131                        }
1132                        if (c4 == '=')
1133                                continue;
1134                        c4 = CHAR64(c4);
1135                        *fbufp = ((c3 & 0x03) << 6) | c4;
1136                        if (*fbufp++ == '\n' || fbufp >= &fbuf[MAXLINE])
1137                        {
1138                                if (*--fbufp != '\n' ||
1139                                    (fbufp > fbuf && *--fbufp != '\r'))
1140                                {
1141                                        pxflags |= PXLF_NOADDEOL;
1142                                        fbufp++;
1143                                }
1144                                if (!putxline((char *) fbuf, fbufp - fbuf,
1145                                              mci, pxflags))
1146                                        goto writeerr;
1147                                pxflags &= ~PXLF_NOADDEOL;
1148                                fbufp = fbuf;
1149                        }
1150                }
1151        }
1152        else
1153        {
1154                int off;
1155
1156                /* quoted-printable */
1157                pxflags |= PXLF_NOADDEOL;
1158                fbufp = fbuf;
1159                while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf,
1160                                   sizeof buf) != NULL)
1161                {
1162                        off = mime_fromqp((unsigned char *) buf, &fbufp,
1163                                          &fbuf[MAXLINE] - fbufp);
1164again:
1165                        if (off < -1)
1166                                continue;
1167
1168                        if (fbufp - fbuf > 0)
1169                        {
1170                                if (!putxline((char *) fbuf, fbufp - fbuf - 1,
1171                                                mci, pxflags))
1172                                        goto writeerr;
1173                        }
1174                        fbufp = fbuf;
1175                        if (off >= 0 && buf[off] != '\0')
1176                        {
1177                                off = mime_fromqp((unsigned char *) (buf + off),
1178                                                  &fbufp,
1179                                                  &fbuf[MAXLINE] - fbufp);
1180                                goto again;
1181                        }
1182                }
1183        }
1184
1185        /* force out partial last line */
1186        if (fbufp > fbuf)
1187        {
1188                *fbufp = '\0';
1189                if (!putxline((char *) fbuf, fbufp - fbuf, mci, pxflags))
1190                        goto writeerr;
1191        }
1192
1193        /*
1194        **  The decoded text may end without an EOL.  Since this function
1195        **  is only called for text/plain MIME messages, it is safe to
1196        **  add an extra one at the end just in case.  This is a hack,
1197        **  but so is auto-converting MIME in the first place.
1198        */
1199
1200        if (!putline("", mci))
1201                goto writeerr;
1202
1203        if (tTd(43, 3))
1204                sm_dprintf("\t\t\tmime7to8 => %s to 8bit done\n", cte);
1205        return true;
1206
1207  writeerr:
1208        return false;
1209}
1210/*
1211**  The following is based on Borenstein's "codes.c" module, with simplifying
1212**  changes as we do not deal with multipart, and to do the translation in-core,
1213**  with an attempt to prevent overrun of output buffers.
1214**
1215**  What is needed here are changes to defend this code better against
1216**  bad encodings. Questionable to always return 0xFF for bad mappings.
1217*/
1218
1219static char index_hex[128] =
1220{
1221        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1222        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1223        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1224        0, 1, 2, 3,  4, 5, 6, 7,  8, 9,-1,-1, -1,-1,-1,-1,
1225        -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1226        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1227        -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
1228        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
1229};
1230
1231# define HEXCHAR(c)  (((c) < 0 || (c) > 127) ? -1 : index_hex[(c)])
1232
1233/*
1234**  MIME_FROMQP -- decode quoted printable string
1235**
1236**      Parameters:
1237**              infile -- input (encoded) string
1238**              outfile -- output string
1239**              maxlen -- size of output buffer
1240**
1241**      Returns:
1242**              -2 if decoding failure
1243**              -1 if infile completely decoded into outfile
1244**              >= 0 is the position in infile decoding
1245**                      reached before maxlen was reached
1246*/
1247
1248static int
1249mime_fromqp(infile, outfile, maxlen)
1250        unsigned char *infile;
1251        unsigned char **outfile;
1252        int maxlen;             /* Max # of chars allowed in outfile */
1253{
1254        int c1, c2;
1255        int nchar = 0;
1256        unsigned char *b;
1257
1258        /* decrement by one for trailing '\0', at least one other char */
1259        if (--maxlen < 1)
1260                return 0;
1261
1262        b = infile;
1263        while ((c1 = *infile++) != '\0' && nchar < maxlen)
1264        {
1265                if (c1 == '=')
1266                {
1267                        if ((c1 = *infile++) == '\0')
1268                                break;
1269
1270                        if (c1 == '\n' || (c1 = HEXCHAR(c1)) == -1)
1271                        {
1272                                /* ignore it and the rest of the buffer */
1273                                return -2;
1274                        }
1275                        else
1276                        {
1277                                do
1278                                {
1279                                        if ((c2 = *infile++) == '\0')
1280                                        {
1281                                                c2 = -1;
1282                                                break;
1283                                        }
1284                                } while ((c2 = HEXCHAR(c2)) == -1);
1285
1286                                if (c2 == -1)
1287                                        break;
1288                                nchar++;
1289                                *(*outfile)++ = c1 << 4 | c2;
1290                        }
1291                }
1292                else
1293                {
1294                        nchar++;
1295                        *(*outfile)++ = c1;
1296                        if (c1 == '\n')
1297                                break;
1298                }
1299        }
1300        *(*outfile)++ = '\0';
1301        if (nchar >= maxlen)
1302                return (infile - b - 1);
1303        return -1;
1304}
1305#endif /* MIME7TO8 */
Note: See TracBrowser for help on using the repository browser.