free() allocated memory when the ares search can't be made
[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 <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <ctype.h>
20
21 #ifdef WIN32
22 #include "nameser.h"
23 #endif
24
25 #include "ares.h"
26 #include "ares_private.h"
27
28 struct search_query {
29   /* Arguments passed to ares_search */
30   ares_channel channel;
31   char *name;                   /* copied into an allocated buffer */
32   int dnsclass;
33   int type;
34   ares_callback callback;
35   void *arg;
36
37   int status_as_is;             /* error status from trying as-is */
38   int next_domain;              /* next search domain to try */
39   int trying_as_is;             /* current query is for name as-is */
40 };
41
42 static void search_callback(void *arg, int status, unsigned char *abuf,
43                             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, 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, 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, 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
96   /* Count the number of dots in name. */
97   ndots = 0;
98   for (p = name; *p; p++)
99     {
100       if (*p == '.')
101         ndots++;
102     }
103
104   /* If ndots is at least the channel ndots threshold (usually 1),
105    * then we try the name as-is first.  Otherwise, we try the name
106    * as-is last.
107    */
108   if (ndots >= channel->ndots)
109     {
110       /* Try the name as-is first. */
111       squery->next_domain = 0;
112       squery->trying_as_is = 1;
113       ares_query(channel, name, dnsclass, type, search_callback, squery);
114     }
115   else
116     {
117       /* Try the name as-is last; start with the first search domain. */
118       squery->next_domain = 1;
119       squery->trying_as_is = 0;
120       status = cat_domain(name, channel->domains[0], &s);
121       if (status == ARES_SUCCESS)
122         {
123           ares_query(channel, s, dnsclass, type, search_callback, squery);
124           free(s);
125         }
126       else
127       {
128         /* failed, free the malloc()ed memory */
129         free(squery->name);
130         free(squery);
131         callback(arg, status, NULL, 0);
132       }
133     }
134 }
135
136 static void search_callback(void *arg, int status, unsigned char *abuf,
137                             int alen)
138 {
139   struct search_query *squery = (struct search_query *) arg;
140   ares_channel channel = squery->channel;
141   char *s;
142
143   /* Stop searching unless we got a non-fatal error. */
144   if (status != ARES_ENODATA && status != ARES_ESERVFAIL
145       && status != ARES_ENOTFOUND)
146     end_squery(squery, status, abuf, alen);
147   else
148     {
149       /* Save the status if we were trying as-is. */
150       if (squery->trying_as_is)
151         squery->status_as_is = status;
152       if (squery->next_domain < channel->ndomains)
153         {
154           /* Try the next domain. */
155           status = cat_domain(squery->name,
156                               channel->domains[squery->next_domain], &s);
157           if (status != ARES_SUCCESS)
158             end_squery(squery, status, NULL, 0);
159           else
160             {
161               squery->trying_as_is = 0;
162               squery->next_domain++;
163               ares_query(channel, s, squery->dnsclass, squery->type,
164                          search_callback, squery);
165               free(s);
166             }
167         }
168       else if (squery->status_as_is == -1)
169         {
170           /* Try the name as-is at the end. */
171           squery->trying_as_is = 1;
172           ares_query(channel, squery->name, squery->dnsclass, squery->type,
173                      search_callback, squery);
174         }
175       else
176         end_squery(squery, squery->status_as_is, NULL, 0);
177     }
178 }
179
180 static void end_squery(struct search_query *squery, int status,
181                        unsigned char *abuf, int alen)
182 {
183   squery->callback(squery->arg, status, abuf, alen);
184   free(squery->name);
185   free(squery);
186 }
187
188 /* Concatenate two domains. */
189 static int cat_domain(const char *name, const char *domain, char **s)
190 {
191   size_t nlen = strlen(name);
192   size_t dlen = strlen(domain);
193
194   *s = malloc(nlen + 1 + dlen + 1);
195   if (!*s)
196     return ARES_ENOMEM;
197   memcpy(*s, name, nlen);
198   (*s)[nlen] = '.';
199   memcpy(*s + nlen + 1, domain, dlen);
200   (*s)[nlen + 1 + dlen] = 0;
201   return ARES_SUCCESS;
202 }
203
204 /* Determine if this name only yields one query.  If it does, set *s to
205  * the string we should query, in an allocated buffer.  If not, set *s
206  * to NULL.
207  */
208 static int single_domain(ares_channel channel, const char *name, char **s)
209 {
210   size_t len = strlen(name);
211   const char *hostaliases;
212   FILE *fp;
213   char *line = NULL;
214   int linesize, status;
215   const char *p, *q;
216
217   /* If the name contains a trailing dot, then the single query is the name
218    * sans the trailing dot.
219    */
220   if (name[len - 1] == '.')
221     {
222       *s = strdup(name);
223       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
224     }
225
226   if (!(channel->flags & ARES_FLAG_NOALIASES) && !strchr(name, '.'))
227     {
228       /* The name might be a host alias. */
229       hostaliases = getenv("HOSTALIASES");
230       if (hostaliases)
231         {
232           fp = fopen(hostaliases, "r");
233           if (fp)
234             {
235               while ((status = ares__read_line(fp, &line, &linesize))
236                      == ARES_SUCCESS)
237                 {
238                   if (strncasecmp(line, name, len) != 0 ||
239                       !isspace((unsigned char)line[len]))
240                     continue;
241                   p = line + len;
242                   while (isspace((unsigned char)*p))
243                     p++;
244                   if (*p)
245                     {
246                       q = p + 1;
247                       while (*q && !isspace((unsigned char)*q))
248                         q++;
249                       *s = malloc(q - p + 1);
250                       if (*s)
251                         {
252                           memcpy(*s, p, q - p);
253                           (*s)[q - p] = 0;
254                         }
255                       free(line);
256                       fclose(fp);
257                       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
258                     }
259                 }
260               free(line);
261               fclose(fp);
262               if (status != ARES_SUCCESS)
263                 return status;
264             }
265         }
266     }
267
268   if (channel->flags & ARES_FLAG_NOSEARCH || channel->ndomains == 0)
269     {
270       /* No domain search to do; just try the name as-is. */
271       *s = strdup(name);
272       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
273     }
274
275   *s = NULL;
276   return ARES_SUCCESS;
277 }