diff options
Diffstat (limited to 'dns.c')
-rw-r--r-- | dns.c | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/dns.c b/dns.c new file mode 100644 index 0000000000000..fc5213f1ca605 --- /dev/null +++ b/dns.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2008 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Simon 'corecode' Schubert <corecode@fs.ei.tum.de> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <errno.h> +#include <netdb.h> +#include <resolv.h> +#include <string.h> +#include <stdlib.h> + +#include "dma.h" + +static int +sort_pref(const void *a, const void *b) +{ + const struct mx_hostentry *ha = a, *hb = b; + int v; + + /* sort increasing by preference primarily */ + v = ha->pref - hb->pref; + if (v != 0) + return (v); + + /* sort PF_INET6 before PF_INET */ + v = - (ha->ai.ai_family - hb->ai.ai_family); + return (v); +} + +static int +add_host(int pref, const char *host, int port, struct mx_hostentry **he, size_t *ps) +{ + struct addrinfo hints, *res, *res0 = NULL; + char servname[10]; + struct mx_hostentry *p; + const int count_inc = 10; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + snprintf(servname, sizeof(servname), "%d", port); + err = getaddrinfo(host, servname, &hints, &res0); + if (err) + return (err == EAI_AGAIN ? 1 : -1); + + for (res = res0; res != NULL; res = res->ai_next) { + if (*ps + 1 >= roundup(*ps, count_inc)) { + size_t newsz = roundup(*ps + 2, count_inc); + *he = reallocf(*he, newsz * sizeof(**he)); + if (*he == NULL) + goto out; + } + + p = &(*he)[*ps]; + strlcpy(p->host, host, sizeof(p->host)); + p->pref = pref; + p->ai = *res; + p->ai.ai_addr = NULL; + bcopy(res->ai_addr, &p->sa, p->ai.ai_addrlen); + + getnameinfo((struct sockaddr *)&p->sa, p->ai.ai_addrlen, + p->addr, sizeof(p->addr), + NULL, 0, NI_NUMERICHOST); + + (*ps)++; + } + freeaddrinfo(res0); + + return (0); + +out: + if (res0 != NULL) + freeaddrinfo(res0); + return (1); +} + +int +dns_get_mx_list(const char *host, int port, struct mx_hostentry **he, int no_mx) +{ + char outname[MAXDNAME]; + ns_msg msg; + ns_rr rr; + const char *searchhost; + const unsigned char *cp; + unsigned char *ans; + struct mx_hostentry *hosts = NULL; + size_t nhosts = 0; + size_t anssz; + int pref; + int cname_recurse; + int have_mx = 0; + int err; + int i; + + res_init(); + searchhost = host; + cname_recurse = 0; + + anssz = 65536; + ans = malloc(anssz); + if (ans == NULL) + return (1); + + if (no_mx) + goto out; + +repeat: + err = res_search(searchhost, ns_c_in, ns_t_mx, ans, anssz); + if (err < 0) { + switch (h_errno) { + case NO_DATA: + /* + * Host exists, but no MX (or CNAME) entry. + * Not an error, use host name instead. + */ + goto out; + case TRY_AGAIN: + /* transient error */ + goto transerr; + case NO_RECOVERY: + case HOST_NOT_FOUND: + default: + errno = ENOENT; + goto err; + } + } + + if (!ns_initparse(ans, anssz, &msg)) + goto transerr; + + switch (ns_msg_getflag(msg, ns_f_rcode)) { + case ns_r_noerror: + break; + case ns_r_nxdomain: + goto err; + default: + goto transerr; + } + + for (i = 0; i < ns_msg_count(msg, ns_s_an); i++) { + if (ns_parserr(&msg, ns_s_an, i, &rr)) + goto transerr; + + cp = ns_rr_rdata(rr); + + switch (ns_rr_type(rr)) { + case ns_t_mx: + have_mx = 1; + pref = ns_get16(cp); + cp += 2; + err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), + cp, outname, sizeof(outname)); + if (err < 0) + goto transerr; + + err = add_host(pref, outname, port, &hosts, &nhosts); + if (err == -1) + goto err; + break; + + case ns_t_cname: + err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), + cp, outname, sizeof(outname)); + if (err < 0) + goto transerr; + + /* Prevent a CNAME loop */ + if (cname_recurse++ > 10) + goto err; + + searchhost = outname; + goto repeat; + + default: + break; + } + } + +out: + err = 0; + if (0) { +transerr: + if (nhosts == 0) + err = 1; + } + if (0) { +err: + err = -1; + } + + free(ans); + + if (err == 0) { + if (!have_mx) { + /* + * If we didn't find any MX, use the hostname instead. + */ + err = add_host(0, host, port, &hosts, &nhosts); + } else if (nhosts == 0) { + /* + * We did get MX, but couldn't resolve any of them + * due to transient errors. + */ + err = 1; + } + } + + if (nhosts > 0) { + qsort(hosts, nhosts, sizeof(*hosts), sort_pref); + /* terminate list */ + *hosts[nhosts].host = 0; + } else { + if (hosts != NULL) + free(hosts); + hosts = NULL; + } + + *he = hosts; + return (err); + + free(ans); + if (hosts != NULL) + free(hosts); + return (err); +} + +#if defined(TESTING) +int +main(int argc, char **argv) +{ + struct mx_hostentry *he, *p; + int err; + + err = dns_get_mx_list(argv[1], 53, &he, 0); + if (err) + return (err); + + for (p = he; *p->host != 0; p++) { + printf("%d\t%s\t%s\n", p->pref, p->host, p->addr); + } + + return (0); +} +#endif |