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