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