1 /* 2 * Copyright (c) 1983 Eric P. Allman 3 * Copyright (c) 1988, 1993 4 * The Regents of the University of California. All rights reserved. 5 * 6 * %sccs.include.redist.c% 7 */ 8 9 #ifndef lint 10 static char sccsid[] = "@(#)collect.c 8.20 (Berkeley) 08/07/94"; 11 #endif /* not lint */ 12 13 # include <errno.h> 14 # include "sendmail.h" 15 16 /* 17 ** COLLECT -- read & parse message header & make temp file. 18 ** 19 ** Creates a temporary file name and copies the standard 20 ** input to that file. Leading UNIX-style "From" lines are 21 ** stripped off (after important information is extracted). 22 ** 23 ** Parameters: 24 ** fp -- file to read. 25 ** smtpmode -- if set, we are running SMTP: give an RFC821 26 ** style message to say we are ready to collect 27 ** input, and never ignore a single dot to mean 28 ** end of message. 29 ** requeueflag -- this message will be requeued later, so 30 ** don't do final processing on it. 31 ** hdrp -- the location to stash the header. 32 ** e -- the current envelope. 33 ** 34 ** Returns: 35 ** none. 36 ** 37 ** Side Effects: 38 ** Temp file is created and filled. 39 ** The from person may be set. 40 */ 41 42 char *CollectErrorMessage; 43 bool CollectErrno; 44 45 static jmp_buf CtxCollectTimeout; 46 static int collecttimeout(); 47 static bool CollectProgress; 48 static EVENT *CollectTimeout; 49 50 /* values for input state machine */ 51 #define IS_NORM 0 /* middle of line */ 52 #define IS_BOL 1 /* beginning of line */ 53 #define IS_DOT 2 /* read a dot at beginning of line */ 54 #define IS_DOTCR 3 /* read ".\r" at beginning of line */ 55 #define IS_CR 4 /* read a carriage return */ 56 57 /* values for message state machine */ 58 #define MS_UFROM 0 /* reading Unix from line */ 59 #define MS_HEADER 1 /* reading message header */ 60 #define MS_BODY 2 /* reading message body */ 61 62 63 collect(fp, smtpmode, requeueflag, hdrp, e) 64 FILE *fp; 65 bool smtpmode; 66 bool requeueflag; 67 HDR **hdrp; 68 register ENVELOPE *e; 69 { 70 register FILE *tf; 71 bool ignrdot = smtpmode ? FALSE : IgnrDot; 72 time_t dbto = smtpmode ? TimeOuts.to_datablock : 0; 73 register char *bp; 74 register int c; 75 bool inputerr = FALSE; 76 bool headeronly = FALSE; 77 char *buf; 78 int buflen; 79 int istate; 80 int mstate; 81 char *pbp; 82 char peekbuf[8]; 83 char bufbuf[MAXLINE]; 84 extern char *hvalue(); 85 extern bool isheader(); 86 87 CollectErrorMessage = NULL; 88 CollectErrno = 0; 89 if (hdrp == NULL) 90 hdrp = &e->e_header; 91 else 92 headeronly = TRUE; 93 94 /* 95 ** Create the temp file name and create the file. 96 */ 97 98 if (!headeronly) 99 { 100 struct stat stbuf; 101 102 e->e_df = queuename(e, 'd'); 103 e->e_df = newstr(e->e_df); 104 if ((tf = dfopen(e->e_df, O_WRONLY|O_CREAT|O_TRUNC, FileMode)) == NULL) 105 { 106 syserr("Cannot create %s", e->e_df); 107 e->e_flags |= EF_NORETURN; 108 finis(); 109 } 110 if (fstat(fileno(tf), &stbuf) < 0) 111 e->e_dfino = -1; 112 else 113 e->e_dfino = stbuf.st_ino; 114 HasEightBits = FALSE; 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 /* 125 ** Read the message. 126 ** 127 ** This is done using two interleaved state machines. 128 ** The input state machine is looking for things like 129 ** hidden dots; the message state machine is handling 130 ** the larger picture (e.g., header versus body). 131 */ 132 133 buf = bp = bufbuf; 134 buflen = sizeof bufbuf; 135 pbp = peekbuf; 136 istate = IS_BOL; 137 mstate = SaveFrom ? MS_HEADER : MS_UFROM; 138 CollectProgress = FALSE; 139 140 /* if transmitting binary, don't map NL to EOL */ 141 if (e->e_bodytype != NULL && strcasecmp(e->e_bodytype, "8BITMIME") == 0) 142 e->e_flags |= EF_NL_NOT_EOL; 143 144 if (dbto != 0) 145 { 146 /* handle possible input timeout */ 147 if (setjmp(CtxCollectTimeout) != 0) 148 { 149 #ifdef LOG 150 syslog(LOG_NOTICE, 151 "timeout waiting for input from %s during message collect", 152 CurHostName ? CurHostName : "<local machine>"); 153 #endif 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(InChannel) && !ferror(InChannel)) 172 { 173 errno = 0; 174 c = fgetc(InChannel); 175 if (errno != EINTR) 176 break; 177 clearerr(InChannel); 178 } 179 CollectProgress = TRUE; 180 if (TrafficLogFile != NULL) 181 { 182 if (istate == IS_BOL) 183 fprintf(TrafficLogFile, "%05d <<< ", 184 getpid()); 185 if (c == EOF) 186 fprintf(TrafficLogFile, "[EOF]\n"); 187 else 188 fputc(c, TrafficLogFile); 189 } 190 if (c == EOF) 191 goto readerr; 192 if (SevenBitInput) 193 c &= 0x7f; 194 else 195 HasEightBits |= bitset(0x80, c); 196 e->e_msgsize++; 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') 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 { 246 ungetc(c, InChannel); 247 c = '\r'; 248 } 249 else if (!bitset(EF_CRLF_NOT_EOL, e->e_flags)) 250 istate = IS_BOL; 251 break; 252 } 253 254 if (c == '\r') 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 if (mstate == MS_BODY) 265 { 266 /* just put the character out */ 267 fputc(c, tf); 268 continue; 269 } 270 271 /* header -- buffer up */ 272 if (bp >= &buf[buflen - 2]) 273 { 274 char *obuf; 275 276 if (mstate != MS_HEADER) 277 break; 278 279 /* out of space for header */ 280 obuf = buf; 281 if (buflen < MEMCHUNKSIZE) 282 buflen *= 2; 283 else 284 buflen += MEMCHUNKSIZE; 285 buf = xalloc(buflen); 286 bcopy(obuf, buf, bp - obuf); 287 bp = &buf[bp - obuf]; 288 if (obuf != bufbuf) 289 free(obuf); 290 } 291 *bp++ = c; 292 if (istate == IS_BOL) 293 break; 294 } 295 *bp = '\0'; 296 297 nextstate: 298 if (tTd(30, 35)) 299 printf("nextstate, istate=%d, mstate=%d, line = \"%s\"\n", 300 istate, mstate, buf); 301 switch (mstate) 302 { 303 case MS_UFROM: 304 mstate = MS_HEADER; 305 if (strncmp(buf, "From ", 5) == 0) 306 { 307 eatfrom(buf, e); 308 continue; 309 } 310 /* fall through */ 311 312 case MS_HEADER: 313 if (!isheader(buf)) 314 { 315 mstate = MS_BODY; 316 goto nextstate; 317 } 318 319 /* check for possible continuation line */ 320 do 321 { 322 clearerr(InChannel); 323 errno = 0; 324 c = fgetc(InChannel); 325 } while (errno == EINTR); 326 if (c != EOF) 327 ungetc(c, InChannel); 328 if (c == ' ' || c == '\t') 329 { 330 /* yep -- defer this */ 331 continue; 332 } 333 334 /* trim off trailing CRLF or NL */ 335 if (*--bp != '\n' || *--bp != '\r') 336 bp++; 337 *bp = '\0'; 338 if (bitset(H_EOH, chompheader(buf, FALSE, e))) 339 mstate = MS_BODY; 340 break; 341 342 case MS_BODY: 343 if (tTd(30, 1)) 344 printf("EOH\n"); 345 if (headeronly) 346 goto readerr; 347 bp = buf; 348 349 /* toss blank line */ 350 if ((!bitset(EF_CRLF_NOT_EOL, e->e_flags) && 351 bp[0] == '\r' && bp[1] == '\n') || 352 (!bitset(EF_NL_NOT_EOL, e->e_flags) && 353 bp[0] == '\n')) 354 { 355 break; 356 } 357 358 /* if not a blank separator, write it out */ 359 while (*bp != '\0') 360 fputc(*bp++, tf); 361 break; 362 } 363 bp = buf; 364 } 365 366 readerr: 367 if ((feof(fp) && smtpmode) || ferror(fp)) 368 { 369 if (tTd(30, 1)) 370 printf("collect: read error\n"); 371 inputerr = TRUE; 372 } 373 374 /* reset global timer */ 375 clrevent(CollectTimeout); 376 377 if (headeronly) 378 return; 379 380 if (tf != NULL) 381 { 382 if (fflush(tf) != 0) 383 tferror(tf, e); 384 if (fsync(fileno(tf)) < 0 || fclose(tf) < 0) 385 { 386 tferror(tf, e); 387 finis(); 388 } 389 } 390 391 if (CollectErrorMessage != NULL && Errors <= 0) 392 { 393 if (CollectErrno != 0) 394 { 395 errno = CollectErrno; 396 syserr(CollectErrorMessage, e->e_df); 397 finis(); 398 } 399 usrerr(CollectErrorMessage); 400 } 401 else if (inputerr && (OpMode == MD_SMTP || OpMode == MD_DAEMON)) 402 { 403 /* An EOF when running SMTP is an error */ 404 char *host; 405 char *problem; 406 407 host = RealHostName; 408 if (host == NULL) 409 host = "localhost"; 410 411 if (feof(fp)) 412 problem = "unexpected close"; 413 else if (ferror(fp)) 414 problem = "I/O error"; 415 else 416 problem = "read timeout"; 417 # ifdef LOG 418 if (LogLevel > 0 && feof(fp)) 419 syslog(LOG_NOTICE, 420 "collect: %s on connection from %s, sender=%s: %s\n", 421 problem, host, e->e_from.q_paddr, errstring(errno)); 422 # endif 423 if (feof(fp)) 424 usrerr("451 collect: %s on connection from %s, from=%s", 425 problem, host, e->e_from.q_paddr); 426 else 427 syserr("451 collect: %s on connection from %s, from=%s", 428 problem, host, e->e_from.q_paddr); 429 430 /* don't return an error indication */ 431 e->e_to = NULL; 432 e->e_flags &= ~EF_FATALERRS; 433 e->e_flags |= EF_CLRQUEUE; 434 435 /* and don't try to deliver the partial message either */ 436 if (InChild) 437 ExitStat = EX_QUIT; 438 finis(); 439 } 440 441 /* 442 ** Find out some information from the headers. 443 ** Examples are who is the from person & the date. 444 */ 445 446 eatheader(e, !requeueflag); 447 448 /* collect statistics */ 449 if (OpMode != MD_VERIFY) 450 markstats(e, (ADDRESS *) NULL); 451 452 /* 453 ** Add an Apparently-To: line if we have no recipient lines. 454 */ 455 456 if (hvalue("to", e->e_header) == NULL && 457 hvalue("cc", e->e_header) == NULL && 458 hvalue("bcc", e->e_header) == NULL && 459 hvalue("apparently-to", e->e_header) == NULL) 460 { 461 register ADDRESS *q; 462 463 /* create an Apparently-To: field */ 464 /* that or reject the message.... */ 465 for (q = e->e_sendqueue; q != NULL; q = q->q_next) 466 { 467 if (q->q_alias != NULL) 468 continue; 469 if (tTd(30, 3)) 470 printf("Adding Apparently-To: %s\n", q->q_paddr); 471 addheader("Apparently-To", q->q_paddr, &e->e_header); 472 } 473 } 474 475 /* check for message too large */ 476 if (MaxMessageSize > 0 && e->e_msgsize > MaxMessageSize) 477 { 478 usrerr("552 Message exceeds maximum fixed size (%ld)", 479 MaxMessageSize); 480 } 481 482 /* check for illegal 8-bit data */ 483 if (HasEightBits) 484 { 485 e->e_flags |= EF_HAS8BIT; 486 if (bitset(MM_MIME8BIT, MimeMode)) 487 { 488 /* convert it to MIME */ 489 if (hvalue("MIME-Version", e->e_header) == NULL) 490 { 491 char mimebuf[20]; 492 493 strcpy(mimebuf, "MIME-Version: 1.0"); 494 chompheader(mimebuf, FALSE, e); 495 } 496 if (e->e_bodytype == NULL) 497 e->e_bodytype = "8BITMIME"; 498 } 499 else if (!bitset(MM_PASS8BIT, MimeMode)) 500 usrerr("554 Eight bit data not allowed"); 501 } 502 503 if ((e->e_dfp = fopen(e->e_df, "r")) == NULL) 504 { 505 /* we haven't acked receipt yet, so just chuck this */ 506 syserr("Cannot reopen %s", e->e_df); 507 finis(); 508 } 509 } 510 511 512 static 513 collecttimeout(timeout) 514 time_t timeout; 515 { 516 /* if no progress was made, die now */ 517 if (!CollectProgress) 518 longjmp(CtxCollectTimeout, 1); 519 520 /* otherwise reset the timeout */ 521 CollectTimeout = setevent(timeout, collecttimeout, timeout); 522 CollectProgress = FALSE; 523 } 524 /* 525 ** TFERROR -- signal error on writing the temporary file. 526 ** 527 ** Parameters: 528 ** tf -- the file pointer for the temporary file. 529 ** 530 ** Returns: 531 ** none. 532 ** 533 ** Side Effects: 534 ** Gives an error message. 535 ** Arranges for following output to go elsewhere. 536 */ 537 538 tferror(tf, e) 539 FILE *tf; 540 register ENVELOPE *e; 541 { 542 CollectErrno = errno; 543 if (errno == ENOSPC) 544 { 545 struct stat st; 546 long avail; 547 long bsize; 548 549 e->e_flags |= EF_NORETURN; 550 if (fstat(fileno(tf), &st) < 0) 551 st.st_size = 0; 552 (void) freopen(e->e_df, "w", tf); 553 if (st.st_size <= 0) 554 fprintf(tf, "\n*** Mail could not be accepted"); 555 else if (sizeof st.st_size > sizeof (long)) 556 fprintf(tf, "\n*** Mail of at least %qd bytes could not be accepted\n", 557 st.st_size); 558 else 559 fprintf(tf, "\n*** Mail of at least %ld bytes could not be accepted\n", 560 st.st_size); 561 fprintf(tf, "*** at %s due to lack of disk space for temp file.\n", 562 MyHostName); 563 avail = freespace(QueueDir, &bsize); 564 if (avail > 0) 565 { 566 if (bsize > 1024) 567 avail *= bsize / 1024; 568 else if (bsize < 1024) 569 avail /= 1024 / bsize; 570 fprintf(tf, "*** Currently, %ld kilobytes are available for mail temp files.\n", 571 avail); 572 } 573 CollectErrorMessage = "452 Out of disk space for temp file"; 574 } 575 else 576 { 577 CollectErrorMessage = "cannot write message body to disk (%s)"; 578 } 579 (void) freopen("/dev/null", "w", tf); 580 } 581 /* 582 ** EATFROM -- chew up a UNIX style from line and process 583 ** 584 ** This does indeed make some assumptions about the format 585 ** of UNIX messages. 586 ** 587 ** Parameters: 588 ** fm -- the from line. 589 ** 590 ** Returns: 591 ** none. 592 ** 593 ** Side Effects: 594 ** extracts what information it can from the header, 595 ** such as the date. 596 */ 597 598 # ifndef NOTUNIX 599 600 char *DowList[] = 601 { 602 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL 603 }; 604 605 char *MonthList[] = 606 { 607 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 608 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 609 NULL 610 }; 611 612 eatfrom(fm, e) 613 char *fm; 614 register ENVELOPE *e; 615 { 616 register char *p; 617 register char **dt; 618 619 if (tTd(30, 2)) 620 printf("eatfrom(%s)\n", fm); 621 622 /* find the date part */ 623 p = fm; 624 while (*p != '\0') 625 { 626 /* skip a word */ 627 while (*p != '\0' && *p != ' ') 628 p++; 629 while (*p == ' ') 630 p++; 631 if (!(isascii(*p) && isupper(*p)) || 632 p[3] != ' ' || p[13] != ':' || p[16] != ':') 633 continue; 634 635 /* we have a possible date */ 636 for (dt = DowList; *dt != NULL; dt++) 637 if (strncmp(*dt, p, 3) == 0) 638 break; 639 if (*dt == NULL) 640 continue; 641 642 for (dt = MonthList; *dt != NULL; dt++) 643 if (strncmp(*dt, &p[4], 3) == 0) 644 break; 645 if (*dt != NULL) 646 break; 647 } 648 649 if (*p != '\0') 650 { 651 char *q; 652 extern char *arpadate(); 653 654 /* we have found a date */ 655 q = xalloc(25); 656 (void) strncpy(q, p, 25); 657 q[24] = '\0'; 658 q = arpadate(q); 659 define('a', newstr(q), e); 660 } 661 } 662 663 # endif /* NOTUNIX */ 664