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 <sys/param.h>
38#include <netinet/in.h>
39#include <arpa/inet.h>
40#include <arpa/nameser.h>
41#include <errno.h>
42#include <netdb.h>
43#include <resolv.h>
44#include <string.h>
45#include <stdlib.h>
46
47#include "dma.h"
48
49static int
50sort_pref(const void *a, const void *b)
51{
52	const struct mx_hostentry *ha = a, *hb = b;
53	int v;
54
55	/* sort increasing by preference primarily */
56	v = ha->pref - hb->pref;
57	if (v != 0)
58		return (v);
59
60	/* sort PF_INET6 before PF_INET */
61	v = - (ha->ai.ai_family - hb->ai.ai_family);
62	return (v);
63}
64
65static int
66add_host(int pref, const char *host, int port, struct mx_hostentry **he, size_t *ps)
67{
68	struct addrinfo hints, *res, *res0 = NULL;
69	char servname[10];
70	struct mx_hostentry *p;
71	const int count_inc = 10;
72
73	memset(&hints, 0, sizeof(hints));
74	hints.ai_family = PF_UNSPEC;
75	hints.ai_socktype = SOCK_STREAM;
76	hints.ai_protocol = IPPROTO_TCP;
77
78	snprintf(servname, sizeof(servname), "%d", port);
79	switch (getaddrinfo(host, servname, &hints, &res0)) {
80	case 0:
81		break;
82	case EAI_AGAIN:
83	case EAI_NONAME:
84		/*
85		 * EAI_NONAME gets returned for:
86		 * SMARTHOST set but DNS server not reachable -> defer
87		 * SMARTHOST set but DNS server returns "host does not exist"
88		 *           -> buggy configuration
89		 *           -> either defer or bounce would be ok -> defer
90		 * MX entry was returned by DNS server but name doesn't resolve
91		 *           -> hopefully transient situation -> defer
92		 * all other DNS problems should have been caught earlier
93		 * in dns_get_mx_list().
94		 */
95		goto out;
96	default:
97		return(-1);
98	}
99
100	for (res = res0; res != NULL; res = res->ai_next) {
101		if (*ps + 1 >= roundup(*ps, count_inc)) {
102			size_t newsz = roundup(*ps + 2, count_inc);
103			*he = reallocf(*he, newsz * sizeof(**he));
104			if (*he == NULL)
105				goto out;
106		}
107
108		p = &(*he)[*ps];
109		strlcpy(p->host, host, sizeof(p->host));
110		p->pref = pref;
111		p->ai = *res;
112		p->ai.ai_addr = NULL;
113		bcopy(res->ai_addr, &p->sa, p->ai.ai_addrlen);
114
115		getnameinfo((struct sockaddr *)&p->sa, p->ai.ai_addrlen,
116			    p->addr, sizeof(p->addr),
117			    NULL, 0, NI_NUMERICHOST);
118
119		(*ps)++;
120	}
121	freeaddrinfo(res0);
122
123	return (0);
124
125out:
126	if (res0 != NULL)
127		freeaddrinfo(res0);
128	return (1);
129}
130
131int
132dns_get_mx_list(const char *host, int port, struct mx_hostentry **he, int no_mx)
133{
134	char outname[MAXDNAME];
135	ns_msg msg;
136	ns_rr rr;
137	const char *searchhost;
138	const unsigned char *cp;
139	unsigned char *ans;
140	struct mx_hostentry *hosts = NULL;
141	size_t nhosts = 0;
142	size_t anssz;
143	int pref;
144	int cname_recurse;
145	int have_mx = 0;
146	int err;
147	int i;
148
149	res_init();
150	searchhost = host;
151	cname_recurse = 0;
152
153	anssz = 65536;
154	ans = malloc(anssz);
155	if (ans == NULL)
156		return (1);
157
158	if (no_mx)
159		goto out;
160
161repeat:
162	err = res_search(searchhost, ns_c_in, ns_t_mx, ans, anssz);
163	if (err < 0) {
164		switch (h_errno) {
165		case NO_DATA:
166			/*
167			 * Host exists, but no MX (or CNAME) entry.
168			 * Not an error, use host name instead.
169			 */
170			goto out;
171		case TRY_AGAIN:
172			/* transient error */
173			goto transerr;
174		case NO_RECOVERY:
175		case HOST_NOT_FOUND:
176		default:
177			errno = ENOENT;
178			goto err;
179		}
180	}
181
182	if (!ns_initparse(ans, anssz, &msg))
183		goto transerr;
184
185	switch (ns_msg_getflag(msg, ns_f_rcode)) {
186	case ns_r_noerror:
187		break;
188	case ns_r_nxdomain:
189		goto err;
190	default:
191		goto transerr;
192	}
193
194	for (i = 0; i < ns_msg_count(msg, ns_s_an); i++) {
195		if (ns_parserr(&msg, ns_s_an, i, &rr))
196			goto transerr;
197
198		cp = ns_rr_rdata(rr);
199
200		switch (ns_rr_type(rr)) {
201		case ns_t_mx:
202			have_mx = 1;
203			pref = ns_get16(cp);
204			cp += 2;
205			err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg),
206						 cp, outname, sizeof(outname));
207			if (err < 0)
208				goto transerr;
209
210			err = add_host(pref, outname, port, &hosts, &nhosts);
211			if (err == -1)
212				goto err;
213			break;
214
215		case ns_t_cname:
216			err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg),
217						 cp, outname, sizeof(outname));
218			if (err < 0)
219				goto transerr;
220
221			/* Prevent a CNAME loop */
222			if (cname_recurse++ > 10)
223				goto err;
224
225			searchhost = outname;
226			goto repeat;
227
228		default:
229			break;
230		}
231	}
232
233out:
234	err = 0;
235	if (0) {
236transerr:
237		if (nhosts == 0)
238			err = 1;
239	}
240	if (0) {
241err:
242		err = -1;
243	}
244
245	free(ans);
246
247	if (err == 0) {
248		if (!have_mx) {
249			/*
250			 * If we didn't find any MX, use the hostname instead.
251			 */
252			err = add_host(0, host, port, &hosts, &nhosts);
253		} else if (nhosts == 0) {
254			/*
255			 * We did get MX, but couldn't resolve any of them
256			 * due to transient errors.
257			 */
258			err = 1;
259		}
260	}
261
262	if (nhosts > 0) {
263		qsort(hosts, nhosts, sizeof(*hosts), sort_pref);
264		/* terminate list */
265		*hosts[nhosts].host = 0;
266	} else {
267		if (hosts != NULL)
268			free(hosts);
269		hosts = NULL;
270	}
271
272	*he = hosts;
273	return (err);
274
275	free(ans);
276	if (hosts != NULL)
277		free(hosts);
278	return (err);
279}
280
281#if defined(TESTING)
282int
283main(int argc, char **argv)
284{
285	struct mx_hostentry *he, *p;
286	int err;
287
288	err = dns_get_mx_list(argv[1], 53, &he, 0);
289	if (err)
290		return (err);
291
292	for (p = he; *p->host != 0; p++) {
293		printf("%d\t%s\t%s\n", p->pref, p->host, p->addr);
294	}
295
296	return (0);
297}
298#endif
299