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