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