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