xref: /freebsd/contrib/dma/dns.c (revision a5921bc3653e2e286715e6fe8d473ec0d02da38c)
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