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