1 /* MIT License
2 *
3 * Copyright (c) 1998 Massachusetts Institute of Technology
4 * Copyright (c) The c-ares project and its contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the next
14 * paragraph) shall be included in all copies or substantial portions of the
15 * Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 *
25 * SPDX-License-Identifier: MIT
26 */
27
28 #include "ares_setup.h"
29
30 #ifdef HAVE_STRINGS_H
31 # include <strings.h>
32 #endif
33
34 #include "ares.h"
35 #include "ares_private.h"
36
37 struct search_query {
38 /* Arguments passed to ares_search */
39 ares_channel_t *channel;
40 char *name; /* copied into an allocated buffer */
41 int dnsclass;
42 int type;
43 ares_callback callback;
44 void *arg;
45 char **domains; /* duplicate for ares_reinit() safety */
46 size_t ndomains;
47
48 int status_as_is; /* error status from trying as-is */
49 size_t next_domain; /* next search domain to try */
50 ares_bool_t trying_as_is; /* current query is for name as-is */
51 size_t timeouts; /* number of timeouts we saw for this request */
52 ares_bool_t ever_got_nodata; /* did we ever get ARES_ENODATA along the way? */
53 };
54
55 static void search_callback(void *arg, int status, int timeouts,
56 unsigned char *abuf, int alen);
57 static void end_squery(struct search_query *squery, ares_status_t status,
58 unsigned char *abuf, size_t alen);
59
ares_search_int(ares_channel_t * channel,const char * name,int dnsclass,int type,ares_callback callback,void * arg)60 static void ares_search_int(ares_channel_t *channel, const char *name,
61 int dnsclass, int type, ares_callback callback,
62 void *arg)
63 {
64 struct search_query *squery;
65 char *s;
66 const char *p;
67 ares_status_t status;
68 size_t ndots;
69
70 /* Per RFC 7686, reject queries for ".onion" domain names with NXDOMAIN. */
71 if (ares__is_onion_domain(name)) {
72 callback(arg, ARES_ENOTFOUND, 0, NULL, 0);
73 return;
74 }
75
76 /* If name only yields one domain to search, then we don't have
77 * to keep extra state, so just do an ares_query().
78 */
79 status = ares__single_domain(channel, name, &s);
80 if (status != ARES_SUCCESS) {
81 callback(arg, (int)status, 0, NULL, 0);
82 return;
83 }
84 if (s) {
85 ares_query(channel, s, dnsclass, type, callback, arg);
86 ares_free(s);
87 return;
88 }
89
90 /* Allocate a search_query structure to hold the state necessary for
91 * doing multiple lookups.
92 */
93 squery = ares_malloc_zero(sizeof(*squery));
94 if (!squery) {
95 callback(arg, ARES_ENOMEM, 0, NULL, 0);
96 return;
97 }
98 squery->channel = channel;
99 squery->name = ares_strdup(name);
100 if (!squery->name) {
101 ares_free(squery);
102 callback(arg, ARES_ENOMEM, 0, NULL, 0);
103 return;
104 }
105
106 /* Duplicate domains for safety during ares_reinit() */
107 if (channel->ndomains) {
108 squery->domains =
109 ares__strsplit_duplicate(channel->domains, channel->ndomains);
110 if (squery->domains == NULL) {
111 ares_free(squery->name);
112 ares_free(squery);
113 callback(arg, ARES_ENOMEM, 0, NULL, 0);
114 return;
115 }
116 squery->ndomains = channel->ndomains;
117 }
118
119 squery->dnsclass = dnsclass;
120 squery->type = type;
121 squery->status_as_is = -1;
122 squery->callback = callback;
123 squery->arg = arg;
124 squery->timeouts = 0;
125 squery->ever_got_nodata = ARES_FALSE;
126
127 /* Count the number of dots in name. */
128 ndots = 0;
129 for (p = name; *p; p++) {
130 if (*p == '.') {
131 ndots++;
132 }
133 }
134
135 /* If ndots is at least the channel ndots threshold (usually 1),
136 * then we try the name as-is first. Otherwise, we try the name
137 * as-is last.
138 */
139 if (ndots >= channel->ndots || squery->ndomains == 0) {
140 /* Try the name as-is first. */
141 squery->next_domain = 0;
142 squery->trying_as_is = ARES_TRUE;
143 ares_query(channel, name, dnsclass, type, search_callback, squery);
144 } else {
145 /* Try the name as-is last; start with the first search domain. */
146 squery->next_domain = 1;
147 squery->trying_as_is = ARES_FALSE;
148 status = ares__cat_domain(name, squery->domains[0], &s);
149 if (status == ARES_SUCCESS) {
150 ares_query(channel, s, dnsclass, type, search_callback, squery);
151 ares_free(s);
152 } else {
153 /* failed, free the malloc()ed memory */
154 ares_free(squery->name);
155 ares_free(squery);
156 callback(arg, (int)status, 0, NULL, 0);
157 }
158 }
159 }
160
ares_search(ares_channel_t * channel,const char * name,int dnsclass,int type,ares_callback callback,void * arg)161 void ares_search(ares_channel_t *channel, const char *name, int dnsclass,
162 int type, ares_callback callback, void *arg)
163 {
164 if (channel == NULL) {
165 return;
166 }
167 ares__channel_lock(channel);
168 ares_search_int(channel, name, dnsclass, type, callback, arg);
169 ares__channel_unlock(channel);
170 }
171
search_callback(void * arg,int status,int timeouts,unsigned char * abuf,int alen)172 static void search_callback(void *arg, int status, int timeouts,
173 unsigned char *abuf, int alen)
174 {
175 struct search_query *squery = (struct search_query *)arg;
176 ares_channel_t *channel = squery->channel;
177 char *s;
178
179 squery->timeouts += (size_t)timeouts;
180
181 /* Stop searching unless we got a non-fatal error. */
182 if (status != ARES_ENODATA && status != ARES_ESERVFAIL &&
183 status != ARES_ENOTFOUND) {
184 end_squery(squery, (ares_status_t)status, abuf, (size_t)alen);
185 } else {
186 /* Save the status if we were trying as-is. */
187 if (squery->trying_as_is) {
188 squery->status_as_is = status;
189 }
190
191 /*
192 * If we ever get ARES_ENODATA along the way, record that; if the search
193 * should run to the very end and we got at least one ARES_ENODATA,
194 * then callers like ares_gethostbyname() may want to try a T_A search
195 * even if the last domain we queried for T_AAAA resource records
196 * returned ARES_ENOTFOUND.
197 */
198 if (status == ARES_ENODATA) {
199 squery->ever_got_nodata = ARES_TRUE;
200 }
201
202 if (squery->next_domain < squery->ndomains) {
203 ares_status_t mystatus;
204 /* Try the next domain. */
205 mystatus = ares__cat_domain(squery->name,
206 squery->domains[squery->next_domain], &s);
207 if (mystatus != ARES_SUCCESS) {
208 end_squery(squery, mystatus, NULL, 0);
209 } else {
210 squery->trying_as_is = ARES_FALSE;
211 squery->next_domain++;
212 ares_query(channel, s, squery->dnsclass, squery->type, search_callback,
213 squery);
214 ares_free(s);
215 }
216 } else if (squery->status_as_is == -1) {
217 /* Try the name as-is at the end. */
218 squery->trying_as_is = ARES_TRUE;
219 ares_query(channel, squery->name, squery->dnsclass, squery->type,
220 search_callback, squery);
221 } else {
222 if (squery->status_as_is == ARES_ENOTFOUND && squery->ever_got_nodata) {
223 end_squery(squery, ARES_ENODATA, NULL, 0);
224 } else {
225 end_squery(squery, (ares_status_t)squery->status_as_is, NULL, 0);
226 }
227 }
228 }
229 }
230
end_squery(struct search_query * squery,ares_status_t status,unsigned char * abuf,size_t alen)231 static void end_squery(struct search_query *squery, ares_status_t status,
232 unsigned char *abuf, size_t alen)
233 {
234 squery->callback(squery->arg, (int)status, (int)squery->timeouts, abuf,
235 (int)alen);
236 ares__strsplit_free(squery->domains, squery->ndomains);
237 ares_free(squery->name);
238 ares_free(squery);
239 }
240
241 /* Concatenate two domains. */
ares__cat_domain(const char * name,const char * domain,char ** s)242 ares_status_t ares__cat_domain(const char *name, const char *domain, char **s)
243 {
244 size_t nlen = ares_strlen(name);
245 size_t dlen = ares_strlen(domain);
246
247 *s = ares_malloc(nlen + 1 + dlen + 1);
248 if (!*s) {
249 return ARES_ENOMEM;
250 }
251 memcpy(*s, name, nlen);
252 (*s)[nlen] = '.';
253 if (strcmp(domain, ".") == 0) {
254 /* Avoid appending the root domain to the separator, which would set *s to
255 an ill-formed value (ending in two consecutive dots). */
256 dlen = 0;
257 }
258 memcpy(*s + nlen + 1, domain, dlen);
259 (*s)[nlen + 1 + dlen] = 0;
260 return ARES_SUCCESS;
261 }
262
263 /* Determine if this name only yields one query. If it does, set *s to
264 * the string we should query, in an allocated buffer. If not, set *s
265 * to NULL.
266 */
ares__single_domain(const ares_channel_t * channel,const char * name,char ** s)267 ares_status_t ares__single_domain(const ares_channel_t *channel,
268 const char *name, char **s)
269 {
270 size_t len = ares_strlen(name);
271 const char *hostaliases;
272 FILE *fp;
273 char *line = NULL;
274 ares_status_t status;
275 size_t linesize;
276 const char *p;
277 const char *q;
278 int error;
279
280 /* If the name contains a trailing dot, then the single query is the name
281 * sans the trailing dot.
282 */
283 if ((len > 0) && (name[len - 1] == '.')) {
284 *s = ares_strdup(name);
285 return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
286 }
287
288 if (!(channel->flags & ARES_FLAG_NOALIASES) && !strchr(name, '.')) {
289 /* The name might be a host alias. */
290 hostaliases = getenv("HOSTALIASES");
291 if (hostaliases) {
292 fp = fopen(hostaliases, "r");
293 if (fp) {
294 while ((status = ares__read_line(fp, &line, &linesize)) ==
295 ARES_SUCCESS) {
296 if (strncasecmp(line, name, len) != 0 || !ISSPACE(line[len])) {
297 continue;
298 }
299 p = line + len;
300 while (ISSPACE(*p)) {
301 p++;
302 }
303 if (*p) {
304 q = p + 1;
305 while (*q && !ISSPACE(*q)) {
306 q++;
307 }
308 *s = ares_malloc((size_t)(q - p + 1));
309 if (*s) {
310 memcpy(*s, p, (size_t)(q - p));
311 (*s)[q - p] = 0;
312 }
313 ares_free(line);
314 fclose(fp);
315 return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
316 }
317 }
318 ares_free(line);
319 fclose(fp);
320 if (status != ARES_SUCCESS && status != ARES_EOF) {
321 return status;
322 }
323 } else {
324 error = ERRNO;
325 switch (error) {
326 case ENOENT:
327 case ESRCH:
328 break;
329 default:
330 DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
331 strerror(error)));
332 DEBUGF(fprintf(stderr, "Error opening file: %s\n", hostaliases));
333 *s = NULL;
334 return ARES_EFILE;
335 }
336 }
337 }
338 }
339
340 if (channel->flags & ARES_FLAG_NOSEARCH || channel->ndomains == 0) {
341 /* No domain search to do; just try the name as-is. */
342 *s = ares_strdup(name);
343 return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
344 }
345
346 *s = NULL;
347 return ARES_SUCCESS;
348 }
349