summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/smtpd/mta.c375
-rw-r--r--usr.sbin/smtpd/mta_session.c542
-rw-r--r--usr.sbin/smtpd/smtpd.h55
3 files changed, 599 insertions, 373 deletions
diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c
index 500c77a5255..a28b572fbc4 100644
--- a/usr.sbin/smtpd/mta.c
+++ b/usr.sbin/smtpd/mta.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: mta.c,v 1.133 2012/07/29 13:56:24 eric Exp $ */
+/* $OpenBSD: mta.c,v 1.134 2012/08/18 15:45:12 eric Exp $ */
/*
* Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -43,22 +43,106 @@
#include "smtpd.h"
#include "log.h"
+#define MTA_MAXCONN 5 /* connections per route */
+#define MTA_MAXMAIL 100 /* mails per session */
+#define MTA_MAXRCPT 1000 /* rcpt per mail */
+
+struct mta_batch2 {
+ uint64_t id;
+ struct tree tasks; /* map route to task */
+};
+
+SPLAY_HEAD(mta_route_tree, mta_route);
+
static void mta_imsg(struct imsgev *, struct imsg *);
static void mta_shutdown(void);
static void mta_sig_handler(int, short, void *);
-static void
+static struct mta_route *mta_route_for(struct envelope *);
+static void mta_route_drain(struct mta_route *);
+static void mta_route_free(struct mta_route *);
+static void mta_envelope_done(struct mta_task *, struct envelope *, const char *);
+static int mta_route_cmp(struct mta_route *, struct mta_route *);
+
+SPLAY_PROTOTYPE(mta_route_tree, mta_route, entry, mta_route_cmp);
+
+static struct mta_route_tree routes = SPLAY_INITIALIZER(&routes);
+static struct tree batches = SPLAY_INITIALIZER(&batches);
+
+void
mta_imsg(struct imsgev *iev, struct imsg *imsg)
{
- struct ssl *ssl;
+ struct mta_route *route;
+ struct mta_batch2 *batch;
+ struct mta_task *task;
+ struct envelope *e;
+ struct ssl *ssl;
+ uint64_t id;
log_imsg(PROC_MTA, iev->proc, imsg);
if (iev->proc == PROC_QUEUE) {
switch (imsg->hdr.type) {
+
case IMSG_BATCH_CREATE:
+ id = *(uint64_t*)(imsg->data);
+ batch = xmalloc(sizeof *batch, "mta_batch");
+ batch->id = id;
+ tree_init(&batch->tasks);
+ tree_xset(&batches, batch->id, batch);
+ log_trace(TRACE_MTA,
+ "mta: batch:%016" PRIx64 " created", batch->id);
+ return;
+
case IMSG_BATCH_APPEND:
+ e = xmalloc(sizeof *e, "mta:envelope");
+ memmove(e, imsg->data, sizeof *e);
+
+ route = mta_route_for(e);
+ batch = tree_xget(&batches, e->batch_id);
+
+ if ((task = tree_get(&batch->tasks, route->id)) == NULL) {
+ log_trace(TRACE_MTA, "mta: new task for %s",
+ mta_route_to_text(route));
+ task = xmalloc(sizeof *task, "mta_task");
+ TAILQ_INIT(&task->envelopes);
+ task->route = route;
+ tree_xset(&batch->tasks, route->id, task);
+ task->msgid = evpid_to_msgid(e->id);
+ task->sender = e->sender;
+ route->refcount += 1;
+ }
+
+ /* Technically, we could handle that by adding a msg
+ * level, but the batch sent by the scheduler should
+ * be valid.
+ */
+ if (task->msgid != evpid_to_msgid(e->id))
+ errx(1, "msgid mismatch in batch");
+
+ /* XXX honour route->maxrcpt */
+ TAILQ_INSERT_TAIL(&task->envelopes, e, entry);
+ log_debug("mta: received evp:%016" PRIx64 " for <%s@%s>",
+ e->id, e->dest.user, e->dest.domain);
+ return;
+
case IMSG_BATCH_CLOSE:
+ id = *(uint64_t*)(imsg->data);
+ batch = tree_xpop(&batches, id);
+ log_trace(TRACE_MTA, "mta: batch:%016" PRIx64 " closed",
+ batch->id);
+ /* for all tasks, queue them on there route */
+ while (tree_poproot(&batch->tasks, &id, (void**)&task)) {
+ if (id != task->route->id)
+ errx(1, "route id mismatch!");
+ task->route->refcount -= 1;
+ task->route->ntask += 1;
+ TAILQ_INSERT_TAIL(&task->route->tasks, task, entry);
+ mta_route_drain(task->route);
+ }
+ free(batch);
+ return;
+
case IMSG_QUEUE_MESSAGE_FD:
mta_session_imsg(iev, imsg);
return;
@@ -94,14 +178,10 @@ mta_imsg(struct imsgev *iev, struct imsg *imsg)
if (ssl == NULL)
fatal(NULL);
*ssl = *(struct ssl *)imsg->data;
- ssl->ssl_cert = strdup((char *)imsg->data +
- sizeof *ssl);
- if (ssl->ssl_cert == NULL)
- fatal(NULL);
- ssl->ssl_key = strdup((char *)imsg->data +
- sizeof *ssl + ssl->ssl_cert_len);
- if (ssl->ssl_key == NULL)
- fatal(NULL);
+ ssl->ssl_cert = xstrdup((char*)imsg->data + sizeof *ssl,
+ "mta:ssl_cert");
+ ssl->ssl_key = xstrdup((char*)imsg->data +
+ sizeof *ssl + ssl->ssl_cert_len, "mta_ssl_key");
SPLAY_INSERT(ssltree, env->sc_ssl, ssl);
return;
@@ -200,3 +280,276 @@ mta(void)
return (0);
}
+
+const char *
+mta_response_status(const char *r)
+{
+ switch (r[0]) {
+ case '2':
+ return "Sent";
+ case '4':
+ case '5':
+ return "RemoteError";
+ default:
+ return "LocalError";
+ }
+}
+
+int
+mta_response_delivery(const char *r)
+{
+ switch (r[0]) {
+ case '2':
+ return IMSG_QUEUE_DELIVERY_OK;
+ case '5':
+ case '6':
+ return IMSG_QUEUE_DELIVERY_PERMFAIL;
+ default:
+ return IMSG_QUEUE_DELIVERY_TEMPFAIL;
+ }
+}
+
+const char *
+mta_response_text(const char *r)
+{
+ return (r + 4);
+}
+
+void
+mta_route_error(struct mta_route *route, const char *error)
+{
+ route->nfail += 1;
+ strlcpy(route->errorline, error, sizeof route->errorline);
+ log_warnx("mta: %s error: %s", mta_route_to_text(route), error);
+}
+
+void
+mta_route_ok(struct mta_route *route)
+{
+ log_debug("mta: %s ready", mta_route_to_text(route));
+ route->nfail = 0;
+}
+
+void
+mta_route_collect(struct mta_route *route)
+{
+ route->nsession -= 1;
+
+ mta_route_drain(route);
+}
+
+const char *
+mta_route_to_text(struct mta_route *route)
+{
+ static char buf[1024];
+ const char *coma = "";
+
+ buf[0] = '\0';
+
+ snprintf(buf, sizeof buf, "route:%s[", route->hostname);
+
+ if (route->flags & ROUTE_STARTTLS) {
+ coma = ",";
+ strlcat(buf, "starttls", sizeof buf);
+ }
+
+ if (route->flags & ROUTE_SMTPS) {
+ strlcat(buf, coma, sizeof buf);
+ coma = ",";
+ strlcat(buf, "smtps", sizeof buf);
+ }
+
+ if (route->flags & ROUTE_AUTH) {
+ strlcat(buf, coma, sizeof buf);
+ coma = ",";
+ strlcat(buf, "auth=", sizeof buf);
+ strlcat(buf, route->auth, sizeof buf);
+ }
+
+ if (route->cert) {
+ strlcat(buf, coma, sizeof buf);
+ coma = ",";
+ strlcat(buf, "cert=", sizeof buf);
+ strlcat(buf, route->cert, sizeof buf);
+ }
+
+ if (route->flags & ROUTE_MX) {
+ strlcat(buf, coma, sizeof buf);
+ coma = ",";
+ strlcat(buf, "mx", sizeof buf);
+ }
+ strlcat(buf, "]", sizeof buf);
+
+ return (buf);
+}
+
+static struct mta_route *
+mta_route_for(struct envelope *e)
+{
+ struct ssl ssl;
+ struct mta_route key, *route;
+
+ bzero(&key, sizeof key);
+
+ key.flags = e->agent.mta.relay.flags;
+ if (e->agent.mta.relay.hostname[0]) {
+ key.hostname = e->agent.mta.relay.hostname;
+ key.flags |= ROUTE_MX;
+ } else
+ key.hostname = e->dest.domain;
+ key.port = e->agent.mta.relay.port;
+ key.cert = e->agent.mta.relay.cert;
+ if (!key.cert[0])
+ key.cert = NULL;
+ key.auth = e->agent.mta.relay.authmap;
+ if (!key.auth[0])
+ key.auth = NULL;
+
+ if ((route = SPLAY_FIND(mta_route_tree, &routes, &key)) == NULL) {
+ route = xcalloc(1, sizeof *route, "mta_route");
+ TAILQ_INIT(&route->tasks);
+ route->id = generate_uid();
+ route->flags = key.flags;
+ route->hostname = xstrdup(key.hostname, "mta: hostname");
+ route->port = key.port;
+ route->cert = key.cert ? xstrdup(key.cert, "mta: cert") : NULL;
+ route->auth = key.auth ? xstrdup(key.auth, "mta: auth") : NULL;
+ if (route->cert) {
+ strlcpy(ssl.ssl_name, route->cert, sizeof(ssl.ssl_name));
+ route->ssl = SPLAY_FIND(ssltree, env->sc_ssl, &ssl);
+ }
+ SPLAY_INSERT(mta_route_tree, &routes, route);
+
+ route->maxconn = MTA_MAXCONN;
+ route->maxmail = MTA_MAXMAIL;
+ route->maxrcpt = MTA_MAXRCPT;
+
+ log_trace(TRACE_MTA, "mta: new %s", mta_route_to_text(route));
+ } else {
+ log_trace(TRACE_MTA, "mta: reusing %s", mta_route_to_text(route));
+ }
+
+ return (route);
+}
+
+static void
+mta_route_free(struct mta_route *route)
+{
+ log_trace(TRACE_MTA, "mta: freeing %s", mta_route_to_text(route));
+ SPLAY_REMOVE(mta_route_tree, &routes, route);
+ free(route->hostname);
+ if (route->cert)
+ free(route->cert);
+ if (route->auth)
+ free(route->auth);
+ free(route);
+}
+
+static void
+mta_route_drain(struct mta_route *route)
+{
+ struct mta_task *task;
+ struct envelope *e;
+
+ log_debug("mta: draining %s (tasks=%i, refs=%i, sessions=%i)",
+ mta_route_to_text(route),
+ route->ntask, route->refcount, route->nsession);
+
+ if (route->ntask == 0 && route->refcount == 0 && route->nsession == 0) {
+ mta_route_free(route);
+ return;
+ }
+
+ if (route->ntask == 0) {
+ log_debug("mta: no task for %s", mta_route_to_text(route));
+ return;
+ }
+
+ if (route->nfail > 3) {
+ /* Three connection errors in a row: consider that the route
+ * has a problem.
+ */
+ log_debug("mta: too many failures on %s",
+ mta_route_to_text(route));
+
+ while ((task = TAILQ_FIRST(&route->tasks))) {
+ TAILQ_REMOVE(&route->tasks, task, entry);
+ route->ntask -= 1;
+ while((e = TAILQ_FIRST(&task->envelopes)))
+ mta_envelope_done(task, e, route->errorline);
+ route->refcount -= 1;
+ free(task);
+ }
+ route->nfail = 0;
+ /* XXX maybe close the route for while */
+ return;
+ }
+
+ /* make sure there are one session for each task */
+ while (route->nsession < route->ntask) {
+ /* if we have reached the max number of session, wait */
+ if (route->nsession >= route->maxconn) {
+ log_debug("mta: max conn reached for %s",
+ mta_route_to_text(route));
+ return;
+ }
+ route->nsession += 1;
+ mta_session(route);
+ }
+}
+
+static void
+mta_envelope_done(struct mta_task *task, struct envelope *e, const char *status)
+{
+ envelope_set_errormsg(e, "%s", status);
+
+ log_info("%016" PRIx64 ": to=<%s@%s>, delay=%s, relay=%s, stat=%s (%s)",
+ e->id, e->dest.user,
+ e->dest.domain,
+ duration_to_text(time(NULL) - e->creation),
+ task->route->hostname,
+ mta_response_status(e->errorline),
+ mta_response_text(e->errorline));
+
+ imsg_compose_event(env->sc_ievs[PROC_QUEUE],
+ mta_response_delivery(e->errorline), 0, 0, -1, e, sizeof(*e));
+ TAILQ_REMOVE(&task->envelopes, e, entry);
+ free(e);
+}
+
+static int
+mta_route_cmp(struct mta_route *a, struct mta_route *b)
+{
+ int r;
+
+ if (a->flags < b->flags)
+ return (-1);
+ if (a->flags > b->flags)
+ return (1);
+
+ if (a->port < b->port)
+ return (-1);
+ if (a->port > b->port)
+ return (1);
+
+ if (a->auth == NULL && b->auth)
+ return (-1);
+ if (a->auth && b->auth == NULL)
+ return (1);
+ if (a->auth && ((r = strcmp(a->auth, b->auth))))
+ return (r);
+
+ if (a->cert == NULL && b->cert)
+ return (-1);
+ if (a->cert && b->cert == NULL)
+ return (1);
+ if (a->cert && ((r = strcmp(a->cert, b->cert))))
+ return (r);
+
+ if ((r = strcmp(a->hostname, b->hostname)))
+ return (r);
+
+ return (0);
+}
+
+SPLAY_GENERATE(mta_route_tree, mta_route, entry, mta_route_cmp);
diff --git a/usr.sbin/smtpd/mta_session.c b/usr.sbin/smtpd/mta_session.c
index c3a57d0c843..c29ede6dc52 100644
--- a/usr.sbin/smtpd/mta_session.c
+++ b/usr.sbin/smtpd/mta_session.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: mta_session.c,v 1.8 2012/08/10 11:05:55 eric Exp $ */
+/* $OpenBSD: mta_session.c,v 1.9 2012/08/18 15:45:12 eric Exp $ */
/*
* Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -79,199 +79,112 @@ enum mta_state {
#define MTA_EXT_AUTH 0x02
#define MTA_EXT_PIPELINING 0x04
-struct mta_relay {
- TAILQ_ENTRY(mta_relay) entry;
+struct mta_host {
+ TAILQ_ENTRY(mta_host) entry;
struct sockaddr_storage sa;
char fqdn[MAXHOSTNAMELEN];
int used;
};
struct mta_session {
- SPLAY_ENTRY(mta_session) entry;
- u_int64_t id;
- enum mta_state state;
- char *host;
- int port;
- int flags;
- TAILQ_HEAD(,mta_relay) relays;
- char *authmap;
+ uint64_t id;
+ struct mta_route *route;
+
char *secret;
- FILE *datafp;
- TAILQ_HEAD(,mta_task) tasks;
+ int flags;
+ int msgcount;
+ enum mta_state state;
+ TAILQ_HEAD(, mta_host) hosts;
+ struct mta_task *task;
+ FILE *datafp;
struct envelope *currevp;
struct iobuf iobuf;
struct io io;
- int ext; /* extension */
+ int ext;
struct ssl *ssl;
};
-struct mta_task {
- SPLAY_ENTRY(mta_task) entry;
- uint64_t id;
-
- struct mta_session *session;
- TAILQ_ENTRY(mta_task) list;
-
- struct mailaddr sender;
- TAILQ_HEAD(,envelope) envelopes;
-};
-
-SPLAY_HEAD(mta_session_tree, mta_session);
-SPLAY_HEAD(mta_task_tree, mta_task);
-
static void mta_io(struct io *, int);
-static struct mta_session *mta_session_lookup(u_int64_t);
static void mta_enter_state(struct mta_session *, int);
static void mta_status(struct mta_session *, int, const char *, ...);
-static int mta_envelope_done(struct mta_task *, struct envelope *,
- const char *);
+static void mta_envelope_done(struct mta_task *, struct envelope *, 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 int mta_session_cmp(struct mta_session *, struct mta_session *);
-
-static int mta_task_cmp(struct mta_task *, struct mta_task *);
-static struct mta_task * mta_task_lookup(uint64_t, int);
-static struct mta_task * mta_task_create(uint64_t, struct mta_session *);
-static void mta_task_free(struct mta_task *);
-
-SPLAY_PROTOTYPE(mta_session_tree, mta_session, entry, mta_session_cmp);
-SPLAY_PROTOTYPE(mta_task_tree, mta_task, entry, mta_task_cmp);
-
static const char * mta_strstate(int);
-static struct mta_session_tree mta_sessions = SPLAY_INITIALIZER(&mta_sessions);
-static struct mta_task_tree mta_tasks = SPLAY_INITIALIZER(&mta_tasks);
+static struct tree sessions = SPLAY_INITIALIZER(&sessions);
void
-mta_session_imsg(struct imsgev *iev, struct imsg *imsg)
+mta_session(struct mta_route *route)
{
- struct mta_batch *mta_batch;
- struct mta_task *task;
- struct mta_session *s;
- struct mta_relay *relay;
- struct envelope *e;
- struct secret *secret;
- struct dns *dns;
- struct ssl key;
- char *cert;
- void *ptr;
-
- switch (imsg->hdr.type) {
- case IMSG_BATCH_CREATE:
- mta_batch = imsg->data;
-
- s = calloc(1, sizeof *s);
- if (s == NULL)
- fatal(NULL);
- s->id = mta_batch->id;
- s->state = MTA_INIT;
-
- /* establish host name */
- if (mta_batch->relay.hostname[0]) {
- s->host = strdup(mta_batch->relay.hostname);
- s->flags |= MTA_FORCE_MX;
- }
-
- /* establish port */
- s->port = mta_batch->relay.port;
-
- /* use auth? */
- if ((mta_batch->relay.flags & F_SSL) &&
- (mta_batch->relay.flags & F_AUTH)) {
- s->flags |= MTA_USE_AUTH;
- s->authmap = strdup(mta_batch->relay.authmap);
- if (s->authmap == NULL)
- fatalx("mta: strdup authmap");
- }
+ struct mta_session *session;
- /* force a particular SSL mode? */
- switch (mta_batch->relay.flags & F_SSL) {
- case F_SSL:
- s->flags |= MTA_FORCE_ANYSSL;
+ session = xcalloc(1, sizeof *session, "mta_session");
+ session->id = generate_uid();
+ session->route = route;
+ session->state = MTA_INIT;
+ tree_xset(&sessions, session->id, session);
+ TAILQ_INIT(&session->hosts);
+
+ if (route->flags & ROUTE_MX)
+ session->flags |= MTA_FORCE_MX;
+ if (route->flags & ROUTE_SSL && route->flags & ROUTE_AUTH)
+ session->flags |= MTA_USE_AUTH;
+ if (route->cert)
+ session->flags |= MTA_USE_CERT;
+ switch (route->flags & ROUTE_SSL) {
+ case ROUTE_SSL:
+ session->flags |= MTA_FORCE_ANYSSL;
break;
- case F_SMTPS:
- s->flags |= MTA_FORCE_SMTPS;
+ case ROUTE_SMTPS:
+ session->flags |= MTA_FORCE_SMTPS;
break;
- case F_STARTTLS:
+ case ROUTE_STARTTLS:
/* STARTTLS is tried by default */
break;
default:
- s->flags |= MTA_ALLOW_PLAIN;
- }
-
- /* have cert? */
- cert = mta_batch->relay.cert;
- if (cert[0] != '\0') {
- s->flags |= MTA_USE_CERT;
- strlcpy(key.ssl_name, cert, sizeof(key.ssl_name));
- s->ssl = SPLAY_FIND(ssltree, env->sc_ssl, &key);
- }
-
- TAILQ_INIT(&s->relays);
- TAILQ_INIT(&s->tasks);
- SPLAY_INSERT(mta_session_tree, &mta_sessions, s);
-
- if (mta_task_create(s->id, s) == NULL)
- fatalx("mta_session_imsg: mta_task_create");
+ session->flags |= MTA_ALLOW_PLAIN;
+ }
- log_debug("mta: %p: new session for batch %" PRIu64, s, s->id);
+ log_debug("mta: %p: spawned for %s", session, mta_route_to_text(route));
- return;
+ mta_enter_state(session, MTA_INIT);
+}
- case IMSG_BATCH_APPEND:
- e = imsg->data;
- task = mta_task_lookup(e->batch_id, 1);
- e = malloc(sizeof *e);
- if (e == NULL)
- fatal(NULL);
- *e = *(struct envelope *)imsg->data;
- s = task->session;
- if (s->host == NULL) {
- s->host = strdup(e->dest.domain);
- if (s->host == NULL)
- fatal("strdup");
- }
- if (TAILQ_FIRST(&task->envelopes) == NULL)
- task->sender = e->sender;
- log_debug("mta: %p: adding <%s@%s> from envelope %016" PRIx64,
- s, e->dest.user, e->dest.domain, e->id);
- TAILQ_INSERT_TAIL(&task->envelopes, e, entry);
- return;
+void
+mta_session_imsg(struct imsgev *iev, struct imsg *imsg)
+{
+ struct mta_batch *batch;
+ struct mta_session *s;
+ struct mta_host *host;
+ struct secret *secret;
+ struct dns *dns;
+ const char *error;
+ void *ptr;
- case IMSG_BATCH_CLOSE:
- mta_batch = imsg->data;
- task = mta_task_lookup(mta_batch->id, 1);
- s = task->session;
- if (s->flags & MTA_USE_CERT && s->ssl == NULL) {
- mta_status(s, 1, "190 certificate not found");
- mta_enter_state(s, MTA_DONE);
- } else
- mta_enter_state(s, MTA_INIT);
- return;
+ switch(imsg->hdr.type) {
case IMSG_QUEUE_MESSAGE_FD:
- mta_batch = imsg->data;
+ batch = imsg->data;
if (imsg->fd == -1)
fatalx("mta: cannot obtain msgfd");
- s = mta_session_lookup(mta_batch->id);
+ s = tree_xget(&sessions, batch->id);
s->datafp = fdopen(imsg->fd, "r");
if (s->datafp == NULL)
fatal("mta: fdopen");
- mta_enter_state(s, MTA_CONNECT);
+ mta_enter_state(s, MTA_SMTP_MAIL);
return;
case IMSG_LKA_SECRET:
/* LKA responded to AUTH lookup. */
secret = imsg->data;
- s = mta_session_lookup(secret->id);
- s->secret = strdup(secret->secret);
- if (s->secret == NULL)
- fatal(NULL);
- else if (s->secret[0] == '\0') {
- mta_status(s, 1, "190 secrets lookup failed");
+ s = tree_xget(&sessions, secret->id);
+ s->secret = xstrdup(secret->secret, "mta: secret");
+ if (s->secret[0] == '\0') {
+ mta_route_error(s->route, "secrets lookup failed");
mta_enter_state(s, MTA_DONE);
} else
mta_enter_state(s, MTA_MX);
@@ -279,50 +192,50 @@ mta_session_imsg(struct imsgev *iev, struct imsg *imsg)
case IMSG_DNS_HOST:
dns = imsg->data;
- s = mta_session_lookup(dns->id);
- relay = calloc(1, sizeof *relay);
- if (relay == NULL)
- fatal(NULL);
- relay->sa = dns->ss;
- TAILQ_INSERT_TAIL(&s->relays, relay, entry);
+ s = tree_xget(&sessions, dns->id);
+ host = xcalloc(1, sizeof *host, "mta: host");
+ host->sa = dns->ss;
+ TAILQ_INSERT_TAIL(&s->hosts, host, entry);
return;
case IMSG_DNS_HOST_END:
/* LKA responded to DNS lookup. */
dns = imsg->data;
- s = mta_session_lookup(dns->id);
- if (dns->error) {
- if (dns->error == DNS_RETRY)
- mta_status(s, 1, "100 MX lookup failed temporarily");
- else if (dns->error == DNS_EINVAL)
- mta_status(s, 1, "600 Invalid domain name");
- else if (dns->error == DNS_ENONAME)
- mta_status(s, 1, "600 Domain does not exist");
- else if (dns->error == DNS_ENOTFOUND)
- mta_status(s, 1, "600 No MX address found for domain");
- mta_enter_state(s, MTA_DONE);
- } else
- mta_enter_state(s, MTA_DATA);
+ s = tree_xget(&sessions, dns->id);
+ if (!dns->error) {
+ mta_enter_state(s, MTA_CONNECT);
+ return;
+ }
+ if (dns->error == DNS_RETRY)
+ error = "100 MX lookup failed temporarily";
+ else if (dns->error == DNS_EINVAL)
+ error = "600 Invalid domain name";
+ else if (dns->error == DNS_ENONAME)
+ error = "600 Domain does not exist";
+ else if (dns->error == DNS_ENOTFOUND)
+ error = "600 No MX address found for domain";
+ else
+ error = "100 Weird error";
+ mta_route_error(s->route, error);
+ mta_enter_state(s, MTA_CONNECT);
return;
case IMSG_DNS_PTR:
dns = imsg->data;
- s = mta_session_lookup(dns->id);
- relay = TAILQ_FIRST(&s->relays);
+ s = tree_xget(&sessions, dns->id);
+ host = TAILQ_FIRST(&s->hosts);
if (dns->error)
- strlcpy(relay->fqdn, "<unknown>", sizeof relay->fqdn);
+ strlcpy(host->fqdn, "<unknown>", sizeof host->fqdn);
else
- strlcpy(relay->fqdn, dns->host, sizeof relay->fqdn);
- log_debug("mta: %p: connected to %s", s, relay->fqdn);
+ strlcpy(host->fqdn, dns->host, sizeof host->fqdn);
+ log_debug("mta: %p: connected to %s", s, host->fqdn);
/* check if we need to start tls now... */
- if (((s->flags & MTA_FORCE_ANYSSL) && relay->used == 1) ||
+ if (((s->flags & MTA_FORCE_ANYSSL) && host->used == 1) ||
(s->flags & MTA_FORCE_SMTPS)) {
log_debug("mta: %p: trying smtps (ssl=%p)...", s, s->ssl);
- ptr = ssl_mta_init(s->ssl);
- if (ptr == NULL)
+ if ((ptr = ssl_mta_init(s->ssl)) == NULL)
fatalx("mta: ssl_mta_init");
-
io_start_tls(&s->io, ptr);
} else {
mta_enter_state(s, MTA_SMTP_BANNER);
@@ -339,16 +252,14 @@ mta_enter_state(struct mta_session *s, int newstate)
{
int oldstate;
struct secret secret;
- struct mta_relay *relay;
- struct mta_task *task;
+ struct mta_route *route;
+ struct mta_host *host;
struct sockaddr *sa;
- struct envelope *e;
int max_reuse;
ssize_t q;
struct mta_batch batch;
again:
- task = TAILQ_FIRST(&s->tasks);
oldstate = s->state;
log_trace(TRACE_MTA, "mta: %p: %s -> %s", s,
@@ -362,7 +273,7 @@ mta_enter_state(struct mta_session *s, int newstate)
switch (s->state) {
case MTA_INIT:
- if (s->flags & MTA_USE_AUTH)
+ if (s->route->auth)
mta_enter_state(s, MTA_SECRET);
else
mta_enter_state(s, MTA_MX);
@@ -372,9 +283,8 @@ mta_enter_state(struct mta_session *s, int newstate)
/*
* Obtain message body fd.
*/
- e = TAILQ_FIRST(&task->envelopes);
batch.id = s->id;
- batch.msgid = evpid_to_msgid(e->id);
+ batch.msgid = s->task->msgid;
imsg_compose_event(env->sc_ievs[PROC_QUEUE],
IMSG_QUEUE_MESSAGE_FD, 0, 0, -1, &batch, sizeof(batch));
break;
@@ -385,8 +295,8 @@ mta_enter_state(struct mta_session *s, int newstate)
*/
bzero(&secret, sizeof(secret));
secret.id = s->id;
- strlcpy(secret.mapname, s->authmap, sizeof(secret.mapname));
- strlcpy(secret.host, s->host, sizeof(secret.host));
+ strlcpy(secret.mapname, s->route->auth, sizeof(secret.mapname));
+ strlcpy(secret.host, s->route->hostname, sizeof(secret.host));
imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_SECRET,
0, 0, -1, &secret, sizeof(secret));
break;
@@ -395,10 +305,10 @@ mta_enter_state(struct mta_session *s, int newstate)
/*
* Lookup MX record.
*/
- if (s->flags & MTA_FORCE_MX)
- dns_query_host(s->host, s->port, s->id);
+ if (s->flags & MTA_FORCE_MX) /* XXX */
+ dns_query_host(s->route->hostname, s->route->port, s->id);
else
- dns_query_mx(s->host, 0, s->id);
+ dns_query_mx(s->route->hostname, 0, s->id);
break;
case MTA_CONNECT:
@@ -417,21 +327,21 @@ mta_enter_state(struct mta_session *s, int newstate)
max_reuse = 1;
/* pick next mx */
- while ((relay = TAILQ_FIRST(&s->relays))) {
- if (relay->used == max_reuse) {
- TAILQ_REMOVE(&s->relays, relay, entry);
- free(relay);
+ while ((host = TAILQ_FIRST(&s->hosts))) {
+ if (host->used == max_reuse) {
+ TAILQ_REMOVE(&s->hosts, host, entry);
+ free(host);
continue;
}
- relay->used++;
+ host->used++;
log_debug("mta: %p: connecting to %s...", s,
- ss_to_text(&relay->sa));
- sa = (struct sockaddr *)&relay->sa;
+ ss_to_text(&host->sa));
+ sa = (struct sockaddr *)&host->sa;
- if (s->port)
- sa_set_port(sa, s->port);
- else if ((s->flags & MTA_FORCE_ANYSSL) && relay->used == 1)
+ if (s->route->port)
+ sa_set_port(sa, s->route->port);
+ else if ((s->flags & MTA_FORCE_ANYSSL) && host->used == 1)
sa_set_port(sa, 465);
else if (s->flags & MTA_FORCE_SMTPS)
sa_set_port(sa, 465);
@@ -450,14 +360,14 @@ mta_enter_state(struct mta_session *s, int newstate)
* so there is no need to try the same
* relay again.
*/
- TAILQ_REMOVE(&s->relays, relay, entry);
- free(relay);
+ TAILQ_REMOVE(&s->hosts, host, entry);
+ free(host);
continue;
}
return;
}
/* tried them all? */
- mta_status(s, 1, "150 Can not connect to MX");
+ mta_route_error(s->route, "150 Can not connect to MX");
mta_enter_state(s, MTA_DONE);
break;
@@ -465,28 +375,21 @@ mta_enter_state(struct mta_session *s, int newstate)
/*
* Kill the mta session.
*/
-
- log_debug("mta: %p: deleting session...", s);
+ log_debug("mta: %p: session done", s);
io_clear(&s->io);
iobuf_clear(&s->iobuf);
-
- if (TAILQ_FIRST(&s->tasks))
- fatalx("all tasks should have been deleted already");
-
- /* deallocate resources */
- SPLAY_REMOVE(mta_session_tree, &mta_sessions, s);
- while ((relay = TAILQ_FIRST(&s->relays))) {
- TAILQ_REMOVE(&s->relays, relay, entry);
- free(relay);
- }
-
+ if (s->task)
+ fatalx("current task should have been deleted already");
if (s->datafp)
fclose(s->datafp);
-
- free(s->authmap);
- free(s->secret);
- free(s->host);
+ s->datafp = NULL;
+ while ((host = TAILQ_FIRST(&s->hosts))) {
+ TAILQ_REMOVE(&s->hosts, host, entry);
+ free(host);
+ }
+ route = s->route;
free(s);
+ mta_route_collect(route);
break;
case MTA_SMTP_BANNER:
@@ -521,31 +424,45 @@ mta_enter_state(struct mta_session *s, int newstate)
log_debug("mta: %p: not using AUTH on non-TLS session",
s);
mta_enter_state(s, MTA_CONNECT);
- } else
+ } else {
+ mta_route_ok(s->route);
mta_enter_state(s, MTA_SMTP_READY);
+ }
break;
case MTA_SMTP_READY:
/* ready to send a new mail */
- if (task)
- mta_enter_state(s, MTA_SMTP_MAIL);
- else
+ if (s->msgcount >= s->route->maxmail) {
+ log_debug("mta: %p: cannot send more message to %s", s,
+ mta_route_to_text(s->route));
+ mta_enter_state(s, MTA_SMTP_QUIT);
+ } else if ((s->task = TAILQ_FIRST(&s->route->tasks))) {
+ log_debug("mta: %p: handling next task for %s", s,
+ mta_route_to_text(s->route));
+ TAILQ_REMOVE(&s->route->tasks, s->task, entry);
+ s->route->ntask -= 1;
+ s->task->session = s;
+ mta_enter_state(s, MTA_DATA);
+ } else {
+ log_debug("mta: %p: no pending task for %s", s,
+ mta_route_to_text(s->route));
+ /* XXX stay open for a while? */
mta_enter_state(s, MTA_SMTP_QUIT);
+ }
break;
case MTA_SMTP_MAIL:
- if (task->sender.user[0] &&
- task->sender.domain[0])
+ if (s->task->sender.user[0] && s->task->sender.domain[0])
mta_send(s, "MAIL FROM: <%s@%s>",
- task->sender.user,
- task->sender.domain);
+ s->task->sender.user, s->task->sender.domain);
else
mta_send(s, "MAIL FROM: <>");
+ io_set_write(&s->io);
break;
case MTA_SMTP_RCPT:
if (s->currevp == NULL)
- s->currevp = TAILQ_FIRST(&task->envelopes);
+ s->currevp = TAILQ_FIRST(&s->task->envelopes);
mta_send(s, "RCPT TO: <%s@%s>",
s->currevp->dest.user,
s->currevp->dest.domain);
@@ -558,7 +475,7 @@ mta_enter_state(struct mta_session *s, int newstate)
case MTA_SMTP_BODY:
if (s->datafp == NULL) {
- log_debug("mta: %p: end-of-file", s);
+ log_trace(TRACE_MTA, "mta: %p: end-of-file", s);
mta_enter_state(s, MTA_SMTP_DONE);
break;
}
@@ -580,10 +497,7 @@ mta_enter_state(struct mta_session *s, int newstate)
break;
case MTA_SMTP_RSET:
- if (task == NULL)
- mta_enter_state(s, MTA_SMTP_QUIT);
- else
- mta_send(s, "RSET");
+ mta_send(s, "RSET");
break;
default:
@@ -600,7 +514,6 @@ mta_response(struct mta_session *s, char *line)
{
void *ssl;
struct envelope *evp;
- struct mta_task *task = TAILQ_FIRST(&s->tasks);
switch (s->state) {
@@ -612,7 +525,7 @@ mta_response(struct mta_session *s, char *line)
if (line[0] != '2') {
if ((s->flags & MTA_USE_AUTH) ||
!(s->flags & MTA_ALLOW_PLAIN)) {
- mta_status(s, 1, line);
+ mta_route_error(s->route, line);
mta_enter_state(s, MTA_DONE);
return;
}
@@ -624,10 +537,11 @@ mta_response(struct mta_session *s, char *line)
case MTA_SMTP_HELO:
if (line[0] != '2') {
- mta_status(s, 1, line);
+ mta_route_error(s->route, line);
mta_enter_state(s, MTA_DONE);
return;
}
+ mta_route_ok(s->route);
mta_enter_state(s, MTA_SMTP_READY);
break;
@@ -638,7 +552,7 @@ mta_response(struct mta_session *s, char *line)
return;
}
/* stop here if ssl can't be used */
- mta_status(s, 1, line);
+ mta_route_error(s->route, line);
mta_enter_state(s, MTA_DONE);
return;
}
@@ -651,10 +565,11 @@ mta_response(struct mta_session *s, char *line)
case MTA_SMTP_AUTH:
if (line[0] != '2') {
- mta_status(s, 1, line);
+ mta_route_error(s->route, line);
mta_enter_state(s, MTA_DONE);
return;
}
+ mta_route_ok(s->route);
mta_enter_state(s, MTA_SMTP_READY);
break;
@@ -670,9 +585,12 @@ mta_response(struct mta_session *s, char *line)
case MTA_SMTP_RCPT:
evp = s->currevp;
s->currevp = TAILQ_NEXT(s->currevp, entry);
- if (line[0] != '2' && mta_envelope_done(task, evp, line)) {
+ if (line[0] != '2') {
+ mta_envelope_done(s->task, evp, line);
+ if (TAILQ_EMPTY(&s->task->envelopes)) {
mta_enter_state(s, MTA_SMTP_RSET);
break;
+ }
}
if (s->currevp == NULL)
mta_enter_state(s, MTA_SMTP_DATA);
@@ -691,6 +609,8 @@ mta_response(struct mta_session *s, char *line)
case MTA_SMTP_DONE:
mta_status(s, 0, line);
+ if (line[0] == '2')
+ s->msgcount++;
mta_enter_state(s, MTA_SMTP_READY);
break;
@@ -709,7 +629,7 @@ mta_io(struct io *io, int evt)
struct mta_session *s = io->arg;
char *line, *msg;
ssize_t len;
- struct mta_relay *relay;
+ struct mta_host *host;
const char *error;
int cont;
@@ -720,8 +640,8 @@ mta_io(struct io *io, int evt)
case IO_CONNECTED:
io_set_timeout(io, 300000);
io_set_write(io);
- relay = TAILQ_FIRST(&s->relays);
- dns_query_ptr(&relay->sa, s->id);
+ host = TAILQ_FIRST(&s->hosts);
+ dns_query_ptr(&host->sa, s->id);
break;
case IO_TLSREADY:
@@ -734,7 +654,6 @@ mta_io(struct io *io, int evt)
case IO_DATAIN:
nextline:
-
line = iobuf_getline(&s->iobuf, &len);
if (line == NULL) {
if (iobuf_len(&s->iobuf) >= SMTP_LINE_MAX) {
@@ -878,11 +797,10 @@ mta_queue_data(struct mta_session *s)
}
static void
-mta_status(struct mta_session *s, int alltasks, const char *fmt, ...)
+mta_status(struct mta_session *s, int connerr, const char *fmt, ...)
{
- char *status;
struct envelope *e;
- struct mta_task *task;
+ char *status;
va_list ap;
va_start(ap, fmt);
@@ -890,141 +808,43 @@ mta_status(struct mta_session *s, int alltasks, const char *fmt, ...)
fatal("vasprintf");
va_end(ap);
- log_debug("mta: %p: new status for %s: %s", s,
- alltasks ? "all tasks" : "current task", status);
- while ((task = TAILQ_FIRST(&s->tasks))) {
- while((e = TAILQ_FIRST(&task->envelopes)))
- if (mta_envelope_done(task, e, status))
- break;
- if (!alltasks)
- break;
+ if (s->task) {
+ while((e = TAILQ_FIRST(&s->task->envelopes)))
+ mta_envelope_done(s->task, e, status);
+ free(s->task);
+ s->task = NULL;
}
+ if (connerr)
+ mta_route_error(s->route, status);
+
free(status);
}
-static int
+static void
mta_envelope_done(struct mta_task *task, struct envelope *e, const char *status)
{
- struct mta_session *s = task->session;
- struct mta_relay *relay = TAILQ_FIRST(&s->relays);
- u_int16_t msg;
+ struct mta_host *host = TAILQ_FIRST(&task->session->hosts);
envelope_set_errormsg(e, "%s", status);
- log_info("%016" PRIx64 ": to=<%s@%s>, delay=%" PRId64 ", relay=%s [%s], stat=%s (%s)",
+ log_info("%016" PRIx64 ": to=<%s@%s>, delay=%s, relay=%s [%s], stat=%s (%s)",
e->id, e->dest.user,
e->dest.domain,
- (int64_t) (time(NULL) - e->creation),
- relay ? relay->fqdn : "(none)",
- relay ? ss_to_text(&relay->sa) : "",
- *status == '2' ? "Sent" :
- *status == '5' ? "RemoteError" :
- *status == '4' ? "RemoteError" : "LocalError",
- status + 4);
-
- switch (e->errorline[0]) {
- case '2':
- msg = IMSG_QUEUE_DELIVERY_OK;
- break;
- case '5':
- case '6':
- msg = IMSG_QUEUE_DELIVERY_PERMFAIL;
- break;
- default:
- msg = IMSG_QUEUE_DELIVERY_TEMPFAIL;
- break;
- }
- imsg_compose_event(env->sc_ievs[PROC_QUEUE], msg,
- 0, 0, -1, e, sizeof(*e));
- TAILQ_REMOVE(&task->envelopes, e, entry);
- free(e);
-
- if (TAILQ_FIRST(&task->envelopes))
- return (0);
-
- mta_task_free(task);
- return (1);
-
-}
-
-static int
-mta_session_cmp(struct mta_session *a, struct mta_session *b)
-{
- return (a->id < b->id ? -1 : a->id > b->id);
-}
-
-static struct mta_session *
-mta_session_lookup(u_int64_t id)
-{
- struct mta_session key, *res;
-
- key.id = id;
- if ((res = SPLAY_FIND(mta_session_tree, &mta_sessions, &key)) == NULL)
- fatalx("mta_session_lookup: session not found");
- return (res);
-}
-
-SPLAY_GENERATE(mta_session_tree, mta_session, entry, mta_session_cmp);
-
-static struct mta_task *
-mta_task_create(u_int64_t id, struct mta_session *s)
-{
- struct mta_task *t;
-
- if ((t = mta_task_lookup(id, 0)))
- fatalx("mta_task_create: duplicate task id");
- if ((t = calloc(1, sizeof(*t))) == NULL)
- return (NULL);
-
- t->id = id;
- t->session = s;
- SPLAY_INSERT(mta_task_tree, &mta_tasks, t);
- TAILQ_INIT(&t->envelopes);
- if (s)
- TAILQ_INSERT_TAIL(&s->tasks, t, list);
+ duration_to_text(time(NULL) - e->creation),
+ host->fqdn,
+ ss_to_text(&host->sa),
+ mta_response_status(e->errorline),
+ mta_response_text(e->errorline));
- return (t);
-}
+ imsg_compose_event(env->sc_ievs[PROC_QUEUE],
+ mta_response_delivery(e->errorline), 0, 0, -1, e, sizeof(*e));
-static int
-mta_task_cmp(struct mta_task *a, struct mta_task *b)
-{
- return (a->id < b->id ? -1 : a->id > b->id);
-}
-
-static struct mta_task *
-mta_task_lookup(u_int64_t id, int strict)
-{
- struct mta_task key, *res;
-
- key.id = id;
- res = SPLAY_FIND(mta_task_tree, &mta_tasks, &key);
- if (res == NULL && strict)
- fatalx("mta_task_lookup: task not found");
- return (res);
-}
-
-static void
-mta_task_free(struct mta_task *t)
-{
- struct envelope *e;
-
- if (t->session)
- TAILQ_REMOVE(&t->session->tasks, t, list);
-
- SPLAY_REMOVE(mta_task_tree, &mta_tasks, t);
-
- while ((e = TAILQ_FIRST(&t->envelopes))) {
- TAILQ_REMOVE(&t->envelopes, e, entry);
- free(e);
- }
+ TAILQ_REMOVE(&task->envelopes, e, entry);
- free(t);
+ free(e);
}
-SPLAY_GENERATE(mta_task_tree, mta_task, entry, mta_task_cmp);
-
#define CASE(x) case x : return #x
static const char *
diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h
index 1f752ecc3de..d0b0f7222fd 100644
--- a/usr.sbin/smtpd/smtpd.h
+++ b/usr.sbin/smtpd/smtpd.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: smtpd.h,v 1.320 2012/08/10 11:05:55 eric Exp $ */
+/* $OpenBSD: smtpd.h,v 1.321 2012/08/18 15:45:12 eric Exp $ */
/*
* Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
@@ -85,6 +85,13 @@
#define F_SCERT 0x01
#define F_CCERT 0x02
+/* must match F_* for mta */
+#define ROUTE_STARTTLS 0x01
+#define ROUTE_SMTPS 0x02
+#define ROUTE_SSL (ROUTE_STARTTLS | ROUTE_SMTPS)
+#define ROUTE_AUTH 0x04
+#define ROUTE_MX 0x08
+
typedef u_int32_t objid_t;
struct netaddr {
@@ -778,6 +785,44 @@ struct mfa_session {
struct filter_msg fm;
};
+struct mta_session;
+
+struct mta_route {
+ SPLAY_ENTRY(mta_route) entry;
+ uint64_t id;
+
+ uint8_t flags;
+ char *hostname;
+ uint16_t port;
+ char *cert;
+ char *auth;
+ void *ssl;
+
+ /* route limits */
+ int maxconn; /* in parallel */
+ int maxmail; /* per session */
+ int maxrcpt; /* per mail */
+
+ int refcount;
+
+ int ntask;
+ TAILQ_HEAD(, mta_task) tasks;
+
+ int nsession;
+
+ int nfail;
+ char errorline[64];
+};
+
+struct mta_task {
+ TAILQ_ENTRY(mta_task) entry;
+ struct mta_route *route;
+ uint32_t msgid;
+ TAILQ_HEAD(, envelope) envelopes;
+ struct mailaddr sender;
+ struct mta_session *session;
+};
+
struct mta_batch {
u_int64_t id;
struct relayhost relay;
@@ -1019,8 +1064,16 @@ SPLAY_PROTOTYPE(mfatree, mfa_session, nodes, mfa_session_cmp);
/* mta.c */
pid_t mta(void);
+int mta_response_delivery(const char *);
+const char *mta_response_status(const char *);
+const char *mta_response_text(const char *);
+void mta_route_ok(struct mta_route *);
+void mta_route_error(struct mta_route *, const char *);
+void mta_route_collect(struct mta_route *);
+const char *mta_route_to_text(struct mta_route *);
/* mta_session.c */
+void mta_session(struct mta_route *);
void mta_session_imsg(struct imsgev *, struct imsg *);
/* parse.y */