xref: /freebsd/contrib/dma/mail.c (revision 640235e2c2ba32947f7c59d168437ffa1280f1e6)
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 void
45 bounce(struct qitem *it, const char *reason)
46 {
47 	struct queue bounceq;
48 	char line[1000];
49 	size_t pos;
50 	int error;
51 
52 	/* Don't bounce bounced mails */
53 	if (it->sender[0] == 0) {
54 		syslog(LOG_INFO, "can not bounce a bounce message, discarding");
55 		exit(EX_SOFTWARE);
56 	}
57 
58 	bzero(&bounceq, sizeof(bounceq));
59 	LIST_INIT(&bounceq.queue);
60 	bounceq.sender = "";
61 	if (add_recp(&bounceq, it->sender, EXPAND_WILDCARD) != 0)
62 		goto fail;
63 
64 	if (newspoolf(&bounceq) != 0)
65 		goto fail;
66 
67 	syslog(LOG_ERR, "delivery failed, bouncing as %s", bounceq.id);
68 	setlogident("%s", bounceq.id);
69 
70 	error = fprintf(bounceq.mailf,
71 		"Received: from MAILER-DAEMON\n"
72 		"\tid %s\n"
73 		"\tby %s (%s);\n"
74 		"\t%s\n"
75 		"X-Original-To: <%s>\n"
76 		"From: MAILER-DAEMON <>\n"
77 		"To: %s\n"
78 		"Subject: Mail delivery failed\n"
79 		"Message-Id: <%s@%s>\n"
80 		"Date: %s\n"
81 		"\n"
82 		"This is the %s at %s.\n"
83 		"\n"
84 		"There was an error delivering your mail to <%s>.\n"
85 		"\n"
86 		"%s\n"
87 		"\n"
88 		"%s\n"
89 		"\n",
90 		bounceq.id,
91 		hostname(), VERSION,
92 		rfc822date(),
93 		it->addr,
94 		it->sender,
95 		bounceq.id, hostname(),
96 		rfc822date(),
97 		VERSION, hostname(),
98 		it->addr,
99 		reason,
100 		config.features & FULLBOUNCE ?
101 		    "Original message follows." :
102 		    "Message headers follow.");
103 	if (error < 0)
104 		goto fail;
105 
106 	if (fseek(it->mailf, 0, SEEK_SET) != 0)
107 		goto fail;
108 	if (config.features & FULLBOUNCE) {
109 		while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) {
110 			if (fwrite(line, 1, pos, bounceq.mailf) != pos)
111 				goto fail;
112 		}
113 	} else {
114 		while (!feof(it->mailf)) {
115 			if (fgets(line, sizeof(line), it->mailf) == NULL)
116 				break;
117 			if (line[0] == '\n')
118 				break;
119 			if (fwrite(line, strlen(line), 1, bounceq.mailf) != 1)
120 				goto fail;
121 		}
122 	}
123 
124 	if (linkspool(&bounceq) != 0)
125 		goto fail;
126 	/* bounce is safe */
127 
128 	delqueue(it);
129 
130 	run_queue(&bounceq);
131 	/* NOTREACHED */
132 
133 fail:
134 	syslog(LOG_CRIT, "error creating bounce: %m");
135 	delqueue(it);
136 	exit(EX_IOERR);
137 }
138 
139 struct parse_state {
140 	char addr[1000];
141 	int pos;
142 
143 	enum {
144 		NONE = 0,
145 		START,
146 		MAIN,
147 		EOL,
148 		QUIT
149 	} state;
150 	int comment;
151 	int quote;
152 	int brackets;
153 	int esc;
154 };
155 
156 /*
157  * Simplified RFC2822 header/address parsing.
158  * We copy escapes and quoted strings directly, since
159  * we have to pass them like this to the mail server anyways.
160  * XXX local addresses will need treatment
161  */
162 static int
163 parse_addrs(struct parse_state *ps, char *s, struct queue *queue)
164 {
165 	char *addr;
166 
167 again:
168 	switch (ps->state) {
169 	case NONE:
170 		return (-1);
171 
172 	case START:
173 		/* init our data */
174 		bzero(ps, sizeof(*ps));
175 
176 		/* skip over header name */
177 		while (*s != ':')
178 			s++;
179 		s++;
180 		ps->state = MAIN;
181 		break;
182 
183 	case MAIN:
184 		/* all fine */
185 		break;
186 
187 	case EOL:
188 		switch (*s) {
189 		case ' ':
190 		case '\t':
191 			s++;
192 			/* continue */
193 			break;
194 
195 		default:
196 			ps->state = QUIT;
197 			if (ps->pos != 0)
198 				goto newaddr;
199 			return (0);
200 		}
201 
202 	case QUIT:
203 		return (0);
204 	}
205 
206 	for (; *s != 0; s++) {
207 		if (ps->esc) {
208 			ps->esc = 0;
209 
210 			switch (*s) {
211 			case '\r':
212 			case '\n':
213 				goto err;
214 
215 			default:
216 				goto copy;
217 			}
218 		}
219 
220 		if (ps->quote) {
221 			switch (*s) {
222 			case '"':
223 				ps->quote = 0;
224 				goto copy;
225 
226 			case '\\':
227 				ps->esc = 1;
228 				goto copy;
229 
230 			case '\r':
231 			case '\n':
232 				goto eol;
233 
234 			default:
235 				goto copy;
236 			}
237 		}
238 
239 		switch (*s) {
240 		case '(':
241 			ps->comment++;
242 			break;
243 
244 		case ')':
245 			if (ps->comment)
246 				ps->comment--;
247 			else
248 				goto err;
249 			goto skip;
250 
251 		case '"':
252 			ps->quote = 1;
253 			goto copy;
254 
255 		case '\\':
256 			ps->esc = 1;
257 			goto copy;
258 
259 		case '\r':
260 		case '\n':
261 			goto eol;
262 		}
263 
264 		if (ps->comment)
265 			goto skip;
266 
267 		switch (*s) {
268 		case ' ':
269 		case '\t':
270 			/* ignore whitespace */
271 			goto skip;
272 
273 		case '<':
274 			/* this is the real address now */
275 			ps->brackets = 1;
276 			ps->pos = 0;
277 			goto skip;
278 
279 		case '>':
280 			if (!ps->brackets)
281 				goto err;
282 			ps->brackets = 0;
283 
284 			s++;
285 			goto newaddr;
286 
287 		case ':':
288 			/* group - ignore */
289 			ps->pos = 0;
290 			goto skip;
291 
292 		case ',':
293 		case ';':
294 			/*
295 			 * Next address, copy previous one.
296 			 * However, we might be directly after
297 			 * a <address>, or have two consecutive
298 			 * commas.
299 			 * Skip the comma unless there is
300 			 * really something to copy.
301 			 */
302 			if (ps->pos == 0)
303 				goto skip;
304 			s++;
305 			goto newaddr;
306 
307 		default:
308 			goto copy;
309 		}
310 
311 copy:
312 		if (ps->comment)
313 			goto skip;
314 
315 		if (ps->pos + 1 == sizeof(ps->addr))
316 			goto err;
317 		ps->addr[ps->pos++] = *s;
318 
319 skip:
320 		;
321 	}
322 
323 eol:
324 	ps->state = EOL;
325 	return (0);
326 
327 err:
328 	ps->state = QUIT;
329 	return (-1);
330 
331 newaddr:
332 	ps->addr[ps->pos] = 0;
333 	ps->pos = 0;
334 	addr = strdup(ps->addr);
335 	if (addr == NULL)
336 		errlog(EX_SOFTWARE, "strdup");
337 
338 	if (add_recp(queue, addr, EXPAND_WILDCARD) != 0)
339 		errlogx(EX_DATAERR, "invalid recipient `%s'", addr);
340 
341 	goto again;
342 }
343 
344 int
345 readmail(struct queue *queue, int nodot, int recp_from_header)
346 {
347 	struct parse_state parse_state;
348 	char line[1000];	/* by RFC2822 */
349 	size_t linelen;
350 	size_t error;
351 	int had_headers = 0;
352 	int had_from = 0;
353 	int had_messagid = 0;
354 	int had_date = 0;
355 	int had_last_line = 0;
356 	int nocopy = 0;
357 
358 	parse_state.state = NONE;
359 
360 	error = fprintf(queue->mailf,
361 		"Received: from %s (uid %d)\n"
362 		"\t(envelope-from %s)\n"
363 		"\tid %s\n"
364 		"\tby %s (%s);\n"
365 		"\t%s\n",
366 		username, useruid,
367 		queue->sender,
368 		queue->id,
369 		hostname(), VERSION,
370 		rfc822date());
371 	if ((ssize_t)error < 0)
372 		return (-1);
373 
374 	while (!feof(stdin)) {
375 		if (fgets(line, sizeof(line) - 1, stdin) == NULL)
376 			break;
377 		if (had_last_line)
378 			errlogx(EX_DATAERR, "bad mail input format:"
379 				" from %s (uid %d) (envelope-from %s)",
380 				username, useruid, queue->sender);
381 		linelen = strlen(line);
382 		if (linelen == 0 || line[linelen - 1] != '\n') {
383 			/*
384 			 * This line did not end with a newline character.
385 			 * If we fix it, it better be the last line of
386 			 * the file.
387 			 */
388 			line[linelen] = '\n';
389 			line[linelen + 1] = 0;
390 			had_last_line = 1;
391 		}
392 		if (!had_headers) {
393 			/*
394 			 * Unless this is a continuation, switch of
395 			 * the Bcc: nocopy flag.
396 			 */
397 			if (!(line[0] == ' ' || line[0] == '\t'))
398 				nocopy = 0;
399 
400 			if (strprefixcmp(line, "Date:") == 0)
401 				had_date = 1;
402 			else if (strprefixcmp(line, "Message-Id:") == 0)
403 				had_messagid = 1;
404 			else if (strprefixcmp(line, "From:") == 0)
405 				had_from = 1;
406 			else if (strprefixcmp(line, "Bcc:") == 0)
407 				nocopy = 1;
408 
409 			if (parse_state.state != NONE) {
410 				if (parse_addrs(&parse_state, line, queue) < 0) {
411 					errlogx(EX_DATAERR, "invalid address in header\n");
412 					/* NOTREACHED */
413 				}
414 			}
415 
416 			if (recp_from_header && (
417 					strprefixcmp(line, "To:") == 0 ||
418 					strprefixcmp(line, "Cc:") == 0 ||
419 					strprefixcmp(line, "Bcc:") == 0)) {
420 				parse_state.state = START;
421 				if (parse_addrs(&parse_state, line, queue) < 0) {
422 					errlogx(EX_DATAERR, "invalid address in header\n");
423 					/* NOTREACHED */
424 				}
425 			}
426 		}
427 
428 		if (strcmp(line, "\n") == 0 && !had_headers) {
429 			had_headers = 1;
430 			while (!had_date || !had_messagid || !had_from) {
431 				if (!had_date) {
432 					had_date = 1;
433 					snprintf(line, sizeof(line), "Date: %s\n", rfc822date());
434 				} else if (!had_messagid) {
435 					/* XXX msgid, assign earlier and log? */
436 					had_messagid = 1;
437 					snprintf(line, sizeof(line), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n",
438 						 (uintmax_t)time(NULL),
439 						 queue->id,
440 						 (uintmax_t)random(),
441 						 hostname());
442 				} else if (!had_from) {
443 					had_from = 1;
444 					snprintf(line, sizeof(line), "From: <%s>\n", queue->sender);
445 				}
446 				if (fwrite(line, strlen(line), 1, queue->mailf) != 1)
447 					return (-1);
448 			}
449 			strcpy(line, "\n");
450 		}
451 		if (!nodot && linelen == 2 && line[0] == '.')
452 			break;
453 		if (!nocopy) {
454 			if (fwrite(line, strlen(line), 1, queue->mailf) != 1)
455 				return (-1);
456 		}
457 	}
458 
459 	return (0);
460 }
461