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