diff options
-rw-r--r-- | usr.sbin/smtpd/mta.c | 375 | ||||
-rw-r--r-- | usr.sbin/smtpd/mta_session.c | 542 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h | 55 |
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 */ |