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