diff options
Diffstat (limited to 'smtpd/dns.c')
-rw-r--r-- | smtpd/dns.c | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/smtpd/dns.c b/smtpd/dns.c new file mode 100644 index 00000000..a3107e89 --- /dev/null +++ b/smtpd/dns.c @@ -0,0 +1,379 @@ +/* $OpenBSD: dns.c,v 1.89 2019/09/18 11:26:30 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2011-2014 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 + * 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/socket.h> +#include <sys/tree.h> +#include <sys/queue.h> +#include <sys/uio.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <netdb.h> + +#include <asr.h> +#include <event.h> +#include <netdb.h> +#include <resolv.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" +#include "unpack_dns.h" + +/* On OpenBSD, this function is not needed because we don't free addrinfo */ +#if defined(NOOP_ASR_FREEADDRINFO) +#define asr_freeaddrinfo(x) do { } while(0); +#endif + +struct dns_lookup { + struct dns_session *session; + char *host; + int preference; +}; + +struct dns_session { + struct mproc *p; + uint64_t reqid; + int type; + char name[HOST_NAME_MAX+1]; + size_t mxfound; + int error; + int refcount; +}; + +static void dns_lookup_host(struct dns_session *, const char *, int); +static void dns_dispatch_host(struct asr_result *, void *); +static void dns_dispatch_mx(struct asr_result *, void *); +static void dns_dispatch_mx_preference(struct asr_result *, void *); + +static int +domainname_is_addr(const char *s, struct sockaddr *sa, socklen_t *sl) +{ + struct addrinfo hints, *res; + socklen_t sl2; + size_t l; + char buf[SMTPD_MAXDOMAINPARTSIZE]; + int i6, error; + + if (*s != '[') + return (0); + + i6 = (strncasecmp("[IPv6:", s, 6) == 0); + s += i6 ? 6 : 1; + + l = strlcpy(buf, s, sizeof(buf)); + if (l >= sizeof(buf) || l == 0 || buf[l - 1] != ']') + return (0); + + buf[l - 1] = '\0'; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_socktype = SOCK_STREAM; + if (i6) + hints.ai_family = AF_INET6; + + res = NULL; + if ((error = getaddrinfo(buf, NULL, &hints, &res))) { + log_warnx("getaddrinfo: %s", gai_strerror(error)); + } + + if (!res) + return (0); + + if (sa && sl) { + sl2 = *sl; + if (sl2 > res->ai_addrlen) + sl2 = res->ai_addrlen; + memmove(sa, res->ai_addr, sl2); + *sl = res->ai_addrlen; + } + + freeaddrinfo(res); + return (1); +} + +void +dns_imsg(struct mproc *p, struct imsg *imsg) +{ + struct sockaddr_storage ss; + struct dns_session *s; + struct sockaddr *sa; + struct asr_query *as; + struct msg m; + const char *domain, *mx, *host; + socklen_t sl; + + s = xcalloc(1, sizeof *s); + s->type = imsg->hdr.type; + s->p = p; + + m_msg(&m, imsg); + m_get_id(&m, &s->reqid); + + switch (s->type) { + + case IMSG_MTA_DNS_HOST: + m_get_string(&m, &host); + m_end(&m); + dns_lookup_host(s, host, -1); + return; + + case IMSG_MTA_DNS_MX: + m_get_string(&m, &domain); + m_end(&m); + (void)strlcpy(s->name, domain, sizeof(s->name)); + + sa = (struct sockaddr *)&ss; + sl = sizeof(ss); + + if (domainname_is_addr(domain, sa, &sl)) { + m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_string(s->p, sockaddr_to_text(sa)); + m_add_sockaddr(s->p, sa); + m_add_int(s->p, -1); + m_close(s->p); + + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, DNS_OK); + m_close(s->p); + free(s); + return; + } + + as = res_query_async(s->name, C_IN, T_MX, NULL); + if (as == NULL) { + log_warn("warn: res_query_async: %s", s->name); + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, DNS_EINVAL); + m_close(s->p); + free(s); + return; + } + + event_asr_run(as, dns_dispatch_mx, s); + return; + + case IMSG_MTA_DNS_MX_PREFERENCE: + m_get_string(&m, &domain); + m_get_string(&m, &mx); + m_end(&m); + (void)strlcpy(s->name, mx, sizeof(s->name)); + + as = res_query_async(domain, C_IN, T_MX, NULL); + if (as == NULL) { + m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, DNS_ENOTFOUND); + m_close(s->p); + free(s); + return; + } + + event_asr_run(as, dns_dispatch_mx_preference, s); + return; + + default: + log_warnx("warn: bad dns request %d", s->type); + fatal(NULL); + } +} + +static void +dns_dispatch_host(struct asr_result *ar, void *arg) +{ + 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_MTA_DNS_HOST, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_string(s->p, lookup->host); + m_add_sockaddr(s->p, ai->ai_addr); + m_add_int(s->p, lookup->preference); + m_close(s->p); + } + free(lookup->host); + free(lookup); + if (ar->ar_addrinfo) + asr_freeaddrinfo(ar->ar_addrinfo); + + if (ar->ar_gai_errno) + s->error = ar->ar_gai_errno; + + if (--s->refcount) + return; + + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + 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 void +dns_dispatch_mx(struct asr_result *ar, void *arg) +{ + struct dns_session *s = arg; + struct unpack pack; + struct dns_header h; + struct dns_query q; + struct dns_rr rr; + char buf[512]; + size_t found; + + if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA && + ar->ar_h_errno != NOTIMP) { + + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + 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; + } + + 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'; + dns_lookup_host(s, buf, rr.rr.mx.preference); + found++; + } + free(ar->ar_data); + + /* fallback to host if no MX is found. */ + if (found == 0) + dns_lookup_host(s, s->name, 0); +} + +static void +dns_dispatch_mx_preference(struct asr_result *ar, void *arg) +{ + struct dns_session *s = arg; + struct unpack pack; + struct dns_header h; + struct dns_query q; + struct dns_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; + } + 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; + } + } + } + + free(ar->ar_data); + + m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); + 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_lookup_host(struct dns_session *s, const char *host, int preference) +{ + struct dns_lookup *lookup; + struct addrinfo hints; + char hostcopy[HOST_NAME_MAX+1]; + char *p; + void *as; + + lookup = xcalloc(1, sizeof *lookup); + lookup->preference = preference; + lookup->host = xstrdup(host); + lookup->session = s; + s->refcount++; + + if (*host == '[') { + if (strncasecmp("[IPv6:", host, 6) == 0) + host += 6; + else + host += 1; + (void)strlcpy(hostcopy, host, sizeof hostcopy); + p = strchr(hostcopy, ']'); + if (p) + *p = 0; + host = hostcopy; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_ADDRCONFIG; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + as = getaddrinfo_async(host, NULL, &hints, NULL); + event_asr_run(as, dns_dispatch_host, lookup); +} |