iwm: Fix S:N reporting in ifconfig(8)
[dragonfly.git] / libexec / dma / dns.c
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
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
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
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
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