608cb153d329b617c28ef2a618c22056b6932b9b
[external/c-ares.git] / ares_search.c
1
2 /* Copyright 1998 by the Massachusetts Institute of Technology.
3  *
4  * Permission to use, copy, modify, and distribute this
5  * software and its documentation for any purpose and without
6  * fee is hereby granted, provided that the above copyright
7  * notice appear in all copies and that both that copyright
8  * notice and this permission notice appear in supporting
9  * documentation, and that the name of M.I.T. not be used in
10  * advertising or publicity pertaining to distribution of the
11  * software without specific, written prior permission.
12  * M.I.T. makes no representations about the suitability of
13  * this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  */
16
17 #include "ares_setup.h"
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <ctype.h>
23 #include <errno.h>
24
25 #ifdef HAVE_STRINGS_H
26 #  include <strings.h>
27 #endif
28
29 #include "ares.h"
30 #include "ares_private.h"
31
32 struct search_query {
33   /* Arguments passed to ares_search */
34   ares_channel channel;
35   char *name;                   /* copied into an allocated buffer */
36   int dnsclass;
37   int type;
38   ares_callback callback;
39   void *arg;
40
41   int status_as_is;             /* error status from trying as-is */
42   int next_domain;              /* next search domain to try */
43   int trying_as_is;             /* current query is for name as-is */
44   int timeouts;                 /* number of timeouts we saw for this request */
45   int ever_got_nodata;          /* did we ever get ARES_ENODATA along the way? */
46 };
47
48 static void search_callback(void *arg, int status, int timeouts,
49                             unsigned char *abuf, int alen);
50 static void end_squery(struct search_query *squery, int status,
51                        unsigned char *abuf, int alen);
52 static int cat_domain(const char *name, const char *domain, char **s);
53 static int single_domain(ares_channel channel, const char *name, char **s);
54
55 void ares_search(ares_channel channel, const char *name, int dnsclass,
56                  int type, ares_callback callback, void *arg)
57 {
58   struct search_query *squery;
59   char *s;
60   const char *p;
61   int status, ndots;
62
63   /* If name only yields one domain to search, then we don't have
64    * to keep extra state, so just do an ares_query().
65    */
66   status = single_domain(channel, name, &s);
67   if (status != ARES_SUCCESS)
68     {
69       callback(arg, status, 0, NULL, 0);
70       return;
71     }
72   if (s)
73     {
74       ares_query(channel, s, dnsclass, type, callback, arg);
75       free(s);
76       return;
77     }
78
79   /* Allocate a search_query structure to hold the state necessary for
80    * doing multiple lookups.
81    */
82   squery = malloc(sizeof(struct search_query));
83   if (!squery)
84     {
85       callback(arg, ARES_ENOMEM, 0, NULL, 0);
86       return;
87     }
88   squery->channel = channel;
89   squery->name = strdup(name);
90   if (!squery->name)
91     {
92       free(squery);
93       callback(arg, ARES_ENOMEM, 0, NULL, 0);
94       return;
95     }
96   squery->dnsclass = dnsclass;
97   squery->type = type;
98   squery->status_as_is = -1;
99   squery->callback = callback;
100   squery->arg = arg;
101   squery->timeouts = 0;
102   squery->ever_got_nodata = 0;
103
104   /* Count the number of dots in name. */
105   ndots = 0;
106   for (p = name; *p; p++)
107     {
108       if (*p == '.')
109         ndots++;
110     }
111
112   /* If ndots is at least the channel ndots threshold (usually 1),
113    * then we try the name as-is first.  Otherwise, we try the name
114    * as-is last.
115    */
116   if (ndots >= channel->ndots)
117     {
118       /* Try the name as-is first. */
119       squery->next_domain = 0;
120       squery->trying_as_is = 1;
121       ares_query(channel, name, dnsclass, type, search_callback, squery);
122     }
123   else
124     {
125       /* Try the name as-is last; start with the first search domain. */
126       squery->next_domain = 1;
127       squery->trying_as_is = 0;
128       status = cat_domain(name, channel->domains[0], &s);
129       if (status == ARES_SUCCESS)
130         {
131           ares_query(channel, s, dnsclass, type, search_callback, squery);
132           free(s);
133         }
134       else
135       {
136         /* failed, free the malloc()ed memory */
137         free(squery->name);
138         free(squery);
139         callback(arg, status, 0, NULL, 0);
140       }
141     }
142 }
143
144 static void search_callback(void *arg, int status, int timeouts,
145                             unsigned char *abuf, int alen)
146 {
147   struct search_query *squery = (struct search_query *) arg;
148   ares_channel channel = squery->channel;
149   char *s;
150
151   squery->timeouts += timeouts;
152
153   /* Stop searching unless we got a non-fatal error. */
154   if (status != ARES_ENODATA && status != ARES_ESERVFAIL
155       && status != ARES_ENOTFOUND)
156     end_squery(squery, status, abuf, alen);
157   else
158     {
159       /* Save the status if we were trying as-is. */
160       if (squery->trying_as_is)
161         squery->status_as_is = status;
162
163       /*
164        * If we ever get ARES_ENODATA along the way, record that; if the search
165        * should run to the very end and we got at least one ARES_ENODATA,
166        * then callers like ares_gethostbyname() may want to try a T_A search
167        * even if the last domain we queried for T_AAAA resource records
168        * returned ARES_ENOTFOUND.
169        */
170       if (status == ARES_ENODATA)
171         squery->ever_got_nodata = 1;
172
173       if (squery->next_domain < channel->ndomains)
174         {
175           /* Try the next domain. */
176           status = cat_domain(squery->name,
177                               channel->domains[squery->next_domain], &s);
178           if (status != ARES_SUCCESS)
179             end_squery(squery, status, NULL, 0);
180           else
181             {
182               squery->trying_as_is = 0;
183               squery->next_domain++;
184               ares_query(channel, s, squery->dnsclass, squery->type,
185                          search_callback, squery);
186               free(s);
187             }
188         }
189       else if (squery->status_as_is == -1)
190         {
191           /* Try the name as-is at the end. */
192           squery->trying_as_is = 1;
193           ares_query(channel, squery->name, squery->dnsclass, squery->type,
194                      search_callback, squery);
195         }
196       else {
197         if (squery->status_as_is == ARES_ENOTFOUND && squery->ever_got_nodata) {
198           end_squery(squery, ARES_ENODATA, NULL, 0);
199         }
200         else
201           end_squery(squery, squery->status_as_is, NULL, 0);
202       }
203     }
204 }
205
206 static void end_squery(struct search_query *squery, int status,
207                        unsigned char *abuf, int alen)
208 {
209   squery->callback(squery->arg, status, squery->timeouts, abuf, alen);
210   free(squery->name);
211   free(squery);
212 }
213
214 /* Concatenate two domains. */
215 static int cat_domain(const char *name, const char *domain, char **s)
216 {
217   size_t nlen = strlen(name);
218   size_t dlen = strlen(domain);
219
220   *s = malloc(nlen + 1 + dlen + 1);
221   if (!*s)
222     return ARES_ENOMEM;
223   memcpy(*s, name, nlen);
224   (*s)[nlen] = '.';
225   memcpy(*s + nlen + 1, domain, dlen);
226   (*s)[nlen + 1 + dlen] = 0;
227   return ARES_SUCCESS;
228 }
229
230 /* Determine if this name only yields one query.  If it does, set *s to
231  * the string we should query, in an allocated buffer.  If not, set *s
232  * to NULL.
233  */
234 static int single_domain(ares_channel channel, const char *name, char **s)
235 {
236   size_t len = strlen(name);
237   const char *hostaliases;
238   FILE *fp;
239   char *line = NULL;
240   int status;
241   size_t linesize;
242   const char *p, *q;
243   int error;
244
245   /* If the name contains a trailing dot, then the single query is the name
246    * sans the trailing dot.
247    */
248   if (name[len - 1] == '.')
249     {
250       *s = strdup(name);
251       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
252     }
253
254   if (!(channel->flags & ARES_FLAG_NOALIASES) && !strchr(name, '.'))
255     {
256       /* The name might be a host alias. */
257       hostaliases = getenv("HOSTALIASES");
258       if (hostaliases)
259         {
260           fp = fopen(hostaliases, "r");
261           if (fp)
262             {
263               while ((status = ares__read_line(fp, &line, &linesize))
264                      == ARES_SUCCESS)
265                 {
266                   if (strncasecmp(line, name, len) != 0 ||
267                       !ISSPACE(line[len]))
268                     continue;
269                   p = line + len;
270                   while (ISSPACE(*p))
271                     p++;
272                   if (*p)
273                     {
274                       q = p + 1;
275                       while (*q && !ISSPACE(*q))
276                         q++;
277                       *s = malloc(q - p + 1);
278                       if (*s)
279                         {
280                           memcpy(*s, p, q - p);
281                           (*s)[q - p] = 0;
282                         }
283                       free(line);
284                       fclose(fp);
285                       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
286                     }
287                 }
288               free(line);
289               fclose(fp);
290               if (status != ARES_SUCCESS && status != ARES_EOF)
291                 return status;
292             }
293           else
294             {
295               error = errno;
296               switch(error)
297                 {
298                 case ENOENT:
299                 case ESRCH:
300                   break;
301                 default:
302                   DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n",
303                                  error, strerror(error)));
304                   DEBUGF(fprintf(stderr, "Error opening file: %s\n",
305                                  hostaliases));
306                   *s = NULL;
307                   return ARES_EFILE;
308                 }
309             }
310         }
311     }
312
313   if (channel->flags & ARES_FLAG_NOSEARCH || channel->ndomains == 0)
314     {
315       /* No domain search to do; just try the name as-is. */
316       *s = strdup(name);
317       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
318     }
319
320   *s = NULL;
321   return ARES_SUCCESS;
322 }