1 /* $OpenBSD: res_search_async.c,v 1.7 2013/04/30 12:02:39 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 33 /* 34 * Unlike res_query_async(), this function returns a valid packet only if 35 * h_errno is NETDB_SUCCESS. 36 */ 37 struct async * 38 res_search_async(const char *name, int class, int type, struct asr *asr) 39 { 40 struct asr_ctx *ac; 41 struct async *as; 42 43 DPRINT("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type); 44 45 ac = asr_use_resolver(asr); 46 as = res_search_async_ctx(name, class, type, ac); 47 asr_ctx_unref(ac); 48 49 return (as); 50 } 51 52 struct async * 53 res_search_async_ctx(const char *name, int class, int type, struct asr_ctx *ac) 54 { 55 struct async *as; 56 57 DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class, 58 type); 59 60 if ((as = async_new(ac, ASR_SEARCH)) == NULL) 61 goto err; /* errno set */ 62 as->as_run = res_search_async_run; 63 if ((as->as.search.name = strdup(name)) == NULL) 64 goto err; /* errno set */ 65 66 as->as.search.class = class; 67 as->as.search.type = type; 68 69 return (as); 70 err: 71 if (as) 72 async_free(as); 73 return (NULL); 74 } 75 76 #define HERRNO_UNSET -2 77 78 static int 79 res_search_async_run(struct async *as, struct async_res *ar) 80 { 81 int r; 82 char fqdn[MAXDNAME]; 83 84 next: 85 switch (as->as_state) { 86 87 case ASR_STATE_INIT: 88 89 as->as.search.saved_h_errno = HERRNO_UNSET; 90 async_set_state(as, ASR_STATE_NEXT_DOMAIN); 91 break; 92 93 case ASR_STATE_NEXT_DOMAIN: 94 /* 95 * Reset flags to be able to identify the case in 96 * STATE_SUBQUERY. 97 */ 98 as->as_dom_flags = 0; 99 100 r = asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn)); 101 if (r == -1) { 102 async_set_state(as, ASR_STATE_NOT_FOUND); 103 break; 104 } 105 if (r == 0) { 106 ar->ar_errno = EINVAL; 107 ar->ar_h_errno = NO_RECOVERY; 108 ar->ar_datalen = -1; 109 ar->ar_data = NULL; 110 async_set_state(as, ASR_STATE_HALT); 111 break; 112 } 113 as->as.search.subq = res_query_async_ctx(fqdn, 114 as->as.search.class, as->as.search.type, as->as_ctx); 115 if (as->as.search.subq == NULL) { 116 ar->ar_errno = errno; 117 if (errno == EINVAL) 118 ar->ar_h_errno = NO_RECOVERY; 119 else 120 ar->ar_h_errno = NETDB_INTERNAL; 121 ar->ar_datalen = -1; 122 ar->ar_data = NULL; 123 async_set_state(as, ASR_STATE_HALT); 124 break; 125 } 126 async_set_state(as, ASR_STATE_SUBQUERY); 127 break; 128 129 case ASR_STATE_SUBQUERY: 130 131 if ((r = async_run(as->as.search.subq, ar)) == ASYNC_COND) 132 return (ASYNC_COND); 133 as->as.search.subq = NULL; 134 135 if (ar->ar_h_errno == NETDB_SUCCESS) { 136 async_set_state(as, ASR_STATE_HALT); 137 break; 138 } 139 140 /* 141 * The original res_search() does this in the domain search 142 * loop, but only for ECONNREFUSED. I think we can do better 143 * because technically if we get an errno, it means 144 * we couldn't reach any nameserver, so there is no point 145 * in trying further. 146 */ 147 if (ar->ar_errno) { 148 async_set_state(as, ASR_STATE_HALT); 149 break; 150 } 151 152 free(ar->ar_data); 153 154 /* 155 * The original resolver does something like this. 156 */ 157 if (as->as_dom_flags & (ASYNC_DOM_NDOTS | ASYNC_DOM_ASIS)) 158 as->as.search.saved_h_errno = ar->ar_h_errno; 159 160 if (as->as_dom_flags & ASYNC_DOM_DOMAIN) { 161 if (ar->ar_h_errno == NO_DATA) 162 as->as.search.flags |= ASYNC_NODATA; 163 else if (ar->ar_h_errno == TRY_AGAIN) 164 as->as.search.flags |= ASYNC_AGAIN; 165 } 166 167 async_set_state(as, ASR_STATE_NEXT_DOMAIN); 168 break; 169 170 case ASR_STATE_NOT_FOUND: 171 172 if (as->as.search.saved_h_errno != HERRNO_UNSET) 173 ar->ar_h_errno = as->as.search.saved_h_errno; 174 else if (as->as.search.flags & ASYNC_NODATA) 175 ar->ar_h_errno = NO_DATA; 176 else if (as->as.search.flags & ASYNC_AGAIN) 177 ar->ar_h_errno = TRY_AGAIN; 178 /* 179 * Else, we got the ar_h_errno value set by res_query_async() 180 * for the last domain. 181 */ 182 ar->ar_datalen = -1; 183 ar->ar_data = NULL; 184 async_set_state(as, ASR_STATE_HALT); 185 break; 186 187 case ASR_STATE_HALT: 188 189 return (ASYNC_DONE); 190 191 default: 192 ar->ar_errno = EOPNOTSUPP; 193 ar->ar_h_errno = NETDB_INTERNAL; 194 async_set_state(as, ASR_STATE_HALT); 195 break; 196 } 197 goto next; 198 } 199