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