aboutsummaryrefslogtreecommitdiffstats
path: root/smtpd/bounce.c
diff options
context:
space:
mode:
Diffstat (limited to 'smtpd/bounce.c')
-rw-r--r--smtpd/bounce.c820
1 files changed, 820 insertions, 0 deletions
diff --git a/smtpd/bounce.c b/smtpd/bounce.c
new file mode 100644
index 00000000..4a4a0992
--- /dev/null
+++ b/smtpd/bounce.c
@@ -0,0 +1,820 @@
+/* $OpenBSD: bounce.c,v 1.82 2020/04/24 11:34:07 eric Exp $ */
+
+/*
+ * Copyright (c) 2009 Gilles Chehade <gilles@poolp.org>
+ * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "smtpd.h"
+#include "log.h"
+
+#define BOUNCE_MAXRUN 2
+#define BOUNCE_HIWAT 65535
+
+enum {
+ BOUNCE_EHLO,
+ BOUNCE_MAIL,
+ BOUNCE_RCPT,
+ BOUNCE_DATA,
+ BOUNCE_DATA_NOTICE,
+ BOUNCE_DATA_MESSAGE,
+ BOUNCE_DATA_END,
+ BOUNCE_QUIT,
+ BOUNCE_CLOSE,
+};
+
+struct bounce_envelope {
+ TAILQ_ENTRY(bounce_envelope) entry;
+ uint64_t id;
+ struct mailaddr dest;
+ char *report;
+ uint8_t esc_class;
+ uint8_t esc_code;
+};
+
+struct bounce_message {
+ SPLAY_ENTRY(bounce_message) sp_entry;
+ TAILQ_ENTRY(bounce_message) entry;
+ uint32_t msgid;
+ struct delivery_bounce bounce;
+ char *smtpname;
+ char *to;
+ time_t timeout;
+ TAILQ_HEAD(, bounce_envelope) envelopes;
+};
+
+struct bounce_session {
+ char *smtpname;
+ struct bounce_message *msg;
+ FILE *msgfp;
+ int state;
+ struct io *io;
+ uint64_t boundary;
+};
+
+SPLAY_HEAD(bounce_message_tree, bounce_message);
+static int bounce_message_cmp(const struct bounce_message *,
+ const struct bounce_message *);
+SPLAY_PROTOTYPE(bounce_message_tree, bounce_message, sp_entry,
+ bounce_message_cmp);
+
+static void bounce_drain(void);
+static void bounce_send(struct bounce_session *, const char *, ...);
+static int bounce_next_message(struct bounce_session *);
+static int bounce_next(struct bounce_session *);
+static void bounce_delivery(struct bounce_message *, int, const char *);
+static void bounce_status(struct bounce_session *, const char *, ...);
+static void bounce_io(struct io *, int, void *);
+static void bounce_timeout(int, short, void *);
+static void bounce_free(struct bounce_session *);
+static const char *action_str(const struct delivery_bounce *);
+
+static struct tree wait_fd;
+static struct bounce_message_tree messages;
+static TAILQ_HEAD(, bounce_message) pending;
+
+static int nmessage = 0;
+static int running = 0;
+static struct event ev_timer;
+
+static void
+bounce_init(void)
+{
+ static int init = 0;
+
+ if (init == 0) {
+ TAILQ_INIT(&pending);
+ SPLAY_INIT(&messages);
+ tree_init(&wait_fd);
+ evtimer_set(&ev_timer, bounce_timeout, NULL);
+ init = 1;
+ }
+}
+
+void
+bounce_add(uint64_t evpid)
+{
+ char buf[LINE_MAX], *line;
+ struct envelope evp;
+ struct bounce_message key, *msg;
+ struct bounce_envelope *be;
+
+ bounce_init();
+
+ if (queue_envelope_load(evpid, &evp) == 0) {
+ m_create(p_scheduler, IMSG_QUEUE_DELIVERY_PERMFAIL, 0, 0, -1);
+ m_add_evpid(p_scheduler, evpid);
+ m_close(p_scheduler);
+ return;
+ }
+
+ if (evp.type != D_BOUNCE)
+ errx(1, "bounce: evp:%016" PRIx64 " is not of type D_BOUNCE!",
+ evp.id);
+
+ key.msgid = evpid_to_msgid(evpid);
+ key.bounce = evp.agent.bounce;
+ key.smtpname = evp.smtpname;
+
+ switch (evp.esc_class) {
+ case ESC_STATUS_OK:
+ key.bounce.type = B_DELIVERED;
+ break;
+ case ESC_STATUS_TEMPFAIL:
+ key.bounce.type = B_DELAYED;
+ break;
+ default:
+ key.bounce.type = B_FAILED;
+ }
+
+ key.bounce.dsn_ret = evp.dsn_ret;
+ key.bounce.ttl = evp.ttl;
+ msg = SPLAY_FIND(bounce_message_tree, &messages, &key);
+ if (msg == NULL) {
+ msg = xcalloc(1, sizeof(*msg));
+ msg->msgid = key.msgid;
+ msg->bounce = key.bounce;
+
+ TAILQ_INIT(&msg->envelopes);
+
+ msg->smtpname = xstrdup(evp.smtpname);
+ (void)snprintf(buf, sizeof(buf), "%s@%s", evp.sender.user,
+ evp.sender.domain);
+ msg->to = xstrdup(buf);
+ nmessage += 1;
+ SPLAY_INSERT(bounce_message_tree, &messages, msg);
+ log_debug("debug: bounce: new message %08" PRIx32,
+ msg->msgid);
+ stat_increment("bounce.message", 1);
+ } else
+ TAILQ_REMOVE(&pending, msg, entry);
+
+ line = evp.errorline;
+ if (strlen(line) > 4 && (*line == '1' || *line == '6'))
+ line += 4;
+ (void)snprintf(buf, sizeof(buf), "%s@%s: %s", evp.dest.user,
+ evp.dest.domain, line);
+
+ be = xmalloc(sizeof *be);
+ be->id = evpid;
+ be->report = xstrdup(buf);
+ (void)strlcpy(be->dest.user, evp.dest.user, sizeof(be->dest.user));
+ (void)strlcpy(be->dest.domain, evp.dest.domain,
+ sizeof(be->dest.domain));
+ be->esc_class = evp.esc_class;
+ be->esc_code = evp.esc_code;
+ TAILQ_INSERT_TAIL(&msg->envelopes, be, entry);
+ log_debug("debug: bounce: adding report %16"PRIx64": %s", be->id, be->report);
+
+ msg->timeout = time(NULL) + 1;
+ TAILQ_INSERT_TAIL(&pending, msg, entry);
+
+ stat_increment("bounce.envelope", 1);
+ bounce_drain();
+}
+
+void
+bounce_fd(int fd)
+{
+ struct bounce_session *s;
+ struct bounce_message *msg;
+
+ log_debug("debug: bounce: got enqueue socket %d", fd);
+
+ if (fd == -1 || TAILQ_EMPTY(&pending)) {
+ log_debug("debug: bounce: cancelling");
+ if (fd != -1)
+ close(fd);
+ running -= 1;
+ bounce_drain();
+ return;
+ }
+
+ msg = TAILQ_FIRST(&pending);
+
+ s = xcalloc(1, sizeof(*s));
+ s->smtpname = xstrdup(msg->smtpname);
+ s->state = BOUNCE_EHLO;
+ s->io = io_new();
+ io_set_callback(s->io, bounce_io, s);
+ io_set_fd(s->io, fd);
+ io_set_timeout(s->io, 30000);
+ io_set_read(s->io);
+ s->boundary = generate_uid();
+
+ log_debug("debug: bounce: new session %p", s);
+ stat_increment("bounce.session", 1);
+}
+
+static void
+bounce_timeout(int fd, short ev, void *arg)
+{
+ log_debug("debug: bounce: timeout");
+
+ bounce_drain();
+}
+
+static void
+bounce_drain()
+{
+ struct bounce_message *msg;
+ struct timeval tv;
+ time_t t;
+
+ log_debug("debug: bounce: drain: nmessage=%d running=%d",
+ nmessage, running);
+
+ while (1) {
+ if (running >= BOUNCE_MAXRUN) {
+ log_debug("debug: bounce: max session reached");
+ return;
+ }
+
+ if (nmessage == 0) {
+ log_debug("debug: bounce: no more messages");
+ return;
+ }
+
+ if (running >= nmessage) {
+ log_debug("debug: bounce: enough sessions running");
+ return;
+ }
+
+ if ((msg = TAILQ_FIRST(&pending)) == NULL) {
+ log_debug("debug: bounce: no more pending messages");
+ return;
+ }
+
+ t = time(NULL);
+ if (msg->timeout > t) {
+ log_debug("debug: bounce: next message not ready yet");
+ if (!evtimer_pending(&ev_timer, NULL)) {
+ log_debug("debug: bounce: setting timer");
+ tv.tv_sec = msg->timeout - t;
+ tv.tv_usec = 0;
+ evtimer_add(&ev_timer, &tv);
+ }
+ return;
+ }
+
+ log_debug("debug: bounce: requesting new enqueue socket...");
+ m_compose(p_pony, IMSG_QUEUE_SMTP_SESSION, 0, 0, -1, NULL, 0);
+
+ running += 1;
+ }
+}
+
+static void
+bounce_send(struct bounce_session *s, const char *fmt, ...)
+{
+ va_list ap;
+ char *p;
+ int len;
+
+ va_start(ap, fmt);
+ if ((len = vasprintf(&p, fmt, ap)) == -1)
+ fatal("bounce: vasprintf");
+ va_end(ap);
+
+ log_trace(TRACE_BOUNCE, "bounce: %p: >>> %s", s, p);
+
+ io_xprintf(s->io, "%s\r\n", p);
+
+ free(p);
+}
+
+static const char *
+bounce_duration(long long int d)
+{
+ static char buf[32];
+
+ if (d < 60) {
+ (void)snprintf(buf, sizeof buf, "%lld second%s", d,
+ (d == 1) ? "" : "s");
+ } else if (d < 3600) {
+ d = d / 60;
+ (void)snprintf(buf, sizeof buf, "%lld minute%s", d,
+ (d == 1) ? "" : "s");
+ }
+ else if (d < 3600 * 24) {
+ d = d / 3600;
+ (void)snprintf(buf, sizeof buf, "%lld hour%s", d,
+ (d == 1) ? "" : "s");
+ }
+ else {
+ d = d / (3600 * 24);
+ (void)snprintf(buf, sizeof buf, "%lld day%s", d,
+ (d == 1) ? "" : "s");
+ }
+ return (buf);
+}
+
+#define NOTICE_INTRO \
+ " Hi!\r\n\r\n" \
+ " This is the MAILER-DAEMON, please DO NOT REPLY to this email.\r\n"
+
+const char *notice_error =
+ " An error has occurred while attempting to deliver a message for\r\n"
+ " the following list of recipients:\r\n\r\n";
+
+const char *notice_warning =
+ " A message is delayed for more than %s for the following\r\n"
+ " list of recipients:\r\n\r\n";
+
+const char *notice_warning2 =
+ " Please note that this is only a temporary failure report.\r\n"
+ " The message is kept in the queue for up to %s.\r\n"
+ " You DO NOT NEED to re-send the message to these recipients.\r\n\r\n";
+
+const char *notice_success =
+ " Your message was successfully delivered to these recipients.\r\n\r\n";
+
+const char *notice_relay =
+ " Your message was relayed to these recipients.\r\n\r\n";
+
+static int
+bounce_next_message(struct bounce_session *s)
+{
+ struct bounce_message *msg;
+ char buf[LINE_MAX];
+ int fd;
+ time_t now;
+
+ again:
+
+ now = time(NULL);
+
+ TAILQ_FOREACH(msg, &pending, entry) {
+ if (msg->timeout > now)
+ continue;
+ if (strcmp(msg->smtpname, s->smtpname))
+ continue;
+ break;
+ }
+ if (msg == NULL)
+ return (0);
+
+ TAILQ_REMOVE(&pending, msg, entry);
+ SPLAY_REMOVE(bounce_message_tree, &messages, msg);
+
+ if ((fd = queue_message_fd_r(msg->msgid)) == -1) {
+ bounce_delivery(msg, IMSG_QUEUE_DELIVERY_TEMPFAIL,
+ "Could not open message fd");
+ goto again;
+ }
+
+ if ((s->msgfp = fdopen(fd, "r")) == NULL) {
+ (void)snprintf(buf, sizeof(buf), "fdopen: %s", strerror(errno));
+ log_warn("warn: bounce: fdopen");
+ close(fd);
+ bounce_delivery(msg, IMSG_QUEUE_DELIVERY_TEMPFAIL, buf);
+ goto again;
+ }
+
+ s->msg = msg;
+ return (1);
+}
+
+static int
+bounce_next(struct bounce_session *s)
+{
+ struct bounce_envelope *evp;
+ char *line = NULL;
+ size_t n, sz = 0;
+ ssize_t len;
+
+ switch (s->state) {
+ case BOUNCE_EHLO:
+ bounce_send(s, "EHLO %s", s->smtpname);
+ s->state = BOUNCE_MAIL;
+ break;
+
+ case BOUNCE_MAIL:
+ case BOUNCE_DATA_END:
+ log_debug("debug: bounce: %p: getting next message...", s);
+ if (bounce_next_message(s) == 0) {
+ log_debug("debug: bounce: %p: no more messages", s);
+ bounce_send(s, "QUIT");
+ s->state = BOUNCE_CLOSE;
+ break;
+ }
+ log_debug("debug: bounce: %p: found message %08"PRIx32,
+ s, s->msg->msgid);
+ bounce_send(s, "MAIL FROM: <>");
+ s->state = BOUNCE_RCPT;
+ break;
+
+ case BOUNCE_RCPT:
+ bounce_send(s, "RCPT TO: <%s>", s->msg->to);
+ s->state = BOUNCE_DATA;
+ break;
+
+ case BOUNCE_DATA:
+ bounce_send(s, "DATA");
+ s->state = BOUNCE_DATA_NOTICE;
+ break;
+
+ case BOUNCE_DATA_NOTICE:
+ /* Construct an appropriate notice. */
+
+ io_xprintf(s->io,
+ "Subject: Delivery status notification: %s\r\n"
+ "From: Mailer Daemon <MAILER-DAEMON@%s>\r\n"
+ "To: %s\r\n"
+ "Date: %s\r\n"
+ "MIME-Version: 1.0\r\n"
+ "Content-Type: multipart/mixed;"
+ "boundary=\"%16" PRIu64 "/%s\"\r\n"
+ "\r\n"
+ "This is a MIME-encapsulated message.\r\n"
+ "\r\n",
+ action_str(&s->msg->bounce),
+ s->smtpname,
+ s->msg->to,
+ time_to_text(time(NULL)),
+ s->boundary,
+ s->smtpname);
+
+ io_xprintf(s->io,
+ "--%16" PRIu64 "/%s\r\n"
+ "Content-Description: Notification\r\n"
+ "Content-Type: text/plain; charset=us-ascii\r\n"
+ "\r\n"
+ NOTICE_INTRO
+ "\r\n",
+ s->boundary, s->smtpname);
+
+ switch (s->msg->bounce.type) {
+ case B_FAILED:
+ io_xprint(s->io, notice_error);
+ break;
+ case B_DELAYED:
+ io_xprintf(s->io, notice_warning,
+ bounce_duration(s->msg->bounce.delay));
+ break;
+ case B_DELIVERED:
+ io_xprint(s->io, s->msg->bounce.mta_without_dsn ?
+ notice_relay : notice_success);
+ break;
+ default:
+ log_warn("warn: bounce: unknown bounce_type");
+ }
+
+ TAILQ_FOREACH(evp, &s->msg->envelopes, entry) {
+ io_xprint(s->io, evp->report);
+ io_xprint(s->io, "\r\n");
+ }
+ io_xprint(s->io, "\r\n");
+
+ if (s->msg->bounce.type == B_DELAYED)
+ io_xprintf(s->io, notice_warning2,
+ bounce_duration(s->msg->bounce.ttl));
+
+ io_xprintf(s->io,
+ " Below is a copy of the original message:\r\n"
+ "\r\n");
+
+ io_xprintf(s->io,
+ "--%16" PRIu64 "/%s\r\n"
+ "Content-Description: Delivery Report\r\n"
+ "Content-Type: message/delivery-status\r\n"
+ "\r\n",
+ s->boundary, s->smtpname);
+
+ io_xprintf(s->io,
+ "Reporting-MTA: dns; %s\r\n"
+ "\r\n",
+ s->smtpname);
+
+ TAILQ_FOREACH(evp, &s->msg->envelopes, entry) {
+ io_xprintf(s->io,
+ "Final-Recipient: rfc822; %s@%s\r\n"
+ "Action: %s\r\n"
+ "Status: %s\r\n"
+ "\r\n",
+ evp->dest.user,
+ evp->dest.domain,
+ action_str(&s->msg->bounce),
+ esc_code(evp->esc_class, evp->esc_code));
+ }
+
+ log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]",
+ s, io_queued(s->io));
+
+ s->state = BOUNCE_DATA_MESSAGE;
+ break;
+
+ case BOUNCE_DATA_MESSAGE:
+ io_xprintf(s->io,
+ "--%16" PRIu64 "/%s\r\n"
+ "Content-Description: Message headers\r\n"
+ "Content-Type: text/rfc822-headers\r\n"
+ "\r\n",
+ s->boundary, s->smtpname);
+
+ n = io_queued(s->io);
+ while (io_queued(s->io) < BOUNCE_HIWAT) {
+ if ((len = getline(&line, &sz, s->msgfp)) == -1)
+ break;
+ if (len == 1 && line[0] == '\n' && /* end of headers */
+ s->msg->bounce.type == B_DELIVERED &&
+ s->msg->bounce.dsn_ret == DSN_RETHDRS) {
+ free(line);
+ fclose(s->msgfp);
+ s->msgfp = NULL;
+ io_xprintf(s->io,
+ "\r\n--%16" PRIu64 "/%s--\r\n", s->boundary,
+ s->smtpname);
+ bounce_send(s, ".");
+ s->state = BOUNCE_DATA_END;
+ return (0);
+ }
+ line[len - 1] = '\0';
+ io_xprintf(s->io, "%s%s\r\n",
+ (len == 2 && line[0] == '.') ? "." : "", line);
+ }
+ free(line);
+
+ if (ferror(s->msgfp)) {
+ fclose(s->msgfp);
+ s->msgfp = NULL;
+ bounce_delivery(s->msg, IMSG_QUEUE_DELIVERY_TEMPFAIL,
+ "Error reading message");
+ s->msg = NULL;
+ return (-1);
+ }
+
+ io_xprintf(s->io,
+ "\r\n--%16" PRIu64 "/%s--\r\n", s->boundary, s->smtpname);
+
+ log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]",
+ s, io_queued(s->io) - n);
+
+ if (feof(s->msgfp)) {
+ fclose(s->msgfp);
+ s->msgfp = NULL;
+ bounce_send(s, ".");
+ s->state = BOUNCE_DATA_END;
+ }
+ break;
+
+ case BOUNCE_QUIT:
+ bounce_send(s, "QUIT");
+ s->state = BOUNCE_CLOSE;
+ break;
+
+ default:
+ fatalx("bounce: bad state");
+ }
+
+ return (0);
+}
+
+
+static void
+bounce_delivery(struct bounce_message *msg, int delivery, const char *status)
+{
+ struct bounce_envelope *be;
+ struct envelope evp;
+ size_t n;
+ const char *f;
+
+ n = 0;
+ while ((be = TAILQ_FIRST(&msg->envelopes))) {
+ if (delivery == IMSG_QUEUE_DELIVERY_TEMPFAIL) {
+ if (queue_envelope_load(be->id, &evp) == 0) {
+ fatalx("could not reload envelope!");
+ }
+ evp.retry++;
+ evp.lasttry = msg->timeout;
+ envelope_set_errormsg(&evp, "%s", status);
+ queue_envelope_update(&evp);
+ m_create(p_scheduler, delivery, 0, 0, -1);
+ m_add_envelope(p_scheduler, &evp);
+ m_close(p_scheduler);
+ } else {
+ m_create(p_scheduler, delivery, 0, 0, -1);
+ m_add_evpid(p_scheduler, be->id);
+ m_close(p_scheduler);
+ queue_envelope_delete(be->id);
+ }
+ TAILQ_REMOVE(&msg->envelopes, be, entry);
+ free(be->report);
+ free(be);
+ n += 1;
+ }
+
+
+ if (delivery == IMSG_QUEUE_DELIVERY_TEMPFAIL)
+ f = "TempFail";
+ else if (delivery == IMSG_QUEUE_DELIVERY_PERMFAIL)
+ f = "PermFail";
+ else
+ f = NULL;
+
+ if (f)
+ log_warnx("warn: %s injecting failure report on message %08"
+ PRIx32 " to <%s> for %zu envelope%s: %s",
+ f, msg->msgid, msg->to, n, n > 1 ? "s":"", status);
+
+ nmessage -= 1;
+ stat_decrement("bounce.message", 1);
+ stat_decrement("bounce.envelope", n);
+ free(msg->smtpname);
+ free(msg->to);
+ free(msg);
+}
+
+static void
+bounce_status(struct bounce_session *s, const char *fmt, ...)
+{
+ va_list ap;
+ char *status;
+ int len, delivery;
+
+ /* Ignore if there is no message */
+ if (s->msg == NULL)
+ return;
+
+ va_start(ap, fmt);
+ if ((len = vasprintf(&status, fmt, ap)) == -1)
+ fatal("bounce: vasprintf");
+ va_end(ap);
+
+ if (*status == '2')
+ delivery = IMSG_QUEUE_DELIVERY_OK;
+ else if (*status == '5' || *status == '6')
+ delivery = IMSG_QUEUE_DELIVERY_PERMFAIL;
+ else
+ delivery = IMSG_QUEUE_DELIVERY_TEMPFAIL;
+
+ bounce_delivery(s->msg, delivery, status);
+ s->msg = NULL;
+ if (s->msgfp)
+ fclose(s->msgfp);
+
+ free(status);
+}
+
+static void
+bounce_free(struct bounce_session *s)
+{
+ log_debug("debug: bounce: %p: deleting session", s);
+
+ io_free(s->io);
+
+ free(s->smtpname);
+ free(s);
+
+ running -= 1;
+ stat_decrement("bounce.session", 1);
+ bounce_drain();
+}
+
+static void
+bounce_io(struct io *io, int evt, void *arg)
+{
+ struct bounce_session *s = arg;
+ const char *error;
+ char *line, *msg;
+ int cont;
+ size_t len;
+
+ log_trace(TRACE_IO, "bounce: %p: %s %s", s, io_strevent(evt),
+ io_strio(io));
+
+ switch (evt) {
+ case IO_DATAIN:
+ nextline:
+ line = io_getline(s->io, &len);
+ if (line == NULL && io_datalen(s->io) >= LINE_MAX) {
+ bounce_status(s, "Input too long");
+ bounce_free(s);
+ return;
+ }
+
+ if (line == NULL)
+ break;
+
+ /* Strip trailing '\r' */
+ if (len && line[len - 1] == '\r')
+ line[--len] = '\0';
+
+ log_trace(TRACE_BOUNCE, "bounce: %p: <<< %s", s, line);
+
+ if ((error = parse_smtp_response(line, len, &msg, &cont))) {
+ bounce_status(s, "Bad response: %s", error);
+ bounce_free(s);
+ return;
+ }
+ if (cont)
+ goto nextline;
+
+ if (s->state == BOUNCE_CLOSE) {
+ bounce_free(s);
+ return;
+ }
+
+ if (line[0] != '2' && line[0] != '3') { /* fail */
+ bounce_status(s, "%s", line);
+ s->state = BOUNCE_QUIT;
+ } else if (s->state == BOUNCE_DATA_END) { /* accepted */
+ bounce_status(s, "%s", line);
+ }
+
+ if (bounce_next(s) == -1) {
+ bounce_free(s);
+ return;
+ }
+
+ io_set_write(io);
+ break;
+
+ case IO_LOWAT:
+ if (s->state == BOUNCE_DATA_MESSAGE)
+ if (bounce_next(s) == -1) {
+ bounce_free(s);
+ return;
+ }
+ if (io_queued(s->io) == 0)
+ io_set_read(io);
+ break;
+
+ default:
+ bounce_status(s, "442 i/o error %d", evt);
+ bounce_free(s);
+ break;
+ }
+}
+
+static int
+bounce_message_cmp(const struct bounce_message *a,
+ const struct bounce_message *b)
+{
+ int r;
+
+ if (a->msgid < b->msgid)
+ return (-1);
+ if (a->msgid > b->msgid)
+ return (1);
+ if ((r = strcmp(a->smtpname, b->smtpname)))
+ return (r);
+
+ return memcmp(&a->bounce, &b->bounce, sizeof (a->bounce));
+}
+
+static const char *
+action_str(const struct delivery_bounce *b)
+{
+ switch (b->type) {
+ case B_FAILED:
+ return ("failed");
+ case B_DELAYED:
+ return ("delayed");
+ case B_DELIVERED:
+ if (b->mta_without_dsn)
+ return ("relayed");
+
+ return ("delivered");
+ default:
+ log_warn("warn: bounce: unknown bounce_type");
+ return ("");
+ }
+}
+
+SPLAY_GENERATE(bounce_message_tree, bounce_message, sp_entry,
+ bounce_message_cmp);