xref: /freebsd/contrib/dma/dns.c (revision fae50821ae22099d0406090228bc4c5b6af693ad)
1 /*
2  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/types.h>
36 #include <netinet/in.h>
37 #include <arpa/inet.h>
38 #include <arpa/nameser.h>
39 #include <errno.h>
40 #include <netdb.h>
41 #include <resolv.h>
42 #include <string.h>
43 #include <stdlib.h>
44 
45 #include "dma.h"
46 
47 static int
48 sort_pref(const void *a, const void *b)
49 {
50 	const struct mx_hostentry *ha = a, *hb = b;
51 	int v;
52 
53 	/* sort increasing by preference primarily */
54 	v = ha->pref - hb->pref;
55 	if (v != 0)
56 		return (v);
57 
58 	/* sort PF_INET6 before PF_INET */
59 	v = - (ha->ai.ai_family - hb->ai.ai_family);
60 	return (v);
61 }
62 
63 static int
64 add_host(int pref, const char *host, int port, struct mx_hostentry **he, size_t *ps)
65 {
66 	struct addrinfo hints, *res, *res0 = NULL;
67 	char servname[10];
68 	struct mx_hostentry *p;
69 	const int count_inc = 10;
70 	int err;
71 
72 	memset(&hints, 0, sizeof(hints));
73 	hints.ai_family = PF_UNSPEC;
74 	hints.ai_socktype = SOCK_STREAM;
75 	hints.ai_protocol = IPPROTO_TCP;
76 
77 	snprintf(servname, sizeof(servname), "%d", port);
78 	err = getaddrinfo(host, servname, &hints, &res0);
79 	if (err)
80 		return (err == EAI_AGAIN ? 1 : -1);
81 
82 	for (res = res0; res != NULL; res = res->ai_next) {
83 		if (*ps + 1 >= roundup(*ps, count_inc)) {
84 			size_t newsz = roundup(*ps + 2, count_inc);
85 			*he = reallocf(*he, newsz * sizeof(**he));
86 			if (*he == NULL)
87 				goto out;
88 		}
89 
90 		p = &(*he)[*ps];
91 		strlcpy(p->host, host, sizeof(p->host));
92 		p->pref = pref;
93 		p->ai = *res;
94 		p->ai.ai_addr = NULL;
95 		bcopy(res->ai_addr, &p->sa, p->ai.ai_addrlen);
96 
97 		getnameinfo((struct sockaddr *)&p->sa, p->ai.ai_addrlen,
98 			    p->addr, sizeof(p->addr),
99 			    NULL, 0, NI_NUMERICHOST);
100 
101 		(*ps)++;
102 	}
103 	freeaddrinfo(res0);
104 
105 	return (0);
106 
107 out:
108 	if (res0 != NULL)
109 		freeaddrinfo(res0);
110 	return (1);
111 }
112 
113 int
114 dns_get_mx_list(const char *host, int port, struct mx_hostentry **he, int no_mx)
115 {
116 	char outname[MAXDNAME];
117 	ns_msg msg;
118 	ns_rr rr;
119 	const char *searchhost;
120 	const unsigned char *cp;
121 	unsigned char *ans;
122 	struct mx_hostentry *hosts = NULL;
123 	size_t nhosts = 0;
124 	size_t anssz;
125 	int pref;
126 	int cname_recurse;
127 	int have_mx = 0;
128 	int err;
129 	int i;
130 
131 	res_init();
132 	searchhost = host;
133 	cname_recurse = 0;
134 
135 	anssz = 65536;
136 	ans = malloc(anssz);
137 	if (ans == NULL)
138 		return (1);
139 
140 	if (no_mx)
141 		goto out;
142 
143 repeat:
144 	err = res_search(searchhost, ns_c_in, ns_t_mx, ans, anssz);
145 	if (err < 0) {
146 		switch (h_errno) {
147 		case NO_DATA:
148 			/*
149 			 * Host exists, but no MX (or CNAME) entry.
150 			 * Not an error, use host name instead.
151 			 */
152 			goto out;
153 		case TRY_AGAIN:
154 			/* transient error */
155 			goto transerr;
156 		case NO_RECOVERY:
157 		case HOST_NOT_FOUND:
158 		default:
159 			errno = ENOENT;
160 			goto err;
161 		}
162 	}
163 
164 	if (!ns_initparse(ans, anssz, &msg))
165 		goto transerr;
166 
167 	switch (ns_msg_getflag(msg, ns_f_rcode)) {
168 	case ns_r_noerror:
169 		break;
170 	case ns_r_nxdomain:
171 		goto err;
172 	default:
173 		goto transerr;
174 	}
175 
176 	for (i = 0; i < ns_msg_count(msg, ns_s_an); i++) {
177 		if (ns_parserr(&msg, ns_s_an, i, &rr))
178 			goto transerr;
179 
180 		cp = ns_rr_rdata(rr);
181 
182 		switch (ns_rr_type(rr)) {
183 		case ns_t_mx:
184 			have_mx = 1;
185 			pref = ns_get16(cp);
186 			cp += 2;
187 			err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg),
188 						 cp, outname, sizeof(outname));
189 			if (err < 0)
190 				goto transerr;
191 
192 			err = add_host(pref, outname, port, &hosts, &nhosts);
193 			if (err == -1)
194 				goto err;
195 			break;
196 
197 		case ns_t_cname:
198 			err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg),
199 						 cp, outname, sizeof(outname));
200 			if (err < 0)
201 				goto transerr;
202 
203 			/* Prevent a CNAME loop */
204 			if (cname_recurse++ > 10)
205 				goto err;
206 
207 			searchhost = outname;
208 			goto repeat;
209 
210 		default:
211 			break;
212 		}
213 	}
214 
215 out:
216 	err = 0;
217 	if (0) {
218 transerr:
219 		if (nhosts == 0)
220 			err = 1;
221 	}
222 	if (0) {
223 err:
224 		err = -1;
225 	}
226 
227 	free(ans);
228 
229 	if (err == 0) {
230 		if (!have_mx) {
231 			/*
232 			 * If we didn't find any MX, use the hostname instead.
233 			 */
234 			err = add_host(0, host, port, &hosts, &nhosts);
235 		} else if (nhosts == 0) {
236 			/*
237 			 * We did get MX, but couldn't resolve any of them
238 			 * due to transient errors.
239 			 */
240 			err = 1;
241 		}
242 	}
243 
244 	if (nhosts > 0) {
245 		qsort(hosts, nhosts, sizeof(*hosts), sort_pref);
246 		/* terminate list */
247 		*hosts[nhosts].host = 0;
248 	} else {
249 		if (hosts != NULL)
250 			free(hosts);
251 		hosts = NULL;
252 	}
253 
254 	*he = hosts;
255 	return (err);
256 
257 	free(ans);
258 	if (hosts != NULL)
259 		free(hosts);
260 	return (err);
261 }
262 
263 #if defined(TESTING)
264 int
265 main(int argc, char **argv)
266 {
267 	struct mx_hostentry *he, *p;
268 	int err;
269 
270 	err = dns_get_mx_list(argv[1], 53, &he, 0);
271 	if (err)
272 		return (err);
273 
274 	for (p = he; *p->host != 0; p++) {
275 		printf("%d\t%s\t%s\n", p->pref, p->host, p->addr);
276 	}
277 
278 	return (0);
279 }
280 #endif
281