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.142.2.1 2006/05/23 01:32:08 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'); 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 bt = mimeboundary(buf, boundaries); 531 if (bt != MBT_NOTSEP) 532 break; 533 if (!putline(buf, mci)) 534 goto writeerr; 535 } 536 if (sm_io_eof(e->e_dfp)) 537 bt = MBT_FINAL; 538 } 539 else if (!MapNLtoCRLF || 540 (sectionsize / 8 < sectionhighbits && !use_qp)) 541 { 542 /* use base64 encoding */ 543 int c1, c2; 544 545 if (tTd(43, 36)) 546 sm_dprintf(" ...Content-Transfer-Encoding: base64\n"); 547 if (!putline("Content-Transfer-Encoding: base64", mci)) 548 goto writeerr; 549 (void) sm_snprintf(buf, sizeof buf, 550 "X-MIME-Autoconverted: from 8bit to base64 by %s id %s", 551 MyHostName, e->e_id); 552 if (!putline(buf, mci) || !putline("", mci)) 553 goto writeerr; 554 mci->mci_flags &= ~MCIF_INHEADER; 555 while ((c1 = mime_getchar_crlf(e->e_dfp, boundaries, &bt)) != 556 SM_IO_EOF) 557 { 558 if (linelen > 71) 559 { 560 *bp = '\0'; 561 if (!putline(buf, mci)) 562 goto writeerr; 563 linelen = 0; 564 bp = buf; 565 } 566 linelen += 4; 567 *bp++ = Base64Code[(c1 >> 2)]; 568 c1 = (c1 & 0x03) << 4; 569 c2 = mime_getchar_crlf(e->e_dfp, boundaries, &bt); 570 if (c2 == SM_IO_EOF) 571 { 572 *bp++ = Base64Code[c1]; 573 *bp++ = '='; 574 *bp++ = '='; 575 break; 576 } 577 c1 |= (c2 >> 4) & 0x0f; 578 *bp++ = Base64Code[c1]; 579 c1 = (c2 & 0x0f) << 2; 580 c2 = mime_getchar_crlf(e->e_dfp, boundaries, &bt); 581 if (c2 == SM_IO_EOF) 582 { 583 *bp++ = Base64Code[c1]; 584 *bp++ = '='; 585 break; 586 } 587 c1 |= (c2 >> 6) & 0x03; 588 *bp++ = Base64Code[c1]; 589 *bp++ = Base64Code[c2 & 0x3f]; 590 } 591 *bp = '\0'; 592 if (!putline(buf, mci)) 593 goto writeerr; 594 } 595 else 596 { 597 /* use quoted-printable encoding */ 598 int c1, c2; 599 int fromstate; 600 BITMAP256 badchars; 601 602 /* set up map of characters that must be mapped */ 603 clrbitmap(badchars); 604 for (c1 = 0x00; c1 < 0x20; c1++) 605 setbitn(c1, badchars); 606 clrbitn('\t', badchars); 607 for (c1 = 0x7f; c1 < 0x100; c1++) 608 setbitn(c1, badchars); 609 setbitn('=', badchars); 610 if (bitnset(M_EBCDIC, mci->mci_mailer->m_flags)) 611 for (p = "!\"#$@[\\]^`{|}~"; *p != '\0'; p++) 612 setbitn(*p, badchars); 613 614 if (tTd(43, 36)) 615 sm_dprintf(" ...Content-Transfer-Encoding: quoted-printable\n"); 616 if (!putline("Content-Transfer-Encoding: quoted-printable", 617 mci)) 618 goto writeerr; 619 (void) sm_snprintf(buf, sizeof buf, 620 "X-MIME-Autoconverted: from 8bit to quoted-printable by %s id %s", 621 MyHostName, e->e_id); 622 if (!putline(buf, mci) || !putline("", mci)) 623 goto writeerr; 624 mci->mci_flags &= ~MCIF_INHEADER; 625 fromstate = 0; 626 c2 = '\n'; 627 while ((c1 = mime_getchar(e->e_dfp, boundaries, &bt)) != 628 SM_IO_EOF) 629 { 630 if (c1 == '\n') 631 { 632 if (c2 == ' ' || c2 == '\t') 633 { 634 *bp++ = '='; 635 *bp++ = Base16Code[(c2 >> 4) & 0x0f]; 636 *bp++ = Base16Code[c2 & 0x0f]; 637 } 638 if (buf[0] == '.' && bp == &buf[1]) 639 { 640 buf[0] = '='; 641 *bp++ = Base16Code[('.' >> 4) & 0x0f]; 642 *bp++ = Base16Code['.' & 0x0f]; 643 } 644 *bp = '\0'; 645 if (!putline(buf, mci)) 646 goto writeerr; 647 linelen = fromstate = 0; 648 bp = buf; 649 c2 = c1; 650 continue; 651 } 652 if (c2 == ' ' && linelen == 4 && fromstate == 4 && 653 bitnset(M_ESCFROM, mci->mci_mailer->m_flags)) 654 { 655 *bp++ = '='; 656 *bp++ = '2'; 657 *bp++ = '0'; 658 linelen += 3; 659 } 660 else if (c2 == ' ' || c2 == '\t') 661 { 662 *bp++ = c2; 663 linelen++; 664 } 665 if (linelen > 72 && 666 (linelen > 75 || c1 != '.' || 667 (linelen > 73 && c2 == '.'))) 668 { 669 if (linelen > 73 && c2 == '.') 670 bp--; 671 else 672 c2 = '\n'; 673 *bp++ = '='; 674 *bp = '\0'; 675 if (!putline(buf, mci)) 676 goto writeerr; 677 linelen = fromstate = 0; 678 bp = buf; 679 if (c2 == '.') 680 { 681 *bp++ = '.'; 682 linelen++; 683 } 684 } 685 if (bitnset(bitidx(c1), badchars)) 686 { 687 *bp++ = '='; 688 *bp++ = Base16Code[(c1 >> 4) & 0x0f]; 689 *bp++ = Base16Code[c1 & 0x0f]; 690 linelen += 3; 691 } 692 else if (c1 != ' ' && c1 != '\t') 693 { 694 if (linelen < 4 && c1 == "From"[linelen]) 695 fromstate++; 696 *bp++ = c1; 697 linelen++; 698 } 699 c2 = c1; 700 } 701 702 /* output any saved character */ 703 if (c2 == ' ' || c2 == '\t') 704 { 705 *bp++ = '='; 706 *bp++ = Base16Code[(c2 >> 4) & 0x0f]; 707 *bp++ = Base16Code[c2 & 0x0f]; 708 linelen += 3; 709 } 710 711 if (linelen > 0 || boundaries[0] != NULL) 712 { 713 *bp = '\0'; 714 if (!putline(buf, mci)) 715 goto writeerr; 716 } 717 718 } 719 if (tTd(43, 3)) 720 sm_dprintf("\t\t\tmime8to7=>%s (basic)\n", MimeBoundaryNames[bt]); 721 return bt; 722 723 writeerr: 724 return SM_IO_EOF; 725 } 726 /* 727 ** MIME_GETCHAR -- get a character for MIME processing 728 ** 729 ** Treats boundaries as SM_IO_EOF. 730 ** 731 ** Parameters: 732 ** fp -- the input file. 733 ** boundaries -- the current MIME boundaries. 734 ** btp -- if the return value is SM_IO_EOF, *btp is set to 735 ** the type of the boundary. 736 ** 737 ** Returns: 738 ** The next character in the input stream. 739 */ 740 741 static int 742 mime_getchar(fp, boundaries, btp) 743 register SM_FILE_T *fp; 744 char **boundaries; 745 int *btp; 746 { 747 int c; 748 static unsigned char *bp = NULL; 749 static int buflen = 0; 750 static bool atbol = true; /* at beginning of line */ 751 static int bt = MBT_SYNTAX; /* boundary type of next SM_IO_EOF */ 752 static unsigned char buf[128]; /* need not be a full line */ 753 int start = 0; /* indicates position of - in buffer */ 754 755 if (buflen == 1 && *bp == '\n') 756 { 757 /* last \n in buffer may be part of next MIME boundary */ 758 c = *bp; 759 } 760 else if (buflen > 0) 761 { 762 buflen--; 763 return *bp++; 764 } 765 else 766 c = sm_io_getc(fp, SM_TIME_DEFAULT); 767 bp = buf; 768 buflen = 0; 769 if (c == '\n') 770 { 771 /* might be part of a MIME boundary */ 772 *bp++ = c; 773 atbol = true; 774 c = sm_io_getc(fp, SM_TIME_DEFAULT); 775 if (c == '\n') 776 { 777 (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c); 778 return c; 779 } 780 start = 1; 781 } 782 if (c != SM_IO_EOF) 783 *bp++ = c; 784 else 785 bt = MBT_FINAL; 786 if (atbol && c == '-') 787 { 788 /* check for a message boundary */ 789 c = sm_io_getc(fp, SM_TIME_DEFAULT); 790 if (c != '-') 791 { 792 if (c != SM_IO_EOF) 793 *bp++ = c; 794 else 795 bt = MBT_FINAL; 796 buflen = bp - buf - 1; 797 bp = buf; 798 return *bp++; 799 } 800 801 /* got "--", now check for rest of separator */ 802 *bp++ = '-'; 803 while (bp < &buf[sizeof buf - 2] && 804 (c = sm_io_getc(fp, SM_TIME_DEFAULT)) != SM_IO_EOF && 805 c != '\n') 806 { 807 *bp++ = c; 808 } 809 *bp = '\0'; /* XXX simply cut off? */ 810 bt = mimeboundary((char *) &buf[start], boundaries); 811 switch (bt) 812 { 813 case MBT_FINAL: 814 case MBT_INTERMED: 815 /* we have a message boundary */ 816 buflen = 0; 817 *btp = bt; 818 return SM_IO_EOF; 819 } 820 821 if (bp < &buf[sizeof buf - 2] && c != SM_IO_EOF) 822 *bp++ = c; 823 } 824 825 atbol = c == '\n'; 826 buflen = bp - buf - 1; 827 if (buflen < 0) 828 { 829 *btp = bt; 830 return SM_IO_EOF; 831 } 832 bp = buf; 833 return *bp++; 834 } 835 /* 836 ** MIME_GETCHAR_CRLF -- do mime_getchar, but translate NL => CRLF 837 ** 838 ** Parameters: 839 ** fp -- the input file. 840 ** boundaries -- the current MIME boundaries. 841 ** btp -- if the return value is SM_IO_EOF, *btp is set to 842 ** the type of the boundary. 843 ** 844 ** Returns: 845 ** The next character in the input stream. 846 */ 847 848 static int 849 mime_getchar_crlf(fp, boundaries, btp) 850 register SM_FILE_T *fp; 851 char **boundaries; 852 int *btp; 853 { 854 static bool sendlf = false; 855 int c; 856 857 if (sendlf) 858 { 859 sendlf = false; 860 return '\n'; 861 } 862 c = mime_getchar(fp, boundaries, btp); 863 if (c == '\n' && MapNLtoCRLF) 864 { 865 sendlf = true; 866 return '\r'; 867 } 868 return c; 869 } 870 /* 871 ** MIMEBOUNDARY -- determine if this line is a MIME boundary & its type 872 ** 873 ** Parameters: 874 ** line -- the input line. 875 ** boundaries -- the set of currently pending boundaries. 876 ** 877 ** Returns: 878 ** MBT_NOTSEP -- if this is not a separator line 879 ** MBT_INTERMED -- if this is an intermediate separator 880 ** MBT_FINAL -- if this is a final boundary 881 ** MBT_SYNTAX -- if this is a boundary for the wrong 882 ** enclosure -- i.e., a syntax error. 883 */ 884 885 static int 886 mimeboundary(line, boundaries) 887 register char *line; 888 char **boundaries; 889 { 890 int type = MBT_NOTSEP; 891 int i; 892 int savec; 893 894 if (line[0] != '-' || line[1] != '-' || boundaries == NULL) 895 return MBT_NOTSEP; 896 i = strlen(line); 897 if (i > 0 && line[i - 1] == '\n') 898 i--; 899 900 /* strip off trailing whitespace */ 901 while (i > 0 && (line[i - 1] == ' ' || line[i - 1] == '\t' 902 #if _FFR_MIME_CR_OK 903 || line[i - 1] == '\r' 904 #endif /* _FFR_MIME_CR_OK */ 905 )) 906 i--; 907 savec = line[i]; 908 line[i] = '\0'; 909 910 if (tTd(43, 5)) 911 sm_dprintf("mimeboundary: line=\"%s\"... ", line); 912 913 /* check for this as an intermediate boundary */ 914 if (isboundary(&line[2], boundaries) >= 0) 915 type = MBT_INTERMED; 916 else if (i > 2 && strncmp(&line[i - 2], "--", 2) == 0) 917 { 918 /* check for a final boundary */ 919 line[i - 2] = '\0'; 920 if (isboundary(&line[2], boundaries) >= 0) 921 type = MBT_FINAL; 922 line[i - 2] = '-'; 923 } 924 925 line[i] = savec; 926 if (tTd(43, 5)) 927 sm_dprintf("%s\n", MimeBoundaryNames[type]); 928 return type; 929 } 930 /* 931 ** DEFCHARSET -- return default character set for message 932 ** 933 ** The first choice for character set is for the mailer 934 ** corresponding to the envelope sender. If neither that 935 ** nor the global configuration file has a default character 936 ** set defined, return "unknown-8bit" as recommended by 937 ** RFC 1428 section 3. 938 ** 939 ** Parameters: 940 ** e -- the envelope for this message. 941 ** 942 ** Returns: 943 ** The default character set for that mailer. 944 */ 945 946 char * 947 defcharset(e) 948 register ENVELOPE *e; 949 { 950 if (e != NULL && e->e_from.q_mailer != NULL && 951 e->e_from.q_mailer->m_defcharset != NULL) 952 return e->e_from.q_mailer->m_defcharset; 953 if (DefaultCharSet != NULL) 954 return DefaultCharSet; 955 return "unknown-8bit"; 956 } 957 /* 958 ** ISBOUNDARY -- is a given string a currently valid boundary? 959 ** 960 ** Parameters: 961 ** line -- the current input line. 962 ** boundaries -- the list of valid boundaries. 963 ** 964 ** Returns: 965 ** The index number in boundaries if the line is found. 966 ** -1 -- otherwise. 967 ** 968 */ 969 970 static int 971 isboundary(line, boundaries) 972 char *line; 973 char **boundaries; 974 { 975 register int i; 976 977 for (i = 0; i <= MAXMIMENESTING && boundaries[i] != NULL; i++) 978 { 979 if (strcmp(line, boundaries[i]) == 0) 980 return i; 981 } 982 return -1; 983 } 984 #endif /* MIME8TO7 */ 985 986 #if MIME7TO8 987 static int mime_fromqp __P((unsigned char *, unsigned char **, int)); 988 989 /* 990 ** MIME7TO8 -- output 7 bit encoded MIME body in 8 bit format 991 ** 992 ** This is a hack. Supports translating the two 7-bit body-encodings 993 ** (quoted-printable and base64) to 8-bit coded bodies. 994 ** 995 ** There is not much point in supporting multipart here, as the UA 996 ** will be able to deal with encoded MIME bodies if it can parse MIME 997 ** multipart messages. 998 ** 999 ** Note also that we won't be called unless it is a text/plain MIME 1000 ** message, encoded base64 or QP and mailer flag '9' has been defined 1001 ** on mailer. 1002 ** 1003 ** Contributed by Marius Olaffson <marius@rhi.hi.is>. 1004 ** 1005 ** Parameters: 1006 ** mci -- mailer connection information. 1007 ** header -- the header for this body part. 1008 ** e -- envelope. 1009 ** 1010 ** Returns: 1011 ** true iff body was written successfully 1012 */ 1013 1014 static char index_64[128] = 1015 { 1016 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 1017 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 1018 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, 1019 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1, 1020 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, 1021 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, 1022 -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, 1023 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 1024 }; 1025 1026 # define CHAR64(c) (((c) < 0 || (c) > 127) ? -1 : index_64[(c)]) 1027 1028 bool 1029 mime7to8(mci, header, e) 1030 register MCI *mci; 1031 HDR *header; 1032 register ENVELOPE *e; 1033 { 1034 int pxflags; 1035 register char *p; 1036 char *cte; 1037 char **pvp; 1038 unsigned char *fbufp; 1039 char buf[MAXLINE]; 1040 unsigned char fbuf[MAXLINE + 1]; 1041 char pvpbuf[MAXLINE]; 1042 extern unsigned char MimeTokenTab[256]; 1043 1044 p = hvalue("Content-Transfer-Encoding", header); 1045 if (p == NULL || 1046 (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL, 1047 MimeTokenTab, false)) == NULL || 1048 pvp[0] == NULL) 1049 { 1050 /* "can't happen" -- upper level should have caught this */ 1051 syserr("mime7to8: unparsable CTE %s", p == NULL ? "<NULL>" : p); 1052 1053 /* avoid bounce loops */ 1054 e->e_flags |= EF_DONT_MIME; 1055 1056 /* cheap failsafe algorithm -- should work on text/plain */ 1057 if (p != NULL) 1058 { 1059 (void) sm_snprintf(buf, sizeof buf, 1060 "Content-Transfer-Encoding: %s", p); 1061 if (!putline(buf, mci)) 1062 goto writeerr; 1063 } 1064 if (!putline("", mci)) 1065 goto writeerr; 1066 mci->mci_flags &= ~MCIF_INHEADER; 1067 while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf) 1068 != NULL) 1069 { 1070 if (!putline(buf, mci)) 1071 goto writeerr; 1072 } 1073 return true; 1074 } 1075 cataddr(pvp, NULL, buf, sizeof buf, '\0'); 1076 cte = sm_rpool_strdup_x(e->e_rpool, buf); 1077 1078 mci->mci_flags |= MCIF_INHEADER; 1079 if (!putline("Content-Transfer-Encoding: 8bit", mci)) 1080 goto writeerr; 1081 (void) sm_snprintf(buf, sizeof buf, 1082 "X-MIME-Autoconverted: from %.200s to 8bit by %s id %s", 1083 cte, MyHostName, e->e_id); 1084 if (!putline(buf, mci) || !putline("", mci)) 1085 goto writeerr; 1086 mci->mci_flags &= ~MCIF_INHEADER; 1087 1088 /* 1089 ** Translate body encoding to 8-bit. Supports two types of 1090 ** encodings; "base64" and "quoted-printable". Assume qp if 1091 ** it is not base64. 1092 */ 1093 1094 pxflags = PXLF_MAPFROM; 1095 if (sm_strcasecmp(cte, "base64") == 0) 1096 { 1097 int c1, c2, c3, c4; 1098 1099 fbufp = fbuf; 1100 while ((c1 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT)) != 1101 SM_IO_EOF) 1102 { 1103 if (isascii(c1) && isspace(c1)) 1104 continue; 1105 1106 do 1107 { 1108 c2 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT); 1109 } while (isascii(c2) && isspace(c2)); 1110 if (c2 == SM_IO_EOF) 1111 break; 1112 1113 do 1114 { 1115 c3 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT); 1116 } while (isascii(c3) && isspace(c3)); 1117 if (c3 == SM_IO_EOF) 1118 break; 1119 1120 do 1121 { 1122 c4 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT); 1123 } while (isascii(c4) && isspace(c4)); 1124 if (c4 == SM_IO_EOF) 1125 break; 1126 1127 if (c1 == '=' || c2 == '=') 1128 continue; 1129 c1 = CHAR64(c1); 1130 c2 = CHAR64(c2); 1131 1132 #if MIME7TO8_OLD 1133 #define CHK_EOL if (*--fbufp != '\n' || (fbufp > fbuf && *--fbufp != '\r')) \ 1134 ++fbufp; 1135 #else /* MIME7TO8_OLD */ 1136 #define CHK_EOL if (*--fbufp != '\n' || (fbufp > fbuf && *--fbufp != '\r')) \ 1137 { \ 1138 ++fbufp; \ 1139 pxflags |= PXLF_NOADDEOL; \ 1140 } 1141 #endif /* MIME7TO8_OLD */ 1142 1143 #define PUTLINE64 \ 1144 do \ 1145 { \ 1146 if (*fbufp++ == '\n' || fbufp >= &fbuf[MAXLINE]) \ 1147 { \ 1148 CHK_EOL; \ 1149 if (!putxline((char *) fbuf, fbufp - fbuf, mci, pxflags)) \ 1150 goto writeerr; \ 1151 pxflags &= ~PXLF_NOADDEOL; \ 1152 fbufp = fbuf; \ 1153 } \ 1154 } while (0) 1155 1156 *fbufp = (c1 << 2) | ((c2 & 0x30) >> 4); 1157 PUTLINE64; 1158 if (c3 == '=') 1159 continue; 1160 c3 = CHAR64(c3); 1161 *fbufp = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2); 1162 PUTLINE64; 1163 if (c4 == '=') 1164 continue; 1165 c4 = CHAR64(c4); 1166 *fbufp = ((c3 & 0x03) << 6) | c4; 1167 PUTLINE64; 1168 } 1169 } 1170 else 1171 { 1172 int off; 1173 1174 /* quoted-printable */ 1175 pxflags |= PXLF_NOADDEOL; 1176 fbufp = fbuf; 1177 while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, 1178 sizeof buf) != NULL) 1179 { 1180 off = mime_fromqp((unsigned char *) buf, &fbufp, 1181 &fbuf[MAXLINE] - fbufp); 1182 again: 1183 if (off < -1) 1184 continue; 1185 1186 if (fbufp - fbuf > 0) 1187 { 1188 if (!putxline((char *) fbuf, fbufp - fbuf - 1, 1189 mci, pxflags)) 1190 goto writeerr; 1191 } 1192 fbufp = fbuf; 1193 if (off >= 0 && buf[off] != '\0') 1194 { 1195 off = mime_fromqp((unsigned char *) (buf + off), 1196 &fbufp, 1197 &fbuf[MAXLINE] - fbufp); 1198 goto again; 1199 } 1200 } 1201 } 1202 1203 /* force out partial last line */ 1204 if (fbufp > fbuf) 1205 { 1206 *fbufp = '\0'; 1207 if (!putxline((char *) fbuf, fbufp - fbuf, mci, pxflags)) 1208 goto writeerr; 1209 } 1210 1211 /* 1212 ** The decoded text may end without an EOL. Since this function 1213 ** is only called for text/plain MIME messages, it is safe to 1214 ** add an extra one at the end just in case. This is a hack, 1215 ** but so is auto-converting MIME in the first place. 1216 */ 1217 1218 if (!putline("", mci)) 1219 goto writeerr; 1220 1221 if (tTd(43, 3)) 1222 sm_dprintf("\t\t\tmime7to8 => %s to 8bit done\n", cte); 1223 return true; 1224 1225 writeerr: 1226 return false; 1227 } 1228 /* 1229 ** The following is based on Borenstein's "codes.c" module, with simplifying 1230 ** changes as we do not deal with multipart, and to do the translation in-core, 1231 ** with an attempt to prevent overrun of output buffers. 1232 ** 1233 ** What is needed here are changes to defend this code better against 1234 ** bad encodings. Questionable to always return 0xFF for bad mappings. 1235 */ 1236 1237 static char index_hex[128] = 1238 { 1239 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 1240 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 1241 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 1242 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-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 -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, 1246 -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1 1247 }; 1248 1249 # define HEXCHAR(c) (((c) < 0 || (c) > 127) ? -1 : index_hex[(c)]) 1250 1251 /* 1252 ** MIME_FROMQP -- decode quoted printable string 1253 ** 1254 ** Parameters: 1255 ** infile -- input (encoded) string 1256 ** outfile -- output string 1257 ** maxlen -- size of output buffer 1258 ** 1259 ** Returns: 1260 ** -2 if decoding failure 1261 ** -1 if infile completely decoded into outfile 1262 ** >= 0 is the position in infile decoding 1263 ** reached before maxlen was reached 1264 */ 1265 1266 static int 1267 mime_fromqp(infile, outfile, maxlen) 1268 unsigned char *infile; 1269 unsigned char **outfile; 1270 int maxlen; /* Max # of chars allowed in outfile */ 1271 { 1272 int c1, c2; 1273 int nchar = 0; 1274 unsigned char *b; 1275 1276 /* decrement by one for trailing '\0', at least one other char */ 1277 if (--maxlen < 1) 1278 return 0; 1279 1280 b = infile; 1281 while ((c1 = *infile++) != '\0' && nchar < maxlen) 1282 { 1283 if (c1 == '=') 1284 { 1285 if ((c1 = *infile++) == '\0') 1286 break; 1287 1288 if (c1 == '\n' || (c1 = HEXCHAR(c1)) == -1) 1289 { 1290 /* ignore it and the rest of the buffer */ 1291 return -2; 1292 } 1293 else 1294 { 1295 do 1296 { 1297 if ((c2 = *infile++) == '\0') 1298 { 1299 c2 = -1; 1300 break; 1301 } 1302 } while ((c2 = HEXCHAR(c2)) == -1); 1303 1304 if (c2 == -1) 1305 break; 1306 nchar++; 1307 *(*outfile)++ = c1 << 4 | c2; 1308 } 1309 } 1310 else 1311 { 1312 nchar++; 1313 *(*outfile)++ = c1; 1314 if (c1 == '\n') 1315 break; 1316 } 1317 } 1318 *(*outfile)++ = '\0'; 1319 if (nchar >= maxlen) 1320 return (infile - b - 1); 1321 return -1; 1322 } 1323 #endif /* MIME7TO8 */ 1324