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