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