xref: /dragonfly/libexec/dma/mail.c (revision e0ecab34)
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 			goto skip;
273 
274 		case '>':
275 			if (!ps->brackets)
276 				goto err;
277 			ps->brackets = 0;
278 
279 			s++;
280 			goto newaddr;
281 
282 		case ':':
283 			/* group - ignore */
284 			ps->pos = 0;
285 			goto skip;
286 
287 		case ',':
288 		case ';':
289 			s++;
290 			goto newaddr;
291 
292 		default:
293 			goto copy;
294 		}
295 
296 copy:
297 		if (ps->comment)
298 			goto skip;
299 
300 		if (ps->pos + 1 == sizeof(ps->addr))
301 			goto err;
302 		ps->addr[ps->pos++] = *s;
303 
304 skip:
305 		;
306 	}
307 
308 eol:
309 	ps->state = EOL;
310 	return (0);
311 
312 err:
313 	ps->state = QUIT;
314 	return (-1);
315 
316 newaddr:
317 	ps->addr[ps->pos] = 0;
318 	ps->pos = 0;
319 	addr = strdup(ps->addr);
320 	if (addr == NULL)
321 		errlog(1, NULL);
322 
323 	add_recp(queue, addr, 1);
324 	fprintf(stderr, "parsed `%s'\n", addr);
325 	goto again;
326 }
327 
328 int
329 readmail(struct queue *queue, int nodot, int recp_from_header)
330 {
331 	struct parse_state parse_state;
332 	char line[1000];	/* by RFC2822 */
333 	size_t linelen;
334 	size_t error;
335 	int had_headers = 0;
336 	int had_from = 0;
337 	int had_messagid = 0;
338 	int had_date = 0;
339 	int nocopy = 0;
340 
341 	parse_state.state = NONE;
342 
343 	error = fprintf(queue->mailf,
344 		"Received: from %s (uid %d)\n"
345 		"\t(envelope-from %s)\n"
346 		"\tid %s\n"
347 		"\tby %s (%s)\n"
348 		"\t%s\n",
349 		username, getuid(),
350 		queue->sender,
351 		queue->id,
352 		hostname(), VERSION,
353 		rfc822date());
354 	if ((ssize_t)error < 0)
355 		return (-1);
356 
357 	while (!feof(stdin)) {
358 		if (fgets(line, sizeof(line), stdin) == NULL)
359 			break;
360 		linelen = strlen(line);
361 		if (linelen == 0 || line[linelen - 1] != '\n') {
362 			errno = EINVAL;		/* XXX mark permanent errors */
363 			return (-1);
364 		}
365 		if (!had_headers) {
366 			/*
367 			 * Unless this is a continuation, switch of
368 			 * the Bcc: nocopy flag.
369 			 */
370 			if (!(line[0] == ' ' || line[0] == '\t'))
371 				nocopy = 0;
372 
373 			if (strprefixcmp(line, "Date:") == 0)
374 				had_date = 1;
375 			else if (strprefixcmp(line, "Message-Id:") == 0)
376 				had_messagid = 1;
377 			else if (strprefixcmp(line, "From:") == 0)
378 				had_from = 1;
379 			else if (strprefixcmp(line, "Bcc:") == 0)
380 				nocopy = 1;
381 
382 			if (parse_state.state != NONE) {
383 				if (parse_addrs(&parse_state, line, queue) < 0) {
384 					errlogx(1, "invalid address in header\n");
385 					/* NOTREACHED */
386 				}
387 			}
388 
389 			if (recp_from_header && (
390 					strprefixcmp(line, "To:") == 0 ||
391 					strprefixcmp(line, "Cc:") == 0 ||
392 					strprefixcmp(line, "Bcc:") == 0)) {
393 				parse_state.state = START;
394 				if (parse_addrs(&parse_state, line, queue) < 0) {
395 					errlogx(1, "invalid address in header\n");
396 					/* NOTREACHED */
397 				}
398 			}
399 		}
400 
401 		if (strcmp(line, "\n") == 0 && !had_headers) {
402 			had_headers = 1;
403 			while (!had_date || !had_messagid || !had_from) {
404 				if (!had_date) {
405 					had_date = 1;
406 					snprintf(line, sizeof(line), "Date: %s\n", rfc822date());
407 				} else if (!had_messagid) {
408 					/* XXX better msgid, assign earlier and log? */
409 					had_messagid = 1;
410 					snprintf(line, sizeof(line), "Message-Id: <%s@%s>\n",
411 						 queue->id, hostname());
412 				} else if (!had_from) {
413 					had_from = 1;
414 					snprintf(line, sizeof(line), "From: <%s>\n", queue->sender);
415 				}
416 				if (fwrite(line, strlen(line), 1, queue->mailf) != 1)
417 					return (-1);
418 			}
419 			strcpy(line, "\n");
420 		}
421 		if (!nodot && linelen == 2 && line[0] == '.')
422 			break;
423 		if (!nocopy) {
424 			if (fwrite(line, strlen(line), 1, queue->mailf) != 1)
425 				return (-1);
426 		}
427 	}
428 
429 	return (0);
430 }
431