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