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