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