lh | 9ed821d | 2023-04-07 01:36:19 -0700 | [diff] [blame^] | 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_TESTABLE 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 | ares_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 = ares_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 = ares_strdup(name); |
| 84 | if (!squery->name) |
| 85 | { |
| 86 | ares_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 | ares_free(s); |
| 127 | } |
| 128 | else |
| 129 | { |
| 130 | /* failed, free the malloc()ed memory */ |
| 131 | ares_free(squery->name); |
| 132 | ares_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 | ares_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 | ares_free(squery->name); |
| 205 | ares_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 = ares_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_TESTABLE 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 ((len > 0) && (name[len - 1] == '.')) |
| 243 | { |
| 244 | *s = ares_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 = ares_malloc(q - p + 1); |
| 272 | if (*s) |
| 273 | { |
| 274 | memcpy(*s, p, q - p); |
| 275 | (*s)[q - p] = 0; |
| 276 | } |
| 277 | ares_free(line); |
| 278 | fclose(fp); |
| 279 | return (*s) ? ARES_SUCCESS : ARES_ENOMEM; |
| 280 | } |
| 281 | } |
| 282 | ares_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 = ares_strdup(name); |
| 311 | return (*s) ? ARES_SUCCESS : ARES_ENOMEM; |
| 312 | } |
| 313 | |
| 314 | *s = NULL; |
| 315 | return ARES_SUCCESS; |
| 316 | } |