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