1262266Sbapt/*
2289123Sbapt * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>.
3262266Sbapt * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
4262266Sbapt *
5262266Sbapt * This code is derived from software contributed to The DragonFly Project
6289123Sbapt * by Simon Schubert <2@0x2c.org>.
7262266Sbapt *
8262266Sbapt * Redistribution and use in source and binary forms, with or without
9262266Sbapt * modification, are permitted provided that the following conditions
10262266Sbapt * are met:
11262266Sbapt *
12262266Sbapt * 1. Redistributions of source code must retain the above copyright
13262266Sbapt *    notice, this list of conditions and the following disclaimer.
14262266Sbapt * 2. Redistributions in binary form must reproduce the above copyright
15262266Sbapt *    notice, this list of conditions and the following disclaimer in
16262266Sbapt *    the documentation and/or other materials provided with the
17262266Sbapt *    distribution.
18262266Sbapt * 3. Neither the name of The DragonFly Project nor the names of its
19262266Sbapt *    contributors may be used to endorse or promote products derived
20262266Sbapt *    from this software without specific, prior written permission.
21262266Sbapt *
22262266Sbapt * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23262266Sbapt * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24262266Sbapt * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25262266Sbapt * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
26262266Sbapt * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27262266Sbapt * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
28262266Sbapt * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29262266Sbapt * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30262266Sbapt * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31262266Sbapt * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32262266Sbapt * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33262266Sbapt * SUCH DAMAGE.
34262266Sbapt */
35262266Sbapt
36262266Sbapt#include <sys/types.h>
37304587Sbapt#include <sys/param.h>
38262266Sbapt#include <netinet/in.h>
39262266Sbapt#include <arpa/inet.h>
40262266Sbapt#include <arpa/nameser.h>
41262266Sbapt#include <errno.h>
42262266Sbapt#include <netdb.h>
43262266Sbapt#include <resolv.h>
44262266Sbapt#include <string.h>
45262266Sbapt#include <stdlib.h>
46262266Sbapt
47262266Sbapt#include "dma.h"
48262266Sbapt
49262266Sbaptstatic int
50262266Sbaptsort_pref(const void *a, const void *b)
51262266Sbapt{
52262266Sbapt	const struct mx_hostentry *ha = a, *hb = b;
53262266Sbapt	int v;
54262266Sbapt
55262266Sbapt	/* sort increasing by preference primarily */
56262266Sbapt	v = ha->pref - hb->pref;
57262266Sbapt	if (v != 0)
58262266Sbapt		return (v);
59262266Sbapt
60262266Sbapt	/* sort PF_INET6 before PF_INET */
61262266Sbapt	v = - (ha->ai.ai_family - hb->ai.ai_family);
62262266Sbapt	return (v);
63262266Sbapt}
64262266Sbapt
65262266Sbaptstatic int
66262266Sbaptadd_host(int pref, const char *host, int port, struct mx_hostentry **he, size_t *ps)
67262266Sbapt{
68262266Sbapt	struct addrinfo hints, *res, *res0 = NULL;
69262266Sbapt	char servname[10];
70262266Sbapt	struct mx_hostentry *p;
71262266Sbapt	const int count_inc = 10;
72262266Sbapt
73262266Sbapt	memset(&hints, 0, sizeof(hints));
74262266Sbapt	hints.ai_family = PF_UNSPEC;
75262266Sbapt	hints.ai_socktype = SOCK_STREAM;
76262266Sbapt	hints.ai_protocol = IPPROTO_TCP;
77262266Sbapt
78262266Sbapt	snprintf(servname, sizeof(servname), "%d", port);
79289123Sbapt	switch (getaddrinfo(host, servname, &hints, &res0)) {
80289123Sbapt	case 0:
81289123Sbapt		break;
82289123Sbapt	case EAI_AGAIN:
83289123Sbapt	case EAI_NONAME:
84289123Sbapt		/*
85289123Sbapt		 * EAI_NONAME gets returned for:
86289123Sbapt		 * SMARTHOST set but DNS server not reachable -> defer
87289123Sbapt		 * SMARTHOST set but DNS server returns "host does not exist"
88289123Sbapt		 *           -> buggy configuration
89289123Sbapt		 *           -> either defer or bounce would be ok -> defer
90289123Sbapt		 * MX entry was returned by DNS server but name doesn't resolve
91289123Sbapt		 *           -> hopefully transient situation -> defer
92289123Sbapt		 * all other DNS problems should have been caught earlier
93289123Sbapt		 * in dns_get_mx_list().
94289123Sbapt		 */
95289123Sbapt		goto out;
96289123Sbapt	default:
97289123Sbapt		return(-1);
98289123Sbapt	}
99262266Sbapt
100262266Sbapt	for (res = res0; res != NULL; res = res->ai_next) {
101262266Sbapt		if (*ps + 1 >= roundup(*ps, count_inc)) {
102262266Sbapt			size_t newsz = roundup(*ps + 2, count_inc);
103262266Sbapt			*he = reallocf(*he, newsz * sizeof(**he));
104262266Sbapt			if (*he == NULL)
105262266Sbapt				goto out;
106262266Sbapt		}
107262266Sbapt
108262266Sbapt		p = &(*he)[*ps];
109262266Sbapt		strlcpy(p->host, host, sizeof(p->host));
110262266Sbapt		p->pref = pref;
111262266Sbapt		p->ai = *res;
112262266Sbapt		p->ai.ai_addr = NULL;
113262266Sbapt		bcopy(res->ai_addr, &p->sa, p->ai.ai_addrlen);
114262266Sbapt
115262266Sbapt		getnameinfo((struct sockaddr *)&p->sa, p->ai.ai_addrlen,
116262266Sbapt			    p->addr, sizeof(p->addr),
117262266Sbapt			    NULL, 0, NI_NUMERICHOST);
118262266Sbapt
119262266Sbapt		(*ps)++;
120262266Sbapt	}
121262266Sbapt	freeaddrinfo(res0);
122262266Sbapt
123262266Sbapt	return (0);
124262266Sbapt
125262266Sbaptout:
126262266Sbapt	if (res0 != NULL)
127262266Sbapt		freeaddrinfo(res0);
128262266Sbapt	return (1);
129262266Sbapt}
130262266Sbapt
131262266Sbaptint
132262266Sbaptdns_get_mx_list(const char *host, int port, struct mx_hostentry **he, int no_mx)
133262266Sbapt{
134262266Sbapt	char outname[MAXDNAME];
135262266Sbapt	ns_msg msg;
136262266Sbapt	ns_rr rr;
137262266Sbapt	const char *searchhost;
138262266Sbapt	const unsigned char *cp;
139262266Sbapt	unsigned char *ans;
140262266Sbapt	struct mx_hostentry *hosts = NULL;
141262266Sbapt	size_t nhosts = 0;
142262266Sbapt	size_t anssz;
143262266Sbapt	int pref;
144262266Sbapt	int cname_recurse;
145262266Sbapt	int have_mx = 0;
146262266Sbapt	int err;
147262266Sbapt	int i;
148262266Sbapt
149262266Sbapt	res_init();
150262266Sbapt	searchhost = host;
151262266Sbapt	cname_recurse = 0;
152262266Sbapt
153262266Sbapt	anssz = 65536;
154262266Sbapt	ans = malloc(anssz);
155262266Sbapt	if (ans == NULL)
156262266Sbapt		return (1);
157262266Sbapt
158262266Sbapt	if (no_mx)
159262266Sbapt		goto out;
160262266Sbapt
161262266Sbaptrepeat:
162262266Sbapt	err = res_search(searchhost, ns_c_in, ns_t_mx, ans, anssz);
163262266Sbapt	if (err < 0) {
164262266Sbapt		switch (h_errno) {
165262266Sbapt		case NO_DATA:
166262266Sbapt			/*
167262266Sbapt			 * Host exists, but no MX (or CNAME) entry.
168262266Sbapt			 * Not an error, use host name instead.
169262266Sbapt			 */
170262266Sbapt			goto out;
171262266Sbapt		case TRY_AGAIN:
172262266Sbapt			/* transient error */
173262266Sbapt			goto transerr;
174262266Sbapt		case NO_RECOVERY:
175262266Sbapt		case HOST_NOT_FOUND:
176262266Sbapt		default:
177262266Sbapt			errno = ENOENT;
178262266Sbapt			goto err;
179262266Sbapt		}
180262266Sbapt	}
181262266Sbapt
182262266Sbapt	if (!ns_initparse(ans, anssz, &msg))
183262266Sbapt		goto transerr;
184262266Sbapt
185262266Sbapt	switch (ns_msg_getflag(msg, ns_f_rcode)) {
186262266Sbapt	case ns_r_noerror:
187262266Sbapt		break;
188262266Sbapt	case ns_r_nxdomain:
189262266Sbapt		goto err;
190262266Sbapt	default:
191262266Sbapt		goto transerr;
192262266Sbapt	}
193262266Sbapt
194262266Sbapt	for (i = 0; i < ns_msg_count(msg, ns_s_an); i++) {
195262266Sbapt		if (ns_parserr(&msg, ns_s_an, i, &rr))
196262266Sbapt			goto transerr;
197262266Sbapt
198262266Sbapt		cp = ns_rr_rdata(rr);
199262266Sbapt
200262266Sbapt		switch (ns_rr_type(rr)) {
201262266Sbapt		case ns_t_mx:
202262266Sbapt			have_mx = 1;
203262266Sbapt			pref = ns_get16(cp);
204262266Sbapt			cp += 2;
205262266Sbapt			err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg),
206262266Sbapt						 cp, outname, sizeof(outname));
207262266Sbapt			if (err < 0)
208262266Sbapt				goto transerr;
209262266Sbapt
210262266Sbapt			err = add_host(pref, outname, port, &hosts, &nhosts);
211262266Sbapt			if (err == -1)
212262266Sbapt				goto err;
213262266Sbapt			break;
214262266Sbapt
215262266Sbapt		case ns_t_cname:
216262266Sbapt			err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg),
217262266Sbapt						 cp, outname, sizeof(outname));
218262266Sbapt			if (err < 0)
219262266Sbapt				goto transerr;
220262266Sbapt
221262266Sbapt			/* Prevent a CNAME loop */
222262266Sbapt			if (cname_recurse++ > 10)
223262266Sbapt				goto err;
224262266Sbapt
225262266Sbapt			searchhost = outname;
226262266Sbapt			goto repeat;
227262266Sbapt
228262266Sbapt		default:
229262266Sbapt			break;
230262266Sbapt		}
231262266Sbapt	}
232262266Sbapt
233262266Sbaptout:
234262266Sbapt	err = 0;
235262266Sbapt	if (0) {
236262266Sbapttranserr:
237262266Sbapt		if (nhosts == 0)
238262266Sbapt			err = 1;
239262266Sbapt	}
240262266Sbapt	if (0) {
241262266Sbapterr:
242262266Sbapt		err = -1;
243262266Sbapt	}
244262266Sbapt
245262266Sbapt	free(ans);
246262266Sbapt
247262266Sbapt	if (err == 0) {
248262266Sbapt		if (!have_mx) {
249262266Sbapt			/*
250262266Sbapt			 * If we didn't find any MX, use the hostname instead.
251262266Sbapt			 */
252262266Sbapt			err = add_host(0, host, port, &hosts, &nhosts);
253262266Sbapt		} else if (nhosts == 0) {
254262266Sbapt			/*
255262266Sbapt			 * We did get MX, but couldn't resolve any of them
256262266Sbapt			 * due to transient errors.
257262266Sbapt			 */
258262266Sbapt			err = 1;
259262266Sbapt		}
260262266Sbapt	}
261262266Sbapt
262262266Sbapt	if (nhosts > 0) {
263262266Sbapt		qsort(hosts, nhosts, sizeof(*hosts), sort_pref);
264262266Sbapt		/* terminate list */
265262266Sbapt		*hosts[nhosts].host = 0;
266262266Sbapt	} else {
267262266Sbapt		if (hosts != NULL)
268262266Sbapt			free(hosts);
269262266Sbapt		hosts = NULL;
270262266Sbapt	}
271262266Sbapt
272262266Sbapt	*he = hosts;
273262266Sbapt	return (err);
274262266Sbapt
275262266Sbapt	free(ans);
276262266Sbapt	if (hosts != NULL)
277262266Sbapt		free(hosts);
278262266Sbapt	return (err);
279262266Sbapt}
280262266Sbapt
281262266Sbapt#if defined(TESTING)
282262266Sbaptint
283262266Sbaptmain(int argc, char **argv)
284262266Sbapt{
285262266Sbapt	struct mx_hostentry *he, *p;
286262266Sbapt	int err;
287262266Sbapt
288262266Sbapt	err = dns_get_mx_list(argv[1], 53, &he, 0);
289262266Sbapt	if (err)
290262266Sbapt		return (err);
291262266Sbapt
292262266Sbapt	for (p = he; *p->host != 0; p++) {
293262266Sbapt		printf("%d\t%s\t%s\n", p->pref, p->host, p->addr);
294262266Sbapt	}
295262266Sbapt
296262266Sbapt	return (0);
297262266Sbapt}
298262266Sbapt#endif
299