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.12 (Berkeley) 04/15/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 **		smtpmode -- if set, we are running SMTP: give an RFC821
25 **			style message to say we are ready to collect
26 **			input, and never ignore a single dot to mean
27 **			end of message.
28 **		requeueflag -- this message will be requeued later, so
29 **			don't do final processing on it.
30 **		e -- the current envelope.
31 **
32 **	Returns:
33 **		none.
34 **
35 **	Side Effects:
36 **		Temp file is created and filled.
37 **		The from person may be set.
38 */
39 
40 char	*CollectErrorMessage;
41 bool	CollectErrno;
42 
43 collect(smtpmode, requeueflag, e)
44 	bool smtpmode;
45 	bool requeueflag;
46 	register ENVELOPE *e;
47 {
48 	register FILE *tf;
49 	bool ignrdot = smtpmode ? FALSE : IgnrDot;
50 	char buf[MAXLINE], buf2[MAXLINE];
51 	register char *workbuf, *freebuf;
52 	bool inputerr = FALSE;
53 	extern char *hvalue();
54 	extern bool isheader(), flusheol();
55 
56 	CollectErrorMessage = NULL;
57 	CollectErrno = 0;
58 
59 	/*
60 	**  Create the temp file name and create the file.
61 	*/
62 
63 	e->e_df = queuename(e, 'd');
64 	e->e_df = newstr(e->e_df);
65 	if ((tf = dfopen(e->e_df, O_WRONLY|O_CREAT, FileMode)) == NULL)
66 	{
67 		syserr("Cannot create %s", e->e_df);
68 		NoReturn = TRUE;
69 		finis();
70 	}
71 
72 	/*
73 	**  Tell ARPANET to go ahead.
74 	*/
75 
76 	if (smtpmode)
77 		message("354 Enter mail, end with \".\" on a line by itself");
78 
79 	/* set global timer to monitor progress */
80 	sfgetset(TimeOuts.to_datablock);
81 
82 	/*
83 	**  Try to read a UNIX-style From line
84 	*/
85 
86 	if (sfgets(buf, MAXLINE, InChannel, TimeOuts.to_datablock,
87 			"initial message read") == NULL)
88 		goto readerr;
89 	fixcrlf(buf, FALSE);
90 # ifndef NOTUNIX
91 	if (!SaveFrom && strncmp(buf, "From ", 5) == 0)
92 	{
93 		if (!flusheol(buf, InChannel))
94 			goto readerr;
95 		eatfrom(buf, e);
96 		if (sfgets(buf, MAXLINE, InChannel, TimeOuts.to_datablock,
97 				"message header read") == NULL)
98 			goto readerr;
99 		fixcrlf(buf, FALSE);
100 	}
101 # endif /* NOTUNIX */
102 
103 	/*
104 	**  Copy InChannel to temp file & do message editing.
105 	**	To keep certain mailers from getting confused,
106 	**	and to keep the output clean, lines that look
107 	**	like UNIX "From" lines are deleted in the header.
108 	*/
109 
110 	workbuf = buf;		/* `workbuf' contains a header field */
111 	freebuf = buf2;		/* `freebuf' can be used for read-ahead */
112 	for (;;)
113 	{
114 		char *curbuf;
115 		int curbuffree;
116 		register int curbuflen;
117 		char *p;
118 
119 		/* first, see if the header is over */
120 		if (!isheader(workbuf))
121 		{
122 			fixcrlf(workbuf, TRUE);
123 			break;
124 		}
125 
126 		/* if the line is too long, throw the rest away */
127 		if (!flusheol(workbuf, InChannel))
128 			goto readerr;
129 
130 		/* it's okay to toss '\n' now (flusheol() needed it) */
131 		fixcrlf(workbuf, TRUE);
132 
133 		curbuf = workbuf;
134 		curbuflen = strlen(curbuf);
135 		curbuffree = MAXLINE - curbuflen;
136 		p = curbuf + curbuflen;
137 
138 		/* get the rest of this field */
139 		for (;;)
140 		{
141 			int clen;
142 
143 			if (sfgets(freebuf, MAXLINE, InChannel,
144 					TimeOuts.to_datablock,
145 					"message header read") == NULL)
146 			{
147 				freebuf[0] = '\0';
148 				break;
149 			}
150 
151 			/* is this a continuation line? */
152 			if (*freebuf != ' ' && *freebuf != '\t')
153 				break;
154 
155 			if (!flusheol(freebuf, InChannel))
156 				goto readerr;
157 
158 			fixcrlf(freebuf, TRUE);
159 			clen = strlen(freebuf) + 1;
160 
161 			/* if insufficient room, dynamically allocate buffer */
162 			if (clen >= curbuffree)
163 			{
164 				/* reallocate buffer */
165 				int nbuflen = ((p - curbuf) + clen) * 2;
166 				char *nbuf = xalloc(nbuflen);
167 
168 				p = nbuf + curbuflen;
169 				curbuffree = nbuflen - curbuflen;
170 				bcopy(curbuf, nbuf, curbuflen);
171 				if (curbuf != buf && curbuf != buf2)
172 					free(curbuf);
173 				curbuf = nbuf;
174 			}
175 			*p++ = '\n';
176 			bcopy(freebuf, p, clen - 1);
177 			p += clen - 1;
178 			curbuffree -= clen;
179 			curbuflen += clen;
180 		}
181 		*p++ = '\0';
182 
183 		e->e_msgsize += curbuflen;
184 
185 		/*
186 		**  The working buffer now becomes the free buffer, since
187 		**  the free buffer contains a new header field.
188 		**
189 		**  This is premature, since we still havent called
190 		**  chompheader() to process the field we just created
191 		**  (so the call to chompheader() will use `freebuf').
192 		**  This convolution is necessary so that if we break out
193 		**  of the loop due to H_EOH, `workbuf' will always be
194 		**  the next unprocessed buffer.
195 		*/
196 
197 		{
198 			register char *tmp = workbuf;
199 			workbuf = freebuf;
200 			freebuf = tmp;
201 		}
202 
203 		/*
204 		**  Snarf header away.
205 		*/
206 
207 		if (bitset(H_EOH, chompheader(curbuf, FALSE, e)))
208 			break;
209 
210 		/*
211 		**  If the buffer was dynamically allocated, free it.
212 		*/
213 
214 		if (curbuf != buf && curbuf != buf2)
215 			free(curbuf);
216 	}
217 
218 	if (tTd(30, 1))
219 		printf("EOH\n");
220 
221 	if (*workbuf == '\0')
222 	{
223 		/* throw away a blank line */
224 		if (sfgets(buf, MAXLINE, InChannel, TimeOuts.to_datablock,
225 				"message separator read") == NULL)
226 			goto readerr;
227 	}
228 	else if (workbuf == buf2)	/* guarantee `buf' contains data */
229 		(void) strcpy(buf, buf2);
230 
231 	/*
232 	**  Collect the body of the message.
233 	*/
234 
235 	for (;;)
236 	{
237 		register char *bp = buf;
238 
239 		fixcrlf(buf, TRUE);
240 
241 		/* check for end-of-message */
242 		if (!ignrdot && buf[0] == '.' && (buf[1] == '\n' || buf[1] == '\0'))
243 			break;
244 
245 		/* check for transparent dot */
246 		if ((OpMode == MD_SMTP || OpMode == MD_DAEMON) &&
247 		    bp[0] == '.' && bp[1] == '.')
248 			bp++;
249 
250 		/*
251 		**  Figure message length, output the line to the temp
252 		**  file, and insert a newline if missing.
253 		*/
254 
255 		e->e_msgsize += strlen(bp) + 1;
256 		fputs(bp, tf);
257 		fputs("\n", tf);
258 		if (ferror(tf))
259 			tferror(tf, e);
260 		if (sfgets(buf, MAXLINE, InChannel, TimeOuts.to_datablock,
261 				"message body read") == NULL)
262 			goto readerr;
263 	}
264 
265 	if (feof(InChannel) || ferror(InChannel))
266 	{
267 readerr:
268 		if (tTd(30, 1))
269 			printf("collect: read error\n");
270 		inputerr = TRUE;
271 	}
272 
273 	/* reset global timer */
274 	sfgetset((time_t) 0);
275 
276 	if (fflush(tf) != 0)
277 		tferror(tf, e);
278 	if (fsync(fileno(tf)) < 0 || fclose(tf) < 0)
279 	{
280 		tferror(tf, e);
281 		finis();
282 	}
283 
284 	if (CollectErrorMessage != NULL && Errors <= 0)
285 	{
286 		if (CollectErrno != 0)
287 		{
288 			errno = CollectErrno;
289 			syserr(CollectErrorMessage, e->e_df);
290 			finis();
291 		}
292 		usrerr(CollectErrorMessage);
293 	}
294 	else if (inputerr && (OpMode == MD_SMTP || OpMode == MD_DAEMON))
295 	{
296 		/* An EOF when running SMTP is an error */
297 		char *host;
298 		char *problem;
299 
300 		host = RealHostName;
301 		if (host == NULL)
302 			host = "localhost";
303 
304 		if (feof(InChannel))
305 			problem = "unexpected close";
306 		else if (ferror(InChannel))
307 			problem = "I/O error";
308 		else
309 			problem = "read timeout";
310 # ifdef LOG
311 		if (LogLevel > 0 && feof(InChannel))
312 			syslog(LOG_NOTICE,
313 			    "collect: %s on connection from %s, sender=%s: %m\n",
314 			    problem, host, e->e_from.q_paddr);
315 # endif
316 		if (feof(InChannel))
317 			usrerr("451 collect: %s on connection from %s, from=%s",
318 				problem, host, e->e_from.q_paddr);
319 		else
320 			syserr("451 collect: %s on connection from %s, from=%s",
321 				problem, host, e->e_from.q_paddr);
322 
323 		/* don't return an error indication */
324 		e->e_to = NULL;
325 		e->e_flags &= ~EF_FATALERRS;
326 		e->e_flags |= EF_CLRQUEUE;
327 
328 		/* and don't try to deliver the partial message either */
329 		if (InChild)
330 			ExitStat = EX_QUIT;
331 		finis();
332 	}
333 
334 	/*
335 	**  Find out some information from the headers.
336 	**	Examples are who is the from person & the date.
337 	*/
338 
339 	eatheader(e, !requeueflag);
340 
341 	/* collect statistics */
342 	if (OpMode != MD_VERIFY)
343 		markstats(e, (ADDRESS *) NULL);
344 
345 	/*
346 	**  Add an Apparently-To: line if we have no recipient lines.
347 	*/
348 
349 	if (hvalue("to", e) == NULL && hvalue("cc", e) == NULL &&
350 	    hvalue("bcc", e) == NULL && hvalue("apparently-to", e) == NULL)
351 	{
352 		register ADDRESS *q;
353 
354 		/* create an Apparently-To: field */
355 		/*    that or reject the message.... */
356 		for (q = e->e_sendqueue; q != NULL; q = q->q_next)
357 		{
358 			if (q->q_alias != NULL)
359 				continue;
360 			if (tTd(30, 3))
361 				printf("Adding Apparently-To: %s\n", q->q_paddr);
362 			addheader("Apparently-To", q->q_paddr, e);
363 		}
364 	}
365 
366 	/* check for message too large */
367 	if (MaxMessageSize > 0 && e->e_msgsize > MaxMessageSize)
368 	{
369 		usrerr("552 Message exceeds maximum fixed size (%ld)",
370 			MaxMessageSize);
371 	}
372 
373 	if ((e->e_dfp = fopen(e->e_df, "r")) == NULL)
374 	{
375 		/* we haven't acked receipt yet, so just chuck this */
376 		syserr("Cannot reopen %s", e->e_df);
377 		finis();
378 	}
379 }
380 /*
381 **  FLUSHEOL -- if not at EOL, throw away rest of input line.
382 **
383 **	Parameters:
384 **		buf -- last line read in (checked for '\n'),
385 **		fp -- file to be read from.
386 **
387 **	Returns:
388 **		FALSE on error from sfgets(), TRUE otherwise.
389 **
390 **	Side Effects:
391 **		none.
392 */
393 
394 bool
395 flusheol(buf, fp)
396 	char *buf;
397 	FILE *fp;
398 {
399 	register char *p = buf;
400 	char junkbuf[MAXLINE];
401 
402 	while (strchr(p, '\n') == NULL)
403 	{
404 		CollectErrorMessage = "553 header line too long";
405 		CollectErrno = 0;
406 		if (sfgets(junkbuf, MAXLINE, fp, TimeOuts.to_datablock,
407 				"long line flush") == NULL)
408 			return (FALSE);
409 		p = junkbuf;
410 	}
411 
412 	return (TRUE);
413 }
414 /*
415 **  TFERROR -- signal error on writing the temporary file.
416 **
417 **	Parameters:
418 **		tf -- the file pointer for the temporary file.
419 **
420 **	Returns:
421 **		none.
422 **
423 **	Side Effects:
424 **		Gives an error message.
425 **		Arranges for following output to go elsewhere.
426 */
427 
428 tferror(tf, e)
429 	FILE *tf;
430 	register ENVELOPE *e;
431 {
432 	CollectErrno = errno;
433 	if (errno == ENOSPC)
434 	{
435 		struct stat st;
436 		long avail;
437 		long bsize;
438 
439 		NoReturn = TRUE;
440 		if (fstat(fileno(tf), &st) < 0)
441 			st.st_size = 0;
442 		(void) freopen(e->e_df, "w", tf);
443 		if (st.st_size <= 0)
444 			fprintf(tf, "\n*** Mail could not be accepted");
445 		else if (sizeof st.st_size > sizeof (long))
446 			fprintf(tf, "\n*** Mail of at least %qd bytes could not be accepted\n",
447 				st.st_size);
448 		else
449 			fprintf(tf, "\n*** Mail of at least %ld bytes could not be accepted\n",
450 				st.st_size);
451 		fprintf(tf, "*** at %s due to lack of disk space for temp file.\n",
452 			MyHostName);
453 		avail = freespace(QueueDir, &bsize);
454 		if (avail > 0)
455 		{
456 			if (bsize > 1024)
457 				avail *= bsize / 1024;
458 			else if (bsize < 1024)
459 				avail /= 1024 / bsize;
460 			fprintf(tf, "*** Currently, %ld kilobytes are available for mail temp files.\n",
461 				avail);
462 		}
463 		CollectErrorMessage = "452 Out of disk space for temp file";
464 	}
465 	else
466 	{
467 		CollectErrorMessage = "cannot write message body to disk (%s)";
468 	}
469 	(void) freopen("/dev/null", "w", tf);
470 }
471 /*
472 **  EATFROM -- chew up a UNIX style from line and process
473 **
474 **	This does indeed make some assumptions about the format
475 **	of UNIX messages.
476 **
477 **	Parameters:
478 **		fm -- the from line.
479 **
480 **	Returns:
481 **		none.
482 **
483 **	Side Effects:
484 **		extracts what information it can from the header,
485 **		such as the date.
486 */
487 
488 # ifndef NOTUNIX
489 
490 char	*DowList[] =
491 {
492 	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
493 };
494 
495 char	*MonthList[] =
496 {
497 	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
498 	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
499 	NULL
500 };
501 
502 eatfrom(fm, e)
503 	char *fm;
504 	register ENVELOPE *e;
505 {
506 	register char *p;
507 	register char **dt;
508 
509 	if (tTd(30, 2))
510 		printf("eatfrom(%s)\n", fm);
511 
512 	/* find the date part */
513 	p = fm;
514 	while (*p != '\0')
515 	{
516 		/* skip a word */
517 		while (*p != '\0' && *p != ' ')
518 			p++;
519 		while (*p == ' ')
520 			p++;
521 		if (!(isascii(*p) && isupper(*p)) ||
522 		    p[3] != ' ' || p[13] != ':' || p[16] != ':')
523 			continue;
524 
525 		/* we have a possible date */
526 		for (dt = DowList; *dt != NULL; dt++)
527 			if (strncmp(*dt, p, 3) == 0)
528 				break;
529 		if (*dt == NULL)
530 			continue;
531 
532 		for (dt = MonthList; *dt != NULL; dt++)
533 			if (strncmp(*dt, &p[4], 3) == 0)
534 				break;
535 		if (*dt != NULL)
536 			break;
537 	}
538 
539 	if (*p != '\0')
540 	{
541 		char *q;
542 		extern char *arpadate();
543 
544 		/* we have found a date */
545 		q = xalloc(25);
546 		(void) strncpy(q, p, 25);
547 		q[24] = '\0';
548 		q = arpadate(q);
549 		define('a', newstr(q), e);
550 	}
551 }
552 
553 # endif /* NOTUNIX */
554