aboutsummaryrefslogtreecommitdiffstats
path: root/usr.sbin/smtpd/mta_session.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/smtpd/mta_session.c')
-rw-r--r--usr.sbin/smtpd/mta_session.c2008
1 files changed, 2008 insertions, 0 deletions
diff --git a/usr.sbin/smtpd/mta_session.c b/usr.sbin/smtpd/mta_session.c
new file mode 100644
index 00000000..ad1c3c84
--- /dev/null
+++ b/usr.sbin/smtpd/mta_session.c
@@ -0,0 +1,2008 @@
+/* $OpenBSD: mta_session.c,v 1.136 2020/05/21 15:38:05 millert Exp $ */
+
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2008 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 <sys/stat.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <openssl/ssl.h>
+#include <pwd.h>
+#include <resolv.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+#include "log.h"
+#include "ssl.h"
+
+#define MAX_TRYBEFOREDISABLE 10
+
+#define MTA_HIWAT 65535
+
+enum mta_state {
+ MTA_INIT,
+ MTA_BANNER,
+ MTA_EHLO,
+ MTA_HELO,
+ MTA_LHLO,
+ MTA_STARTTLS,
+ MTA_AUTH,
+ MTA_AUTH_PLAIN,
+ MTA_AUTH_LOGIN,
+ MTA_AUTH_LOGIN_USER,
+ MTA_AUTH_LOGIN_PASS,
+ MTA_READY,
+ MTA_MAIL,
+ MTA_RCPT,
+ MTA_DATA,
+ MTA_BODY,
+ MTA_EOM,
+ MTA_LMTP_EOM,
+ MTA_RSET,
+ MTA_QUIT,
+};
+
+#define MTA_FORCE_ANYSSL 0x0001
+#define MTA_FORCE_SMTPS 0x0002
+#define MTA_FORCE_TLS 0x0004
+#define MTA_FORCE_PLAIN 0x0008
+#define MTA_WANT_SECURE 0x0010
+#define MTA_DOWNGRADE_PLAIN 0x0080
+
+#define MTA_TLS 0x0100
+#define MTA_TLS_VERIFIED 0x0200
+
+#define MTA_FREE 0x0400
+#define MTA_LMTP 0x0800
+#define MTA_WAIT 0x1000
+#define MTA_HANGON 0x2000
+#define MTA_RECONN 0x4000
+
+#define MTA_EXT_STARTTLS 0x01
+#define MTA_EXT_PIPELINING 0x02
+#define MTA_EXT_AUTH 0x04
+#define MTA_EXT_AUTH_PLAIN 0x08
+#define MTA_EXT_AUTH_LOGIN 0x10
+#define MTA_EXT_SIZE 0x20
+
+struct mta_session {
+ uint64_t id;
+ struct mta_relay *relay;
+ struct mta_route *route;
+ char *helo;
+ char *mxname;
+
+ char *username;
+
+ int flags;
+
+ int attempt;
+ int use_smtps;
+ int use_starttls;
+ int use_smtp_tls;
+ int ready;
+
+ struct event ev;
+ struct io *io;
+ int ext;
+
+ size_t ext_size;
+
+ size_t msgtried;
+ size_t msgcount;
+ size_t rcptcount;
+ int hangon;
+
+ enum mta_state state;
+ struct mta_task *task;
+ struct mta_envelope *currevp;
+ FILE *datafp;
+ size_t datalen;
+
+ size_t failures;
+
+ char replybuf[2048];
+};
+
+static void mta_session_init(void);
+static void mta_start(int fd, short ev, void *arg);
+static void mta_io(struct io *, int, void *);
+static void mta_free(struct mta_session *);
+static void mta_getnameinfo_cb(void *, int, const char *, const char *);
+static void mta_on_ptr(void *, void *, void *);
+static void mta_on_timeout(struct runq *, void *);
+static void mta_connect(struct mta_session *);
+static void mta_enter_state(struct mta_session *, int);
+static void mta_flush_task(struct mta_session *, int, const char *, size_t, int);
+static void mta_error(struct mta_session *, const char *, ...);
+static void mta_send(struct mta_session *, char *, ...);
+static ssize_t mta_queue_data(struct mta_session *);
+static void mta_response(struct mta_session *, char *);
+static const char * mta_strstate(int);
+static void mta_cert_init(struct mta_session *);
+static void mta_cert_init_cb(void *, int, const char *, const void *, size_t);
+static void mta_cert_verify(struct mta_session *);
+static void mta_cert_verify_cb(void *, int);
+static void mta_tls_verified(struct mta_session *);
+static struct mta_session *mta_tree_pop(struct tree *, uint64_t);
+static const char * dsn_strret(enum dsn_ret);
+static const char * dsn_strnotify(uint8_t);
+
+void mta_hoststat_update(const char *, const char *);
+void mta_hoststat_reschedule(const char *);
+void mta_hoststat_cache(const char *, uint64_t);
+void mta_hoststat_uncache(const char *, uint64_t);
+
+
+static void mta_filter_begin(struct mta_session *);
+static void mta_filter_end(struct mta_session *);
+static void mta_connected(struct mta_session *);
+static void mta_disconnected(struct mta_session *);
+
+static void mta_report_link_connect(struct mta_session *, const char *, int,
+ const struct sockaddr_storage *,
+ const struct sockaddr_storage *);
+static void mta_report_link_greeting(struct mta_session *, const char *);
+static void mta_report_link_identify(struct mta_session *, const char *, const char *);
+static void mta_report_link_tls(struct mta_session *, const char *);
+static void mta_report_link_disconnect(struct mta_session *);
+static void mta_report_link_auth(struct mta_session *, const char *, const char *);
+static void mta_report_tx_reset(struct mta_session *, uint32_t);
+static void mta_report_tx_begin(struct mta_session *, uint32_t);
+static void mta_report_tx_mail(struct mta_session *, uint32_t, const char *, int);
+static void mta_report_tx_rcpt(struct mta_session *, uint32_t, const char *, int);
+static void mta_report_tx_envelope(struct mta_session *, uint32_t, uint64_t);
+static void mta_report_tx_data(struct mta_session *, uint32_t, int);
+static void mta_report_tx_commit(struct mta_session *, uint32_t, size_t);
+static void mta_report_tx_rollback(struct mta_session *, uint32_t);
+static void mta_report_protocol_client(struct mta_session *, const char *);
+static void mta_report_protocol_server(struct mta_session *, const char *);
+#if 0
+static void mta_report_filter_response(struct mta_session *, int, int, const char *);
+#endif
+static void mta_report_timeout(struct mta_session *);
+
+
+static struct tree wait_helo;
+static struct tree wait_ptr;
+static struct tree wait_fd;
+static struct tree wait_tls_init;
+static struct tree wait_tls_verify;
+
+static struct runq *hangon;
+
+#define SESSION_FILTERED(s) \
+ ((s)->relay->dispatcher->u.remote.filtername)
+
+static void
+mta_session_init(void)
+{
+ static int init = 0;
+
+ if (!init) {
+ tree_init(&wait_helo);
+ tree_init(&wait_ptr);
+ tree_init(&wait_fd);
+ tree_init(&wait_tls_init);
+ tree_init(&wait_tls_verify);
+ runq_init(&hangon, mta_on_timeout);
+ init = 1;
+ }
+}
+
+void
+mta_session(struct mta_relay *relay, struct mta_route *route, const char *mxname)
+{
+ struct mta_session *s;
+ struct timeval tv;
+
+ mta_session_init();
+
+ s = xcalloc(1, sizeof *s);
+ s->id = generate_uid();
+ s->relay = relay;
+ s->route = route;
+ s->mxname = xstrdup(mxname);
+
+ mta_filter_begin(s);
+
+ if (relay->flags & RELAY_LMTP)
+ s->flags |= MTA_LMTP;
+ switch (relay->tls) {
+ case RELAY_TLS_SMTPS:
+ s->flags |= MTA_FORCE_SMTPS;
+ s->flags |= MTA_WANT_SECURE;
+ break;
+ case RELAY_TLS_STARTTLS:
+ s->flags |= MTA_FORCE_TLS;
+ s->flags |= MTA_WANT_SECURE;
+ break;
+ case RELAY_TLS_OPPORTUNISTIC:
+ /* do not force anything, try tls then smtp */
+ break;
+ case RELAY_TLS_NO:
+ s->flags |= MTA_FORCE_PLAIN;
+ break;
+ default:
+ fatalx("bad value for relay->tls: %d", relay->tls);
+ }
+
+ log_debug("debug: mta: %p: spawned for relay %s", s,
+ mta_relay_to_text(relay));
+ stat_increment("mta.session", 1);
+
+ if (route->dst->ptrname || route->dst->lastptrquery) {
+ /* We want to delay the connection since to always notify
+ * the relay asynchronously.
+ */
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ evtimer_set(&s->ev, mta_start, s);
+ evtimer_add(&s->ev, &tv);
+ } else if (waitq_wait(&route->dst->ptrname, mta_on_ptr, s)) {
+ resolver_getnameinfo(s->route->dst->sa, 0, mta_getnameinfo_cb, s);
+ }
+}
+
+void
+mta_session_imsg(struct mproc *p, struct imsg *imsg)
+{
+ struct mta_session *s;
+ struct msg m;
+ uint64_t reqid;
+ const char *name;
+ int status;
+ struct stat sb;
+
+ switch (imsg->hdr.type) {
+
+ case IMSG_MTA_OPEN_MESSAGE:
+ m_msg(&m, imsg);
+ m_get_id(&m, &reqid);
+ m_end(&m);
+
+ s = mta_tree_pop(&wait_fd, reqid);
+ if (s == NULL) {
+ if (imsg->fd != -1)
+ close(imsg->fd);
+ return;
+ }
+
+ if (imsg->fd == -1) {
+ log_debug("debug: mta: failed to obtain msg fd");
+ mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL,
+ "Could not get message fd", 0, 0);
+ mta_enter_state(s, MTA_READY);
+ return;
+ }
+
+ if ((s->ext & MTA_EXT_SIZE) && s->ext_size != 0) {
+ if (fstat(imsg->fd, &sb) == -1) {
+ log_debug("debug: mta: failed to stat msg fd");
+ mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL,
+ "Could not stat message fd", 0, 0);
+ mta_enter_state(s, MTA_READY);
+ close(imsg->fd);
+ return;
+ }
+ if (sb.st_size > (off_t)s->ext_size) {
+ log_debug("debug: mta: message too large for peer");
+ mta_flush_task(s, IMSG_MTA_DELIVERY_PERMFAIL,
+ "message too large for peer", 0, 0);
+ mta_enter_state(s, MTA_READY);
+ close(imsg->fd);
+ return;
+ }
+ }
+
+ s->datafp = fdopen(imsg->fd, "r");
+ if (s->datafp == NULL)
+ fatal("mta: fdopen");
+
+ mta_enter_state(s, MTA_MAIL);
+ return;
+
+ case IMSG_MTA_LOOKUP_HELO:
+ m_msg(&m, imsg);
+ m_get_id(&m, &reqid);
+ m_get_int(&m, &status);
+ if (status == LKA_OK)
+ m_get_string(&m, &name);
+ m_end(&m);
+
+ s = mta_tree_pop(&wait_helo, reqid);
+ if (s == NULL)
+ return;
+
+ if (status == LKA_OK) {
+ s->helo = xstrdup(name);
+ mta_connect(s);
+ } else {
+ mta_source_error(s->relay, s->route,
+ "Failed to retrieve helo string");
+ mta_free(s);
+ }
+ return;
+
+ default:
+ errx(1, "mta_session_imsg: unexpected %s imsg",
+ imsg_to_str(imsg->hdr.type));
+ }
+}
+
+static struct mta_session *
+mta_tree_pop(struct tree *wait, uint64_t reqid)
+{
+ struct mta_session *s;
+
+ s = tree_xpop(wait, reqid);
+ if (s->flags & MTA_FREE) {
+ log_debug("debug: mta: %p: zombie session", s);
+ mta_free(s);
+ return (NULL);
+ }
+ s->flags &= ~MTA_WAIT;
+
+ return (s);
+}
+
+static void
+mta_free(struct mta_session *s)
+{
+ struct mta_relay *relay;
+ struct mta_route *route;
+
+ log_debug("debug: mta: %p: session done", s);
+
+ mta_disconnected(s);
+
+ if (s->ready)
+ s->relay->nconn_ready -= 1;
+
+ if (s->flags & MTA_HANGON) {
+ log_debug("debug: mta: %p: cancelling hangon timer", s);
+ runq_cancel(hangon, s);
+ }
+
+ if (s->io)
+ io_free(s->io);
+
+ if (s->task)
+ fatalx("current task should have been deleted already");
+ if (s->datafp) {
+ fclose(s->datafp);
+ s->datalen = 0;
+ }
+ free(s->helo);
+
+ relay = s->relay;
+ route = s->route;
+ free(s->username);
+ free(s->mxname);
+ free(s);
+ stat_decrement("mta.session", 1);
+ mta_route_collect(relay, route);
+}
+
+static void
+mta_getnameinfo_cb(void *arg, int gaierrno, const char *host, const char *serv)
+{
+ struct mta_session *s = arg;
+ struct mta_host *h;
+
+ h = s->route->dst;
+ h->lastptrquery = time(NULL);
+ if (host)
+ h->ptrname = xstrdup(host);
+ waitq_run(&h->ptrname, h->ptrname);
+}
+
+static void
+mta_on_timeout(struct runq *runq, void *arg)
+{
+ struct mta_session *s = arg;
+
+ log_debug("mta: timeout for session hangon");
+
+ s->flags &= ~MTA_HANGON;
+ s->hangon++;
+
+ mta_enter_state(s, MTA_READY);
+}
+
+static void
+mta_on_ptr(void *tag, void *arg, void *data)
+{
+ struct mta_session *s = arg;
+
+ mta_connect(s);
+}
+
+static void
+mta_start(int fd, short ev, void *arg)
+{
+ struct mta_session *s = arg;
+
+ mta_connect(s);
+}
+
+static void
+mta_connect(struct mta_session *s)
+{
+ struct sockaddr_storage ss;
+ struct sockaddr *sa;
+ int portno;
+ const char *schema;
+
+ if (s->helo == NULL) {
+ if (s->relay->helotable && s->route->src->sa) {
+ m_create(p_lka, IMSG_MTA_LOOKUP_HELO, 0, 0, -1);
+ m_add_id(p_lka, s->id);
+ m_add_string(p_lka, s->relay->helotable);
+ m_add_sockaddr(p_lka, s->route->src->sa);
+ m_close(p_lka);
+ tree_xset(&wait_helo, s->id, s);
+ s->flags |= MTA_WAIT;
+ return;
+ }
+ else if (s->relay->heloname)
+ s->helo = xstrdup(s->relay->heloname);
+ else
+ s->helo = xstrdup(env->sc_hostname);
+ }
+
+ if (s->io) {
+ io_free(s->io);
+ s->io = NULL;
+ }
+
+ s->use_smtps = s->use_starttls = s->use_smtp_tls = 0;
+
+ switch (s->attempt) {
+ case 0:
+ if (s->flags & MTA_FORCE_SMTPS)
+ s->use_smtps = 1; /* smtps */
+ else if (s->flags & (MTA_FORCE_TLS|MTA_FORCE_ANYSSL))
+ s->use_starttls = 1; /* tls, tls+smtps */
+ else if (!(s->flags & MTA_FORCE_PLAIN))
+ s->use_smtp_tls = 1;
+ break;
+ case 1:
+ if (s->flags & MTA_FORCE_ANYSSL) {
+ s->use_smtps = 1; /* tls+smtps */
+ break;
+ }
+ else if (s->flags & MTA_DOWNGRADE_PLAIN) {
+ /* smtp, with tls failure */
+ break;
+ }
+ default:
+ mta_free(s);
+ return;
+ }
+ portno = s->use_smtps ? 465 : 25;
+
+ /* Override with relay-specified port */
+ if (s->relay->port)
+ portno = s->relay->port;
+
+ memmove(&ss, s->route->dst->sa, SA_LEN(s->route->dst->sa));
+ sa = (struct sockaddr *)&ss;
+
+ if (sa->sa_family == AF_INET)
+ ((struct sockaddr_in *)sa)->sin_port = htons(portno);
+ else if (sa->sa_family == AF_INET6)
+ ((struct sockaddr_in6 *)sa)->sin6_port = htons(portno);
+
+ s->attempt += 1;
+ if (s->use_smtp_tls)
+ schema = "smtp://";
+ else if (s->use_starttls)
+ schema = "smtp+tls://";
+ else if (s->use_smtps)
+ schema = "smtps://";
+ else if (s->flags & MTA_LMTP)
+ schema = "lmtp://";
+ else
+ schema = "smtp+notls://";
+
+ log_info("%016"PRIx64" mta "
+ "connecting address=%s%s:%d host=%s",
+ s->id, schema, sa_to_text(s->route->dst->sa),
+ portno, s->route->dst->ptrname);
+
+ mta_enter_state(s, MTA_INIT);
+ s->io = io_new();
+ io_set_callback(s->io, mta_io, s);
+ io_set_timeout(s->io, 300000);
+ if (io_connect(s->io, sa, s->route->src->sa) == -1) {
+ /*
+ * This error is most likely a "no route",
+ * so there is no need to try again.
+ */
+ log_debug("debug: mta: io_connect failed: %s", io_error(s->io));
+ if (errno == EADDRNOTAVAIL)
+ mta_source_error(s->relay, s->route, io_error(s->io));
+ else
+ mta_error(s, "Connection failed: %s", io_error(s->io));
+ mta_free(s);
+ }
+}
+
+static void
+mta_enter_state(struct mta_session *s, int newstate)
+{
+ struct mta_envelope *e;
+ size_t envid_sz;
+ int oldstate;
+ ssize_t q;
+ char ibuf[LINE_MAX];
+ char obuf[LINE_MAX];
+ int offset;
+ const char *srs_sender;
+
+again:
+ oldstate = s->state;
+
+ log_trace(TRACE_MTA, "mta: %p: %s -> %s", s,
+ mta_strstate(oldstate),
+ mta_strstate(newstate));
+
+ s->state = newstate;
+
+ memset(s->replybuf, 0, sizeof s->replybuf);
+
+ /* don't try this at home! */
+#define mta_enter_state(_s, _st) do { newstate = _st; goto again; } while (0)
+
+ switch (s->state) {
+ case MTA_INIT:
+ case MTA_BANNER:
+ break;
+
+ case MTA_EHLO:
+ s->ext = 0;
+ mta_send(s, "EHLO %s", s->helo);
+ mta_report_link_identify(s, "EHLO", s->helo);
+ break;
+
+ case MTA_HELO:
+ s->ext = 0;
+ mta_send(s, "HELO %s", s->helo);
+ mta_report_link_identify(s, "HELO", s->helo);
+ break;
+
+ case MTA_LHLO:
+ s->ext = 0;
+ mta_send(s, "LHLO %s", s->helo);
+ mta_report_link_identify(s, "LHLO", s->helo);
+ break;
+
+ case MTA_STARTTLS:
+ if (s->flags & MTA_DOWNGRADE_PLAIN)
+ mta_enter_state(s, MTA_AUTH);
+ if (s->flags & MTA_TLS) /* already started */
+ mta_enter_state(s, MTA_AUTH);
+ else if ((s->ext & MTA_EXT_STARTTLS) == 0) {
+ if (s->flags & MTA_FORCE_TLS || s->flags & MTA_WANT_SECURE) {
+ mta_error(s, "TLS required but not supported by remote host");
+ s->flags |= MTA_RECONN;
+ }
+ else
+ /* server doesn't support starttls, do not use it */
+ mta_enter_state(s, MTA_AUTH);
+ }
+ else
+ mta_send(s, "STARTTLS");
+ break;
+
+ case MTA_AUTH:
+ if (s->relay->secret && s->flags & MTA_TLS) {
+ if (s->ext & MTA_EXT_AUTH) {
+ if (s->ext & MTA_EXT_AUTH_PLAIN) {
+ mta_enter_state(s, MTA_AUTH_PLAIN);
+ break;
+ }
+ if (s->ext & MTA_EXT_AUTH_LOGIN) {
+ mta_enter_state(s, MTA_AUTH_LOGIN);
+ break;
+ }
+ log_debug("debug: mta: %p: no supported AUTH method on session", s);
+ mta_error(s, "no supported AUTH method");
+ }
+ else {
+ log_debug("debug: mta: %p: AUTH not advertised on session", s);
+ mta_error(s, "AUTH not advertised");
+ }
+ }
+ else if (s->relay->secret) {
+ log_debug("debug: mta: %p: not using AUTH on non-TLS "
+ "session", s);
+ mta_error(s, "Refuse to AUTH over unsecure channel");
+ mta_connect(s);
+ } else {
+ mta_enter_state(s, MTA_READY);
+ }
+ break;
+
+ case MTA_AUTH_PLAIN:
+ memset(ibuf, 0, sizeof ibuf);
+ if (base64_decode(s->relay->secret, (unsigned char *)ibuf,
+ sizeof(ibuf)-1) == -1) {
+ log_debug("debug: mta: %p: credentials too large on session", s);
+ mta_error(s, "Credentials too large");
+ break;
+ }
+ s->username = xstrdup(ibuf+1);
+ mta_send(s, "AUTH PLAIN %s", s->relay->secret);
+ break;
+
+ case MTA_AUTH_LOGIN:
+ mta_send(s, "AUTH LOGIN");
+ break;
+
+ case MTA_AUTH_LOGIN_USER:
+ memset(ibuf, 0, sizeof ibuf);
+ if (base64_decode(s->relay->secret, (unsigned char *)ibuf,
+ sizeof(ibuf)-1) == -1) {
+ log_debug("debug: mta: %p: credentials too large on session", s);
+ mta_error(s, "Credentials too large");
+ break;
+ }
+ s->username = xstrdup(ibuf+1);
+
+ memset(obuf, 0, sizeof obuf);
+ base64_encode((unsigned char *)ibuf + 1, strlen(ibuf + 1), obuf, sizeof obuf);
+ mta_send(s, "%s", obuf);
+
+ memset(ibuf, 0, sizeof ibuf);
+ memset(obuf, 0, sizeof obuf);
+ break;
+
+ case MTA_AUTH_LOGIN_PASS:
+ memset(ibuf, 0, sizeof ibuf);
+ if (base64_decode(s->relay->secret, (unsigned char *)ibuf,
+ sizeof(ibuf)-1) == -1) {
+ log_debug("debug: mta: %p: credentials too large on session", s);
+ mta_error(s, "Credentials too large");
+ break;
+ }
+
+ offset = strlen(ibuf+1)+2;
+ memset(obuf, 0, sizeof obuf);
+ base64_encode((unsigned char *)ibuf + offset, strlen(ibuf + offset), obuf, sizeof obuf);
+ mta_send(s, "%s", obuf);
+
+ memset(ibuf, 0, sizeof ibuf);
+ memset(obuf, 0, sizeof obuf);
+ break;
+
+ case MTA_READY:
+ /* Ready to send a new mail */
+ if (s->ready == 0) {
+ s->ready = 1;
+ s->relay->nconn_ready += 1;
+ mta_route_ok(s->relay, s->route);
+ }
+
+ if (s->msgtried >= MAX_TRYBEFOREDISABLE) {
+ log_info("%016"PRIx64" mta host-rejects-all-mails",
+ s->id);
+ mta_route_down(s->relay, s->route);
+ mta_enter_state(s, MTA_QUIT);
+ break;
+ }
+
+ if (s->msgcount >= s->relay->limits->max_mail_per_session) {
+ log_debug("debug: mta: "
+ "%p: cannot send more message to relay %s", s,
+ mta_relay_to_text(s->relay));
+ mta_enter_state(s, MTA_QUIT);
+ break;
+ }
+
+ /*
+ * When downgrading from opportunistic TLS, clear flag and
+ * possibly reuse the same task (forbidden in other cases).
+ */
+ if (s->flags & MTA_DOWNGRADE_PLAIN)
+ s->flags &= ~MTA_DOWNGRADE_PLAIN;
+ else if (s->task)
+ fatalx("task should be NULL at this point");
+
+ if (s->task == NULL)
+ s->task = mta_route_next_task(s->relay, s->route);
+ if (s->task == NULL) {
+ log_debug("debug: mta: %p: no task for relay %s",
+ s, mta_relay_to_text(s->relay));
+
+ if (s->relay->nconn > 1 ||
+ s->hangon >= s->relay->limits->sessdelay_keepalive) {
+ mta_enter_state(s, MTA_QUIT);
+ break;
+ }
+
+ log_debug("mta: debug: last connection: hanging on for %llds",
+ (long long)(s->relay->limits->sessdelay_keepalive -
+ s->hangon));
+ s->flags |= MTA_HANGON;
+ runq_schedule(hangon, 1, s);
+ break;
+ }
+
+ log_debug("debug: mta: %p: handling next task for relay %s", s,
+ mta_relay_to_text(s->relay));
+
+ stat_increment("mta.task.running", 1);
+
+ m_create(p_queue, IMSG_MTA_OPEN_MESSAGE, 0, 0, -1);
+ m_add_id(p_queue, s->id);
+ m_add_msgid(p_queue, s->task->msgid);
+ m_close(p_queue);
+
+ tree_xset(&wait_fd, s->id, s);
+ s->flags |= MTA_WAIT;
+ break;
+
+ case MTA_MAIL:
+ s->currevp = TAILQ_FIRST(&s->task->envelopes);
+
+ e = s->currevp;
+ s->hangon = 0;
+ s->msgtried++;
+ envid_sz = strlen(e->dsn_envid);
+
+ /* SRS-encode if requested for the relay action, AND we're not
+ * bouncing, AND we have an RCPT which means we are forwarded,
+ * AND the RCPT has a '@' just for sanity check (will always).
+ */
+ if (env->sc_srs_key != NULL &&
+ s->relay->srs &&
+ strchr(s->task->sender, '@') &&
+ e->rcpt &&
+ strchr(e->rcpt, '@')) {
+ /* encode and replace task sender with new SRS-sender */
+ srs_sender = srs_encode(s->task->sender,
+ strchr(e->rcpt, '@') + 1);
+ if (srs_sender) {
+ free(s->task->sender);
+ s->task->sender = xstrdup(srs_sender);
+ }
+ }
+
+ if (s->ext & MTA_EXT_DSN) {
+ mta_send(s, "MAIL FROM:<%s>%s%s%s%s",
+ s->task->sender,
+ e->dsn_ret ? " RET=" : "",
+ e->dsn_ret ? dsn_strret(e->dsn_ret) : "",
+ envid_sz ? " ENVID=" : "",
+ envid_sz ? e->dsn_envid : "");
+ } else
+ mta_send(s, "MAIL FROM:<%s>", s->task->sender);
+ break;
+
+ case MTA_RCPT:
+ if (s->currevp == NULL)
+ s->currevp = TAILQ_FIRST(&s->task->envelopes);
+
+ e = s->currevp;
+ if (s->ext & MTA_EXT_DSN) {
+ mta_send(s, "RCPT TO:<%s>%s%s%s%s",
+ e->dest,
+ e->dsn_notify ? " NOTIFY=" : "",
+ e->dsn_notify ? dsn_strnotify(e->dsn_notify) : "",
+ e->dsn_orcpt ? " ORCPT=rfc822;" : "",
+ e->dsn_orcpt ? e->dsn_orcpt : "");
+ } else
+ mta_send(s, "RCPT TO:<%s>", e->dest);
+
+ mta_report_tx_envelope(s, s->task->msgid, e->id);
+ s->rcptcount++;
+ break;
+
+ case MTA_DATA:
+ fseek(s->datafp, 0, SEEK_SET);
+ mta_send(s, "DATA");
+ break;
+
+ case MTA_BODY:
+ if (s->datafp == NULL) {
+ log_trace(TRACE_MTA, "mta: %p: end-of-file", s);
+ mta_enter_state(s, MTA_EOM);
+ break;
+ }
+
+ if ((q = mta_queue_data(s)) == -1) {
+ s->flags |= MTA_FREE;
+ break;
+ }
+ if (q == 0) {
+ mta_enter_state(s, MTA_BODY);
+ break;
+ }
+
+ log_trace(TRACE_MTA, "mta: %p: >>> [...%zd bytes...]", s, q);
+ break;
+
+ case MTA_EOM:
+ mta_send(s, ".");
+ break;
+
+ case MTA_LMTP_EOM:
+ /* LMTP reports status of each delivery, so enable read */
+ io_set_read(s->io);
+ break;
+
+ case MTA_RSET:
+ if (s->datafp) {
+ fclose(s->datafp);
+ s->datafp = NULL;
+ s->datalen = 0;
+ }
+ mta_send(s, "RSET");
+ break;
+
+ case MTA_QUIT:
+ mta_send(s, "QUIT");
+ break;
+
+ default:
+ fatalx("mta_enter_state: unknown state");
+ }
+#undef mta_enter_state
+}
+
+/*
+ * Handle a response to an SMTP command
+ */
+static void
+mta_response(struct mta_session *s, char *line)
+{
+ struct mta_envelope *e;
+ struct sockaddr_storage ss;
+ struct sockaddr *sa;
+ const char *domain;
+ char *pbuf;
+ socklen_t sa_len;
+ char buf[LINE_MAX];
+ int delivery;
+
+ switch (s->state) {
+
+ case MTA_BANNER:
+ if (line[0] != '2') {
+ mta_error(s, "BANNER rejected: %s", line);
+ s->flags |= MTA_FREE;
+ return;
+ }
+
+ pbuf = "";
+ if (strlen(line) > 4) {
+ (void)strlcpy(buf, line + 4, sizeof buf);
+ if ((pbuf = strchr(buf, ' ')))
+ *pbuf = '\0';
+ pbuf = valid_domainpart(buf) ? buf : "";
+ }
+ mta_report_link_greeting(s, pbuf);
+
+ if (s->flags & MTA_LMTP)
+ mta_enter_state(s, MTA_LHLO);
+ else
+ mta_enter_state(s, MTA_EHLO);
+ break;
+
+ case MTA_EHLO:
+ if (line[0] != '2') {
+ /* rejected at ehlo state */
+ if ((s->relay->flags & RELAY_AUTH) ||
+ (s->flags & MTA_WANT_SECURE)) {
+ mta_error(s, "EHLO rejected: %s", line);
+ s->flags |= MTA_FREE;
+ return;
+ }
+ mta_enter_state(s, MTA_HELO);
+ return;
+ }
+ if (!(s->flags & MTA_FORCE_PLAIN))
+ mta_enter_state(s, MTA_STARTTLS);
+ else
+ mta_enter_state(s, MTA_READY);
+ break;
+
+ case MTA_HELO:
+ if (line[0] != '2') {
+ mta_error(s, "HELO rejected: %s", line);
+ s->flags |= MTA_FREE;
+ return;
+ }
+ mta_enter_state(s, MTA_READY);
+ break;
+
+ case MTA_LHLO:
+ if (line[0] != '2') {
+ mta_error(s, "LHLO rejected: %s", line);
+ s->flags |= MTA_FREE;
+ return;
+ }
+ mta_enter_state(s, MTA_READY);
+ break;
+
+ case MTA_STARTTLS:
+ if (line[0] != '2') {
+ if (!(s->flags & MTA_WANT_SECURE)) {
+ mta_enter_state(s, MTA_AUTH);
+ return;
+ }
+ /* XXX mark that the MX doesn't support STARTTLS */
+ mta_error(s, "STARTTLS rejected: %s", line);
+ s->flags |= MTA_FREE;
+ return;
+ }
+
+ mta_cert_init(s);
+ break;
+
+ case MTA_AUTH_PLAIN:
+ if (line[0] != '2') {
+ mta_error(s, "AUTH rejected: %s", line);
+ mta_report_link_auth(s, s->username, "fail");
+ s->flags |= MTA_FREE;
+ return;
+ }
+ mta_report_link_auth(s, s->username, "pass");
+ mta_enter_state(s, MTA_READY);
+ break;
+
+ case MTA_AUTH_LOGIN:
+ if (strncmp(line, "334 ", 4) != 0) {
+ mta_error(s, "AUTH rejected: %s", line);
+ mta_report_link_auth(s, s->username, "fail");
+ s->flags |= MTA_FREE;
+ return;
+ }
+ mta_enter_state(s, MTA_AUTH_LOGIN_USER);
+ break;
+
+ case MTA_AUTH_LOGIN_USER:
+ if (strncmp(line, "334 ", 4) != 0) {
+ mta_error(s, "AUTH rejected: %s", line);
+ mta_report_link_auth(s, s->username, "fail");
+ s->flags |= MTA_FREE;
+ return;
+ }
+ mta_enter_state(s, MTA_AUTH_LOGIN_PASS);
+ break;
+
+ case MTA_AUTH_LOGIN_PASS:
+ if (line[0] != '2') {
+ mta_error(s, "AUTH rejected: %s", line);
+ mta_report_link_auth(s, s->username, "fail");
+ s->flags |= MTA_FREE;
+ return;
+ }
+ mta_report_link_auth(s, s->username, "pass");
+ mta_enter_state(s, MTA_READY);
+ break;
+
+ case MTA_MAIL:
+ if (line[0] != '2') {
+ if (line[0] == '5')
+ delivery = IMSG_MTA_DELIVERY_PERMFAIL;
+ else
+ delivery = IMSG_MTA_DELIVERY_TEMPFAIL;
+
+ mta_flush_task(s, delivery, line, 0, 0);
+ mta_enter_state(s, MTA_RSET);
+ return;
+ }
+ mta_report_tx_begin(s, s->task->msgid);
+ mta_report_tx_mail(s, s->task->msgid, s->task->sender, 1);
+ mta_enter_state(s, MTA_RCPT);
+ break;
+
+ case MTA_RCPT:
+ e = s->currevp;
+
+ /* remove envelope from hosttat cache if there */
+ if ((domain = strchr(e->dest, '@')) != NULL) {
+ domain++;
+ mta_hoststat_uncache(domain, e->id);
+ }
+
+ s->currevp = TAILQ_NEXT(s->currevp, entry);
+ if (line[0] == '2') {
+ s->failures = 0;
+ /*
+ * this host is up, reschedule envelopes that
+ * were cached for reschedule.
+ */
+ if (domain)
+ mta_hoststat_reschedule(domain);
+ }
+ else {
+ mta_report_tx_rollback(s, s->task->msgid);
+ mta_report_tx_reset(s, s->task->msgid);
+ if (line[0] == '5')
+ delivery = IMSG_MTA_DELIVERY_PERMFAIL;
+ else
+ delivery = IMSG_MTA_DELIVERY_TEMPFAIL;
+ s->failures++;
+
+ /* remove failed envelope from task list */
+ TAILQ_REMOVE(&s->task->envelopes, e, entry);
+ stat_decrement("mta.envelope", 1);
+
+ /* log right away */
+ (void)snprintf(buf, sizeof(buf), "%s",
+ mta_host_to_text(s->route->dst));
+
+ e->session = s->id;
+ /* XXX */
+ /*
+ * getsockname() can only fail with ENOBUFS here
+ * best effort, don't log source ...
+ */
+ sa_len = sizeof(ss);
+ sa = (struct sockaddr *)&ss;
+ if (getsockname(io_fileno(s->io), sa, &sa_len) == -1)
+ mta_delivery_log(e, NULL, buf, delivery, line);
+ else
+ mta_delivery_log(e, sa_to_text(sa),
+ buf, delivery, line);
+
+ if (domain)
+ mta_hoststat_update(domain, e->status);
+ mta_delivery_notify(e);
+
+ if (s->relay->limits->max_failures_per_session &&
+ s->failures == s->relay->limits->max_failures_per_session) {
+ mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL,
+ "Too many consecutive errors, closing connection", 0, 1);
+ mta_enter_state(s, MTA_QUIT);
+ break;
+ }
+
+ /*
+ * if no more envelopes, flush failed queue
+ */
+ if (TAILQ_EMPTY(&s->task->envelopes)) {
+ mta_flush_task(s, IMSG_MTA_DELIVERY_OK,
+ "No envelope", 0, 0);
+ mta_enter_state(s, MTA_RSET);
+ break;
+ }
+ }
+
+ switch (line[0]) {
+ case '2':
+ mta_report_tx_rcpt(s,
+ s->task->msgid, e->dest, 1);
+ break;
+ case '4':
+ mta_report_tx_rcpt(s,
+ s->task->msgid, e->dest, -1);
+ break;
+ case '5':
+ mta_report_tx_rcpt(s,
+ s->task->msgid, e->dest, 0);
+ break;
+ }
+
+ if (s->currevp == NULL)
+ mta_enter_state(s, MTA_DATA);
+ else
+ mta_enter_state(s, MTA_RCPT);
+ break;
+
+ case MTA_DATA:
+ if (line[0] == '2' || line[0] == '3') {
+ mta_report_tx_data(s, s->task->msgid, 1);
+ mta_enter_state(s, MTA_BODY);
+ break;
+ }
+
+ if (line[0] == '5')
+ delivery = IMSG_MTA_DELIVERY_PERMFAIL;
+ else
+ delivery = IMSG_MTA_DELIVERY_TEMPFAIL;
+ mta_report_tx_data(s, s->task->msgid,
+ delivery == IMSG_MTA_DELIVERY_TEMPFAIL ? -1 : 0);
+ mta_report_tx_rollback(s, s->task->msgid);
+ mta_report_tx_reset(s, s->task->msgid);
+ mta_flush_task(s, delivery, line, 0, 0);
+ mta_enter_state(s, MTA_RSET);
+ break;
+
+ case MTA_LMTP_EOM:
+ case MTA_EOM:
+ if (line[0] == '2') {
+ delivery = IMSG_MTA_DELIVERY_OK;
+ s->msgtried = 0;
+ s->msgcount++;
+ }
+ else if (line[0] == '5')
+ delivery = IMSG_MTA_DELIVERY_PERMFAIL;
+ else
+ delivery = IMSG_MTA_DELIVERY_TEMPFAIL;
+ if (delivery != IMSG_MTA_DELIVERY_OK) {
+ mta_report_tx_rollback(s, s->task->msgid);
+ mta_report_tx_reset(s, s->task->msgid);
+ }
+ else {
+ mta_report_tx_commit(s, s->task->msgid, s->datalen);
+ mta_report_tx_reset(s, s->task->msgid);
+ }
+ mta_flush_task(s, delivery, line, (s->flags & MTA_LMTP) ? 1 : 0, 0);
+ if (s->task) {
+ s->rcptcount--;
+ mta_enter_state(s, MTA_LMTP_EOM);
+ } else {
+ s->rcptcount = 0;
+ if (s->relay->limits->sessdelay_transaction) {
+ log_debug("debug: mta: waiting for %llds before next transaction",
+ (long long int)s->relay->limits->sessdelay_transaction);
+ s->hangon = s->relay->limits->sessdelay_transaction -1;
+ s->flags |= MTA_HANGON;
+ runq_schedule(hangon,
+ s->relay->limits->sessdelay_transaction, s);
+ }
+ else
+ mta_enter_state(s, MTA_READY);
+ }
+ break;
+
+ case MTA_RSET:
+ s->rcptcount = 0;
+
+ if (s->task) {
+ mta_report_tx_rollback(s, s->task->msgid);
+ mta_report_tx_reset(s, s->task->msgid);
+ }
+ if (s->relay->limits->sessdelay_transaction) {
+ log_debug("debug: mta: waiting for %llds after reset",
+ (long long int)s->relay->limits->sessdelay_transaction);
+ s->hangon = s->relay->limits->sessdelay_transaction -1;
+ s->flags |= MTA_HANGON;
+ runq_schedule(hangon,
+ s->relay->limits->sessdelay_transaction, s);
+ }
+ else
+ mta_enter_state(s, MTA_READY);
+ break;
+
+ default:
+ fatalx("mta_response() bad state");
+ }
+}
+
+static void
+mta_io(struct io *io, int evt, void *arg)
+{
+ struct mta_session *s = arg;
+ char *line, *msg, *p;
+ size_t len;
+ const char *error;
+ int cont;
+
+ log_trace(TRACE_IO, "mta: %p: %s %s", s, io_strevent(evt),
+ io_strio(io));
+
+ switch (evt) {
+
+ case IO_CONNECTED:
+ mta_connected(s);
+
+ if (s->use_smtps) {
+ io_set_write(io);
+ mta_cert_init(s);
+ }
+ else {
+ mta_enter_state(s, MTA_BANNER);
+ io_set_read(io);
+ }
+ break;
+
+ case IO_TLSREADY:
+ log_info("%016"PRIx64" mta tls ciphers=%s",
+ s->id, ssl_to_text(io_tls(s->io)));
+ s->flags |= MTA_TLS;
+
+ mta_report_link_tls(s,
+ ssl_to_text(io_tls(s->io)));
+
+ mta_cert_verify(s);
+ break;
+
+ case IO_DATAIN:
+ nextline:
+ line = io_getline(s->io, &len);
+ if (line == NULL) {
+ if (io_datalen(s->io) >= LINE_MAX) {
+ mta_error(s, "Input too long");
+ mta_free(s);
+ }
+ return;
+ }
+
+ /* Strip trailing '\r' */
+ if (len && line[len - 1] == '\r')
+ line[--len] = '\0';
+
+ log_trace(TRACE_MTA, "mta: %p: <<< %s", s, line);
+ mta_report_protocol_server(s, line);
+
+ if ((error = parse_smtp_response(line, len, &msg, &cont))) {
+ mta_error(s, "Bad response: %s", error);
+ mta_free(s);
+ return;
+ }
+
+ /* read extensions */
+ if (s->state == MTA_EHLO) {
+ if (strcmp(msg, "STARTTLS") == 0)
+ s->ext |= MTA_EXT_STARTTLS;
+ else if (strncmp(msg, "AUTH ", 5) == 0) {
+ s->ext |= MTA_EXT_AUTH;
+ if ((p = strstr(msg, " PLAIN")) &&
+ (*(p+6) == '\0' || *(p+6) == ' '))
+ s->ext |= MTA_EXT_AUTH_PLAIN;
+ if ((p = strstr(msg, " LOGIN")) &&
+ (*(p+6) == '\0' || *(p+6) == ' '))
+ s->ext |= MTA_EXT_AUTH_LOGIN;
+ }
+ else if (strcmp(msg, "PIPELINING") == 0)
+ s->ext |= MTA_EXT_PIPELINING;
+ else if (strcmp(msg, "DSN") == 0)
+ s->ext |= MTA_EXT_DSN;
+ else if (strncmp(msg, "SIZE ", 5) == 0) {
+ s->ext_size = strtonum(msg+5, 0, UINT32_MAX, &error);
+ if (error == NULL)
+ s->ext |= MTA_EXT_SIZE;
+ }
+ }
+
+ /* continuation reply, we parse out the repeating statuses and ESC */
+ if (cont) {
+ if (s->replybuf[0] == '\0')
+ (void)strlcat(s->replybuf, line, sizeof s->replybuf);
+ else if (len > 4) {
+ p = line + 4;
+ if (isdigit((unsigned char)p[0]) && p[1] == '.' &&
+ isdigit((unsigned char)p[2]) && p[3] == '.' &&
+ isdigit((unsigned char)p[4]) && isspace((unsigned char)p[5]))
+ p += 5;
+ (void)strlcat(s->replybuf, p, sizeof s->replybuf);
+ }
+ goto nextline;
+ }
+
+ /* last line of a reply, check if we're on a continuation to parse out status and ESC.
+ * if we overflow reply buffer or are not on continuation, log entire last line.
+ */
+ if (s->replybuf[0] == '\0')
+ (void)strlcat(s->replybuf, line, sizeof s->replybuf);
+ else if (len > 4) {
+ p = line + 4;
+ if (isdigit((unsigned char)p[0]) && p[1] == '.' &&
+ isdigit((unsigned char)p[2]) && p[3] == '.' &&
+ isdigit((unsigned char)p[4]) && isspace((unsigned char)p[5]))
+ p += 5;
+ if (strlcat(s->replybuf, p, sizeof s->replybuf) >= sizeof s->replybuf)
+ (void)strlcpy(s->replybuf, line, sizeof s->replybuf);
+ }
+
+ if (s->state == MTA_QUIT) {
+ log_info("%016"PRIx64" mta disconnected reason=quit messages=%zu",
+ s->id, s->msgcount);
+ mta_free(s);
+ return;
+ }
+ io_set_write(io);
+ mta_response(s, s->replybuf);
+ if (s->flags & MTA_FREE) {
+ mta_free(s);
+ return;
+ }
+ if (s->flags & MTA_RECONN) {
+ s->flags &= ~MTA_RECONN;
+ mta_connect(s);
+ return;
+ }
+
+ if (io_datalen(s->io)) {
+ log_debug("debug: mta: remaining data in input buffer");
+ mta_error(s, "Remote host sent too much data");
+ if (s->flags & MTA_WAIT)
+ s->flags |= MTA_FREE;
+ else
+ mta_free(s);
+ }
+ break;
+
+ case IO_LOWAT:
+ if (s->state == MTA_BODY) {
+ mta_enter_state(s, MTA_BODY);
+ if (s->flags & MTA_FREE) {
+ mta_free(s);
+ return;
+ }
+ }
+
+ if (io_queued(s->io) == 0)
+ io_set_read(io);
+ break;
+
+ case IO_TIMEOUT:
+ log_debug("debug: mta: %p: connection timeout", s);
+ mta_error(s, "Connection timeout");
+ mta_report_timeout(s);
+ if (!s->ready)
+ mta_connect(s);
+ else
+ mta_free(s);
+ break;
+
+ case IO_ERROR:
+ case IO_TLSERROR:
+ log_debug("debug: mta: %p: IO error: %s", s, io_error(io));
+
+ if (s->state == MTA_STARTTLS && s->use_smtp_tls) {
+ /* error in non-strict SSL negotiation, downgrade to plain */
+ log_info("smtp-out: Error on session %016"PRIx64
+ ": opportunistic TLS failed, "
+ "downgrading to plain", s->id);
+ s->flags &= ~MTA_TLS;
+ s->flags |= MTA_DOWNGRADE_PLAIN;
+ mta_connect(s);
+ break;
+ }
+
+ mta_error(s, "IO Error: %s", io_error(io));
+ mta_free(s);
+ break;
+
+ case IO_DISCONNECTED:
+ log_debug("debug: mta: %p: disconnected in state %s",
+ s, mta_strstate(s->state));
+ mta_error(s, "Connection closed unexpectedly");
+ if (!s->ready)
+ mta_connect(s);
+ else
+ mta_free(s);
+ break;
+
+ default:
+ fatalx("mta_io() bad event");
+ }
+}
+
+static void
+mta_send(struct mta_session *s, char *fmt, ...)
+{
+ va_list ap;
+ char *p;
+ int len;
+
+ va_start(ap, fmt);
+ if ((len = vasprintf(&p, fmt, ap)) == -1)
+ fatal("mta: vasprintf");
+ va_end(ap);
+
+ log_trace(TRACE_MTA, "mta: %p: >>> %s", s, p);
+
+ if (strncasecmp(p, "AUTH PLAIN ", 11) == 0)
+ mta_report_protocol_client(s, "AUTH PLAIN ********");
+ else if (s->state == MTA_AUTH_LOGIN_USER || s->state == MTA_AUTH_LOGIN_PASS)
+ mta_report_protocol_client(s, "********");
+ else
+ mta_report_protocol_client(s, p);
+
+ io_xprintf(s->io, "%s\r\n", p);
+
+ free(p);
+}
+
+/*
+ * Queue some data into the input buffer
+ */
+static ssize_t
+mta_queue_data(struct mta_session *s)
+{
+ char *ln = NULL;
+ size_t sz = 0, q;
+ ssize_t len;
+
+ q = io_queued(s->io);
+
+ while (io_queued(s->io) < MTA_HIWAT) {
+ if ((len = getline(&ln, &sz, s->datafp)) == -1)
+ break;
+ if (ln[len - 1] == '\n')
+ ln[len - 1] = '\0';
+ s->datalen += io_xprintf(s->io, "%s%s\r\n", *ln == '.' ? "." : "", ln);
+ }
+
+ free(ln);
+ if (ferror(s->datafp)) {
+ mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL,
+ "Error reading content file", 0, 0);
+ return (-1);
+ }
+
+ if (feof(s->datafp)) {
+ fclose(s->datafp);
+ s->datafp = NULL;
+ }
+
+ return (io_queued(s->io) - q);
+}
+
+static void
+mta_flush_task(struct mta_session *s, int delivery, const char *error, size_t count,
+ int cache)
+{
+ struct mta_envelope *e;
+ char relay[LINE_MAX];
+ size_t n;
+ struct sockaddr_storage ss;
+ struct sockaddr *sa;
+ socklen_t sa_len;
+ const char *domain;
+
+ (void)snprintf(relay, sizeof relay, "%s", mta_host_to_text(s->route->dst));
+ n = 0;
+ while ((e = TAILQ_FIRST(&s->task->envelopes))) {
+
+ if (count && n == count) {
+ stat_decrement("mta.envelope", n);
+ return;
+ }
+
+ TAILQ_REMOVE(&s->task->envelopes, e, entry);
+
+ /* we're about to log, associate session to envelope */
+ e->session = s->id;
+ e->ext = s->ext;
+
+ /* XXX */
+ /*
+ * getsockname() can only fail with ENOBUFS here
+ * best effort, don't log source ...
+ */
+ sa = (struct sockaddr *)&ss;
+ sa_len = sizeof(ss);
+ if (getsockname(io_fileno(s->io), sa, &sa_len) == -1)
+ mta_delivery_log(e, NULL, relay, delivery, error);
+ else
+ mta_delivery_log(e, sa_to_text(sa),
+ relay, delivery, error);
+
+ mta_delivery_notify(e);
+
+ domain = strchr(e->dest, '@');
+ if (domain) {
+ domain++;
+ mta_hoststat_update(domain, error);
+ if (cache)
+ mta_hoststat_cache(domain, e->id);
+ }
+
+ n++;
+ }
+
+ free(s->task->sender);
+ free(s->task);
+ s->task = NULL;
+
+ if (s->datafp) {
+ fclose(s->datafp);
+ s->datafp = NULL;
+ }
+
+ stat_decrement("mta.envelope", n);
+ stat_decrement("mta.task.running", 1);
+ stat_decrement("mta.task", 1);
+}
+
+static void
+mta_error(struct mta_session *s, const char *fmt, ...)
+{
+ va_list ap;
+ char *error;
+ int len;
+
+ va_start(ap, fmt);
+ if ((len = vasprintf(&error, fmt, ap)) == -1)
+ fatal("mta: vasprintf");
+ va_end(ap);
+
+ if (s->msgcount)
+ log_info("smtp-out: Error on session %016"PRIx64
+ " after %zu message%s sent: %s", s->id, s->msgcount,
+ (s->msgcount > 1) ? "s" : "", error);
+ else
+ log_info("%016"PRIx64" mta error reason=%s",
+ s->id, error);
+
+ /*
+ * If not connected yet, and the error is not local, just ignore it
+ * and try to reconnect.
+ */
+ if (s->state == MTA_INIT &&
+ (errno == ETIMEDOUT || errno == ECONNREFUSED)) {
+ log_debug("debug: mta: not reporting route error yet");
+ free(error);
+ return;
+ }
+
+ mta_route_error(s->relay, s->route);
+
+ if (s->task)
+ mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, error, 0, 0);
+
+ free(error);
+}
+
+static void
+mta_cert_init(struct mta_session *s)
+{
+ const char *name;
+ int fallback;
+
+ if (s->relay->pki_name) {
+ name = s->relay->pki_name;
+ fallback = 0;
+ }
+ else {
+ name = s->helo;
+ fallback = 1;
+ }
+
+ if (cert_init(name, fallback, mta_cert_init_cb, s)) {
+ tree_xset(&wait_tls_init, s->id, s);
+ s->flags |= MTA_WAIT;
+ }
+}
+
+static void
+mta_cert_init_cb(void *arg, int status, const char *name, const void *cert,
+ size_t cert_len)
+{
+ struct mta_session *s = arg;
+ void *ssl;
+ char *xname = NULL, *xcert = NULL;
+
+ if (s->flags & MTA_WAIT)
+ mta_tree_pop(&wait_tls_init, s->id);
+
+ if (status == CA_FAIL && s->relay->pki_name) {
+ log_info("%016"PRIx64" mta closing reason=ca-failure", s->id);
+ mta_free(s);
+ return;
+ }
+
+ if (name)
+ xname = xstrdup(name);
+ if (cert)
+ xcert = xmemdup(cert, cert_len);
+ ssl = ssl_mta_init(xname, xcert, cert_len, env->sc_tls_ciphers);
+ free(xname);
+ free(xcert);
+ if (ssl == NULL)
+ fatal("mta: ssl_mta_init");
+ io_start_tls(s->io, ssl);
+}
+
+static void
+mta_cert_verify(struct mta_session *s)
+{
+ const char *name;
+ int fallback;
+
+ if (s->relay->ca_name) {
+ name = s->relay->ca_name;
+ fallback = 0;
+ }
+ else {
+ name = s->helo;
+ fallback = 1;
+ }
+
+ if (cert_verify(io_tls(s->io), name, fallback, mta_cert_verify_cb, s)) {
+ tree_xset(&wait_tls_verify, s->id, s);
+ io_pause(s->io, IO_IN);
+ s->flags |= MTA_WAIT;
+ }
+}
+
+static void
+mta_cert_verify_cb(void *arg, int status)
+{
+ struct mta_session *s = arg;
+ int match, resume = 0;
+ X509 *cert;
+
+ if (s->flags & MTA_WAIT) {
+ mta_tree_pop(&wait_tls_verify, s->id);
+ resume = 1;
+ }
+
+ if (status == CERT_OK) {
+ cert = SSL_get_peer_certificate(io_tls(s->io));
+ if (!cert)
+ status = CERT_NOCERT;
+ else {
+ match = 0;
+ (void)ssl_check_name(cert, s->mxname, &match);
+ X509_free(cert);
+ if (!match) {
+ log_info("%016"PRIx64" mta "
+ "ssl_check_name: no match for '%s' in cert",
+ s->id, s->mxname);
+ status = CERT_INVALID;
+ }
+ }
+ }
+
+ if (status == CERT_OK)
+ s->flags |= MTA_TLS_VERIFIED;
+ else if (s->relay->flags & RELAY_TLS_VERIFY) {
+ errno = 0;
+ mta_error(s, "SSL certificate check failed");
+ mta_free(s);
+ return;
+ }
+
+ mta_tls_verified(s);
+ if (resume)
+ io_resume(s->io, IO_IN);
+}
+
+static void
+mta_tls_verified(struct mta_session *s)
+{
+ X509 *x;
+
+ x = SSL_get_peer_certificate(io_tls(s->io));
+ if (x) {
+ log_info("%016"PRIx64" mta "
+ "server-cert-check result=\"%s\"",
+ s->id,
+ (s->flags & MTA_TLS_VERIFIED) ? "success" : "failure");
+ X509_free(x);
+ }
+
+ if (s->use_smtps) {
+ mta_enter_state(s, MTA_BANNER);
+ io_set_read(s->io);
+ }
+ else
+ mta_enter_state(s, MTA_EHLO);
+}
+
+static const char *
+dsn_strret(enum dsn_ret ret)
+{
+ if (ret == DSN_RETHDRS)
+ return "HDRS";
+ else if (ret == DSN_RETFULL)
+ return "FULL";
+ else {
+ log_debug("mta: invalid ret %d", ret);
+ return "???";
+ }
+}
+
+static const char *
+dsn_strnotify(uint8_t arg)
+{
+ static char buf[32];
+ size_t sz;
+
+ buf[0] = '\0';
+ if (arg & DSN_SUCCESS)
+ (void)strlcat(buf, "SUCCESS,", sizeof(buf));
+
+ if (arg & DSN_FAILURE)
+ (void)strlcat(buf, "FAILURE,", sizeof(buf));
+
+ if (arg & DSN_DELAY)
+ (void)strlcat(buf, "DELAY,", sizeof(buf));
+
+ if (arg & DSN_NEVER)
+ (void)strlcat(buf, "NEVER,", sizeof(buf));
+
+ /* trim trailing comma */
+ sz = strlen(buf);
+ if (sz)
+ buf[sz - 1] = '\0';
+
+ return (buf);
+}
+
+#define CASE(x) case x : return #x
+
+static const char *
+mta_strstate(int state)
+{
+ switch (state) {
+ CASE(MTA_INIT);
+ CASE(MTA_BANNER);
+ CASE(MTA_EHLO);
+ CASE(MTA_HELO);
+ CASE(MTA_STARTTLS);
+ CASE(MTA_AUTH);
+ CASE(MTA_AUTH_PLAIN);
+ CASE(MTA_AUTH_LOGIN);
+ CASE(MTA_AUTH_LOGIN_USER);
+ CASE(MTA_AUTH_LOGIN_PASS);
+ CASE(MTA_READY);
+ CASE(MTA_MAIL);
+ CASE(MTA_RCPT);
+ CASE(MTA_DATA);
+ CASE(MTA_BODY);
+ CASE(MTA_EOM);
+ CASE(MTA_LMTP_EOM);
+ CASE(MTA_RSET);
+ CASE(MTA_QUIT);
+ default:
+ return "MTA_???";
+ }
+}
+
+static void
+mta_filter_begin(struct mta_session *s)
+{
+ if (!SESSION_FILTERED(s))
+ return;
+
+ m_create(p_lka, IMSG_FILTER_SMTP_BEGIN, 0, 0, -1);
+ m_add_id(p_lka, s->id);
+ m_add_string(p_lka, s->relay->dispatcher->u.remote.filtername);
+ m_close(p_lka);
+}
+
+static void
+mta_filter_end(struct mta_session *s)
+{
+ if (!SESSION_FILTERED(s))
+ return;
+
+ m_create(p_lka, IMSG_FILTER_SMTP_END, 0, 0, -1);
+ m_add_id(p_lka, s->id);
+ m_close(p_lka);
+}
+
+static void
+mta_connected(struct mta_session *s)
+{
+ struct sockaddr_storage sa_src;
+ struct sockaddr_storage sa_dest;
+ int sa_len;
+
+ log_info("%016"PRIx64" mta connected", s->id);
+
+ sa_len = sizeof sa_src;
+ if (getsockname(io_fileno(s->io),
+ (struct sockaddr *)&sa_src, &sa_len) == -1)
+ bzero(&sa_src, sizeof sa_src);
+ sa_len = sizeof sa_dest;
+ if (getpeername(io_fileno(s->io),
+ (struct sockaddr *)&sa_dest, &sa_len) == -1)
+ bzero(&sa_dest, sizeof sa_dest);
+
+ mta_report_link_connect(s,
+ s->route->dst->ptrname, 1,
+ &sa_src,
+ &sa_dest);
+}
+
+static void
+mta_disconnected(struct mta_session *s)
+{
+ mta_report_link_disconnect(s);
+ mta_filter_end(s);
+}
+
+
+static void
+mta_report_link_connect(struct mta_session *s, const char *rdns, int fcrdns,
+ const struct sockaddr_storage *ss_src,
+ const struct sockaddr_storage *ss_dest)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_link_connect("smtp-out", s->id, rdns, fcrdns, ss_src, ss_dest);
+}
+
+static void
+mta_report_link_greeting(struct mta_session *s,
+ const char *domain)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_link_greeting("smtp-out", s->id, domain);
+}
+
+static void
+mta_report_link_identify(struct mta_session *s, const char *method, const char *identity)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_link_identify("smtp-out", s->id, method, identity);
+}
+
+static void
+mta_report_link_tls(struct mta_session *s, const char *ssl)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_link_tls("smtp-out", s->id, ssl);
+}
+
+static void
+mta_report_link_disconnect(struct mta_session *s)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_link_disconnect("smtp-out", s->id);
+}
+
+static void
+mta_report_link_auth(struct mta_session *s, const char *user, const char *result)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_link_auth("smtp-out", s->id, user, result);
+}
+
+static void
+mta_report_tx_reset(struct mta_session *s, uint32_t msgid)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_tx_reset("smtp-out", s->id, msgid);
+}
+
+static void
+mta_report_tx_begin(struct mta_session *s, uint32_t msgid)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_tx_begin("smtp-out", s->id, msgid);
+}
+
+static void
+mta_report_tx_mail(struct mta_session *s, uint32_t msgid, const char *address, int ok)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_tx_mail("smtp-out", s->id, msgid, address, ok);
+}
+
+static void
+mta_report_tx_rcpt(struct mta_session *s, uint32_t msgid, const char *address, int ok)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_tx_rcpt("smtp-out", s->id, msgid, address, ok);
+}
+
+static void
+mta_report_tx_envelope(struct mta_session *s, uint32_t msgid, uint64_t evpid)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_tx_envelope("smtp-out", s->id, msgid, evpid);
+}
+
+static void
+mta_report_tx_data(struct mta_session *s, uint32_t msgid, int ok)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_tx_data("smtp-out", s->id, msgid, ok);
+}
+
+static void
+mta_report_tx_commit(struct mta_session *s, uint32_t msgid, size_t msgsz)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_tx_commit("smtp-out", s->id, msgid, msgsz);
+}
+
+static void
+mta_report_tx_rollback(struct mta_session *s, uint32_t msgid)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_tx_rollback("smtp-out", s->id, msgid);
+}
+
+static void
+mta_report_protocol_client(struct mta_session *s, const char *command)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_protocol_client("smtp-out", s->id, command);
+}
+
+static void
+mta_report_protocol_server(struct mta_session *s, const char *response)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_protocol_server("smtp-out", s->id, response);
+}
+
+#if 0
+static void
+mta_report_filter_response(struct mta_session *s, int phase, int response, const char *param)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_filter_response("smtp-out", s->id, phase, response, param);
+}
+#endif
+
+static void
+mta_report_timeout(struct mta_session *s)
+{
+ if (! SESSION_FILTERED(s))
+ return;
+
+ report_smtp_timeout("smtp-out", s->id);
+}