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