1/*
2 * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>.
3 * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
4 *
5 * This code is derived from software contributed to The DragonFly Project
6 * by Simon Schubert <2@0x2c.org>.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in
16 *    the documentation and/or other materials provided with the
17 *    distribution.
18 * 3. Neither the name of The DragonFly Project nor the names of its
19 *    contributors may be used to endorse or promote products derived
20 *    from this software without specific, prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
26 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include <errno.h>
37#include <inttypes.h>
38#include <signal.h>
39#include <syslog.h>
40#include <unistd.h>
41
42#include "dma.h"
43
44#define MAX_LINE_RFC822	1000
45
46void
47bounce(struct qitem *it, const char *reason)
48{
49	struct queue bounceq;
50	char line[1000];
51	size_t pos;
52	int error;
53
54	/* Don't bounce bounced mails */
55	if (it->sender[0] == 0) {
56		syslog(LOG_INFO, "can not bounce a bounce message, discarding");
57		exit(EX_SOFTWARE);
58	}
59
60	bzero(&bounceq, sizeof(bounceq));
61	LIST_INIT(&bounceq.queue);
62	bounceq.sender = "";
63	if (add_recp(&bounceq, it->sender, EXPAND_WILDCARD) != 0)
64		goto fail;
65
66	if (newspoolf(&bounceq) != 0)
67		goto fail;
68
69	syslog(LOG_ERR, "delivery failed, bouncing as %s", bounceq.id);
70	setlogident("%s", bounceq.id);
71
72	error = fprintf(bounceq.mailf,
73		"Received: from MAILER-DAEMON\n"
74		"\tid %s\n"
75		"\tby %s (%s);\n"
76		"\t%s\n"
77		"X-Original-To: <%s>\n"
78		"From: MAILER-DAEMON <>\n"
79		"To: %s\n"
80		"Subject: Mail delivery failed\n"
81		"Message-Id: <%s@%s>\n"
82		"Date: %s\n"
83		"\n"
84		"This is the %s at %s.\n"
85		"\n"
86		"There was an error delivering your mail to <%s>.\n"
87		"\n"
88		"%s\n"
89		"\n"
90		"%s\n"
91		"\n",
92		bounceq.id,
93		hostname(), VERSION,
94		rfc822date(),
95		it->addr,
96		it->sender,
97		bounceq.id, hostname(),
98		rfc822date(),
99		VERSION, hostname(),
100		it->addr,
101		reason,
102		config.features & FULLBOUNCE ?
103		    "Original message follows." :
104		    "Message headers follow.");
105	if (error < 0)
106		goto fail;
107
108	if (fseek(it->mailf, 0, SEEK_SET) != 0)
109		goto fail;
110	if (config.features & FULLBOUNCE) {
111		while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) {
112			if (fwrite(line, 1, pos, bounceq.mailf) != pos)
113				goto fail;
114		}
115	} else {
116		while (!feof(it->mailf)) {
117			if (fgets(line, sizeof(line), it->mailf) == NULL)
118				break;
119			if (line[0] == '\n')
120				break;
121			if (fwrite(line, strlen(line), 1, bounceq.mailf) != 1)
122				goto fail;
123		}
124	}
125
126	if (linkspool(&bounceq) != 0)
127		goto fail;
128	/* bounce is safe */
129
130	delqueue(it);
131
132	run_queue(&bounceq);
133	/* NOTREACHED */
134
135fail:
136	syslog(LOG_CRIT, "error creating bounce: %m");
137	delqueue(it);
138	exit(EX_IOERR);
139}
140
141struct parse_state {
142	char addr[1000];
143	int pos;
144
145	enum {
146		NONE = 0,
147		START,
148		MAIN,
149		EOL,
150		QUIT
151	} state;
152	int comment;
153	int quote;
154	int brackets;
155	int esc;
156};
157
158/*
159 * Simplified RFC2822 header/address parsing.
160 * We copy escapes and quoted strings directly, since
161 * we have to pass them like this to the mail server anyways.
162 * XXX local addresses will need treatment
163 */
164static int
165parse_addrs(struct parse_state *ps, char *s, struct queue *queue)
166{
167	char *addr;
168
169again:
170	switch (ps->state) {
171	case NONE:
172		return (-1);
173
174	case START:
175		/* init our data */
176		bzero(ps, sizeof(*ps));
177
178		/* skip over header name */
179		while (*s != ':')
180			s++;
181		s++;
182		ps->state = MAIN;
183		break;
184
185	case MAIN:
186		/* all fine */
187		break;
188
189	case EOL:
190		switch (*s) {
191		case ' ':
192		case '\t':
193			s++;
194			/* continue */
195			break;
196
197		default:
198			ps->state = QUIT;
199			if (ps->pos != 0)
200				goto newaddr;
201			return (0);
202		}
203
204	case QUIT:
205		return (0);
206	}
207
208	for (; *s != 0; s++) {
209		if (ps->esc) {
210			ps->esc = 0;
211
212			switch (*s) {
213			case '\r':
214			case '\n':
215				goto err;
216
217			default:
218				goto copy;
219			}
220		}
221
222		if (ps->quote) {
223			switch (*s) {
224			case '"':
225				ps->quote = 0;
226				goto copy;
227
228			case '\\':
229				ps->esc = 1;
230				goto copy;
231
232			case '\r':
233			case '\n':
234				goto eol;
235
236			default:
237				goto copy;
238			}
239		}
240
241		switch (*s) {
242		case '(':
243			ps->comment++;
244			break;
245
246		case ')':
247			if (ps->comment)
248				ps->comment--;
249			else
250				goto err;
251			goto skip;
252
253		case '"':
254			ps->quote = 1;
255			goto copy;
256
257		case '\\':
258			ps->esc = 1;
259			goto copy;
260
261		case '\r':
262		case '\n':
263			goto eol;
264		}
265
266		if (ps->comment)
267			goto skip;
268
269		switch (*s) {
270		case ' ':
271		case '\t':
272			/* ignore whitespace */
273			goto skip;
274
275		case '<':
276			/* this is the real address now */
277			ps->brackets = 1;
278			ps->pos = 0;
279			goto skip;
280
281		case '>':
282			if (!ps->brackets)
283				goto err;
284			ps->brackets = 0;
285
286			s++;
287			goto newaddr;
288
289		case ':':
290			/* group - ignore */
291			ps->pos = 0;
292			goto skip;
293
294		case ',':
295		case ';':
296			/*
297			 * Next address, copy previous one.
298			 * However, we might be directly after
299			 * a <address>, or have two consecutive
300			 * commas.
301			 * Skip the comma unless there is
302			 * really something to copy.
303			 */
304			if (ps->pos == 0)
305				goto skip;
306			s++;
307			goto newaddr;
308
309		default:
310			goto copy;
311		}
312
313copy:
314		if (ps->comment)
315			goto skip;
316
317		if (ps->pos + 1 == sizeof(ps->addr))
318			goto err;
319		ps->addr[ps->pos++] = *s;
320
321skip:
322		;
323	}
324
325eol:
326	ps->state = EOL;
327	return (0);
328
329err:
330	ps->state = QUIT;
331	return (-1);
332
333newaddr:
334	ps->addr[ps->pos] = 0;
335	ps->pos = 0;
336	addr = strdup(ps->addr);
337	if (addr == NULL)
338		errlog(EX_SOFTWARE, "strdup");
339
340	if (add_recp(queue, addr, EXPAND_WILDCARD) != 0)
341		errlogx(EX_DATAERR, "invalid recipient `%s'", addr);
342
343	goto again;
344}
345
346static int
347writeline(struct queue *queue, const char *line, ssize_t linelen)
348{
349	ssize_t len;
350
351	while (linelen > 0) {
352		len = linelen;
353		if (linelen > MAX_LINE_RFC822) {
354			len = MAX_LINE_RFC822 - 10;
355		}
356
357		if (fwrite(line, len, 1, queue->mailf) != 1)
358			return (-1);
359
360		if (linelen <= MAX_LINE_RFC822)
361			break;
362
363		if (fwrite("\n", 1, 1, queue->mailf) != 1)
364			return (-1);
365
366		line += MAX_LINE_RFC822 - 10;
367		linelen = strlen(line);
368	}
369	return (0);
370}
371
372int
373readmail(struct queue *queue, int nodot, int recp_from_header)
374{
375	struct parse_state parse_state;
376	char *line = NULL;
377	ssize_t linelen;
378	size_t linecap = 0;
379	char newline[MAX_LINE_RFC822];
380	size_t error;
381	int had_headers = 0;
382	int had_from = 0;
383	int had_messagid = 0;
384	int had_date = 0;
385	int nocopy = 0;
386	int ret = -1;
387
388	parse_state.state = NONE;
389
390	error = fprintf(queue->mailf,
391		"Received: from %s (uid %d)\n"
392		"\t(envelope-from %s)\n"
393		"\tid %s\n"
394		"\tby %s (%s);\n"
395		"\t%s\n",
396		username, useruid,
397		queue->sender,
398		queue->id,
399		hostname(), VERSION,
400		rfc822date());
401	if ((ssize_t)error < 0)
402		return (-1);
403
404	while (!feof(stdin)) {
405		newline[0] = '\0';
406		if ((linelen = getline(&line, &linecap, stdin)) <= 0)
407			break;
408
409		if (!had_headers) {
410			if (linelen > MAX_LINE_RFC822) {
411				/* XXX also split headers */
412				errlogx(EX_DATAERR, "bad mail input format:"
413				    " from %s (uid %d) (envelope-from %s)",
414				    username, useruid, queue->sender);
415			}
416			/*
417			 * Unless this is a continuation, switch of
418			 * the Bcc: nocopy flag.
419			 */
420			if (!(line[0] == ' ' || line[0] == '\t'))
421				nocopy = 0;
422
423			if (strprefixcmp(line, "Date:") == 0)
424				had_date = 1;
425			else if (strprefixcmp(line, "Message-Id:") == 0)
426				had_messagid = 1;
427			else if (strprefixcmp(line, "From:") == 0)
428				had_from = 1;
429			else if (strprefixcmp(line, "Bcc:") == 0)
430				nocopy = 1;
431
432			if (parse_state.state != NONE) {
433				if (parse_addrs(&parse_state, line, queue) < 0) {
434					errlogx(EX_DATAERR, "invalid address in header\n");
435					/* NOTREACHED */
436				}
437			}
438
439			if (recp_from_header && (
440					strprefixcmp(line, "To:") == 0 ||
441					strprefixcmp(line, "Cc:") == 0 ||
442					strprefixcmp(line, "Bcc:") == 0)) {
443				parse_state.state = START;
444				if (parse_addrs(&parse_state, line, queue) < 0) {
445					errlogx(EX_DATAERR, "invalid address in header\n");
446					/* NOTREACHED */
447				}
448			}
449		}
450
451		if (strcmp(line, "\n") == 0 && !had_headers) {
452			had_headers = 1;
453			while (!had_date || !had_messagid || !had_from) {
454				if (!had_date) {
455					had_date = 1;
456					snprintf(newline, sizeof(newline), "Date: %s\n", rfc822date());
457				} else if (!had_messagid) {
458					/* XXX msgid, assign earlier and log? */
459					had_messagid = 1;
460					snprintf(newline, sizeof(newline), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n",
461						 (uintmax_t)time(NULL),
462						 queue->id,
463						 (uintmax_t)random(),
464						 hostname());
465				} else if (!had_from) {
466					had_from = 1;
467					snprintf(newline, sizeof(newline), "From: <%s>\n", queue->sender);
468				}
469				if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1)
470					goto fail;
471			}
472			strlcpy(newline, "\n", sizeof(newline));
473		}
474		if (!nodot && linelen == 2 && line[0] == '.')
475			break;
476		if (!nocopy) {
477			if (newline[0]) {
478				if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1)
479					goto fail;
480			} else {
481				if (writeline(queue, line, linelen) != 0)
482					goto fail;
483			}
484		}
485	}
486
487	ret = 0;
488fail:
489	free(line);
490	return (ret);
491}
492