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