/* * 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_send.c,v 1.15 2003/06/02 20:18:36 millert Exp $ */ /* * ++Copyright++ 1985, 1989, 1993 * - * Copyright (c) 1985, 1989, 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-- */ /* change this to "0" * if you talk to a lot * of multi-homed SunOS * ("broken") name servers. */ #define CHECK_SRVR_ADDR 1 /* XXX - should be in options.h */ /* * Send query to name server and wait for reply. */ #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 FD_SET /* XXX - should be in portability.h */ #define NFDBITS 32 #define FD_SETSIZE 32 #define FD_SET(n, p) ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS))) #define FD_CLR(n, p) ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS))) #define FD_ISSET(n, p) ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS))) #define FD_ZERO(p) bzero((char *)(p), sizeof(*(p))) #endif #ifndef DEBUG # define Dprint(cond, args) /*empty*/ # define DprintQ(cond, args, query, size) /*empty*/ # define Aerror(file, string, error, address) /*empty*/ # define Perror(file, string, error) /*empty*/ #else # define Dprint(cond, args) if (cond) {fprintf args;} else {} # define DprintQ(cond, args, query, size) if (cond) {\ fprintf args;\ __fp_nquery(query, size, stdout);\ } else {} static char abuf[NI_MAXHOST]; static char pbuf[NI_MAXSERV]; static void Aerror(FILE *, char *, int, struct sockaddr *); static void Perror(FILE *, char *, int); static void Aerror(file, string, error, address) FILE *file; char *string; int error; struct sockaddr *address; { struct dnsres *_resp = NULL; int save = errno; socklen_t salen; if (address->sa_family == AF_INET6) salen = sizeof(struct sockaddr_in6); else if (address->sa_family == AF_INET) salen = sizeof(struct sockaddr_in); else salen = 0; /*unknown, die on connect*/ if (_resp->options & RES_DEBUG) { if (getnameinfo(address, salen, abuf, sizeof(abuf), pbuf, sizeof(pbuf), NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID) != 0) { strlcpy(abuf, "?", sizeof(abuf)); strlcpy(pbuf, "?", sizeof(pbuf)); } fprintf(file, "res_send: %s ([%s].%s): %s\n", string, abuf, pbuf, strerror(error)); } errno = save; } static void Perror(file, string, error) FILE *file; char *string; int error; { struct dnsres *_resp = NULL; int save = errno; if (_resp->options & RES_DEBUG) { fprintf(file, "res_send: %s: %s\n", string, strerror(error)); } errno = save; } #endif static res_send_qhook Qhook = NULL; static res_send_rhook Rhook = NULL; void res_send_setqhook(res_send_qhook hook) { Qhook = hook; } void res_send_setrhook(res_send_rhook hook) { Rhook = hook; } static struct sockaddr * get_nsaddr(struct dnsres *, size_t n); /* * pick appropriate nsaddr_list for use. see res_init() for initialization. */ static struct sockaddr * get_nsaddr(struct dnsres *_resp, size_t n) { struct dnsres_ext *_res_extp = &_resp->ext; if (!_resp->nsaddr_list[n].sin_family) { /* * - _res_extp->nsaddr_list[n] holds an address that is larger * than struct sockaddr, and * - user code did not update _resp->nsaddr_list[n]. */ return (struct sockaddr *)&_res_extp->nsaddr_list[n]; } else { /* * - user code updated _res.nsaddr_list[n], or * - _resp->nsaddr_list[n] has the same content as * _res_extp->nsaddr_list[n]. */ return (struct sockaddr *)&_resp->nsaddr_list[n]; } } /* int * res_isourserver(ina) * looks up "ina" in _resp->ns_addr_list[] * returns: * 0 : not found * >0 : found * author: * paul vixie, 29may94 */ int res_isourserver(struct dnsres *_resp, const struct sockaddr_in *inp) { const struct sockaddr_in6 *in6p = (const struct sockaddr_in6 *)inp; const struct sockaddr_in6 *srv6; const struct sockaddr_in *srv; int ns, ret; ret = 0; switch (inp->sin_family) { case AF_INET6: for (ns = 0; ns < _resp->nscount; ns++) { srv6 = (struct sockaddr_in6 *)get_nsaddr(_resp, ns); if (srv6->sin6_family == in6p->sin6_family && srv6->sin6_port == in6p->sin6_port && srv6->sin6_scope_id == in6p->sin6_scope_id && (IN6_IS_ADDR_UNSPECIFIED(&srv6->sin6_addr) || IN6_ARE_ADDR_EQUAL(&srv6->sin6_addr, &in6p->sin6_addr))) { ret++; break; } } break; case AF_INET: for (ns = 0; ns < _resp->nscount; ns++) { srv = (struct sockaddr_in *)get_nsaddr(_resp, ns); if (srv->sin_family == inp->sin_family && srv->sin_port == inp->sin_port && (srv->sin_addr.s_addr == INADDR_ANY || srv->sin_addr.s_addr == inp->sin_addr.s_addr)) { ret++; break; } } break; } return (ret); } /* int * res_nameinquery(name, type, class, buf, eom) * look for (name,type,class) in the query section of packet (buf,eom) * returns: * -1 : format error * 0 : not found * >0 : found * author: * paul vixie, 29may94 */ int res_nameinquery(name, type, class, buf, eom) const char *name; register int type, class; const u_char *buf, *eom; { register const u_char *cp = buf + DNSRES_HFIXEDSZ; int qdcount = ntohs(((DNSRES_HEADER*)buf)->qdcount); while (qdcount-- > 0) { char tname[DNSRES_MAXDNAME+1]; register int n, ttype, tclass; n = dn_expand(buf, eom, cp, tname, sizeof tname); if (n < 0) return (-1); cp += n; ttype = getshort(cp); cp += DNSRES_INT16SZ; tclass = getshort(cp); cp += DNSRES_INT16SZ; if (ttype == type && tclass == class && strcasecmp(tname, name) == 0) return (1); } return (0); } /* int * res_queriesmatch(buf1, eom1, buf2, eom2) * is there a 1:1 mapping of (name,type,class) * in (buf1,eom1) and (buf2,eom2)? * returns: * -1 : format error * 0 : not a 1:1 mapping * >0 : is a 1:1 mapping * author: * paul vixie, 29may94 */ int res_queriesmatch(buf1, eom1, buf2, eom2) const u_char *buf1, *eom1; const u_char *buf2, *eom2; { register const u_char *cp = buf1 + DNSRES_HFIXEDSZ; int qdcount = ntohs(((DNSRES_HEADER*)buf1)->qdcount); if (qdcount != ntohs(((DNSRES_HEADER*)buf2)->qdcount)) return (0); while (qdcount-- > 0) { char tname[DNSRES_MAXDNAME+1]; register int n, ttype, tclass; n = dn_expand(buf1, eom1, cp, tname, sizeof tname); if (n < 0) return (-1); cp += n; ttype = getshort(cp); cp += DNSRES_INT16SZ; tclass = getshort(cp); cp += DNSRES_INT16SZ; if (!res_nameinquery(tname, ttype, tclass, buf2, eom2)) return (0); } return (1); } /* Parameters that control the behavior of res_send_loop_cb */ enum { RS_LOOP_BREAK = -1, RS_LOOP_CONT = 0, RS_LOOP_REPEAT = 1 }; /* Non-blocking */ int QhookDispatch(void (*cb)(int, struct res_search_state *), struct res_search_state *state) { struct sockaddr *nsap = get_nsaddr(state->_resp, state->ns); int done = 0, loops = 0; do { const struct dnsres_target *q = state->target; res_sendhookact act; act = (*Qhook)((struct sockaddr_in **)&nsap, &state->send_buf, &state->send_buflen, q->answer, q->anslen, &state->resplen); switch (act) { case res_goahead: done = 1; break; case res_nextns: res_close(&state->ds); (*cb)(RS_LOOP_CONT, state); return (-1); case res_done: state->ret = state->resplen; (*cb)(RS_LOOP_BREAK, state); return (-1); case res_modified: /* give the hook another try */ if (++loops < 42) /*doug adams*/ break; /*FALLTHROUGH*/ case res_error: /*FALLTHROUGH*/ default: state->ret = -1; (*cb)(RS_LOOP_BREAK, state); return (-1); } } while (!done); return (0); } int RhookDispatch(void (*cb)(int, struct res_search_state *), struct res_search_state *state) { struct sockaddr *nsap = get_nsaddr(state->_resp, state->ns); int done = 0, loops = 0; do { const struct dnsres_target *q = state->target; res_sendhookact act; act = (*Rhook)((struct sockaddr_in *)nsap, state->send_buf, state->send_buflen, q->answer, q->anslen, &state->resplen); switch (act) { case res_goahead: case res_done: done = 1; break; case res_nextns: res_close(&state->ds); (*cb)(RS_LOOP_CONT, state); return (-1); case res_modified: /* give the hook another try */ if (++loops < 42) /*doug adams*/ break; /*FALLTHROUGH*/ case res_error: /*FALLTHROUGH*/ default: state->ret = -1; (*cb)(RS_LOOP_BREAK, state); return (-1); } } while (!done); return (0); } int res_make_socket(struct dnsres_socket *ds, int af, int type) { if (ds->vc >= 0) res_close(ds); ds->af = af; ds->s = socket(ds->af, type, 0); if (ds->s == -1) return (-1); /* Make the socket non-blocking */ if (fcntl(ds->s, F_SETFL, O_NONBLOCK) == -1) Perror(stderr, "fcntl(O_NONBLOCK)", errno); if (fcntl(ds->s, F_SETFD, 1) == -1) Perror(stderr, "fcntl(F_SETFD)", errno); #ifdef IPV6_MINMTU if (type == SOCK_DGRAM && af == AF_INET6) { const int yes = 1; (void)setsockopt(s, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &yes, sizeof(yes)); } #endif ds->connected = 0; return (0); } void res_send_loop(struct res_search_state *state); void res_send_loop_cb(int ok, struct res_search_state *state); void res_send_loop_bottom(struct res_search_state *state); void res_send_iterator_bottom(struct res_search_state *state); void res_send_vcircuit(struct res_search_state *state, struct sockaddr *nsap, socklen_t salen); void res_send_dgram(struct res_search_state *state, struct sockaddr *nsap, socklen_t salen); void res_send_iterator(struct res_search_state *state) { struct dnsres *_resp = state->_resp; struct sockaddr *nsap = get_nsaddr(_resp, state->ns); socklen_t salen; if (nsap->sa_family == AF_INET6) salen = sizeof(struct sockaddr_in6); else if (nsap->sa_family == AF_INET) salen = sizeof(struct sockaddr_in); else salen = 0; /*unknown, die on connect*/ if (state->badns & (1 << state->ns)) { res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } if (Qhook) { if (QhookDispatch(res_send_loop_cb, state) == -1) return; } Dprint((_resp->options & RES_DEBUG) && getnameinfo(nsap, salen, abuf, sizeof(abuf), NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID) == 0, (stdout, ";; Querying server (# %d) address = %s\n", ns + 1, abuf)); if (state->v_circuit) res_send_vcircuit(state, nsap, salen); else res_send_dgram(state, nsap, salen); } void res_send_vcircuit_writev(int fd, short what, void *arg); void res_send_vcircuit_connectcb(int fd, short what, void *arg); void res_send_vcircuit_readcb(int fd, short what, void *arg); void res_send_vcircuit_read2ndcb(int fd, short what, void *arg); void res_send_vcircuit(struct res_search_state *state, struct sockaddr *nsap, socklen_t salen) { struct dnsres *_resp = state->_resp; struct dnsres_socket *ds = &state->ds; /* * Use virtual circuit; at most one attempt per server. */ state->try = _resp->retry; if ((ds->s < 0) || (ds->vc == 0) || (ds->af != nsap->sa_family)) { if (res_make_socket(ds, nsap->sa_family, SOCK_STREAM) == -1) { state->terrno = errno; Perror(stderr, "socket(vc)", errno); state->badns |= (1 << state->ns); res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } errno = 0; if (connect(ds->s, nsap, salen) < 0) { res_send_vcircuit_connectcb(ds->s, EV_WRITE, state); return; } if (event_initialized(&ds->ev)) event_del(&ds->ev); event_set(&ds->ev, ds->s, EV_WRITE, res_send_vcircuit_connectcb, state); event_add(&ds->ev, NULL); return; } /* We might be able to get rid of the del */ if (event_initialized(&ds->ev)) event_del(&ds->ev); event_set(&ds->ev, ds->s, EV_WRITE, res_send_vcircuit_writev, state); event_add(&ds->ev, NULL); } void res_send_vcircuit_connectcb(int fd, short what, void *arg) { struct res_search_state *state = arg; struct dnsres_socket *ds = &state->ds; int error; socklen_t errsz = sizeof(error); /* Check if the connection completed */ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &errsz) == -1) { Perror(stderr, "getsockopt", errno); /* Just make up an error number */ error = ECONNREFUSED; } if (error) { state->terrno = errno; Aerror(stderr, "connect/vc", errno, nsap); state->badns |= (1 << state->ns); res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } ds->vc = 1; /* Setup the next event. Yeah */ event_set(&ds->ev, ds->s, EV_WRITE, res_send_vcircuit_writev, state); event_add(&ds->ev, NULL); } void res_send_vcircuit_writev(int fd, short what, void *arg) { struct res_search_state *state = arg; struct dnsres *_resp = state->_resp; struct dnsres_socket *ds = &state->ds; struct iovec iov[2]; struct timeval timeout; u_short len; /* * Send length & message */ putshort((u_short)state->send_buflen, (u_char*)&len); iov[0].iov_base = (caddr_t)&len; iov[0].iov_len = DNSRES_INT16SZ; iov[1].iov_base = (caddr_t)state->send_buf; iov[1].iov_len = state->send_buflen; if (writev(ds->s, iov, 2) != (DNSRES_INT16SZ + state->send_buflen)) { state->terrno = errno; Perror(stderr, "write failed", errno); state->badns |= (1 << state->ns); res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } /* Setup the next event. Yeah */ event_set(&ds->ev, ds->s, EV_READ, res_send_vcircuit_readcb, state); timerclear(&timeout); timeout.tv_sec = _resp->retrans; event_add(&ds->ev, &timeout); } void res_send_vcircuit_readcb(int fd, short what, void *arg) { struct res_search_state *state = arg; struct dnsres *_resp = state->_resp; struct dnsres_socket *ds = &state->ds; const struct dnsres_target *q = state->target; u_char *cp; u_short len; struct timeval timeout; int n; state->truncated = 0; /* * Receive length & response */ cp = q->answer; len = DNSRES_INT16SZ; while ((n = read(ds->s, (char *)cp, (int)len)) > 0) { cp += n; if ((len -= n) <= 0) break; } if (n <= 0) { state->terrno = errno; Perror(stderr, "read failed", errno); res_close(&state->ds); /* * A long running process might get its TCP connection * reset if the remote server was restarted. Requery * the server instead of trying a new one. When there * is only one server, this means that a query might * work instead of failing. We only allow one reset * per query to prevent looping. */ if (state->terrno == ECONNRESET && !state->connreset) { state->connreset = 1; res_close(&state->ds); res_send_loop_cb(RS_LOOP_REPEAT, state); return; } res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } state->resplen = getshort(q->answer); if (state->resplen > q->anslen) { Dprint(_resp->options & RES_DEBUG, (stdout, ";; response truncated\n") ); state->truncated = 1; len = q->anslen; } else len = state->resplen; /* Setup state for next read */ state->read_len = len; state->cp = q->answer; /* Setup the next event. Yeah */ event_set(&ds->ev, ds->s, EV_READ, res_send_vcircuit_read2ndcb, state); timerclear(&timeout); timeout.tv_sec = _resp->retrans; event_add(&ds->ev, &timeout); } void res_send_vcircuit_read2ndcb(int fd, short what, void *arg) { struct res_search_state *state = arg; struct dnsres *_resp = state->_resp; struct dnsres_socket *ds = &state->ds; const struct dnsres_target *q = state->target; DNSRES_HEADER *hp = (DNSRES_HEADER *) state->send_buf; DNSRES_HEADER *anhp = (DNSRES_HEADER *) q->answer; u_short len = state->read_len; u_char *cp; int n; cp = state->cp; n = read(ds->s, (char *)cp, (int)len); if (n > 0) { cp += n; len -= n; if (len > 0) { struct timeval timeout; state->read_len = len; state->cp = cp; /* Already setup to point to us, so add again */ timerclear(&timeout); timeout.tv_sec = _resp->retrans; event_add(&ds->ev, &timeout); return; } } if (n <= 0) { state->terrno = errno; Perror(stderr, "read(vc)", errno); res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } if (state->truncated) { /* * Flush rest of answer so connection stays in synch. */ anhp->tc = 1; len = state->resplen - q->anslen; while (len != 0) { char junk[DNSRES_PACKETSZ]; n = (len > sizeof(junk) ? sizeof(junk) : len); if ((n = read(ds->s, junk, n)) > 0) len -= n; else break; } } /* * The calling applicating has bailed out of a previous call * and failed to arrange to have the circuit closed or the * server has got itself confused. Anyway drop the packet and * wait for the correct one. * * Well, that's how it used to be, we take a more conservative * approach and close the socket and retry. */ if (hp->id != anhp->id) { DprintQ((_resp->options & RES_DEBUG) || (_resp->pfcode & RES_PRF_REPLY), (stdout, ";; old answer (unexpected):\n"), ans, (state->resplen>q->anslen)?q->anslen:state->resplen); res_close(&state->ds); res_send_loop_cb(RS_LOOP_REPEAT, state); return; } res_send_iterator_bottom(state); } /* * Use datagrams. */ void res_send_dgram_wait_read(int fd, short what, void *arg); void res_send_dgram_send(int fd, short what, void *arg); void res_send_dgram_sendto(int fd, short what, void *arg); void res_send_dgram_setup_wait(struct res_search_state *state); void res_send_dgram(struct res_search_state *state, struct sockaddr *nsap, socklen_t salen) { struct dnsres *_resp = state->_resp; struct dnsres_socket *ds = &state->ds; if ((ds->s < 0) || ds->vc || (ds->af != nsap->sa_family)) { if (res_make_socket(ds, nsap->sa_family, SOCK_DGRAM) == -1) { state->terrno = errno; Perror(stderr, "socket(dg)", errno); state->badns |= (1 << state->ns); res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } } /* * On a 4.3BSD+ machine (client and server, actually), sending * to a nameserver datagram port with no nameserver will cause * an ICMP port unreachable message to be returned. If our * datagram socket is "connected" to the server, we get an * ECONNREFUSED error on the next socket operation, and select * returns if the error message is received. We can thus * detect the absence of a nameserver without timing out. If * we have sent queries to at least two servers, however, we * don't want to remain connected, as we wish to receive * answers from the first server to respond. */ if (!(_resp->options & RES_INSECURE1) && (_resp->nscount == 1 || (state->try == 0 && state->ns == 0))) { /* * Connect only if we are sure we won't receive a * response from another server. */ if (!ds->connected) { if (connect(ds->s, nsap, salen) < 0) { Aerror(stderr, "connect(dg)", errno, nsap); state->badns |= (1 << state->ns); res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } ds->connected = 1; } if (event_initialized(&ds->ev)) event_del(&ds->ev); event_set(&ds->ev, ds->s, EV_WRITE, res_send_dgram_send, state); event_add(&ds->ev, NULL); } else { /* * Disconnect if we want to listen for responses from * more than one server. */ if (ds->connected) { /* XXX: any errornous address */ struct sockaddr_in no_addr; no_addr.sin_family = AF_INET; no_addr.sin_addr.s_addr = INADDR_ANY; no_addr.sin_port = 0; (void) connect(ds->s, (struct sockaddr *) &no_addr, sizeof(no_addr)); #ifdef IPV6_MINMTU if (af == AF_INET6) { const int yes = 1; (void)setsockopt(s, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &yes, sizeof(yes)); } #endif ds->connected = 0; errno = 0; } /* Tell it where to go */ ds->nsap = nsap; ds->salen = salen; if (event_initialized(&ds->ev)) event_del(&ds->ev); event_set(&ds->ev, ds->s, EV_WRITE, res_send_dgram_sendto, state); event_add(&ds->ev, NULL); } } void res_send_dgram_send(int fd, short what, void *arg) { struct res_search_state *state = arg; if (send(fd, (char*)state->send_buf, state->send_buflen, 0) != state->send_buflen) { Perror(stderr, "send", errno); state->badns |= (1 << state->ns); res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } res_send_dgram_setup_wait(state); } void res_send_dgram_sendto(int fd, short what, void *arg) { struct res_search_state *state = arg; struct dnsres_socket *ds = &state->ds; if (sendto(fd, (char*)state->send_buf, state->send_buflen, 0, ds->nsap, ds->salen) != state->send_buflen) { Aerror(stderr, "sendto", errno, nsap); state->badns |= (1 << state->ns); res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } res_send_dgram_setup_wait(state); } void res_send_dgram_setup_wait(struct res_search_state *state) { struct dnsres_socket *ds = &state->ds; struct dnsres *_resp = state->_resp; struct timeval timeout; /* * Wait for reply */ event_set(&ds->ev, ds->s, EV_READ, res_send_dgram_wait_read, state); timeout.tv_sec = (_resp->retrans << state->try); if (state->try > 0) timeout.tv_sec /= _resp->nscount; if ((long) timeout.tv_sec <= 0) timeout.tv_sec = 1; timeout.tv_usec = 0; event_add(&ds->ev, &timeout); } void res_send_dgram_wait_read(int fd, short what, void *arg) { struct res_search_state *state = arg; struct dnsres *_resp = state->_resp; struct dnsres_socket *ds = &state->ds; const struct dnsres_target *q = state->target; DNSRES_HEADER *hp = (DNSRES_HEADER *) state->send_buf; DNSRES_HEADER *anhp = (DNSRES_HEADER *) q->answer; struct sockaddr_storage from; socklen_t fromlen; if (what == EV_TIMEOUT) { /* * timeout */ Dprint(_resp->options & RES_DEBUG, (stdout, ";; timeout\n")); state->gotsomewhere = 1; res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } errno = 0; fromlen = sizeof(from); state->resplen = recvfrom(ds->s, (char*)q->answer, q->anslen, 0, (struct sockaddr *)&from, &fromlen); if (state->resplen <= 0) { Perror(stderr, "recvfrom", errno); res_close(&state->ds); res_send_loop_cb(RS_LOOP_CONT, state); return; } state->gotsomewhere = 1; if (hp->id != anhp->id) { /* * response from old query, ignore it. * XXX - potential security hazard could * be detected here. */ DprintQ((_resp->options & RES_DEBUG) || (_resp->pfcode & RES_PRF_REPLY), (stdout, ";; old answer:\n"), ans, (state->resplen>q->anslen)?q->anslen:state->resplen); res_send_dgram_setup_wait(state); return; } #if CHECK_SRVR_ADDR if (!(_resp->options & RES_INSECURE1) && !res_isourserver(_resp, (struct sockaddr_in *)&from)) { /* * response from wrong server? ignore it. * XXX - potential security hazard could * be detected here. */ DprintQ((_resp->options & RES_DEBUG) || (_resp->pfcode & RES_PRF_REPLY), (stdout, ";; not our server:\n"), ans, (state->resplen>q->anslen)?q->anslen:state->resplen); res_send_dgram_setup_wait(state); return; } #endif if (!(_resp->options & RES_INSECURE2) && !res_queriesmatch(state->send_buf, state->send_buf + state->send_buflen, q->answer, q->answer + q->anslen)) { /* * response contains wrong query? ignore it. * XXX - potential security hazard could * be detected here. */ DprintQ((_resp->options & RES_DEBUG) || (_resp->pfcode & RES_PRF_REPLY), (stdout, ";; wrong query name:\n"), ans, (state->resplen>q->anslen)?q->anslen:state->resplen); res_send_dgram_setup_wait(state); return; } if (anhp->rcode == DNSRES_SERVFAIL || anhp->rcode == DNSRES_NOTIMP || anhp->rcode == DNSRES_REFUSED) { DprintQ(_resp->options & RES_DEBUG, (stdout, "server rejected query:\n"), ans, (state->resplen>q->anslen)?q->anslen:state->resplen); state->badns |= (1 << state->ns); res_close(&state->ds); /* don't retry if called from dig */ if (!_resp->pfcode) { res_send_loop_cb(RS_LOOP_CONT, state); return; } } if (!(_resp->options & RES_IGNTC) && anhp->tc) { /* * get rest of answer; * use TCP with same */ Dprint(_resp->options & RES_DEBUG, (stdout, ";; truncated answer\n")); state->v_circuit = 1; res_close(&state->ds); res_send_loop_cb(RS_LOOP_REPEAT, state); return; } res_send_iterator_bottom(state); } void res_send_iterator_bottom(struct res_search_state *state) { struct dnsres *_resp = state->_resp; Dprint((_resp->options & RES_DEBUG) || ((_resp->pfcode & RES_PRF_REPLY) && (_resp->pfcode & RES_PRF_HEAD1)), (stdout, ";; got answer:\n")); DprintQ((_resp->options & RES_DEBUG) || (_resp->pfcode & RES_PRF_REPLY), (stdout, "%s", ""), ans, (state->resplen>q->anslen)?q->anslen:state->resplen); /* * If using virtual circuits, we assume that the first server * is preferred over the rest (i.e. it is on the local * machine) and only keep that one open. * If we have temporarily opened a virtual circuit, * or if we haven't been asked to keep a socket open, * close the socket. */ if ((state->v_circuit && (!(_resp->options & RES_USEVC) || state->ns != 0)) || !(_resp->options & RES_STAYOPEN)) { res_close(&state->ds); } if (Rhook) { if (RhookDispatch(res_send_loop_cb, state) == -1) return; } state->ret = state->resplen; res_send_loop_cb(RS_LOOP_BREAK, state); return; } void res_send( struct dnsres *_resp, const u_char *buf, int buflen, u_char *ans, int anslen, void (*cb)(int, struct res_search_state *), struct res_search_state *state ) { DprintQ((_resp->options & RES_DEBUG) || (_resp->pfcode & RES_PRF_QUERY), (stdout, ";; res_send()\n"), buf, buflen); /* Initialize our state */ state->send_buf = buf; state->send_buflen = buflen; state->v_circuit = (_resp->options & RES_USEVC) || buflen > DNSRES_PACKETSZ; state->gotsomewhere = 0; state->terrno = ETIMEDOUT; state->send_cb = cb; state->connreset = 0; state->badns = 0; /* * Send request, RETRY times, or until successful */ state->try = 0; state->ns = 0; res_send_loop(state); } void res_send_loop(struct res_search_state *state) { struct dnsres *_resp = state->_resp; if (state->ns == _resp->nscount) { state->ns = 0; state->try++; if (state->try == _resp->retry) { res_send_loop_bottom(state); return; } } /* This function will always call back to res_send_loop_cb */ res_send_iterator(state); } void res_send_loop_cb(int what, struct res_search_state *state) { /* We terminate on failure */ if ( what == RS_LOOP_BREAK ) { (*state->send_cb)(state->ret, state); return; } if ( what == RS_LOOP_CONT ) { /* Otherwise we try to complete the loop */ state->ns++; } /* RS_LOOP_REPEAT is the remaining case */ res_send_loop(state); } void res_send_loop_bottom(struct res_search_state *state) { res_close(&state->ds); if (!state->v_circuit) { if (!state->gotsomewhere) errno = ECONNREFUSED; /* no nameservers found */ else errno = ETIMEDOUT; /* no answer obtained */ } else errno = state->terrno; (*state->send_cb)(-1, state); } /* * This routine is for closing the socket if a virtual circuit is used and * the program wants to close it. This provides support for endhostent() * which expects to close the socket. * * This routine is not expected to be user visible. */ void res_close(struct dnsres_socket *ds) { if (ds->s >= 0) { if (event_initialized(&ds->ev)) event_del(&ds->ev); (void) close(ds->s); res_init_socket(ds); } }