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
49 static int
sort_pref(const void * a,const void * b)50 sort_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
65 static int
add_host(int pref,const char * host,int port,struct mx_hostentry ** he,size_t * ps)66 add_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
125 out:
126 if (res0 != NULL)
127 freeaddrinfo(res0);
128 return (1);
129 }
130
131 int
dns_get_mx_list(const char * host,int port,struct mx_hostentry ** he,int no_mx)132 dns_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
161 repeat:
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
233 out:
234 err = 0;
235 if (0) {
236 transerr:
237 if (nhosts == 0)
238 err = 1;
239 }
240 if (0) {
241 err:
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
276 #if defined(TESTING)
277 int
main(int argc,char ** argv)278 main(int argc, char **argv)
279 {
280 struct mx_hostentry *he, *p;
281 int err;
282
283 err = dns_get_mx_list(argv[1], 53, &he, 0);
284 if (err)
285 return (err);
286
287 for (p = he; *p->host != 0; p++) {
288 printf("%d\t%s\t%s\n", p->pref, p->host, p->addr);
289 }
290
291 return (0);
292 }
293 #endif
294