xref: /dragonfly/libexec/dma/dns.c (revision 4e8d31bc)
13021968aSSimon Schubert /*
237d59876SJohn Marino  * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>.
33021968aSSimon Schubert  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
43021968aSSimon Schubert  *
53021968aSSimon Schubert  * This code is derived from software contributed to The DragonFly Project
637d59876SJohn Marino  * by Simon Schubert <2@0x2c.org>.
73021968aSSimon Schubert  *
83021968aSSimon Schubert  * Redistribution and use in source and binary forms, with or without
93021968aSSimon Schubert  * modification, are permitted provided that the following conditions
103021968aSSimon Schubert  * are met:
113021968aSSimon Schubert  *
123021968aSSimon Schubert  * 1. Redistributions of source code must retain the above copyright
133021968aSSimon Schubert  *    notice, this list of conditions and the following disclaimer.
143021968aSSimon Schubert  * 2. Redistributions in binary form must reproduce the above copyright
153021968aSSimon Schubert  *    notice, this list of conditions and the following disclaimer in
163021968aSSimon Schubert  *    the documentation and/or other materials provided with the
173021968aSSimon Schubert  *    distribution.
183021968aSSimon Schubert  * 3. Neither the name of The DragonFly Project nor the names of its
193021968aSSimon Schubert  *    contributors may be used to endorse or promote products derived
203021968aSSimon Schubert  *    from this software without specific, prior written permission.
213021968aSSimon Schubert  *
223021968aSSimon Schubert  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
233021968aSSimon Schubert  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
243021968aSSimon Schubert  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
253021968aSSimon Schubert  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
263021968aSSimon Schubert  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
273021968aSSimon Schubert  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
283021968aSSimon Schubert  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
293021968aSSimon Schubert  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
303021968aSSimon Schubert  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
313021968aSSimon Schubert  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
323021968aSSimon Schubert  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
333021968aSSimon Schubert  * SUCH DAMAGE.
343021968aSSimon Schubert  */
353021968aSSimon Schubert 
363021968aSSimon Schubert #include <sys/types.h>
37*92fe556dSDaniel Fojt #include <sys/param.h>
383021968aSSimon Schubert #include <netinet/in.h>
393021968aSSimon Schubert #include <arpa/inet.h>
403021968aSSimon Schubert #include <arpa/nameser.h>
413021968aSSimon Schubert #include <errno.h>
423021968aSSimon Schubert #include <netdb.h>
433021968aSSimon Schubert #include <resolv.h>
443021968aSSimon Schubert #include <string.h>
453021968aSSimon Schubert #include <stdlib.h>
463021968aSSimon Schubert 
473021968aSSimon Schubert #include "dma.h"
483021968aSSimon Schubert 
493021968aSSimon Schubert static int
sort_pref(const void * a,const void * b)503021968aSSimon Schubert sort_pref(const void *a, const void *b)
513021968aSSimon Schubert {
523021968aSSimon Schubert 	const struct mx_hostentry *ha = a, *hb = b;
533021968aSSimon Schubert 	int v;
543021968aSSimon Schubert 
553021968aSSimon Schubert 	/* sort increasing by preference primarily */
563021968aSSimon Schubert 	v = ha->pref - hb->pref;
573021968aSSimon Schubert 	if (v != 0)
583021968aSSimon Schubert 		return (v);
593021968aSSimon Schubert 
603021968aSSimon Schubert 	/* sort PF_INET6 before PF_INET */
613021968aSSimon Schubert 	v = - (ha->ai.ai_family - hb->ai.ai_family);
623021968aSSimon Schubert 	return (v);
633021968aSSimon Schubert }
643021968aSSimon Schubert 
653021968aSSimon Schubert static int
add_host(int pref,const char * host,int port,struct mx_hostentry ** he,size_t * ps)663021968aSSimon Schubert add_host(int pref, const char *host, int port, struct mx_hostentry **he, size_t *ps)
673021968aSSimon Schubert {
683021968aSSimon Schubert 	struct addrinfo hints, *res, *res0 = NULL;
693021968aSSimon Schubert 	char servname[10];
703021968aSSimon Schubert 	struct mx_hostentry *p;
713021968aSSimon Schubert 	const int count_inc = 10;
723021968aSSimon Schubert 
733021968aSSimon Schubert 	memset(&hints, 0, sizeof(hints));
743021968aSSimon Schubert 	hints.ai_family = PF_UNSPEC;
753021968aSSimon Schubert 	hints.ai_socktype = SOCK_STREAM;
763021968aSSimon Schubert 	hints.ai_protocol = IPPROTO_TCP;
773021968aSSimon Schubert 
783021968aSSimon Schubert 	snprintf(servname, sizeof(servname), "%d", port);
79*92fe556dSDaniel Fojt 	switch (getaddrinfo(host, servname, &hints, &res0)) {
80*92fe556dSDaniel Fojt 	case 0:
81*92fe556dSDaniel Fojt 		break;
82*92fe556dSDaniel Fojt 	case EAI_AGAIN:
83*92fe556dSDaniel Fojt 	case EAI_NONAME:
84*92fe556dSDaniel Fojt 		/*
85*92fe556dSDaniel Fojt 		 * EAI_NONAME gets returned for:
86*92fe556dSDaniel Fojt 		 * SMARTHOST set but DNS server not reachable -> defer
87*92fe556dSDaniel Fojt 		 * SMARTHOST set but DNS server returns "host does not exist"
88*92fe556dSDaniel Fojt 		 *           -> buggy configuration
89*92fe556dSDaniel Fojt 		 *           -> either defer or bounce would be ok -> defer
90*92fe556dSDaniel Fojt 		 * MX entry was returned by DNS server but name doesn't resolve
91*92fe556dSDaniel Fojt 		 *           -> hopefully transient situation -> defer
92*92fe556dSDaniel Fojt 		 * all other DNS problems should have been caught earlier
93*92fe556dSDaniel Fojt 		 * in dns_get_mx_list().
94*92fe556dSDaniel Fojt 		 */
95*92fe556dSDaniel Fojt 		goto out;
96*92fe556dSDaniel Fojt 	default:
97*92fe556dSDaniel Fojt 		return(-1);
98*92fe556dSDaniel Fojt 	}
993021968aSSimon Schubert 
1003021968aSSimon Schubert 	for (res = res0; res != NULL; res = res->ai_next) {
1013021968aSSimon Schubert 		if (*ps + 1 >= roundup(*ps, count_inc)) {
1023021968aSSimon Schubert 			size_t newsz = roundup(*ps + 2, count_inc);
1033021968aSSimon Schubert 			*he = reallocf(*he, newsz * sizeof(**he));
1043021968aSSimon Schubert 			if (*he == NULL)
1053021968aSSimon Schubert 				goto out;
1063021968aSSimon Schubert 		}
1073021968aSSimon Schubert 
1083021968aSSimon Schubert 		p = &(*he)[*ps];
1093021968aSSimon Schubert 		strlcpy(p->host, host, sizeof(p->host));
1103021968aSSimon Schubert 		p->pref = pref;
1113021968aSSimon Schubert 		p->ai = *res;
1123021968aSSimon Schubert 		p->ai.ai_addr = NULL;
1133021968aSSimon Schubert 		bcopy(res->ai_addr, &p->sa, p->ai.ai_addrlen);
1143021968aSSimon Schubert 
115725a35c6SSimon Schubert 		getnameinfo((struct sockaddr *)&p->sa, p->ai.ai_addrlen,
1163021968aSSimon Schubert 			    p->addr, sizeof(p->addr),
1173021968aSSimon Schubert 			    NULL, 0, NI_NUMERICHOST);
1183021968aSSimon Schubert 
1193021968aSSimon Schubert 		(*ps)++;
1203021968aSSimon Schubert 	}
1213021968aSSimon Schubert 	freeaddrinfo(res0);
1223021968aSSimon Schubert 
123c8b07ee5SSascha Wildner 	return (0);
1243021968aSSimon Schubert 
1253021968aSSimon Schubert out:
1263021968aSSimon Schubert 	if (res0 != NULL)
1273021968aSSimon Schubert 		freeaddrinfo(res0);
128c8b07ee5SSascha Wildner 	return (1);
1293021968aSSimon Schubert }
1303021968aSSimon Schubert 
1313021968aSSimon Schubert int
dns_get_mx_list(const char * host,int port,struct mx_hostentry ** he,int no_mx)1323021968aSSimon Schubert dns_get_mx_list(const char *host, int port, struct mx_hostentry **he, int no_mx)
1333021968aSSimon Schubert {
1343021968aSSimon Schubert 	char outname[MAXDNAME];
1353021968aSSimon Schubert 	ns_msg msg;
1363021968aSSimon Schubert 	ns_rr rr;
1373021968aSSimon Schubert 	const char *searchhost;
138c8b07ee5SSascha Wildner 	const unsigned char *cp;
139c8b07ee5SSascha Wildner 	unsigned char *ans;
1403021968aSSimon Schubert 	struct mx_hostentry *hosts = NULL;
1413021968aSSimon Schubert 	size_t nhosts = 0;
1423021968aSSimon Schubert 	size_t anssz;
1433021968aSSimon Schubert 	int pref;
1443021968aSSimon Schubert 	int cname_recurse;
145c8b07ee5SSascha Wildner 	int have_mx = 0;
1463021968aSSimon Schubert 	int err;
1473021968aSSimon Schubert 	int i;
1483021968aSSimon Schubert 
1493021968aSSimon Schubert 	res_init();
1503021968aSSimon Schubert 	searchhost = host;
1513021968aSSimon Schubert 	cname_recurse = 0;
1523021968aSSimon Schubert 
1533021968aSSimon Schubert 	anssz = 65536;
1543021968aSSimon Schubert 	ans = malloc(anssz);
1553021968aSSimon Schubert 	if (ans == NULL)
1563021968aSSimon Schubert 		return (1);
1573021968aSSimon Schubert 
1583021968aSSimon Schubert 	if (no_mx)
1593021968aSSimon Schubert 		goto out;
1603021968aSSimon Schubert 
1613021968aSSimon Schubert repeat:
1623021968aSSimon Schubert 	err = res_search(searchhost, ns_c_in, ns_t_mx, ans, anssz);
1633021968aSSimon Schubert 	if (err < 0) {
1643021968aSSimon Schubert 		switch (h_errno) {
1653021968aSSimon Schubert 		case NO_DATA:
1663021968aSSimon Schubert 			/*
1673021968aSSimon Schubert 			 * Host exists, but no MX (or CNAME) entry.
1683021968aSSimon Schubert 			 * Not an error, use host name instead.
1693021968aSSimon Schubert 			 */
1703021968aSSimon Schubert 			goto out;
1713021968aSSimon Schubert 		case TRY_AGAIN:
1723021968aSSimon Schubert 			/* transient error */
1733021968aSSimon Schubert 			goto transerr;
1743021968aSSimon Schubert 		case NO_RECOVERY:
1753021968aSSimon Schubert 		case HOST_NOT_FOUND:
1763021968aSSimon Schubert 		default:
1773021968aSSimon Schubert 			errno = ENOENT;
1783021968aSSimon Schubert 			goto err;
1793021968aSSimon Schubert 		}
1803021968aSSimon Schubert 	}
1813021968aSSimon Schubert 
1823021968aSSimon Schubert 	if (!ns_initparse(ans, anssz, &msg))
1833021968aSSimon Schubert 		goto transerr;
1843021968aSSimon Schubert 
1853021968aSSimon Schubert 	switch (ns_msg_getflag(msg, ns_f_rcode)) {
1863021968aSSimon Schubert 	case ns_r_noerror:
1873021968aSSimon Schubert 		break;
1883021968aSSimon Schubert 	case ns_r_nxdomain:
1893021968aSSimon Schubert 		goto err;
1903021968aSSimon Schubert 	default:
1913021968aSSimon Schubert 		goto transerr;
1923021968aSSimon Schubert 	}
1933021968aSSimon Schubert 
1943021968aSSimon Schubert 	for (i = 0; i < ns_msg_count(msg, ns_s_an); i++) {
1953021968aSSimon Schubert 		if (ns_parserr(&msg, ns_s_an, i, &rr))
1963021968aSSimon Schubert 			goto transerr;
1973021968aSSimon Schubert 
198c8b07ee5SSascha Wildner 		cp = ns_rr_rdata(rr);
1993021968aSSimon Schubert 
2003021968aSSimon Schubert 		switch (ns_rr_type(rr)) {
2013021968aSSimon Schubert 		case ns_t_mx:
202c8b07ee5SSascha Wildner 			have_mx = 1;
2033021968aSSimon Schubert 			pref = ns_get16(cp);
2043021968aSSimon Schubert 			cp += 2;
2053021968aSSimon Schubert 			err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg),
2063021968aSSimon Schubert 						 cp, outname, sizeof(outname));
2073021968aSSimon Schubert 			if (err < 0)
2083021968aSSimon Schubert 				goto transerr;
2093021968aSSimon Schubert 
210c8b07ee5SSascha Wildner 			err = add_host(pref, outname, port, &hosts, &nhosts);
211c8b07ee5SSascha Wildner 			if (err == -1)
212c8b07ee5SSascha Wildner 				goto err;
2133021968aSSimon Schubert 			break;
2143021968aSSimon Schubert 
2153021968aSSimon Schubert 		case ns_t_cname:
2163021968aSSimon Schubert 			err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg),
2173021968aSSimon Schubert 						 cp, outname, sizeof(outname));
2183021968aSSimon Schubert 			if (err < 0)
2193021968aSSimon Schubert 				goto transerr;
2203021968aSSimon Schubert 
2213021968aSSimon Schubert 			/* Prevent a CNAME loop */
2223021968aSSimon Schubert 			if (cname_recurse++ > 10)
2233021968aSSimon Schubert 				goto err;
2243021968aSSimon Schubert 
2253021968aSSimon Schubert 			searchhost = outname;
2263021968aSSimon Schubert 			goto repeat;
2273021968aSSimon Schubert 
2283021968aSSimon Schubert 		default:
2293021968aSSimon Schubert 			break;
2303021968aSSimon Schubert 		}
2313021968aSSimon Schubert 	}
2323021968aSSimon Schubert 
2333021968aSSimon Schubert out:
2343021968aSSimon Schubert 	err = 0;
2353021968aSSimon Schubert 	if (0) {
2363021968aSSimon Schubert transerr:
2373021968aSSimon Schubert 		if (nhosts == 0)
2383021968aSSimon Schubert 			err = 1;
2393021968aSSimon Schubert 	}
2403021968aSSimon Schubert 	if (0) {
2413021968aSSimon Schubert err:
2423021968aSSimon Schubert 		err = -1;
2433021968aSSimon Schubert 	}
2443021968aSSimon Schubert 
2453021968aSSimon Schubert 	free(ans);
2463021968aSSimon Schubert 
247c8b07ee5SSascha Wildner 	if (err == 0) {
248c8b07ee5SSascha Wildner 		if (!have_mx) {
2493021968aSSimon Schubert 			/*
2503021968aSSimon Schubert 			 * If we didn't find any MX, use the hostname instead.
2513021968aSSimon Schubert 			 */
252c8b07ee5SSascha Wildner 			err = add_host(0, host, port, &hosts, &nhosts);
253c8b07ee5SSascha Wildner 		} else if (nhosts == 0) {
254c8b07ee5SSascha Wildner 			/*
255c8b07ee5SSascha Wildner 			 * We did get MX, but couldn't resolve any of them
256c8b07ee5SSascha Wildner 			 * due to transient errors.
257c8b07ee5SSascha Wildner 			 */
258c8b07ee5SSascha Wildner 			err = 1;
259c8b07ee5SSascha Wildner 		}
2603021968aSSimon Schubert 	}
2613021968aSSimon Schubert 
2623021968aSSimon Schubert 	if (nhosts > 0) {
263c8b07ee5SSascha Wildner 		qsort(hosts, nhosts, sizeof(*hosts), sort_pref);
2643021968aSSimon Schubert 		/* terminate list */
2653021968aSSimon Schubert 		*hosts[nhosts].host = 0;
2663021968aSSimon Schubert 	} else {
2673021968aSSimon Schubert 		if (hosts != NULL)
2683021968aSSimon Schubert 			free(hosts);
2693021968aSSimon Schubert 		hosts = NULL;
2703021968aSSimon Schubert 	}
2713021968aSSimon Schubert 
2723021968aSSimon Schubert 	*he = hosts;
2733021968aSSimon Schubert 	return (err);
2743021968aSSimon Schubert }
2753021968aSSimon Schubert 
2763021968aSSimon Schubert #if defined(TESTING)
2773021968aSSimon Schubert int
main(int argc,char ** argv)2783021968aSSimon Schubert main(int argc, char **argv)
2793021968aSSimon Schubert {
2803021968aSSimon Schubert 	struct mx_hostentry *he, *p;
2813021968aSSimon Schubert 	int err;
2823021968aSSimon Schubert 
2833021968aSSimon Schubert 	err = dns_get_mx_list(argv[1], 53, &he, 0);
2843021968aSSimon Schubert 	if (err)
2853021968aSSimon Schubert 		return (err);
2863021968aSSimon Schubert 
2873021968aSSimon Schubert 	for (p = he; *p->host != 0; p++) {
2883021968aSSimon Schubert 		printf("%d\t%s\t%s\n", p->pref, p->host, p->addr);
2893021968aSSimon Schubert 	}
2903021968aSSimon Schubert 
2913021968aSSimon Schubert 	return (0);
2923021968aSSimon Schubert }
2933021968aSSimon Schubert #endif
294