bounce.c revision 1.49
1/*	$OpenBSD: bounce.c,v 1.49 2012/10/02 12:37:38 chl Exp $	*/
2
3/*
4 * Copyright (c) 2009 Gilles Chehade <gilles@openbsd.org>
5 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
6 * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
7 *
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 */
20
21#include <sys/types.h>
22#include <sys/queue.h>
23#include <sys/tree.h>
24#include <sys/param.h>
25#include <sys/socket.h>
26
27#include <err.h>
28#include <errno.h>
29#include <event.h>
30#include <imsg.h>
31#include <inttypes.h>
32#include <pwd.h>
33#include <signal.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <time.h>
38#include <unistd.h>
39
40#include "smtpd.h"
41#include "log.h"
42
43#define BOUNCE_MAXRUN	10
44#define BOUNCE_HIWAT	65535
45
46enum {
47	BOUNCE_EHLO,
48	BOUNCE_MAIL,
49	BOUNCE_RCPT,
50	BOUNCE_DATA,
51	BOUNCE_DATA_NOTICE,
52	BOUNCE_DATA_MESSAGE,
53	BOUNCE_QUIT,
54	BOUNCE_CLOSE,
55};
56
57struct bounce {
58	TAILQ_ENTRY(bounce)	 entry;
59	uint64_t		 id;
60	uint32_t		 msgid;
61	TAILQ_HEAD(, envelope)	 envelopes;
62	size_t			 count;
63	FILE			*msgfp;
64	int			 state;
65	struct iobuf		 iobuf;
66	struct io		 io;
67
68	struct event		 evt;
69};
70
71static void bounce_drain(void);
72static void bounce_commit(uint32_t);
73static void bounce_send(struct bounce *, const char *, ...);
74static int  bounce_next(struct bounce *);
75static void bounce_status(struct bounce *, const char *, ...);
76static void bounce_io(struct io *, int);
77static void bounce_timeout(int, short, void *);
78static void bounce_free(struct bounce *);
79
80static struct tree bounces_by_msgid = SPLAY_INITIALIZER(&bounces_by_msgid);
81static struct tree bounces_by_uid = SPLAY_INITIALIZER(&bounces_by_uid);
82
83static int running = 0;
84static TAILQ_HEAD(, bounce) runnable = TAILQ_HEAD_INITIALIZER(runnable);
85
86void
87bounce_add(uint64_t evpid)
88{
89	struct envelope	*evp;
90	struct bounce	*bounce;
91	struct timeval	 tv;
92
93	evp = xcalloc(1, sizeof *evp, "bounce_add");
94
95	if (queue_envelope_load(evpid, evp) == 0) {
96		evp->id = evpid;
97		imsg_compose_event(env->sc_ievs[PROC_SCHEDULER],
98		    IMSG_QUEUE_DELIVERY_PERMFAIL, 0, 0, -1, evp, sizeof *evp);
99		free(evp);
100		return;
101	}
102
103	if (evp->type != D_BOUNCE)
104		errx(1, "bounce: evp:%016" PRIx64 " is not of type D_BOUNCE!",
105		    evp->id);
106	evp->lasttry = time(NULL);
107
108	bounce = tree_get(&bounces_by_msgid, evpid_to_msgid(evpid));
109	if (bounce == NULL) {
110		bounce = xcalloc(1, sizeof(*bounce), "bounce_add");
111		bounce->msgid = evpid_to_msgid(evpid);
112		tree_xset(&bounces_by_msgid, bounce->msgid, bounce);
113
114		log_debug("bounce: %p: new bounce for msg:%08" PRIx32,
115		    bounce, bounce->msgid);
116
117		TAILQ_INIT(&bounce->envelopes);
118		evtimer_set(&bounce->evt, bounce_timeout, &bounce->msgid);
119		tv.tv_sec = 1;
120		tv.tv_usec = 0;
121		evtimer_add(&bounce->evt, &tv);
122	}
123
124	log_debug("bounce: %p: adding evp:%16" PRIx64, bounce, evp->id);
125
126	TAILQ_INSERT_TAIL(&bounce->envelopes, evp, entry);
127	bounce->count += 1;
128
129	if (bounce->id)
130		return;
131
132	evtimer_del(&bounce->evt);
133	tv.tv_sec = 1;
134	tv.tv_usec = 0;
135	evtimer_add(&bounce->evt, &tv);
136}
137
138void
139bounce_run(uint64_t id, int fd)
140{
141	struct bounce	*bounce;
142	int		 msgfd;
143
144	log_trace(TRACE_BOUNCE, "bounce: run %016" PRIx64 " fd %i", id, fd);
145
146	bounce = tree_xpop(&bounces_by_uid, id);
147
148	if (fd == -1) {
149		bounce_status(bounce, "failed to receive enqueueing socket");
150		bounce_free(bounce);
151		return;
152	}
153
154	if ((msgfd = queue_message_fd_r(bounce->msgid)) == -1) {
155		bounce_status(bounce, "could not open message fd");
156		bounce_free(bounce);
157		return;
158	}
159
160	if ((bounce->msgfp = fdopen(msgfd, "r")) == NULL) {
161		log_warn("bounce_run: fdopen");
162		bounce_status(bounce, "error %i in fdopen", errno);
163		bounce_free(bounce);
164		close(msgfd);
165		return;
166	}
167
168	bounce->state = BOUNCE_EHLO;
169	if (iobuf_init(&bounce->iobuf, 0, 0) == -1) {
170		bounce_status(bounce, "iobuf_init");
171		bounce_free(bounce);
172		close(msgfd);
173		return;
174	}
175	io_init(&bounce->io, fd, bounce, bounce_io, &bounce->iobuf);
176	io_set_timeout(&bounce->io, 30000);
177	io_set_read(&bounce->io);
178}
179
180static void
181bounce_commit(uint32_t msgid)
182{
183	struct bounce	*bounce;
184
185	log_trace(TRACE_BOUNCE, "bounce: commit msg:%08" PRIx32, msgid);
186
187	bounce = tree_xget(&bounces_by_msgid, msgid);
188	bounce->id = generate_uid();
189	evtimer_del(&bounce->evt);
190	TAILQ_INSERT_TAIL(&runnable, bounce, entry);
191
192	bounce_drain();
193}
194
195static void
196bounce_timeout(int fd, short ev, void *arg)
197{
198	uint32_t *msgid = arg;
199
200	bounce_commit(*msgid);
201}
202
203static void
204bounce_drain()
205{
206	struct bounce	*bounce;
207
208	while ((bounce = TAILQ_FIRST(&runnable))) {
209
210		if (running >= BOUNCE_MAXRUN) {
211			log_debug("bounce: max session reached");
212			return;
213		}
214
215		TAILQ_REMOVE(&runnable, bounce, entry);
216		if (TAILQ_FIRST(&bounce->envelopes) == NULL) {
217			log_debug("bounce: %p: no envelopes", bounce);
218			bounce_free(bounce);
219			continue;
220		}
221
222		tree_xset(&bounces_by_uid, bounce->id, bounce);
223
224		log_debug("bounce: %p: requesting enqueue socket with id 0x%016" PRIx64,
225		    bounce, bounce->id);
226
227		imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_SMTP_ENQUEUE,
228		    0, 0, -1, &bounce->id, sizeof (bounce->id));
229
230		running += 1;
231	}
232}
233
234static void
235bounce_send(struct bounce *bounce, const char *fmt, ...)
236{
237	va_list	 ap;
238	char	*p;
239	int	 len;
240
241	va_start(ap, fmt);
242	if ((len = vasprintf(&p, fmt, ap)) == -1)
243		fatal("bounce: vasprintf");
244	va_end(ap);
245
246	log_trace(TRACE_BOUNCE, "bounce: %p: >>> %s", bounce, p);
247
248	iobuf_fqueue(&bounce->iobuf, "%s\n", p);
249
250        free(p);
251}
252
253/* This can simplified once we support PIPELINING */
254static int
255bounce_next(struct bounce *bounce)
256{
257	struct envelope	*evp;
258	char		*line;
259	size_t		 len, s;
260
261	switch(bounce->state) {
262	case BOUNCE_EHLO:
263		bounce_send(bounce, "EHLO %s", env->sc_hostname);
264		bounce->state = BOUNCE_MAIL;
265		break;
266
267	case BOUNCE_MAIL:
268		bounce_send(bounce, "MAIL FROM: <>");
269		bounce->state = BOUNCE_RCPT;
270		break;
271
272	case BOUNCE_RCPT:
273		evp = TAILQ_FIRST(&bounce->envelopes);
274		bounce_send(bounce, "RCPT TO: <%s@%s>",
275		    evp->sender.user, evp->sender.domain);
276		bounce->state = BOUNCE_DATA;
277		break;
278
279	case BOUNCE_DATA:
280		bounce_send(bounce, "DATA");
281		bounce->state = BOUNCE_DATA_NOTICE;
282		break;
283
284	case BOUNCE_DATA_NOTICE:
285		/* Construct an appropriate reason line. */
286
287		/* prevent more envelopes from being added to this bounce */
288		tree_xpop(&bounces_by_msgid, bounce->msgid);
289
290		evp = TAILQ_FIRST(&bounce->envelopes);
291
292		iobuf_fqueue(&bounce->iobuf,
293		    "Subject: Delivery status notification\n"
294		    "From: Mailer Daemon <MAILER-DAEMON@%s>\n"
295		    "To: %s@%s\n"
296		    "Date: %s\n"
297		    "\n"
298		    "Hi !\n"
299		    "\n"
300		    "This is the MAILER-DAEMON, please DO NOT REPLY to this e-mail.\n"
301		    "An error has occurred while attempting to deliver a message.\n"
302		    "\n",
303		    env->sc_hostname,
304		    evp->sender.user, evp->sender.domain,
305		    time_to_text(time(NULL)));
306
307		TAILQ_FOREACH(evp, &bounce->envelopes, entry) {
308			line = evp->errorline;
309			if (strlen(line) > 4 && (*line == '1' || *line == '6'))
310				line += 4;
311			iobuf_fqueue(&bounce->iobuf,
312			    "Recipient: %s@%s\n"
313			    "Reason: %s\n",
314			    evp->dest.user, evp->dest.domain, line);
315		}
316
317		iobuf_fqueue(&bounce->iobuf,
318		    "\n"
319		    "Below is a copy of the original message:\n"
320		    "\n");
321
322		log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]",
323		    bounce, iobuf_queued(&bounce->iobuf));
324
325		bounce->state = BOUNCE_DATA_MESSAGE;
326		break;
327
328	case BOUNCE_DATA_MESSAGE:
329
330		s = iobuf_queued(&bounce->iobuf);
331
332		while (iobuf_len(&bounce->iobuf) < BOUNCE_HIWAT) {
333			line = fgetln(bounce->msgfp, &len);
334			if (line == NULL)
335				break;
336			line[len - 1] = '\0';
337			if(len == 2 && line[0] == '.')
338				iobuf_queue(&bounce->iobuf, ".", 1);
339			iobuf_queue(&bounce->iobuf, line, len);
340			iobuf_queue(&bounce->iobuf, "\n", 1);
341		}
342
343		if (ferror(bounce->msgfp)) {
344			bounce_status(bounce, "460 Error reading message");
345			return (-1);
346		}
347
348		log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]",
349		    bounce, iobuf_queued(&bounce->iobuf) - s);
350
351		if (feof(bounce->msgfp)) {
352			bounce_send(bounce, ".");
353			bounce->state = BOUNCE_QUIT;
354		}
355		break;
356
357	case BOUNCE_QUIT:
358		bounce_send(bounce, "QUIT");
359		bounce->state = BOUNCE_CLOSE;
360		break;
361
362	default:
363		fatalx("bounce: bad state");
364	}
365
366	return (0);
367}
368
369static void
370bounce_status(struct bounce *bounce, const char *fmt, ...)
371{
372	va_list		 ap;
373	char		*status;
374	int		 len, msg;
375	struct envelope	*evp;
376
377	/* ignore if the envelopes have already been updated/deleted */
378	if (TAILQ_FIRST(&bounce->envelopes) == NULL)
379		return;
380
381	va_start(ap, fmt);
382	if ((len = vasprintf(&status, fmt, ap)) == -1)
383		fatal("bounce: vasprintf");
384	va_end(ap);
385
386	if (*status == '2')
387		msg = IMSG_QUEUE_DELIVERY_OK;
388	else if (*status == '5' || *status == '6')
389		msg = IMSG_QUEUE_DELIVERY_PERMFAIL;
390	else
391		msg = IMSG_QUEUE_DELIVERY_TEMPFAIL;
392
393	while ((evp = TAILQ_FIRST(&bounce->envelopes))) {
394		if (msg == IMSG_QUEUE_DELIVERY_TEMPFAIL) {
395			evp->retry++;
396			envelope_set_errormsg(evp, "%s", status);
397			queue_envelope_update(evp);
398			imsg_compose_event(env->sc_ievs[PROC_SCHEDULER], msg, 0, 0, -1,
399			    evp, sizeof *evp);
400		} else {
401			queue_envelope_delete(evp);
402			imsg_compose_event(env->sc_ievs[PROC_SCHEDULER], msg, 0, 0, -1,
403			    &evp->id, sizeof evp->id);
404		}
405		TAILQ_REMOVE(&bounce->envelopes, evp, entry);
406		free(evp);
407	}
408
409	free(status);
410}
411
412static void
413bounce_free(struct bounce *bounce)
414{
415	struct envelope	*evp;
416
417	log_debug("bounce: %p: deleting session", bounce);
418
419	/* if the envelopes where not sent, it is still in the tree */
420	tree_pop(&bounces_by_msgid, bounce->msgid);
421
422	while ((evp = TAILQ_FIRST(&bounce->envelopes))) {
423		TAILQ_REMOVE(&bounce->envelopes, evp, entry);
424		free(evp);
425	}
426
427	if (bounce->msgfp)
428		fclose(bounce->msgfp);
429	iobuf_clear(&bounce->iobuf);
430	io_clear(&bounce->io);
431	free(bounce);
432
433	running -= 1;
434	bounce_drain();
435}
436
437static void
438bounce_io(struct io *io, int evt)
439{
440	struct bounce	*bounce = io->arg;
441	const char	*error;
442	char		*line, *msg;
443	int		 cont;
444	size_t		 len;
445
446	log_trace(TRACE_IO, "bounce: %p: %s %s",
447	    bounce, io_strevent(evt), io_strio(io));
448
449	switch (evt) {
450	case IO_DATAIN:
451	    nextline:
452		line = iobuf_getline(&bounce->iobuf, &len);
453		if (line == NULL) {
454			if (iobuf_len(&bounce->iobuf) >= SMTP_LINE_MAX) {
455				bounce_status(bounce, "150 Input too long");
456				bounce_free(bounce);
457				return;
458			}
459			iobuf_normalize(&bounce->iobuf);
460			break;
461		}
462
463		log_trace(TRACE_BOUNCE, "bounce: %p: <<< %s", bounce, line);
464
465		if ((error = parse_smtp_response(line, len, &msg, &cont))) {
466			bounce_status(bounce, "150 Bad response: %s", error);
467			bounce_free(bounce);
468			return;
469		}
470		if (cont)
471			goto nextline;
472
473		if (bounce->state == BOUNCE_CLOSE) {
474			bounce_free(bounce);
475			return;
476		}
477
478		if (line[0] != '2' && line[0] != '3') {		/* fail */
479			bounce_status(bounce, "%s", line);
480			bounce->state = BOUNCE_QUIT;
481		} else if (bounce->state == BOUNCE_QUIT) {	/* accepted */
482			bounce_status(bounce, "%s", line);
483		}
484
485		if (bounce_next(bounce) == -1) {
486			bounce_free(bounce);
487			return;
488		}
489
490		io_set_write(io);
491		break;
492
493	case IO_LOWAT:
494		if (bounce->state == BOUNCE_DATA_MESSAGE)
495			bounce_next(bounce);
496		if (iobuf_queued(&bounce->iobuf) == 0)
497			io_set_read(io);
498		break;
499
500	default:
501		bounce_status(bounce, "442 i/o error %i", evt);
502		bounce_free(bounce);
503		break;
504	}
505}
506