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