xref: /dragonfly/libexec/dma/mail.c (revision 3d33658b)
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(1);
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(1);
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 		/* FALLTHROUGH */
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(1, NULL);
338 
339 	if (add_recp(queue, addr, EXPAND_WILDCARD) != 0)
340 		errlogx(1, "invalid recipient `%s'", addr);
341 
342 	goto again;
343 }
344 
345 int
346 readmail(struct queue *queue, int nodot, int recp_from_header)
347 {
348 	struct parse_state parse_state;
349 	char line[1000];	/* by RFC2822 */
350 	size_t linelen;
351 	size_t error;
352 	int had_headers = 0;
353 	int had_from = 0;
354 	int had_messagid = 0;
355 	int had_date = 0;
356 	int had_last_line = 0;
357 	int nocopy = 0;
358 
359 	parse_state.state = NONE;
360 
361 	error = fprintf(queue->mailf,
362 		"Received: from %s (uid %d)\n"
363 		"\t(envelope-from %s)\n"
364 		"\tid %s\n"
365 		"\tby %s (%s);\n"
366 		"\t%s\n",
367 		username, useruid,
368 		queue->sender,
369 		queue->id,
370 		hostname(), VERSION,
371 		rfc822date());
372 	if ((ssize_t)error < 0)
373 		return (-1);
374 
375 	while (!feof(stdin)) {
376 		if (fgets(line, sizeof(line) - 1, stdin) == NULL)
377 			break;
378 		if (had_last_line)
379 			errlogx(1, "bad mail input format:"
380 				" from %s (uid %d) (envelope-from %s)",
381 				username, useruid, queue->sender);
382 		linelen = strlen(line);
383 		if (linelen == 0 || line[linelen - 1] != '\n') {
384 			/*
385 			 * This line did not end with a newline character.
386 			 * If we fix it, it better be the last line of
387 			 * the file.
388 			 */
389 			line[linelen] = '\n';
390 			line[linelen + 1] = 0;
391 			had_last_line = 1;
392 		}
393 		if (!had_headers) {
394 			/*
395 			 * Unless this is a continuation, switch of
396 			 * the Bcc: nocopy flag.
397 			 */
398 			if (!(line[0] == ' ' || line[0] == '\t'))
399 				nocopy = 0;
400 
401 			if (strprefixcmp(line, "Date:") == 0)
402 				had_date = 1;
403 			else if (strprefixcmp(line, "Message-Id:") == 0)
404 				had_messagid = 1;
405 			else if (strprefixcmp(line, "From:") == 0)
406 				had_from = 1;
407 			else if (strprefixcmp(line, "Bcc:") == 0)
408 				nocopy = 1;
409 
410 			if (parse_state.state != NONE) {
411 				if (parse_addrs(&parse_state, line, queue) < 0) {
412 					errlogx(1, "invalid address in header\n");
413 					/* NOTREACHED */
414 				}
415 			}
416 
417 			if (recp_from_header && (
418 					strprefixcmp(line, "To:") == 0 ||
419 					strprefixcmp(line, "Cc:") == 0 ||
420 					strprefixcmp(line, "Bcc:") == 0)) {
421 				parse_state.state = START;
422 				if (parse_addrs(&parse_state, line, queue) < 0) {
423 					errlogx(1, "invalid address in header\n");
424 					/* NOTREACHED */
425 				}
426 			}
427 		}
428 
429 		if (strcmp(line, "\n") == 0 && !had_headers) {
430 			had_headers = 1;
431 			while (!had_date || !had_messagid || !had_from) {
432 				if (!had_date) {
433 					had_date = 1;
434 					snprintf(line, sizeof(line), "Date: %s\n", rfc822date());
435 				} else if (!had_messagid) {
436 					/* XXX msgid, assign earlier and log? */
437 					had_messagid = 1;
438 					snprintf(line, sizeof(line), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n",
439 						 (uintmax_t)time(NULL),
440 						 queue->id,
441 						 (uintmax_t)random(),
442 						 hostname());
443 				} else if (!had_from) {
444 					had_from = 1;
445 					snprintf(line, sizeof(line), "From: <%s>\n", queue->sender);
446 				}
447 				if (fwrite(line, strlen(line), 1, queue->mailf) != 1)
448 					return (-1);
449 			}
450 			strcpy(line, "\n");
451 		}
452 		if (!nodot && linelen == 2 && line[0] == '.')
453 			break;
454 		if (!nocopy) {
455 			if (fwrite(line, strlen(line), 1, queue->mailf) != 1)
456 				return (-1);
457 		}
458 	}
459 
460 	return (0);
461 }
462