/* * Copyright 2005 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Niels Provos. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* $OpenBSD: res_query.c,v 1.21 2003/06/02 20:18:36 millert Exp $ */ /* * ++Copyright++ 1988, 1993 * - * Copyright (c) 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * Portions Copyright (c) 1993 by Digital Equipment Corporation. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies, and that * the name of Digital Equipment Corporation not be used in advertising or * publicity pertaining to distribution of the document or software without * specific, written prior permission. * * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. * - * --Copyright-- */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dnsres.h" #include "resolv.h" #include "dnsres-internal.h" #ifndef HAVE_ISSETUGID #define issetugid() 0 #endif const char *hostalias(struct dnsres *, const char *); struct res_search_state * res_search_state_new( struct dnsres *_resp, const char *name, /* domain name */ struct dnsres_target *q, void (*cb)(int, void *), void *cb_arg ) { struct res_search_state *state = calloc(1, sizeof(*state)); if (state == NULL) err(1, "%s: calloc", __func__); state->_resp = _resp; state->target = q; state->name = name; state->cb = cb; state->cb_arg = cb_arg; res_init_socket(&state->ds); return (state); } /* * Formulate a normal query, send, and await answer. * Returned answer is placed in supplied buffer "answer". * Perform preliminary check of answer, returning success only * if no error is indicated and the answer count is nonzero. * Return the size of the response on success, -1 on error. * Error number is left in dr_errno. * * Caller must parse answer and determine whether it answers the question. */ void res_query_cb(int, struct res_search_state *); void res_query_next(struct res_search_state *state); void res_query( struct dnsres *_resp, const char *name, /* domain name */ struct dnsres_target *q, void (*cb)(int, void *), void *cb_arg ) { struct res_search_state *state; /* Create a copy of our current state for callbacks */ state = res_search_state_new(_resp, name, q, cb, cb_arg); res_query_next(state); } void res_query_next(struct res_search_state *state) { struct dnsres *_resp = state->_resp; struct dnsres_target *q = state->target; DNSRES_HEADER *hp = (DNSRES_HEADER *) q->answer; int n; hp->rcode = DNSRES_NOERROR; /* default */ #ifdef DEBUG if (_resp->options & RES_DEBUG) printf(";; res_query(%s, %d, %d)\n", name, class, type); #endif n = res_mkquery(_resp, DNSRES_QUERY, state->name, q->qclass, q->qtype, NULL, 0, NULL, state->buf, sizeof(state->buf)); if (n > 0 && ((_resp->options & RES_USE_EDNS0) || (_resp->options & RES_USE_DNSSEC))) { n = res_opt(_resp, n, state->buf, sizeof(state->buf), q->anslen); } if (n <= 0) { #ifdef DEBUG if (_resp->options & RES_DEBUG) printf(";; res_query: mkquery failed\n"); #endif _resp->dr_errno = DNSRES_NO_RECOVERY; (*state->cb)(n, state->cb_arg); free(state); return; } res_send(_resp, state->buf, n, q->answer, q->anslen, res_query_cb, state); /* BLOCKING */ return; } void res_query_cb(int n, struct res_search_state *state) { struct dnsres *_resp = state->_resp; DNSRES_HEADER *hp = (DNSRES_HEADER *) state->target->answer; /* Count our success */ if (n > 0 && hp->rcode == DNSRES_NOERROR && ntohs(hp->ancount) != 0) { state->ancount += n; state->target->n = n; } /* Let's try again we if can */ if (state->target->next != NULL) { state->target = state->target->next; res_query_next(state); return; } if (state->ancount == 0) { #ifdef DEBUG if (_resp->options & RES_DEBUG) printf(";; rcode = %u, ancount=%u\n", hp->rcode, ntohs(hp->ancount)); #endif switch (hp->rcode) { case DNSRES_NXDOMAIN: _resp->dr_errno = DNSRES_HOST_NOT_FOUND; break; case DNSRES_SERVFAIL: _resp->dr_errno = DNSRES_TRY_AGAIN; break; case DNSRES_NOERROR: _resp->dr_errno = DNSRES_NO_DATA; break; case DNSRES_FORMERR: case DNSRES_NOTIMP: case DNSRES_REFUSED: default: _resp->dr_errno = DNSRES_NO_RECOVERY; break; } (*state->cb)(-1, state->cb_arg); free(state); return; } (*state->cb)(state->ancount, state->cb_arg); free(state); } /* * WARNING: * The original res_search got split apart at all blocking points. Due to * conditional branches that means that every blocking part needs to be split * into a separate function. I realize that this is very ugly and proponents * of threading are going to say: See, I told you. That is as it may be, * the goal is to make this non-blocking and the road to there is very ugly. * - niels */ /* This callback means we are done and supposed to return to the upper level */ void res_search_cb_done(int ret, void *arg) { struct res_search_state *res_state = arg; (*res_state->cb)(ret, res_state->cb_arg); free(res_state); } void res_search_cb_eval(int ret, void *arg); void res_search_continue(struct res_search_state *res_state); void res_search_domain_loop(struct res_search_state *res_state); void res_search_domain_loopbottom(struct res_search_state *res_state); void res_search_almostbottom(struct res_search_state *res_state); void res_search_bottom(struct res_search_state *res_state); /* * Formulate a normal query, send, and retrieve answer in supplied buffer. * Return the size of the response on success, -1 on error. * If enabled, implement search rules until answer or unrecoverable failure * is detected. Error code, if any, is left in dr_errno. * * The callback and callback argument are opaque to this function. * * This is a BLOCKING call. */ void res_search( struct dnsres *_resp, const char *name, /* domain name */ struct dnsres_target *q, void (*cb)(int, void *), void *state ) { const char *cp; u_int dots; int trailing_dot; struct res_search_state *res_state; /* Create the callback state for this part */ res_state = res_search_state_new(_resp, name, q, cb, state); errno = 0; /* default, if we never query */ _resp->dr_errno = DNSRES_HOST_NOT_FOUND; dots = 0; for (cp = name; *cp; cp++) dots += (*cp == '.'); trailing_dot = 0; if (cp > name && *--cp == '.') trailing_dot++; res_state->trailing_dot = trailing_dot; res_state->dots = dots; /* * if there aren't any dots, it could be a user-level alias */ if (!dots && (cp = hostalias(_resp, name)) != NULL) { /* BLOCKING */ res_query(_resp, cp, q, res_search_cb_done, res_state); return; } /* * If there are dots in the name already, let's just give it a try * 'as is'. The threshold can be set with the "ndots" option. */ res_state->saved_herrno = -1; if (dots >= _resp->ndots) { /* BLOCKING */ /* We have a primitive form of conditional here */ res_state->res_conditional_cb = res_search_continue; res_state->tried_as_is++; res_querydomain(_resp, name, NULL, q, res_search_cb_eval, res_state); return; } res_search_continue(res_state); } /* This callback is called when we tried the name as is because it got dots */ void res_search_cb_eval(int ret, void *arg) { struct res_search_state *res_state = arg; struct dnsres *_resp = res_state->_resp; if (ret > 0) { /* We are done */ (*res_state->cb)(ret, res_state->cb_arg); free(res_state); return; } /* The second time we try-as-is we don't want to save the errno */ if (!res_state->dont_save_errno) res_state->saved_herrno = _resp->dr_errno; (*res_state->res_conditional_cb)(res_state); } void res_search_continue(struct res_search_state *res_state) { struct dnsres *_resp = res_state->_resp; u_int dots = res_state->dots; int trailing_dot = res_state->trailing_dot; /* * We do at least one level of search if * - there is no dot and RES_DEFNAME is set, or * - there is at least one dot, there is no trailing dot, * and RES_DNSRCH is set. */ if ((!dots && (_resp->options & RES_DEFNAMES)) || (dots && !trailing_dot && (_resp->options & RES_DNSRCH))) { /* Setup the variable for the loop */ res_state->dont_save_errno = 1; res_state->done = 0; res_state->domain = (const char * const *)_resp->dnsrch; res_search_domain_loop(res_state); return; } res_search_almostbottom(res_state); } void res_search_domain_loop(struct res_search_state *res_state) { const char *cur_domain; struct dnsres_target *target = res_state->target; /* Check termination of the loop */ if (!*res_state->domain || res_state->done) { res_search_almostbottom(res_state); return; } cur_domain = *res_state->domain; /* Prepare for next loop */ res_state->domain++; /* BLOCKING */ res_state->res_conditional_cb = res_search_domain_loopbottom; res_querydomain(res_state->_resp, res_state->name, cur_domain, target, res_search_cb_eval, res_state); return; } void res_search_domain_loopbottom(struct res_search_state *res_state) { struct dnsres *_resp = res_state->_resp; /* * If no server present, give up. * If name isn't found in this domain, keep trying higher * domains in the search list (if that's enabled). * On a NO_DATA error, keep trying, otherwise a wildcard * entry of another type could keep us from finding this * entry higher in the domain. * If we get some other error (negative answer or server * failure), then stop searching up, but try the input name * below in case it's fully-qualified. */ if (errno == ECONNREFUSED) { _resp->dr_errno = DNSRES_TRY_AGAIN; (*res_state->cb)(-1, res_state->cb_arg); free(res_state); return; } switch (_resp->dr_errno) { case DNSRES_NO_DATA: res_state->got_nodata++; /* FALLTHROUGH */ case DNSRES_HOST_NOT_FOUND: /* keep trying */ break; case DNSRES_TRY_AGAIN: { DNSRES_HEADER *hp = (DNSRES_HEADER *) res_state->target->answer; if (hp->rcode == DNSRES_SERVFAIL) { /* try next search element, if any */ res_state->got_servfail++; break; } /* FALLTHROUGH */ } default: /* anything else implies that we're done */ res_state->done++; } /* if we got here for some reason other than DNSRCH, * we only wanted one iteration of the loop, so stop. */ if (!(_resp->options & RES_DNSRCH)) res_state->done++; /* Continue execution of the loop - termination is handled there */ res_search_domain_loop(res_state); } void res_search_almostbottom(struct res_search_state *res_state) { /* if we have not already tried the name "as is", do that now. * note that we do this regardless of how many dots were in the * name or whether it ends with a dot. */ if (!res_state->tried_as_is) { struct dnsres_target *target = res_state->target; res_state->res_conditional_cb = res_search_bottom; res_state->dont_save_errno = 1; /* BLOCKING */ res_querydomain(res_state->_resp, res_state->name, NULL, target, res_search_cb_eval, res_state); return; } res_search_bottom(res_state); } void res_search_bottom(struct res_search_state *res_state) { struct dnsres *_resp = res_state->_resp; /* if we got here, we didn't satisfy the search. * if we did an initial full query, return that query's dr_errno * (note that we wouldn't be here if that query had succeeded). * else if we ever got a nodata, send that back as the reason. * else send back meaningless dr_errno, that being the one from * the last DNSRCH we did. */ if (res_state->saved_herrno != -1) _resp->dr_errno = res_state->saved_herrno; else if (res_state->got_nodata) _resp->dr_errno = DNSRES_NO_DATA; else if (res_state->got_servfail) _resp->dr_errno = DNSRES_TRY_AGAIN; (*res_state->cb)(-1, res_state->cb_arg); free(res_state); } /* * Perform a call on res_query on the concatenation of name and domain, * removing a trailing dot from name if domain is NULL. */ void res_querydomain( struct dnsres *_resp, const char *name, const char *domain, struct dnsres_target *q, void (*cb)(int, void *), void *cb_arg ) { char nbuf[DNSRES_MAXDNAME*2+1+1]; const char *longname = nbuf; int n; #ifdef DEBUG if (_resp->options & RES_DEBUG) printf(";; res_querydomain(%s, %s, %d, %d)\n", name, domain?domain:"", class, type); #endif if (domain == NULL) { /* * Check for trailing '.'; * copy without '.' if present. */ n = strlen(name) - 1; if (n != (0 - 1) && name[n] == '.' && n < sizeof(nbuf) - 1) { bcopy(name, nbuf, n); nbuf[n] = '\0'; } else longname = name; } else snprintf(nbuf, sizeof nbuf, "%.*s.%.*s", DNSRES_MAXDNAME, name, DNSRES_MAXDNAME, domain); /* BLOCKING */ res_query(_resp, longname, q, cb, cb_arg); } const char * hostalias(struct dnsres* _resp, const char *name) { unsigned char *cp1, *cp2; FILE *fp; char *file; char buf[BUFSIZ]; static char abuf[DNSRES_MAXDNAME]; size_t len; if (_resp->options & RES_NOALIASES) return (NULL); file = getenv("HOSTALIASES"); if (issetugid() != 0 || file == NULL || (fp = fopen(file, "r")) == NULL) return (NULL); setbuf(fp, NULL); while ((cp1 = fgetln(fp, &len)) != NULL) { if (cp1[len-1] == '\n') len--; if (len >= sizeof(buf) || len == 0) continue; (void)memcpy(buf, cp1, len); buf[len] = '\0'; for (cp1 = buf; *cp1 && !isspace(*cp1); ++cp1) ; if (!*cp1) break; *cp1 = '\0'; if (!strcasecmp(buf, name)) { while (isspace(*++cp1)) ; if (!*cp1) break; for (cp2 = cp1 + 1; *cp2 && !isspace(*cp2); ++cp2) ; *cp2 = '\0'; strlcpy(abuf, cp1, sizeof(abuf)); fclose(fp); return (abuf); } } fclose(fp); return (NULL); }