diff options
Diffstat (limited to 'smtpd/bounce.c')
-rw-r--r-- | smtpd/bounce.c | 820 |
1 files changed, 0 insertions, 820 deletions
diff --git a/smtpd/bounce.c b/smtpd/bounce.c deleted file mode 100644 index 4a4a0992..00000000 --- a/smtpd/bounce.c +++ /dev/null @@ -1,820 +0,0 @@ -/* $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); |