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