summaryrefslogtreecommitdiff
path: root/usr.sbin/smtpd/dns.c
diff options
context:
space:
mode:
authorGilles Chehade <gilles@cvs.openbsd.org>2013-01-26 09:37:25 +0000
committerGilles Chehade <gilles@cvs.openbsd.org>2013-01-26 09:37:25 +0000
commit52e93b0e61fd0a116dbb373054e2cd0ea3bfcf39 (patch)
tree41934d0fc43bfebf55ba5a199e0d699adf24aff1 /usr.sbin/smtpd/dns.c
parent3b78bd2481525635417ca0fc75396ef754c09171 (diff)
Sync with our smtpd repo:
* first bricks of ldap and sqlite support (not finished but both working) * new table API to replace map API, all lookups are done through tables * improved handling of temporary errors throughout the daemon * improved scheduler and mta logic: connection reuse, optimizes batches * improved queue: more tolerant to admin errors, new layout, less disk-IO * improved memory usage under high load * SSL certs/keys isolated to lookup process to avoid facing network * VIRTUAL support improved, fully virtual setups possible now * runtime tracing of processes through smtpctl trace * ssl_privsep.c sync-ed with relayd * ssl.c no longer contains smtpd specific interfaces * smtpd-specific ssl bits moved to ssl_smtpd.c * update mail address in copyright FLUSH YOUR QUEUE. FLUSH YOUR QUEUE. FLUSH YOUR QUEUE. FLUSH YOUR QUEUE. smtpd.conf(5) simplified, it will require adaptations ok eric@
Diffstat (limited to 'usr.sbin/smtpd/dns.c')
-rw-r--r--usr.sbin/smtpd/dns.c542
1 files changed, 254 insertions, 288 deletions
diff --git a/usr.sbin/smtpd/dns.c b/usr.sbin/smtpd/dns.c
index 22ba723d048..eae44611eb1 100644
--- a/usr.sbin/smtpd/dns.c
+++ b/usr.sbin/smtpd/dns.c
@@ -1,9 +1,9 @@
-/* $OpenBSD: dns.c,v 1.63 2012/11/24 14:01:51 eric Exp $ */
+/* $OpenBSD: dns.c,v 1.64 2013/01/26 09:37:23 gilles Exp $ */
/*
- * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
* Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
- * Copyright (c) 2011 Eric Faurot <eric@faurot.net>
+ * Copyright (c) 2011-2012 Eric Faurot <eric@faurot.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -39,378 +39,344 @@
#include "smtpd.h"
#include "log.h"
-
-struct mx {
- TAILQ_ENTRY(mx) entry;
- char *host;
- int preference;
+struct dns_lookup {
+ struct dns_session *session;
+ int preference;
};
-struct dnssession {
- uint64_t id;
- struct dns query;
- struct event ev;
- struct async *as;
- int preference;
- size_t mxfound;
- TAILQ_HEAD(, mx) mx;
+struct dns_session {
+ struct mproc *p;
+ uint64_t reqid;
+ int type;
+ char name[MAXHOSTNAMELEN];
+ size_t mxfound;
+ int error;
+ int refcount;
};
-static struct dnssession *dnssession_init(struct dns *);
-static void dnssession_destroy(struct dnssession *);
-static void dnssession_mx_insert(struct dnssession *, const char *, int);
-static void dns_asr_event_set(struct dnssession *, struct async_res *);
-static void dns_asr_handler(int, short, void *);
-static int dns_asr_error(int);
-static void dns_asr_dispatch_host(struct dnssession *);
-static void dns_asr_dispatch_mx(struct dnssession *);
-static void dns_asr_dispatch_cname(struct dnssession *);
-static void dns_reply(struct dns *, int);
+struct async_event;
+struct async_event * async_run_event(struct async *,
+ void (*)(int, struct async_res *, void *), void *);
-#define print_dname(a,b,c) asr_strdname(a, b, c)
+static void dns_lookup_host(struct dns_session *, const char *, int);
+static void dns_dispatch_host(int, struct async_res *, void *);
+static void dns_dispatch_ptr(int, struct async_res *, void *);
+static void dns_dispatch_mx(int, struct async_res *, void *);
+static void dns_dispatch_mx_preference(int, struct async_res *, void *);
-/*
- * User interface.
- */
+#define print_dname(a,b,c) asr_strdname(a, b, c)
void
-dns_query_host(char *host, int port, uint64_t id)
+dns_query_host(uint64_t id, const char *host)
{
- struct dns query;
-
- bzero(&query, sizeof(query));
- strlcpy(query.host, host, sizeof(query.host));
- query.port = port;
- query.id = id;
-
- imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_HOST, 0, 0, -1,
- &query, sizeof(query));
+ m_create(p_lka, IMSG_DNS_HOST, 0, 0, -1, 128);
+ m_add_id(p_lka, id);
+ m_add_string(p_lka, host);
+ m_close(p_lka);
}
void
-dns_query_mx(char *host, char *backup, int port, uint64_t id)
+dns_query_ptr(uint64_t id, const struct sockaddr *sa)
{
- struct dns query;
-
- bzero(&query, sizeof(query));
- strlcpy(query.host, host, sizeof(query.host));
- if (backup)
- strlcpy(query.backup, backup, sizeof(query.backup));
- query.port = port;
- query.id = id;
-
- imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_MX, 0, 0, -1,
- &query, sizeof(query));
+ m_create(p_lka, IMSG_DNS_PTR, 0, 0, -1, 128);
+ m_add_id(p_lka, id);
+ m_add_sockaddr(p_lka, sa);
+ m_close(p_lka);
}
void
-dns_query_ptr(struct sockaddr_storage *ss, uint64_t id)
+dns_query_mx(uint64_t id, const char *domain)
{
- struct dns query;
-
- bzero(&query, sizeof(query));
- query.ss = *ss;
- query.id = id;
+ m_create(p_lka, IMSG_DNS_MX, 0, 0, -1, 128);
+ m_add_id(p_lka, id);
+ m_add_string(p_lka, domain);
+ m_close(p_lka);
+}
- imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_PTR, 0, 0, -1,
- &query, sizeof(query));
+void
+dns_query_mx_preference(uint64_t id, const char *domain, const char *mx)
+{
+ m_create(p_lka, IMSG_DNS_MX_PREFERENCE, 0, 0, -1, 128);
+ m_add_id(p_lka, id);
+ m_add_string(p_lka, domain);
+ m_add_string(p_lka, mx);
+ m_close(p_lka);
}
-/* LKA interface */
void
-dns_async(struct imsgev *asker, int type, struct dns *query)
+dns_imsg(struct mproc *p, struct imsg *imsg)
{
- struct dnssession *s;
+ struct sockaddr_storage ss;
+ struct dns_session *s;
+ struct sockaddr *sa;
+ struct async *as;
+ struct msg m;
+ const char *domain, *mx, *host;
+
+ s = xcalloc(1, sizeof *s, "dns_imsg");
+ s->type = imsg->hdr.type;
+ s->p = p;
+
+ m_msg(&m, imsg);
+ m_get_id(&m, &s->reqid);
- query->type = type;
- query->asker = asker;
- s = dnssession_init(query);
+ switch (s->type) {
- switch (type) {
case IMSG_DNS_HOST:
- log_debug("debug: dns: lookup host \"%s\"", query->host);
- if (sockaddr_from_str((struct sockaddr*)&query->ss, PF_UNSPEC,
- query->host) == 0) {
- log_debug("debug: dns: \"%s\" is an IP address",
- query->host);
- query->error = DNS_OK;
- dns_reply(query, IMSG_DNS_HOST);
- dns_reply(query, IMSG_DNS_HOST_END);
- dnssession_destroy(s);
- return;
- }
- dnssession_mx_insert(s, query->host, 0);
- stat_increment("lka.session.host", 1);
- query->error = DNS_ENOTFOUND; /* override later */
- dns_asr_dispatch_host(s);
+ m_get_string(&m, &host);
+ m_end(&m);
+ dns_lookup_host(s, host, -1);
return;
+
case IMSG_DNS_PTR:
- s->as = getnameinfo_async((struct sockaddr*)&query->ss,
- query->ss.ss_len,
- s->query.host, sizeof(s->query.host), NULL, 0, 0, NULL);
- stat_increment("lka.session.cname", 1);
- if (s->as == NULL) {
- log_debug("debug: dns_async: asr_query_cname error");
- break;
- }
- dns_asr_dispatch_cname(s);
+ sa = (struct sockaddr *)&ss;
+ m_get_sockaddr(&m, sa);
+ m_end(&m);
+ as = getnameinfo_async(sa, sa->sa_len, s->name, sizeof(s->name),
+ NULL, 0, 0, NULL);
+ async_run_event(as, dns_dispatch_ptr, s);
return;
+
case IMSG_DNS_MX:
- log_debug("debug: dns: lookup mx \"%s\"", query->host);
- s->as = res_query_async(query->host, C_IN, T_MX, NULL, 0, NULL);
- stat_increment("lka.session.mx", 1);
- if (s->as == NULL) {
- log_debug("debug: dns_async: asr_query_dns error");
- break;
- }
- dns_asr_dispatch_mx(s);
+ m_get_string(&m, &domain);
+ m_end(&m);
+ strlcpy(s->name, domain, sizeof(s->name));
+ as = res_query_async(s->name, C_IN, T_MX, NULL, 0, NULL);
+ async_run_event(as, dns_dispatch_mx, s);
return;
- default:
- log_debug("debug: dns_async: bad request");
- break;
- }
- stat_increment("lka.failure", 1);
- dnssession_destroy(s);
-}
+ case IMSG_DNS_MX_PREFERENCE:
+ m_get_string(&m, &domain);
+ m_get_string(&m, &mx);
+ m_end(&m);
+ strlcpy(s->name, mx, sizeof(s->name));
+ as = res_query_async(domain, C_IN, T_MX, NULL, 0, NULL);
+ async_run_event(as, dns_dispatch_mx_preference, s);
+ return;
-static void
-dns_reply(struct dns *query, int type)
-{
- imsg_compose_event(query->asker, type, 0, 0, -1, query, sizeof(*query));
+ default:
+ log_warnx("warn: bad dns request %i", s->type);
+ fatal(NULL);
+ }
}
static void
-dns_asr_event_set(struct dnssession *s, struct async_res *ar)
+dns_dispatch_host(int ev, struct async_res *ar, void *arg)
{
- struct timeval tv = { 0, 0 };
+ struct dns_session *s;
+ struct dns_lookup *lookup = arg;
+ struct addrinfo *ai;
+
+ s = lookup->session;
+
+ for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) {
+ s->mxfound++;
+ m_create(s->p, IMSG_DNS_HOST, 0, 0, -1, 128);
+ m_add_id(s->p, s->reqid);
+ m_add_sockaddr(s->p, ai->ai_addr);
+ m_add_int(s->p, lookup->preference);
+ m_close(s->p);
+ }
+ free(lookup);
+ if (ar->ar_addrinfo)
+ freeaddrinfo(ar->ar_addrinfo);
- tv.tv_usec = ar->ar_timeout * 1000;
- event_set(&s->ev, ar->ar_fd,
- ar->ar_cond == ASYNC_READ ? EV_READ : EV_WRITE, dns_asr_handler, s);
- event_add(&s->ev, &tv);
-}
+ if (ar->ar_gai_errno)
+ s->error = ar->ar_gai_errno;
-static void
-dns_asr_handler(int fd, short event, void *arg)
-{
- struct dnssession *s = arg;
+ if (--s->refcount)
+ return;
- switch (s->query.type) {
- case IMSG_DNS_HOST:
- dns_asr_dispatch_host(s);
- break;
- case IMSG_DNS_PTR:
- dns_asr_dispatch_cname(s);
- break;
- case IMSG_DNS_MX:
- dns_asr_dispatch_mx(s);
- break;
- default:
- fatalx("bad query type");
- }
+ m_create(s->p, IMSG_DNS_HOST_END, 0, 0, -1, 24);
+ m_add_id(s->p, s->reqid);
+ m_add_int(s->p, s->mxfound ? DNS_OK : DNS_ENOTFOUND);
+ m_close(s->p);
+ free(s);
}
-static int
-dns_asr_error(int ar_err)
+static void
+dns_dispatch_ptr(int ev, struct async_res *ar, void *arg)
{
- switch (ar_err) {
- case 0:
- return DNS_OK;
- case NO_DATA:
- case NO_RECOVERY:
- stat_increment("lka.failure", 1);
- return DNS_EINVAL;
- default:
- return DNS_RETRY;
- }
+ struct dns_session *s = arg;
+
+ /* The error code could be more precise, but we don't currently care */
+ m_create(s->p, IMSG_DNS_PTR, 0, 0, -1, 512);
+ m_add_id(s->p, s->reqid);
+ m_add_int(s->p, ar->ar_gai_errno ? DNS_ENOTFOUND : DNS_OK);
+ if (ar->ar_gai_errno == 0)
+ m_add_string(s->p, s->name);
+ m_close(s->p);
+ free(s);
}
static void
-dns_asr_dispatch_mx(struct dnssession *s)
+dns_dispatch_mx(int ev, struct async_res *ar, void *arg)
{
- struct dns *query = &s->query;
- struct async_res ar;
+ struct dns_session *s = arg;
struct unpack pack;
struct header h;
struct query q;
struct rr rr;
char buf[512];
-
- if (async_run(s->as, &ar) == ASYNC_COND) {
- dns_asr_event_set(s, &ar);
+ size_t found;
+
+ if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA) {
+
+ m_create(s->p, IMSG_DNS_HOST_END, 0, 0, -1, 24);
+ m_add_id(s->p, s->reqid);
+ if (ar->ar_rcode == NXDOMAIN)
+ m_add_int(s->p, DNS_ENONAME);
+ else if (ar->ar_h_errno == NO_RECOVERY)
+ m_add_int(s->p, DNS_EINVAL);
+ else
+ m_add_int(s->p, DNS_RETRY);
+ m_close(s->p);
+ free(s);
+ free(ar->ar_data);
return;
}
- if (ar.ar_h_errno && ar.ar_h_errno != NO_DATA) {
- query->error = ar.ar_rcode == NXDOMAIN ? \
- DNS_ENONAME : dns_asr_error(ar.ar_h_errno);
- dns_reply(query, IMSG_DNS_HOST_END);
- dnssession_destroy(s);
- free(ar.ar_data);
- return;
- }
-
- unpack_init(&pack, ar.ar_data, ar.ar_datalen);
+ unpack_init(&pack, ar->ar_data, ar->ar_datalen);
unpack_header(&pack, &h);
unpack_query(&pack, &q);
+ found = 0;
for (; h.ancount; h.ancount--) {
unpack_rr(&pack, &rr);
if (rr.rr_type != T_MX)
continue;
print_dname(rr.rr.mx.exchange, buf, sizeof(buf));
buf[strlen(buf) - 1] = '\0';
- dnssession_mx_insert(s, buf, rr.rr.mx.preference);
+ dns_lookup_host(s, buf, rr.rr.mx.preference);
+ found++;
}
-
- free(ar.ar_data);
+ free(ar->ar_data);
/* fallback to host if no MX is found. */
- if (TAILQ_EMPTY(&s->mx))
- dnssession_mx_insert(s, query->host, 0);
-
- /* Now we have a sorted list of MX to resolve. Simply "turn" this
- * MX session into a regular host session.
- */
- s->as = NULL;
- s->query.type = IMSG_DNS_HOST;
- s->query.error = DNS_ENOTFOUND; /* override later */
- dns_asr_dispatch_host(s);
+ if (found == 0)
+ dns_lookup_host(s, s->name, 0);
}
static void
-dns_asr_dispatch_host(struct dnssession *s)
+dns_dispatch_mx_preference(int ev, struct async_res *ar, void *arg)
{
- struct dns *query = &s->query;
- struct mx *mx;
- struct async_res ar;
- struct addrinfo hints, *ai;
-
-next:
- /* query all listed hosts in turn */
- while (s->as == NULL) {
-
- mx = TAILQ_FIRST(&s->mx);
- if (mx == NULL || (s->preference != -1
- && s->preference <= mx->preference)) {
- if (mx)
- log_debug("debug: dns: ignoring mx with < pri");
- if (s->mxfound)
- query->error = DNS_OK;
- dns_reply(query, IMSG_DNS_HOST_END);
- dnssession_destroy(s);
- return;
- }
-
- log_debug("debug: dns: resolving address for \"%s\"", mx->host);
-
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = PF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
- s->as = getaddrinfo_async(mx->host, NULL, &hints, NULL);
- TAILQ_REMOVE(&s->mx, mx, entry);
- free(mx->host);
- free(mx);
- }
-
- if (async_run(s->as, &ar) == ASYNC_COND) {
- dns_asr_event_set(s, &ar);
- return;
+ struct dns_session *s = arg;
+ struct unpack pack;
+ struct header h;
+ struct query q;
+ struct rr rr;
+ char buf[512];
+ int error;
+
+ if (ar->ar_h_errno) {
+ if (ar->ar_rcode == NXDOMAIN)
+ error = DNS_ENONAME;
+ else if (ar->ar_h_errno == NO_RECOVERY
+ || ar->ar_h_errno == NO_DATA)
+ error = DNS_EINVAL;
+ else
+ error = DNS_RETRY;
}
-
- if (ar.ar_gai_errno == 0) {
- for (ai = ar.ar_addrinfo; ai; ai = ai->ai_next) {
- memcpy(&query->ss, ai->ai_addr, ai->ai_addrlen);
- log_debug("debug: dns: got address %s",
- ss_to_text(&query->ss));
- dns_reply(query, IMSG_DNS_HOST);
- s->mxfound++;
+ else {
+ error = DNS_ENOTFOUND;
+ unpack_init(&pack, ar->ar_data, ar->ar_datalen);
+ unpack_header(&pack, &h);
+ unpack_query(&pack, &q);
+ for (; h.ancount; h.ancount--) {
+ unpack_rr(&pack, &rr);
+ if (rr.rr_type != T_MX)
+ continue;
+ print_dname(rr.rr.mx.exchange, buf, sizeof(buf));
+ buf[strlen(buf) - 1] = '\0';
+ if (!strcasecmp(s->name, buf)) {
+ error = DNS_OK;
+ break;
+ }
}
- freeaddrinfo(ar.ar_addrinfo);
}
- s->as = NULL;
- goto next;
+ free(ar->ar_data);
+
+ m_create(s->p, IMSG_DNS_MX_PREFERENCE, 0, 0, -1, 36);
+ m_add_id(s->p, s->reqid);
+ m_add_int(s->p, error);
+ if (error == DNS_OK)
+ m_add_int(s->p, rr.rr.mx.preference);
+ m_close(s->p);
+ free(s);
}
static void
-dns_asr_dispatch_cname(struct dnssession *s)
+dns_lookup_host(struct dns_session *s, const char *host, int preference)
{
- struct dns *query = &s->query;
- struct async_res ar;
-
- if (async_run(s->as, &ar) == ASYNC_COND) {
- dns_asr_event_set(s, &ar);
- return;
- }
-
- /* the error code could be more precise, but we don't currently care */
- query->error = ar.ar_gai_errno ? DNS_ENOTFOUND : DNS_OK;
- dns_reply(query, IMSG_DNS_PTR);
- dnssession_destroy(s);
+ struct dns_lookup *lookup;
+ struct addrinfo hints;
+ struct async *as;
+
+ lookup = xcalloc(1, sizeof *lookup, "dns_lookup_host");
+ lookup->preference = preference;
+ lookup->session = s;
+ s->refcount++;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ as = getaddrinfo_async(host, NULL, &hints, NULL);
+ async_run_event(as, dns_dispatch_host, lookup);
}
-static struct dnssession *
-dnssession_init(struct dns *query)
-{
- struct dnssession *s;
-
- s = xcalloc(1, sizeof(struct dnssession), "dnssession_init");
-
- stat_increment("lka.session", 1);
-
- s->id = query->id;
- s->query = *query;
- s->preference = -1;
+/* Generic libevent glue for asr */
- TAILQ_INIT(&s->mx);
+struct async_event {
+ struct async *async;
+ struct event ev;
+ void (*callback)(int, struct async_res *, void *);
+ void *arg;
+};
- return (s);
-}
+static void async_event_dispatch(int, short, void *);
-static void
-dnssession_destroy(struct dnssession *s)
+struct async_event *
+async_run_event(struct async * async,
+ void (*cb)(int, struct async_res *, void *), void *arg)
{
- struct mx *mx;
-
- stat_decrement("lka.session", 1);
- event_del(&s->ev);
-
- while ((mx = TAILQ_FIRST(&s->mx))) {
- TAILQ_REMOVE(&s->mx, mx, entry);
- free(mx->host);
- free(mx);
- }
-
- free(s);
+ struct async_event *aev;
+ struct timeval tv;
+
+ aev = calloc(1, sizeof *aev);
+ if (aev == NULL)
+ return (NULL);
+ aev->async = async;
+ aev->callback = cb;
+ aev->arg = arg;
+ tv.tv_sec = 0;
+ tv.tv_usec = 1;
+ evtimer_set(&aev->ev, async_event_dispatch, aev);
+ evtimer_add(&aev->ev, &tv);
+ return (aev);
}
static void
-dnssession_mx_insert(struct dnssession *s, const char *host, int preference)
+async_event_dispatch(int fd, short ev, void *arg)
{
- struct mx *mx, *e;
-
- mx = xcalloc(1, sizeof *mx, "dnssession_mx_insert");
- mx->host = xstrdup(host, "dnssession_mx_insert");
- mx->preference = preference;
-
- log_debug("debug: dns: found mx \"%s\" with preference %i",
- host, preference);
-
- TAILQ_FOREACH(e, &s->mx, entry) {
- if (mx->preference <= e->preference) {
- TAILQ_INSERT_BEFORE(e, mx, entry);
- goto end;
- }
- }
-
- TAILQ_INSERT_TAIL(&s->mx, mx, entry);
-
-end:
- if (s->preference == -1 && s->query.backup[0]
- && !strcasecmp(host, s->query.backup)) {
- log_debug("debug: dns: found our backup preference");
- s->preference = preference;
+ struct async_event *aev = arg;
+ struct async_res ar;
+ int r;
+ struct timeval tv;
+
+ while ((r = async_run(aev->async, &ar)) == ASYNC_YIELD)
+ aev->callback(r, &ar, aev->arg);
+
+ event_del(&aev->ev);
+ if (r == ASYNC_COND) {
+ event_set(&aev->ev, ar.ar_fd,
+ ar.ar_cond == ASYNC_READ ? EV_READ : EV_WRITE,
+ async_event_dispatch, aev);
+ tv.tv_sec = ar.ar_timeout / 1000;
+ tv.tv_usec = (ar.ar_timeout % 1000) * 1000;
+ event_add(&aev->ev, &tv);
+ } else { /* ASYNC_DONE */
+ aev->callback(r, &ar, aev->arg);
+ free(aev);
}
}