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