1 /* $OpenBSD: res_search_async.c,v 1.18 2015/09/20 14:19:21 eric Exp $ */ 2 /* 3 * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/types.h> 19 #include <sys/socket.h> 20 #include <sys/uio.h> 21 #include <arpa/nameser.h> 22 #include <netdb.h> 23 24 #include <asr.h> 25 #include <errno.h> 26 #include <resolv.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <unistd.h> 30 31 #include "asr_private.h" 32 33 static int res_search_async_run(struct asr_query *, struct asr_result *); 34 static size_t domcat(const char *, const char *, char *, size_t); 35 36 /* 37 * Unlike res_query_async(), this function returns a valid packet only if 38 * h_errno is NETDB_SUCCESS. 39 */ 40 struct asr_query * 41 res_search_async(const char *name, int class, int type, void *asr) 42 { 43 struct asr_ctx *ac; 44 struct asr_query *as; 45 46 DPRINT("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type); 47 48 ac = _asr_use_resolver(asr); 49 as = _res_search_async_ctx(name, class, type, ac); 50 _asr_ctx_unref(ac); 51 52 return (as); 53 } 54 DEF_WEAK(res_search_async); 55 56 struct asr_query * 57 _res_search_async_ctx(const char *name, int class, int type, struct asr_ctx *ac) 58 { 59 struct asr_query *as; 60 char alias[MAXDNAME]; 61 62 DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class, 63 type); 64 65 if (_asr_hostalias(ac, name, alias, sizeof(alias))) 66 return _res_query_async_ctx(alias, class, type, ac); 67 68 if ((as = _asr_async_new(ac, ASR_SEARCH)) == NULL) 69 goto err; /* errno set */ 70 as->as_run = res_search_async_run; 71 if ((as->as.search.name = strdup(name)) == NULL) 72 goto err; /* errno set */ 73 74 as->as.search.class = class; 75 as->as.search.type = type; 76 77 return (as); 78 err: 79 if (as) 80 _asr_async_free(as); 81 return (NULL); 82 } 83 84 #define HERRNO_UNSET -2 85 86 static int 87 res_search_async_run(struct asr_query *as, struct asr_result *ar) 88 { 89 int r; 90 char fqdn[MAXDNAME]; 91 92 next: 93 switch (as->as_state) { 94 95 case ASR_STATE_INIT: 96 97 if (as->as.search.name[0] == '\0') { 98 ar->ar_h_errno = NO_DATA; 99 async_set_state(as, ASR_STATE_HALT); 100 break; 101 } 102 103 as->as.search.saved_h_errno = HERRNO_UNSET; 104 async_set_state(as, ASR_STATE_NEXT_DOMAIN); 105 break; 106 107 case ASR_STATE_NEXT_DOMAIN: 108 /* 109 * Reset flags to be able to identify the case in 110 * STATE_SUBQUERY. 111 */ 112 as->as_dom_flags = 0; 113 114 r = _asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn)); 115 if (r == -1) { 116 async_set_state(as, ASR_STATE_NOT_FOUND); 117 break; 118 } 119 if (r == 0) { 120 ar->ar_errno = EINVAL; 121 ar->ar_h_errno = NO_RECOVERY; 122 ar->ar_datalen = -1; 123 ar->ar_data = NULL; 124 async_set_state(as, ASR_STATE_HALT); 125 break; 126 } 127 as->as.search.subq = _res_query_async_ctx(fqdn, 128 as->as.search.class, as->as.search.type, as->as_ctx); 129 if (as->as.search.subq == NULL) { 130 ar->ar_errno = errno; 131 if (errno == EINVAL) 132 ar->ar_h_errno = NO_RECOVERY; 133 else 134 ar->ar_h_errno = NETDB_INTERNAL; 135 ar->ar_datalen = -1; 136 ar->ar_data = NULL; 137 async_set_state(as, ASR_STATE_HALT); 138 break; 139 } 140 async_set_state(as, ASR_STATE_SUBQUERY); 141 break; 142 143 case ASR_STATE_SUBQUERY: 144 145 if ((r = asr_run(as->as.search.subq, ar)) == ASYNC_COND) 146 return (ASYNC_COND); 147 as->as.search.subq = NULL; 148 149 if (ar->ar_h_errno == NETDB_SUCCESS) { 150 async_set_state(as, ASR_STATE_HALT); 151 break; 152 } 153 154 /* 155 * The original res_search() does this in the domain search 156 * loop, but only for ECONNREFUSED. I think we can do better 157 * because technically if we get an errno, it means 158 * we couldn't reach any nameserver, so there is no point 159 * in trying further. 160 */ 161 if (ar->ar_errno) { 162 async_set_state(as, ASR_STATE_HALT); 163 break; 164 } 165 166 free(ar->ar_data); 167 168 /* 169 * The original resolver does something like this. 170 */ 171 if (as->as_dom_flags & ASYNC_DOM_NDOTS) 172 as->as.search.saved_h_errno = ar->ar_h_errno; 173 174 if (as->as_dom_flags & ASYNC_DOM_DOMAIN) { 175 if (ar->ar_h_errno == NO_DATA) 176 as->as.search.flags |= ASYNC_NODATA; 177 else if (ar->ar_h_errno == TRY_AGAIN) 178 as->as.search.flags |= ASYNC_AGAIN; 179 } 180 181 async_set_state(as, ASR_STATE_NEXT_DOMAIN); 182 break; 183 184 case ASR_STATE_NOT_FOUND: 185 186 if (as->as.search.saved_h_errno != HERRNO_UNSET) 187 ar->ar_h_errno = as->as.search.saved_h_errno; 188 else if (as->as.search.flags & ASYNC_NODATA) 189 ar->ar_h_errno = NO_DATA; 190 else if (as->as.search.flags & ASYNC_AGAIN) 191 ar->ar_h_errno = TRY_AGAIN; 192 /* 193 * Else, we got the ar_h_errno value set by res_query_async() 194 * for the last domain. 195 */ 196 ar->ar_datalen = -1; 197 ar->ar_data = NULL; 198 async_set_state(as, ASR_STATE_HALT); 199 break; 200 201 case ASR_STATE_HALT: 202 203 return (ASYNC_DONE); 204 205 default: 206 ar->ar_errno = EOPNOTSUPP; 207 ar->ar_h_errno = NETDB_INTERNAL; 208 async_set_state(as, ASR_STATE_HALT); 209 break; 210 } 211 goto next; 212 } 213 214 /* 215 * Concatenate a name and a domain name. The result has no trailing dot. 216 * Return the resulting string length, or 0 in case of error. 217 */ 218 static size_t 219 domcat(const char *name, const char *domain, char *buf, size_t buflen) 220 { 221 size_t r; 222 223 r = _asr_make_fqdn(name, domain, buf, buflen); 224 if (r == 0) 225 return (0); 226 buf[r - 1] = '\0'; 227 228 return (r - 1); 229 } 230 231 enum { 232 DOM_INIT, 233 DOM_DOMAIN, 234 DOM_DONE 235 }; 236 237 /* 238 * Implement the search domain strategy. 239 * 240 * This function works as a generator that constructs complete domains in 241 * buffer "buf" of size "len" for the given host name "name", according to the 242 * search rules defined by the resolving context. It is supposed to be called 243 * multiple times (with the same name) to generate the next possible domain 244 * name, if any. 245 * 246 * It returns -1 if all possibilities have been exhausted, 0 if there was an 247 * error generating the next name, or the resulting name length. 248 */ 249 int 250 _asr_iter_domain(struct asr_query *as, const char *name, char * buf, size_t len) 251 { 252 const char *c; 253 int dots; 254 255 switch (as->as_dom_step) { 256 257 case DOM_INIT: 258 /* First call */ 259 260 /* 261 * If "name" is an FQDN, that's the only result and we 262 * don't try anything else. 263 */ 264 if (strlen(name) && name[strlen(name) - 1] == '.') { 265 DPRINT("asr: iter_domain(\"%s\") fqdn\n", name); 266 as->as_dom_flags |= ASYNC_DOM_FQDN; 267 as->as_dom_step = DOM_DONE; 268 return (domcat(name, NULL, buf, len)); 269 } 270 271 /* 272 * Otherwise, we iterate through the specified search domains. 273 */ 274 as->as_dom_step = DOM_DOMAIN; 275 as->as_dom_idx = 0; 276 277 /* 278 * If "name" as enough dots, use it as-is first, as indicated 279 * in resolv.conf(5). 280 */ 281 dots = 0; 282 for (c = name; *c; c++) 283 dots += (*c == '.'); 284 if (dots >= as->as_ctx->ac_ndots) { 285 DPRINT("asr: iter_domain(\"%s\") ndots\n", name); 286 as->as_dom_flags |= ASYNC_DOM_NDOTS; 287 if (strlcpy(buf, name, len) >= len) 288 return (0); 289 return (strlen(buf)); 290 } 291 /* Otherwise, starts using the search domains */ 292 /* FALLTHROUGH */ 293 294 case DOM_DOMAIN: 295 if (as->as_dom_idx < as->as_ctx->ac_domcount && 296 (as->as_ctx->ac_options & RES_DNSRCH || ( 297 as->as_ctx->ac_options & RES_DEFNAMES && 298 as->as_dom_idx == 0 && 299 strchr(name, '.') == NULL))) { 300 DPRINT("asr: iter_domain(\"%s\") domain \"%s\"\n", 301 name, as->as_ctx->ac_dom[as->as_dom_idx]); 302 as->as_dom_flags |= ASYNC_DOM_DOMAIN; 303 return (domcat(name, 304 as->as_ctx->ac_dom[as->as_dom_idx++], buf, len)); 305 } 306 307 /* No more domain to try. */ 308 309 as->as_dom_step = DOM_DONE; 310 311 /* 312 * If the name was not tried as an absolute name before, 313 * do it now. 314 */ 315 if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) { 316 DPRINT("asr: iter_domain(\"%s\") as is\n", name); 317 as->as_dom_flags |= ASYNC_DOM_ASIS; 318 if (strlcpy(buf, name, len) >= len) 319 return (0); 320 return (strlen(buf)); 321 } 322 /* Otherwise, we are done. */ 323 324 case DOM_DONE: 325 default: 326 DPRINT("asr: iter_domain(\"%s\") done\n", name); 327 return (-1); 328 } 329 } 330