xref: /freebsd/contrib/sendmail/src/collect.c (revision c3debd01c8e835bcbdb7c758506f8bd5c7976d2c)
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