1 /* 2 * Copyright (c) 1998 Sendmail, Inc. All rights reserved. 3 * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. 4 * Copyright (c) 1988, 1993 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 #ifndef lint 14 static char sccsid[] = "@(#)collect.c 8.93 (Berkeley) 1/26/1999"; 15 #endif /* not lint */ 16 17 # include <errno.h> 18 # include "sendmail.h" 19 20 /* 21 ** COLLECT -- read & parse message header & make temp file. 22 ** 23 ** Creates a temporary file name and copies the standard 24 ** input to that file. Leading UNIX-style "From" lines are 25 ** stripped off (after important information is extracted). 26 ** 27 ** Parameters: 28 ** fp -- file to read. 29 ** smtpmode -- if set, we are running SMTP: give an RFC821 30 ** style message to say we are ready to collect 31 ** input, and never ignore a single dot to mean 32 ** end of message. 33 ** hdrp -- the location to stash the header. 34 ** e -- the current envelope. 35 ** 36 ** Returns: 37 ** none. 38 ** 39 ** Side Effects: 40 ** Temp file is created and filled. 41 ** The from person may be set. 42 */ 43 44 static jmp_buf CtxCollectTimeout; 45 static void collecttimeout __P((time_t)); 46 static bool CollectProgress; 47 static EVENT *CollectTimeout; 48 49 /* values for input state machine */ 50 #define IS_NORM 0 /* middle of line */ 51 #define IS_BOL 1 /* beginning of line */ 52 #define IS_DOT 2 /* read a dot at beginning of line */ 53 #define IS_DOTCR 3 /* read ".\r" at beginning of line */ 54 #define IS_CR 4 /* read a carriage return */ 55 56 /* values for message state machine */ 57 #define MS_UFROM 0 /* reading Unix from line */ 58 #define MS_HEADER 1 /* reading message header */ 59 #define MS_BODY 2 /* reading message body */ 60 #define MS_DISCARD 3 /* discarding rest of message */ 61 62 void 63 collect(fp, smtpmode, hdrp, e) 64 FILE *fp; 65 bool smtpmode; 66 HDR **hdrp; 67 register ENVELOPE *e; 68 { 69 register FILE *volatile tf; 70 volatile bool ignrdot = smtpmode ? FALSE : IgnrDot; 71 volatile time_t dbto = smtpmode ? TimeOuts.to_datablock : 0; 72 register char *volatile bp; 73 volatile int c = EOF; 74 volatile bool inputerr = FALSE; 75 bool headeronly; 76 char *volatile buf; 77 volatile int buflen; 78 volatile int istate; 79 volatile int mstate; 80 u_char *volatile pbp; 81 int hdrslen = 0; 82 u_char peekbuf[8]; 83 char dfname[MAXQFNAME]; 84 char bufbuf[MAXLINE]; 85 extern bool isheader __P((char *)); 86 extern void tferror __P((FILE *volatile, ENVELOPE *)); 87 88 headeronly = hdrp != NULL; 89 90 /* 91 ** Create the temp file name and create the file. 92 */ 93 94 if (!headeronly) 95 { 96 int tfd; 97 struct stat stbuf; 98 99 strcpy(dfname, queuename(e, 'd')); 100 tfd = dfopen(dfname, O_WRONLY|O_CREAT|O_TRUNC, FileMode, SFF_ANYFILE); 101 if (tfd < 0 || (tf = fdopen(tfd, "w")) == NULL) 102 { 103 syserr("Cannot create %s", dfname); 104 e->e_flags |= EF_NO_BODY_RETN; 105 finis(TRUE, ExitStat); 106 } 107 if (fstat(fileno(tf), &stbuf) < 0) 108 e->e_dfino = -1; 109 else 110 { 111 e->e_dfdev = stbuf.st_dev; 112 e->e_dfino = stbuf.st_ino; 113 } 114 HasEightBits = FALSE; 115 e->e_msgsize = 0; 116 e->e_flags |= EF_HAS_DF; 117 } 118 119 /* 120 ** Tell ARPANET to go ahead. 121 */ 122 123 if (smtpmode) 124 message("354 Enter mail, end with \".\" on a line by itself"); 125 126 if (tTd(30, 2)) 127 printf("collect\n"); 128 129 /* 130 ** Read the message. 131 ** 132 ** This is done using two interleaved state machines. 133 ** The input state machine is looking for things like 134 ** hidden dots; the message state machine is handling 135 ** the larger picture (e.g., header versus body). 136 */ 137 138 buf = bp = bufbuf; 139 buflen = sizeof bufbuf; 140 pbp = peekbuf; 141 istate = IS_BOL; 142 mstate = SaveFrom ? MS_HEADER : MS_UFROM; 143 CollectProgress = FALSE; 144 145 if (dbto != 0) 146 { 147 /* handle possible input timeout */ 148 if (setjmp(CtxCollectTimeout) != 0) 149 { 150 if (LogLevel > 2) 151 sm_syslog(LOG_NOTICE, e->e_id, 152 "timeout waiting for input from %s during message collect", 153 CurHostName ? CurHostName : "<local machine>"); 154 errno = 0; 155 usrerr("451 timeout waiting for input during message collect"); 156 goto readerr; 157 } 158 CollectTimeout = setevent(dbto, collecttimeout, dbto); 159 } 160 161 for (;;) 162 { 163 if (tTd(30, 35)) 164 printf("top, istate=%d, mstate=%d\n", istate, mstate); 165 for (;;) 166 { 167 if (pbp > peekbuf) 168 c = *--pbp; 169 else 170 { 171 while (!feof(fp) && !ferror(fp)) 172 { 173 errno = 0; 174 c = getc(fp); 175 if (errno != EINTR) 176 break; 177 clearerr(fp); 178 } 179 CollectProgress = TRUE; 180 if (TrafficLogFile != NULL && !headeronly) 181 { 182 if (istate == IS_BOL) 183 fprintf(TrafficLogFile, "%05d <<< ", 184 (int) getpid()); 185 if (c == EOF) 186 fprintf(TrafficLogFile, "[EOF]\n"); 187 else 188 putc(c, TrafficLogFile); 189 } 190 if (c == EOF) 191 goto readerr; 192 if (SevenBitInput) 193 c &= 0x7f; 194 else 195 HasEightBits |= bitset(0x80, c); 196 } 197 if (tTd(30, 94)) 198 printf("istate=%d, c=%c (0x%x)\n", 199 istate, c, c); 200 switch (istate) 201 { 202 case IS_BOL: 203 if (c == '.') 204 { 205 istate = IS_DOT; 206 continue; 207 } 208 break; 209 210 case IS_DOT: 211 if (c == '\n' && !ignrdot && 212 !bitset(EF_NL_NOT_EOL, e->e_flags)) 213 goto readerr; 214 else if (c == '\r' && 215 !bitset(EF_CRLF_NOT_EOL, e->e_flags)) 216 { 217 istate = IS_DOTCR; 218 continue; 219 } 220 else if (c != '.' || 221 (OpMode != MD_SMTP && 222 OpMode != MD_DAEMON && 223 OpMode != MD_ARPAFTP)) 224 { 225 *pbp++ = c; 226 c = '.'; 227 } 228 break; 229 230 case IS_DOTCR: 231 if (c == '\n' && !ignrdot) 232 goto readerr; 233 else 234 { 235 /* push back the ".\rx" */ 236 *pbp++ = c; 237 *pbp++ = '\r'; 238 c = '.'; 239 } 240 break; 241 242 case IS_CR: 243 if (c == '\n') 244 istate = IS_BOL; 245 else 246 { 247 ungetc(c, fp); 248 c = '\r'; 249 istate = IS_NORM; 250 } 251 goto bufferchar; 252 } 253 254 if (c == '\r' && !bitset(EF_CRLF_NOT_EOL, e->e_flags)) 255 { 256 istate = IS_CR; 257 continue; 258 } 259 else if (c == '\n' && !bitset(EF_NL_NOT_EOL, e->e_flags)) 260 istate = IS_BOL; 261 else 262 istate = IS_NORM; 263 264 bufferchar: 265 if (!headeronly) 266 e->e_msgsize++; 267 switch (mstate) 268 { 269 case MS_BODY: 270 /* just put the character out */ 271 if (MaxMessageSize <= 0 || 272 e->e_msgsize <= MaxMessageSize) 273 putc(c, tf); 274 275 /* fall through */ 276 277 case MS_DISCARD: 278 continue; 279 } 280 281 /* header -- buffer up */ 282 if (bp >= &buf[buflen - 2]) 283 { 284 char *obuf; 285 286 if (mstate != MS_HEADER) 287 break; 288 289 /* out of space for header */ 290 obuf = buf; 291 if (buflen < MEMCHUNKSIZE) 292 buflen *= 2; 293 else 294 buflen += MEMCHUNKSIZE; 295 buf = xalloc(buflen); 296 bcopy(obuf, buf, bp - obuf); 297 bp = &buf[bp - obuf]; 298 if (obuf != bufbuf) 299 free(obuf); 300 } 301 if (c >= 0200 && c <= 0237) 302 { 303 #if 0 /* causes complaints -- figure out something for 8.9 */ 304 usrerr("Illegal character 0x%x in header", c); 305 #endif 306 } 307 else if (c != '\0') 308 { 309 *bp++ = c; 310 if (MaxHeadersLength > 0 && 311 ++hdrslen > MaxHeadersLength) 312 { 313 sm_syslog(LOG_NOTICE, e->e_id, 314 "headers too large (%d max) from %s during message collect", 315 MaxHeadersLength, 316 CurHostName != NULL ? CurHostName : "localhost"); 317 errno = 0; 318 e->e_flags |= EF_CLRQUEUE; 319 e->e_status = "5.6.0"; 320 usrerr("552 Headers too large (%d max)", 321 MaxHeadersLength); 322 mstate = MS_DISCARD; 323 } 324 } 325 if (istate == IS_BOL) 326 break; 327 } 328 *bp = '\0'; 329 330 nextstate: 331 if (tTd(30, 35)) 332 printf("nextstate, istate=%d, mstate=%d, line = \"%s\"\n", 333 istate, mstate, buf); 334 switch (mstate) 335 { 336 case MS_UFROM: 337 mstate = MS_HEADER; 338 #ifndef NOTUNIX 339 if (strncmp(buf, "From ", 5) == 0) 340 { 341 extern void eatfrom __P((char *volatile, ENVELOPE *)); 342 343 bp = buf; 344 eatfrom(buf, e); 345 continue; 346 } 347 #endif 348 /* fall through */ 349 350 case MS_HEADER: 351 if (!isheader(buf)) 352 { 353 mstate = MS_BODY; 354 goto nextstate; 355 } 356 357 /* check for possible continuation line */ 358 do 359 { 360 clearerr(fp); 361 errno = 0; 362 c = getc(fp); 363 } while (errno == EINTR); 364 if (c != EOF) 365 ungetc(c, fp); 366 if (c == ' ' || c == '\t') 367 { 368 /* yep -- defer this */ 369 continue; 370 } 371 372 /* trim off trailing CRLF or NL */ 373 if (*--bp != '\n' || *--bp != '\r') 374 bp++; 375 *bp = '\0'; 376 377 if (bitset(H_EOH, chompheader(buf, FALSE, hdrp, e))) 378 { 379 mstate = MS_BODY; 380 goto nextstate; 381 } 382 break; 383 384 case MS_BODY: 385 if (tTd(30, 1)) 386 printf("EOH\n"); 387 if (headeronly) 388 goto readerr; 389 bp = buf; 390 391 /* toss blank line */ 392 if ((!bitset(EF_CRLF_NOT_EOL, e->e_flags) && 393 bp[0] == '\r' && bp[1] == '\n') || 394 (!bitset(EF_NL_NOT_EOL, e->e_flags) && 395 bp[0] == '\n')) 396 { 397 break; 398 } 399 400 /* if not a blank separator, write it out */ 401 if (MaxMessageSize <= 0 || 402 e->e_msgsize <= MaxMessageSize) 403 { 404 while (*bp != '\0') 405 putc(*bp++, tf); 406 } 407 break; 408 } 409 bp = buf; 410 } 411 412 readerr: 413 if ((feof(fp) && smtpmode) || ferror(fp)) 414 { 415 const char *errmsg = errstring(errno); 416 417 if (tTd(30, 1)) 418 printf("collect: premature EOM: %s\n", errmsg); 419 if (LogLevel >= 2) 420 sm_syslog(LOG_WARNING, e->e_id, 421 "collect: premature EOM: %s", errmsg); 422 inputerr = TRUE; 423 } 424 425 /* reset global timer */ 426 clrevent(CollectTimeout); 427 428 if (headeronly) 429 return; 430 431 if (tf != NULL && 432 (fflush(tf) != 0 || ferror(tf) || 433 (SuperSafe && fsync(fileno(tf)) < 0) || 434 fclose(tf) < 0)) 435 { 436 tferror(tf, e); 437 flush_errors(TRUE); 438 finis(TRUE, ExitStat); 439 } 440 441 /* An EOF when running SMTP is an error */ 442 if (inputerr && (OpMode == MD_SMTP || OpMode == MD_DAEMON)) 443 { 444 char *host; 445 char *problem; 446 447 host = RealHostName; 448 if (host == NULL) 449 host = "localhost"; 450 451 if (feof(fp)) 452 problem = "unexpected close"; 453 else if (ferror(fp)) 454 problem = "I/O error"; 455 else 456 problem = "read timeout"; 457 if (LogLevel > 0 && feof(fp)) 458 sm_syslog(LOG_NOTICE, e->e_id, 459 "collect: %s on connection from %.100s, sender=%s: %s", 460 problem, host, 461 shortenstring(e->e_from.q_paddr, MAXSHORTSTR), 462 errstring(errno)); 463 if (feof(fp)) 464 usrerr("451 collect: %s on connection from %s, from=%s", 465 problem, host, 466 shortenstring(e->e_from.q_paddr, MAXSHORTSTR)); 467 else 468 syserr("451 collect: %s on connection from %s, from=%s", 469 problem, host, 470 shortenstring(e->e_from.q_paddr, MAXSHORTSTR)); 471 472 /* don't return an error indication */ 473 e->e_to = NULL; 474 e->e_flags &= ~EF_FATALERRS; 475 e->e_flags |= EF_CLRQUEUE; 476 477 /* and don't try to deliver the partial message either */ 478 if (InChild) 479 ExitStat = EX_QUIT; 480 finis(TRUE, ExitStat); 481 } 482 483 /* 484 ** Find out some information from the headers. 485 ** Examples are who is the from person & the date. 486 */ 487 488 eatheader(e, TRUE); 489 490 if (GrabTo && e->e_sendqueue == NULL) 491 usrerr("No recipient addresses found in header"); 492 493 /* collect statistics */ 494 if (OpMode != MD_VERIFY) 495 markstats(e, (ADDRESS *) NULL, FALSE); 496 497 #if _FFR_DSN_RRT_OPTION 498 /* 499 ** If we have a Return-Receipt-To:, turn it into a DSN. 500 */ 501 502 if (RrtImpliesDsn && hvalue("return-receipt-to", e->e_header) != NULL) 503 { 504 ADDRESS *q; 505 506 for (q = e->e_sendqueue; q != NULL; q = q->q_next) 507 if (!bitset(QHASNOTIFY, q->q_flags)) 508 q->q_flags |= QHASNOTIFY|QPINGONSUCCESS; 509 } 510 #endif 511 512 /* 513 ** Add an Apparently-To: line if we have no recipient lines. 514 */ 515 516 if (hvalue("to", e->e_header) != NULL || 517 hvalue("cc", e->e_header) != NULL || 518 hvalue("apparently-to", e->e_header) != NULL) 519 { 520 /* have a valid recipient header -- delete Bcc: headers */ 521 e->e_flags |= EF_DELETE_BCC; 522 } 523 else if (hvalue("bcc", e->e_header) == NULL) 524 { 525 /* no valid recipient headers */ 526 register ADDRESS *q; 527 char *hdr = NULL; 528 529 /* create an Apparently-To: field */ 530 /* that or reject the message.... */ 531 switch (NoRecipientAction) 532 { 533 case NRA_ADD_APPARENTLY_TO: 534 hdr = "Apparently-To"; 535 break; 536 537 case NRA_ADD_TO: 538 hdr = "To"; 539 break; 540 541 case NRA_ADD_BCC: 542 addheader("Bcc", " ", &e->e_header); 543 break; 544 545 case NRA_ADD_TO_UNDISCLOSED: 546 addheader("To", "undisclosed-recipients:;", &e->e_header); 547 break; 548 } 549 550 if (hdr != NULL) 551 { 552 for (q = e->e_sendqueue; q != NULL; q = q->q_next) 553 { 554 if (q->q_alias != NULL) 555 continue; 556 if (tTd(30, 3)) 557 printf("Adding %s: %s\n", 558 hdr, q->q_paddr); 559 addheader(hdr, q->q_paddr, &e->e_header); 560 } 561 } 562 } 563 564 /* check for message too large */ 565 if (MaxMessageSize > 0 && e->e_msgsize > MaxMessageSize) 566 { 567 e->e_flags |= EF_NO_BODY_RETN|EF_CLRQUEUE; 568 e->e_status = "5.2.3"; 569 usrerr("552 Message exceeds maximum fixed size (%ld)", 570 MaxMessageSize); 571 if (LogLevel > 6) 572 sm_syslog(LOG_NOTICE, e->e_id, 573 "message size (%ld) exceeds maximum (%ld)", 574 e->e_msgsize, MaxMessageSize); 575 } 576 577 /* check for illegal 8-bit data */ 578 if (HasEightBits) 579 { 580 e->e_flags |= EF_HAS8BIT; 581 if (!bitset(MM_PASS8BIT|MM_MIME8BIT, MimeMode) && 582 !bitset(EF_IS_MIME, e->e_flags)) 583 { 584 e->e_status = "5.6.1"; 585 usrerr("554 Eight bit data not allowed"); 586 } 587 } 588 else 589 { 590 /* if it claimed to be 8 bits, well, it lied.... */ 591 if (e->e_bodytype != NULL && 592 strcasecmp(e->e_bodytype, "8BITMIME") == 0) 593 e->e_bodytype = "7BIT"; 594 } 595 596 if ((e->e_dfp = fopen(dfname, "r")) == NULL) 597 { 598 /* we haven't acked receipt yet, so just chuck this */ 599 syserr("Cannot reopen %s", dfname); 600 finis(TRUE, ExitStat); 601 } 602 } 603 604 605 static void 606 collecttimeout(timeout) 607 time_t timeout; 608 { 609 /* if no progress was made, die now */ 610 if (!CollectProgress) 611 longjmp(CtxCollectTimeout, 1); 612 613 /* otherwise reset the timeout */ 614 CollectTimeout = setevent(timeout, collecttimeout, timeout); 615 CollectProgress = FALSE; 616 } 617 /* 618 ** TFERROR -- signal error on writing the temporary file. 619 ** 620 ** Parameters: 621 ** tf -- the file pointer for the temporary file. 622 ** e -- the current envelope. 623 ** 624 ** Returns: 625 ** none. 626 ** 627 ** Side Effects: 628 ** Gives an error message. 629 ** Arranges for following output to go elsewhere. 630 */ 631 632 void 633 tferror(tf, e) 634 FILE *volatile tf; 635 register ENVELOPE *e; 636 { 637 setstat(EX_IOERR); 638 if (errno == ENOSPC) 639 { 640 #if STAT64 > 0 641 struct stat64 st; 642 #else 643 struct stat st; 644 #endif 645 long avail; 646 long bsize; 647 extern long freediskspace __P((char *, long *)); 648 649 e->e_flags |= EF_NO_BODY_RETN; 650 651 if ( 652 #if STAT64 > 0 653 fstat64(fileno(tf), &st) 654 #else 655 fstat(fileno(tf), &st) 656 #endif 657 < 0) 658 st.st_size = 0; 659 (void) freopen(queuename(e, 'd'), "w", tf); 660 if (st.st_size <= 0) 661 fprintf(tf, "\n*** Mail could not be accepted"); 662 else if (sizeof st.st_size > sizeof (long)) 663 fprintf(tf, "\n*** Mail of at least %s bytes could not be accepted\n", 664 quad_to_string(st.st_size)); 665 else 666 fprintf(tf, "\n*** Mail of at least %lu bytes could not be accepted\n", 667 (unsigned long) st.st_size); 668 fprintf(tf, "*** at %s due to lack of disk space for temp file.\n", 669 MyHostName); 670 avail = freediskspace(QueueDir, &bsize); 671 if (avail > 0) 672 { 673 if (bsize > 1024) 674 avail *= bsize / 1024; 675 else if (bsize < 1024) 676 avail /= 1024 / bsize; 677 fprintf(tf, "*** Currently, %ld kilobytes are available for mail temp files.\n", 678 avail); 679 } 680 e->e_status = "4.3.1"; 681 usrerr("452 Out of disk space for temp file"); 682 } 683 else 684 syserr("collect: Cannot write tf%s", e->e_id); 685 if (freopen("/dev/null", "w", tf) == NULL) 686 sm_syslog(LOG_ERR, e->e_id, 687 "tferror: freopen(\"/dev/null\") failed: %s", 688 errstring(errno)); 689 } 690 /* 691 ** EATFROM -- chew up a UNIX style from line and process 692 ** 693 ** This does indeed make some assumptions about the format 694 ** of UNIX messages. 695 ** 696 ** Parameters: 697 ** fm -- the from line. 698 ** 699 ** Returns: 700 ** none. 701 ** 702 ** Side Effects: 703 ** extracts what information it can from the header, 704 ** such as the date. 705 */ 706 707 # ifndef NOTUNIX 708 709 char *DowList[] = 710 { 711 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL 712 }; 713 714 char *MonthList[] = 715 { 716 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 717 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 718 NULL 719 }; 720 721 void 722 eatfrom(fm, e) 723 char *volatile fm; 724 register ENVELOPE *e; 725 { 726 register char *p; 727 register char **dt; 728 729 if (tTd(30, 2)) 730 printf("eatfrom(%s)\n", fm); 731 732 /* find the date part */ 733 p = fm; 734 while (*p != '\0') 735 { 736 /* skip a word */ 737 while (*p != '\0' && *p != ' ') 738 p++; 739 while (*p == ' ') 740 p++; 741 if (!(isascii(*p) && isupper(*p)) || 742 p[3] != ' ' || p[13] != ':' || p[16] != ':') 743 continue; 744 745 /* we have a possible date */ 746 for (dt = DowList; *dt != NULL; dt++) 747 if (strncmp(*dt, p, 3) == 0) 748 break; 749 if (*dt == NULL) 750 continue; 751 752 for (dt = MonthList; *dt != NULL; dt++) 753 if (strncmp(*dt, &p[4], 3) == 0) 754 break; 755 if (*dt != NULL) 756 break; 757 } 758 759 if (*p != '\0') 760 { 761 char *q; 762 763 /* we have found a date */ 764 q = xalloc(25); 765 (void) strncpy(q, p, 25); 766 q[24] = '\0'; 767 q = arpadate(q); 768 define('a', newstr(q), e); 769 } 770 } 771 772 # endif /* NOTUNIX */ 773