bounce.c revision 1.44
1/*	$OpenBSD: bounce.c,v 1.44 2012/08/09 09:48:02 eric 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 <event.h>
29#include <imsg.h>
30#include <inttypes.h>
31#include <pwd.h>
32#include <signal.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <time.h>
37#include <unistd.h>
38
39#include "smtpd.h"
40#include "log.h"
41
42#define BOUNCE_HIWAT	65535
43
44enum {
45	BOUNCE_EHLO,
46	BOUNCE_MAIL,
47	BOUNCE_RCPT,
48	BOUNCE_DATA,
49	BOUNCE_DATA_NOTICE,
50	BOUNCE_DATA_MESSAGE,
51	BOUNCE_QUIT,
52	BOUNCE_CLOSE,
53};
54
55struct bounce {
56	struct envelope	 evp;
57	FILE		*msgfp;
58	int		 state;
59	struct iobuf	 iobuf;
60	struct io	 io;
61};
62
63static void bounce_send(struct bounce *, const char *, ...);
64static int  bounce_next(struct bounce *);
65static void bounce_status(struct bounce *, const char *, ...);
66static void bounce_io(struct io *, int);
67
68static void
69bounce_send(struct bounce *bounce, const char *fmt, ...)
70{
71	va_list	 ap;
72	char	*p;
73	int	 len;
74
75	va_start(ap, fmt);
76	if ((len = vasprintf(&p, fmt, ap)) == -1)
77		fatal("bounce: vasprintf");
78	va_end(ap);
79
80	log_trace(TRACE_BOUNCE, "bounce: %p: >>> %s", bounce, p);
81
82	iobuf_fqueue(&bounce->iobuf, "%s\n", p);
83
84        free(p);
85}
86
87/* This can simplified once we support PIPELINING */
88static int
89bounce_next(struct bounce *bounce)
90{
91	char	*line;
92	size_t	 len, s;
93
94	switch(bounce->state) {
95	case BOUNCE_EHLO:
96		bounce_send(bounce, "EHLO %s", env->sc_hostname);
97		bounce->state = BOUNCE_MAIL;
98		break;
99
100	case BOUNCE_MAIL:
101		bounce_send(bounce, "MAIL FROM: <>");
102		bounce->state = BOUNCE_RCPT;
103		break;
104
105	case BOUNCE_RCPT:
106		bounce_send(bounce, "RCPT TO: <%s@%s>",
107		    bounce->evp.sender.user, bounce->evp.sender.domain);
108		bounce->state = BOUNCE_DATA;
109		break;
110
111	case BOUNCE_DATA:
112		bounce_send(bounce, "DATA");
113		bounce->state = BOUNCE_DATA_NOTICE;
114		break;
115
116	case BOUNCE_DATA_NOTICE:
117		/* Construct an appropriate reason line. */
118		line = bounce->evp.errorline;
119		if (strlen(line) > 4 && (*line == '1' || *line == '6'))
120			line += 4;
121
122		iobuf_fqueue(&bounce->iobuf,
123		    "Subject: Delivery status notification\n"
124		    "From: Mailer Daemon <MAILER-DAEMON@%s>\n"
125		    "To: %s@%s\n"
126		    "Date: %s\n"
127		    "\n"
128		    "Hi !\n"
129		    "\n"
130		    "This is the MAILER-DAEMON, please DO NOT REPLY to this e-mail.\n"
131		    "An error has occurred while attempting to deliver a message.\n"
132		    "\n"
133		    "Recipient: %s@%s\n"
134		    "Reason:\n"
135		    "%s\n"
136		    "\n"
137		    "Below is a copy of the original message:\n"
138		    "\n",
139		    env->sc_hostname,
140		    bounce->evp.sender.user, bounce->evp.sender.domain,
141		    time_to_text(time(NULL)),
142		    bounce->evp.dest.user, bounce->evp.dest.domain,
143		    line);
144
145		log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]",
146		    bounce, iobuf_queued(&bounce->iobuf));
147
148		bounce->state = BOUNCE_DATA_MESSAGE;
149		break;
150
151	case BOUNCE_DATA_MESSAGE:
152
153		s = iobuf_queued(&bounce->iobuf);
154
155		while (iobuf_len(&bounce->iobuf) < BOUNCE_HIWAT) {
156			line = fgetln(bounce->msgfp, &len);
157			if (line == NULL)
158				break;
159			line[len - 1] = '\0';
160			if(len == 2 && line[0] == '.')
161				iobuf_queue(&bounce->iobuf, ".", 1);
162			iobuf_queue(&bounce->iobuf, line, len);
163			iobuf_queue(&bounce->iobuf, "\n", 1);
164		}
165
166		if (ferror(bounce->msgfp)) {
167			bounce_status(bounce, "460 Error reading message");
168			return (-1);
169		}
170
171		log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]",
172		    bounce, iobuf_queued(&bounce->iobuf) - s);
173
174		if (feof(bounce->msgfp)) {
175			bounce_send(bounce, ".");
176			bounce->state = BOUNCE_QUIT;
177		}
178
179		break;
180
181	case BOUNCE_QUIT:
182		bounce_send(bounce, "QUIT");
183		bounce->state = BOUNCE_CLOSE;
184		break;
185
186	default:
187		fatalx("bounce: bad state");
188	}
189
190	return (0);
191}
192
193static void
194bounce_status(struct bounce *bounce, const char *fmt, ...)
195{
196	va_list		 ap;
197	char		*status;
198	int		 len, msg;
199	struct envelope *evp;
200
201	/* ignore if the envelope has already been updated/deleted */
202	if (bounce->evp.id == 0)
203		return;
204
205	va_start(ap, fmt);
206	if ((len = vasprintf(&status, fmt, ap)) == -1)
207		fatal("bounce: vasprintf");
208	va_end(ap);
209
210	if (*status == '2')
211		msg = IMSG_QUEUE_DELIVERY_OK;
212	else if (*status == '5' || *status == '6')
213		msg = IMSG_QUEUE_DELIVERY_PERMFAIL;
214	else
215		msg = IMSG_QUEUE_DELIVERY_TEMPFAIL;
216
217	evp = &bounce->evp;
218	if (msg == IMSG_QUEUE_DELIVERY_TEMPFAIL) {
219		evp->retry++;
220		envelope_set_errormsg(evp, "%s", status);
221		queue_envelope_update(evp);
222		imsg_compose_event(env->sc_ievs[PROC_SCHEDULER], msg, 0, 0, -1,
223		    evp, sizeof *evp);
224	} else {
225		queue_envelope_delete(evp);
226		imsg_compose_event(env->sc_ievs[PROC_SCHEDULER], msg, 0, 0, -1,
227		    &evp->id, sizeof evp->id);
228	}
229
230	bounce->evp.id = 0;
231	free(status);
232}
233
234static void
235bounce_free(struct bounce *bounce)
236{
237	log_debug("bounce: %p: deleting session", bounce);
238
239	fclose(bounce->msgfp);
240	iobuf_clear(&bounce->iobuf);
241	io_clear(&bounce->io);
242	free(bounce);
243}
244
245static void
246bounce_io(struct io *io, int evt)
247{
248	struct bounce	*bounce = io->arg;
249	const char	*error;
250	char		*line, *msg;
251	int		 cont;
252	size_t		 len;
253
254	log_trace(TRACE_IO, "bounce: %p: %s %s",
255	    bounce, io_strevent(evt), io_strio(io));
256
257	switch (evt) {
258	case IO_DATAIN:
259	    nextline:
260		line = iobuf_getline(&bounce->iobuf, &len);
261		if (line == NULL) {
262			if (iobuf_len(&bounce->iobuf) >= SMTP_LINE_MAX) {
263				bounce_status(bounce, "150 Input too long");
264				bounce_free(bounce);
265				return;
266			}
267			iobuf_normalize(&bounce->iobuf);
268			break;
269		}
270
271		log_trace(TRACE_BOUNCE, "bounce: %p: <<< %s", bounce, line);
272
273		if ((error = parse_smtp_response(line, len, &msg, &cont))) {
274			bounce_status(bounce, "150 Bad response: %s", error);
275			bounce_free(bounce);
276			return;
277		}
278		if (cont)
279			goto nextline;
280
281		if (bounce->state == BOUNCE_CLOSE) {
282			bounce_free(bounce);
283			return;
284		}
285
286		if (line[0] != '2' && line[0] != '3') {		/* fail */
287			bounce_status(bounce, "%s", line);
288			bounce->state = BOUNCE_QUIT;
289		} else if (bounce->state == BOUNCE_QUIT) {	/* accepted */
290			bounce_status(bounce, "%s", line);
291		}
292
293		if (bounce_next(bounce) == -1) {
294			bounce_free(bounce);
295			return;
296		}
297
298		io_set_write(io);
299		break;
300
301	case IO_LOWAT:
302		if (bounce->state == BOUNCE_DATA_MESSAGE)
303			bounce_next(bounce);
304		if (iobuf_queued(&bounce->iobuf) == 0)
305			io_set_read(io);
306		break;
307
308	default:
309		bounce_status(bounce, "442 i/o error %i", evt);
310		bounce_free(bounce);
311		break;
312	}
313}
314
315int
316bounce_session(int fd, struct envelope *evp)
317{
318	struct bounce	*bounce = NULL;
319	int		 msgfd = -1;
320	FILE		*msgfp = NULL;
321	u_int32_t	 msgid;
322
323	msgid = evpid_to_msgid(evp->id);
324
325	log_debug("bounce: bouncing envelope id %016" PRIx64 "", evp->id);
326
327	/* get message content */
328	if ((msgfd = queue_message_fd_r(msgid)) == -1)
329		return (0);
330
331	msgfp = fdopen(msgfd, "r");
332	if (msgfp == NULL) {
333		log_warn("bounce: fdopen");
334		close(msgfd);
335		return (0);
336	}
337
338	if ((bounce = calloc(1, sizeof(*bounce))) == NULL) {
339		log_warn("bounce: calloc");
340		fclose(msgfp);
341		return (0);
342	}
343
344	bounce->evp = *evp;
345	bounce->msgfp = msgfp;
346	bounce->state = BOUNCE_EHLO;
347
348	iobuf_init(&bounce->iobuf, 0, 0);
349	io_init(&bounce->io, fd, bounce, bounce_io, &bounce->iobuf);
350	io_set_timeout(&bounce->io, 30000);
351	io_set_read(&bounce->io);
352	return (1);
353}
354
355int
356bounce_record_message(struct envelope *e, struct envelope *bounce)
357{
358	if (e->type == D_BOUNCE) {
359		log_debug("mailer daemons loop detected !");
360		return 0;
361	}
362
363	*bounce = *e;
364	bounce->type = D_BOUNCE;
365	bounce->retry = 0;
366	bounce->lasttry = 0;
367	return (queue_envelope_create(bounce));
368}
369