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