aboutsummaryrefslogtreecommitdiffstats
path: root/smtpd/mta_session.c
diff options
context:
space:
mode:
Diffstat (limited to 'smtpd/mta_session.c')
-rw-r--r--smtpd/mta_session.c2008
1 files changed, 0 insertions, 2008 deletions
diff --git a/smtpd/mta_session.c b/smtpd/mta_session.c
deleted file mode 100644
index ad1c3c84..00000000
--- a/smtpd/mta_session.c
+++ /dev/null
@@ -1,2008 +0,0 @@
-/* $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);
-}