1 /* $OpenBSD: res_search_async.c,v 1.2 2012/09/09 09:42:06 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 21 #include <arpa/nameser.h> 22 23 #include <err.h> 24 #include <errno.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <unistd.h> 28 29 /* 30 * TODO: 31 * 32 * - make it possible to reuse ibuf if it was NULL when first called, 33 * to avoid reallocating buffers everytime. 34 */ 35 36 #include "asr.h" 37 #include "asr_private.h" 38 39 static int res_search_async_run(struct async *, struct async_res *); 40 41 /* 42 * Unlike res_query_async(), this function returns a valid packet only if 43 * h_errno is NETDB_SUCCESS. 44 */ 45 struct async * 46 res_search_async(const char *name, int class, int type, unsigned char *ans, 47 int anslen, struct asr *asr) 48 { 49 struct asr_ctx *ac; 50 struct async *as; 51 52 DPRINT("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type); 53 54 ac = asr_use_resolver(asr); 55 as = res_search_async_ctx(name, class, type, ans, anslen, ac); 56 asr_ctx_unref(ac); 57 58 return (as); 59 } 60 61 struct async * 62 res_search_async_ctx(const char *name, int class, int type, unsigned char *ans, 63 int anslen, struct asr_ctx *ac) 64 { 65 struct async *as; 66 67 DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class, type); 68 69 if ((as = 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 if (ans) { 76 as->as.search.flags |= ASYNC_EXTIBUF; 77 as->as.search.ibuf = ans; 78 as->as.search.ibufsize = anslen; 79 } else { 80 as->as.search.ibuf = NULL; 81 as->as.search.ibufsize = 0; 82 } 83 as->as.search.ibuflen = 0; 84 85 as->as.search.class = class; 86 as->as.search.type = type; 87 88 return (as); 89 err: 90 if (as) 91 async_free(as); 92 return (NULL); 93 } 94 95 #define HERRNO_UNSET -2 96 97 static int 98 res_search_async_run(struct async *as, struct async_res *ar) 99 { 100 int r; 101 char fqdn[MAXDNAME]; 102 103 next: 104 switch(as->as_state) { 105 106 case ASR_STATE_INIT: 107 108 as->as.search.saved_h_errno = HERRNO_UNSET; 109 async_set_state(as, ASR_STATE_NEXT_DOMAIN); 110 break; 111 112 case ASR_STATE_NEXT_DOMAIN: 113 114 /* Reset flags to be able to identify the case in STATE_SUBQUERY. */ 115 as->as_dom_flags = 0; 116 117 r = asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn)); 118 if (r == -1) { 119 async_set_state(as, ASR_STATE_NOT_FOUND); 120 break; 121 } 122 if (r > sizeof(fqdn)) { 123 ar->ar_errno = EINVAL; 124 ar->ar_h_errno = NO_RECOVERY; 125 ar->ar_datalen = -1; 126 ar->ar_data = NULL; 127 async_set_state(as, ASR_STATE_HALT); 128 break; 129 } 130 as->as.search.subq = res_query_async_ctx(fqdn, 131 as->as.search.class, as->as.search.type, 132 as->as.search.ibuf, as->as.search.ibufsize, as->as_ctx); 133 if (as->as.search.subq == NULL) { 134 ar->ar_errno = errno; 135 if (errno == EINVAL) 136 ar->ar_h_errno = NO_RECOVERY; 137 else 138 ar->ar_h_errno = NETDB_INTERNAL; 139 ar->ar_datalen = -1; 140 ar->ar_data = NULL; 141 async_set_state(as, ASR_STATE_HALT); 142 break; 143 } 144 async_set_state(as, ASR_STATE_SUBQUERY); 145 break; 146 147 case ASR_STATE_SUBQUERY: 148 149 if ((r = async_run(as->as.search.subq, ar)) == ASYNC_COND) 150 return (ASYNC_COND); 151 as->as.search.subq = NULL; 152 153 if (ar->ar_h_errno == NETDB_SUCCESS) { 154 async_set_state(as, ASR_STATE_HALT); 155 break; 156 } 157 158 /* 159 * The original res_search() does this in the domain search 160 * loop, but only for ECONNREFUSED. I think we can do better 161 * because technically if we get an errno, it means 162 * we couldn't reach any nameserver, so there is no point 163 * in trying further. 164 */ 165 if (ar->ar_errno) { 166 async_set_state(as, ASR_STATE_HALT); 167 break; 168 } 169 170 /* 171 * If we don't use an external buffer, the packet was allocated 172 * by the subquery and it must be freed now. 173 */ 174 if ((as->as.search.flags & ASYNC_EXTIBUF) == 0) 175 free(ar->ar_data); 176 177 /* 178 * The original resolver does something like this, to 179 */ 180 if (as->as_dom_flags & (ASYNC_DOM_NDOTS | ASYNC_DOM_ASIS)) 181 as->as.search.saved_h_errno = ar->ar_h_errno; 182 183 if (as->as_dom_flags & ASYNC_DOM_DOMAIN) { 184 if (ar->ar_h_errno == NO_DATA) 185 as->as.search.flags |= ASYNC_NODATA; 186 else if (ar->ar_h_errno == TRY_AGAIN) 187 as->as.search.flags |= ASYNC_AGAIN; 188 } 189 190 async_set_state(as, ASR_STATE_NEXT_DOMAIN); 191 break; 192 193 case ASR_STATE_NOT_FOUND: 194 195 if (as->as.search.saved_h_errno != HERRNO_UNSET) 196 ar->ar_h_errno = as->as.search.saved_h_errno; 197 else if (as->as.search.flags & ASYNC_NODATA) 198 ar->ar_h_errno = NO_DATA; 199 else if (as->as.search.flags & ASYNC_AGAIN) 200 ar->ar_h_errno = TRY_AGAIN; 201 /* 202 * Else, we got the ar_h_errno value set by res_query_async() 203 * for the last domain. 204 */ 205 ar->ar_datalen = -1; 206 ar->ar_data = NULL; 207 async_set_state(as, ASR_STATE_HALT); 208 break; 209 210 case ASR_STATE_HALT: 211 212 return (ASYNC_DONE); 213 214 default: 215 ar->ar_errno = EOPNOTSUPP; 216 ar->ar_h_errno = NETDB_INTERNAL; 217 async_set_state(as, ASR_STATE_HALT); 218 break; 219 } 220 goto next; 221 } 222