1 /* $OpenBSD: res_search_async.c,v 1.21 2017/02/27 10:44:46 jca 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 61 DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class, 62 type); 63 64 if ((as = _asr_async_new(ac, ASR_SEARCH)) == NULL) 65 goto err; /* errno set */ 66 as->as_run = res_search_async_run; 67 if ((as->as.search.name = strdup(name)) == NULL) 68 goto err; /* errno set */ 69 70 as->as.search.class = class; 71 as->as.search.type = type; 72 73 return (as); 74 err: 75 if (as) 76 _asr_async_free(as); 77 return (NULL); 78 } 79 80 #define HERRNO_UNSET -2 81 82 static int 83 res_search_async_run(struct asr_query *as, struct asr_result *ar) 84 { 85 int r; 86 char fqdn[MAXDNAME]; 87 88 next: 89 switch (as->as_state) { 90 91 case ASR_STATE_INIT: 92 93 if (as->as.search.name[0] == '\0') { 94 ar->ar_h_errno = NO_DATA; 95 async_set_state(as, ASR_STATE_HALT); 96 break; 97 } 98 99 as->as.search.saved_h_errno = HERRNO_UNSET; 100 async_set_state(as, ASR_STATE_NEXT_DOMAIN); 101 break; 102 103 case ASR_STATE_NEXT_DOMAIN: 104 /* 105 * Reset flags to be able to identify the case in 106 * STATE_SUBQUERY. 107 */ 108 as->as_dom_flags = 0; 109 110 r = _asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn)); 111 if (r == -1) { 112 async_set_state(as, ASR_STATE_NOT_FOUND); 113 break; 114 } 115 if (r == 0) { 116 ar->ar_errno = EINVAL; 117 ar->ar_h_errno = NO_RECOVERY; 118 ar->ar_datalen = -1; 119 ar->ar_data = NULL; 120 async_set_state(as, ASR_STATE_HALT); 121 break; 122 } 123 as->as_subq = _res_query_async_ctx(fqdn, 124 as->as.search.class, as->as.search.type, as->as_ctx); 125 if (as->as_subq == NULL) { 126 ar->ar_errno = errno; 127 if (errno == EINVAL) 128 ar->ar_h_errno = NO_RECOVERY; 129 else 130 ar->ar_h_errno = NETDB_INTERNAL; 131 ar->ar_datalen = -1; 132 ar->ar_data = NULL; 133 async_set_state(as, ASR_STATE_HALT); 134 break; 135 } 136 async_set_state(as, ASR_STATE_SUBQUERY); 137 break; 138 139 case ASR_STATE_SUBQUERY: 140 141 if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND) 142 return (ASYNC_COND); 143 as->as_subq = NULL; 144 145 if (ar->ar_h_errno == NETDB_SUCCESS) { 146 async_set_state(as, ASR_STATE_HALT); 147 break; 148 } 149 150 /* 151 * The original res_search() does this in the domain search 152 * loop, but only for ECONNREFUSED. I think we can do better 153 * because technically if we get an errno, it means 154 * we couldn't reach any nameserver, so there is no point 155 * in trying further. 156 */ 157 if (ar->ar_errno) { 158 async_set_state(as, ASR_STATE_HALT); 159 break; 160 } 161 162 free(ar->ar_data); 163 164 /* 165 * The original resolver does something like this. 166 */ 167 if (as->as_dom_flags & ASYNC_DOM_NDOTS) 168 as->as.search.saved_h_errno = ar->ar_h_errno; 169 170 if (as->as_dom_flags & ASYNC_DOM_DOMAIN) { 171 if (ar->ar_h_errno == NO_DATA) 172 as->as_flags |= ASYNC_NODATA; 173 else if (ar->ar_h_errno == TRY_AGAIN) 174 as->as_flags |= ASYNC_AGAIN; 175 } 176 177 async_set_state(as, ASR_STATE_NEXT_DOMAIN); 178 break; 179 180 case ASR_STATE_NOT_FOUND: 181 182 if (as->as.search.saved_h_errno != HERRNO_UNSET) 183 ar->ar_h_errno = as->as.search.saved_h_errno; 184 else if (as->as_flags & ASYNC_NODATA) 185 ar->ar_h_errno = NO_DATA; 186 else if (as->as_flags & ASYNC_AGAIN) 187 ar->ar_h_errno = TRY_AGAIN; 188 /* 189 * Else, we got the ar_h_errno value set by res_query_async() 190 * for the last domain. 191 */ 192 ar->ar_datalen = -1; 193 ar->ar_data = NULL; 194 async_set_state(as, ASR_STATE_HALT); 195 break; 196 197 case ASR_STATE_HALT: 198 199 return (ASYNC_DONE); 200 201 default: 202 ar->ar_errno = EOPNOTSUPP; 203 ar->ar_h_errno = NETDB_INTERNAL; 204 async_set_state(as, ASR_STATE_HALT); 205 break; 206 } 207 goto next; 208 } 209 210 /* 211 * Concatenate a name and a domain name. The result has no trailing dot. 212 * Return the resulting string length, or 0 in case of error. 213 */ 214 static size_t 215 domcat(const char *name, const char *domain, char *buf, size_t buflen) 216 { 217 size_t r; 218 219 r = _asr_make_fqdn(name, domain, buf, buflen); 220 if (r == 0) 221 return (0); 222 buf[r - 1] = '\0'; 223 224 return (r - 1); 225 } 226 227 enum { 228 DOM_INIT, 229 DOM_DOMAIN, 230 DOM_DONE 231 }; 232 233 /* 234 * Implement the search domain strategy. 235 * 236 * This function works as a generator that constructs complete domains in 237 * buffer "buf" of size "len" for the given host name "name", according to the 238 * search rules defined by the resolving context. It is supposed to be called 239 * multiple times (with the same name) to generate the next possible domain 240 * name, if any. 241 * 242 * It returns -1 if all possibilities have been exhausted, 0 if there was an 243 * error generating the next name, or the resulting name length. 244 */ 245 int 246 _asr_iter_domain(struct asr_query *as, const char *name, char * buf, size_t len) 247 { 248 const char *c; 249 int dots; 250 251 switch (as->as_dom_step) { 252 253 case DOM_INIT: 254 /* First call */ 255 256 /* 257 * If "name" is an FQDN, that's the only result and we 258 * don't try anything else. 259 */ 260 if (strlen(name) && name[strlen(name) - 1] == '.') { 261 DPRINT("asr: iter_domain(\"%s\") fqdn\n", name); 262 as->as_dom_flags |= ASYNC_DOM_FQDN; 263 as->as_dom_step = DOM_DONE; 264 return (domcat(name, NULL, buf, len)); 265 } 266 267 /* 268 * Otherwise, we iterate through the specified search domains. 269 */ 270 as->as_dom_step = DOM_DOMAIN; 271 as->as_dom_idx = 0; 272 273 /* 274 * If "name" as enough dots, use it as-is first, as indicated 275 * in resolv.conf(5). 276 */ 277 dots = 0; 278 for (c = name; *c; c++) 279 dots += (*c == '.'); 280 if (dots >= as->as_ctx->ac_ndots) { 281 DPRINT("asr: iter_domain(\"%s\") ndots\n", name); 282 as->as_dom_flags |= ASYNC_DOM_NDOTS; 283 if (strlcpy(buf, name, len) >= len) 284 return (0); 285 return (strlen(buf)); 286 } 287 /* Otherwise, starts using the search domains */ 288 /* FALLTHROUGH */ 289 290 case DOM_DOMAIN: 291 if (as->as_dom_idx < as->as_ctx->ac_domcount && 292 (as->as_ctx->ac_options & RES_DNSRCH || ( 293 as->as_ctx->ac_options & RES_DEFNAMES && 294 as->as_dom_idx == 0 && 295 strchr(name, '.') == NULL))) { 296 DPRINT("asr: iter_domain(\"%s\") domain \"%s\"\n", 297 name, as->as_ctx->ac_dom[as->as_dom_idx]); 298 as->as_dom_flags |= ASYNC_DOM_DOMAIN; 299 return (domcat(name, 300 as->as_ctx->ac_dom[as->as_dom_idx++], buf, len)); 301 } 302 303 /* No more domain to try. */ 304 305 as->as_dom_step = DOM_DONE; 306 307 /* 308 * If the name was not tried as an absolute name before, 309 * do it now. 310 */ 311 if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) { 312 DPRINT("asr: iter_domain(\"%s\") as is\n", name); 313 as->as_dom_flags |= ASYNC_DOM_ASIS; 314 if (strlcpy(buf, name, len) >= len) 315 return (0); 316 return (strlen(buf)); 317 } 318 /* Otherwise, we are done. */ 319 320 case DOM_DONE: 321 default: 322 DPRINT("asr: iter_domain(\"%s\") done\n", name); 323 return (-1); 324 } 325 } 326