87043f9c89
When looping over the current list of sockets we are connected to, use getpeername() not getsockname() to find out who the remote end is. This change avoids spurious close() and (rare) failure. Origin: ISC bug #18625 and fixed in libbind 6.0 Change-Id: I5e85f9ff4b98c237978e4bf4bd85ba0a90d768e6
1168 lines
31 KiB
C
1168 lines
31 KiB
C
/* $NetBSD: res_send.c,v 1.9 2006/01/24 17:41:25 christos Exp $ */
|
|
|
|
/*
|
|
* Copyright 2008 Android Open Source Project (source port randomization)
|
|
* 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. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Berkeley and its contributors.
|
|
* 4. 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 (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
|
|
* Portions Copyright (c) 1996-1999 by Internet Software Consortium.
|
|
*
|
|
* 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.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
#if defined(LIBC_SCCS) && !defined(lint)
|
|
#ifdef notdef
|
|
static const char sccsid[] = "@(#)res_send.c 8.1 (Berkeley) 6/4/93";
|
|
static const char rcsid[] = "Id: res_send.c,v 1.5.2.2.4.5 2004/08/10 02:19:56 marka Exp";
|
|
#else
|
|
__RCSID("$NetBSD: res_send.c,v 1.9 2006/01/24 17:41:25 christos Exp $");
|
|
#endif
|
|
#endif /* LIBC_SCCS and not lint */
|
|
|
|
/* set to 1 to use our small/simple/limited DNS cache */
|
|
#define USE_RESOLV_CACHE 1
|
|
|
|
/*
|
|
* Send query to name server and wait for reply.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/time.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include "arpa_nameser.h"
|
|
#include <arpa/inet.h>
|
|
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#ifdef ANDROID_CHANGES
|
|
#include "resolv_private.h"
|
|
#else
|
|
#include <resolv.h>
|
|
#endif
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <isc/eventlib.h>
|
|
|
|
#if USE_RESOLV_CACHE
|
|
# include <resolv_cache.h>
|
|
#endif
|
|
|
|
#ifndef DE_CONST
|
|
#define DE_CONST(c,v) v = ((c) ? \
|
|
strchr((const void *)(c), *(const char *)(const void *)(c)) : NULL)
|
|
#endif
|
|
|
|
/* Options. Leave them on. */
|
|
#ifndef DEBUG
|
|
#define DEBUG
|
|
#endif
|
|
#include "res_debug.h"
|
|
#include "res_private.h"
|
|
|
|
#define EXT(res) ((res)->_u._ext)
|
|
|
|
static const int highestFD = FD_SETSIZE - 1;
|
|
|
|
/* Forward. */
|
|
|
|
static int get_salen __P((const struct sockaddr *));
|
|
static struct sockaddr * get_nsaddr __P((res_state, size_t));
|
|
static int send_vc(res_state, const u_char *, int,
|
|
u_char *, int, int *, int);
|
|
static int send_dg(res_state, const u_char *, int,
|
|
u_char *, int, int *, int,
|
|
int *, int *);
|
|
static void Aerror(const res_state, FILE *, const char *, int,
|
|
const struct sockaddr *, int);
|
|
static void Perror(const res_state, FILE *, const char *, int);
|
|
static int sock_eq(struct sockaddr *, struct sockaddr *);
|
|
#ifdef NEED_PSELECT
|
|
static int pselect(int, void *, void *, void *,
|
|
struct timespec *,
|
|
const sigset_t *);
|
|
#endif
|
|
void res_pquery(const res_state, const u_char *, int, FILE *);
|
|
|
|
|
|
/* BIONIC-BEGIN: implement source port randomization */
|
|
typedef union {
|
|
struct sockaddr sa;
|
|
struct sockaddr_in sin;
|
|
struct sockaddr_in6 sin6;
|
|
} _sockaddr_union;
|
|
|
|
static int
|
|
random_bind( int s, int family )
|
|
{
|
|
_sockaddr_union u;
|
|
int j;
|
|
socklen_t slen;
|
|
|
|
/* clear all, this also sets the IP4/6 address to 'any' */
|
|
memset( &u, 0, sizeof u );
|
|
|
|
switch (family) {
|
|
case AF_INET:
|
|
u.sin.sin_family = family;
|
|
slen = sizeof u.sin;
|
|
break;
|
|
case AF_INET6:
|
|
u.sin6.sin6_family = family;
|
|
slen = sizeof u.sin6;
|
|
break;
|
|
default:
|
|
errno = EPROTO;
|
|
return -1;
|
|
}
|
|
|
|
/* first try to bind to a random source port a few times */
|
|
for (j = 0; j < 10; j++) {
|
|
/* find a random port between 1025 .. 65534 */
|
|
int port = 1025 + (res_randomid() % (65535-1025));
|
|
if (family == AF_INET)
|
|
u.sin.sin_port = htons(port);
|
|
else
|
|
u.sin6.sin6_port = htons(port);
|
|
|
|
if ( !bind( s, &u.sa, slen ) )
|
|
return 0;
|
|
}
|
|
|
|
/* nothing after 10 tries, our network table is probably busy */
|
|
/* let the system decide which port is best */
|
|
if (family == AF_INET)
|
|
u.sin.sin_port = 0;
|
|
else
|
|
u.sin6.sin6_port = 0;
|
|
|
|
return bind( s, &u.sa, slen );
|
|
}
|
|
/* BIONIC-END */
|
|
|
|
static const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
|
|
|
|
/* Public. */
|
|
|
|
/* int
|
|
* res_isourserver(ina)
|
|
* looks up "ina" in _res.ns_addr_list[]
|
|
* returns:
|
|
* 0 : not found
|
|
* >0 : found
|
|
* author:
|
|
* paul vixie, 29may94
|
|
*/
|
|
__LIBC_HIDDEN__ int
|
|
res_ourserver_p(const res_state statp, const struct sockaddr *sa) {
|
|
const struct sockaddr_in *inp, *srv;
|
|
const struct sockaddr_in6 *in6p, *srv6;
|
|
int ns;
|
|
|
|
switch (sa->sa_family) {
|
|
case AF_INET:
|
|
inp = (const struct sockaddr_in *)(const void *)sa;
|
|
for (ns = 0; ns < statp->nscount; ns++) {
|
|
srv = (struct sockaddr_in *)(void *)get_nsaddr(statp, (size_t)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))
|
|
return (1);
|
|
}
|
|
break;
|
|
case AF_INET6:
|
|
if (EXT(statp).ext == NULL)
|
|
break;
|
|
in6p = (const struct sockaddr_in6 *)(const void *)sa;
|
|
for (ns = 0; ns < statp->nscount; ns++) {
|
|
srv6 = (struct sockaddr_in6 *)(void *)get_nsaddr(statp, (size_t)ns);
|
|
if (srv6->sin6_family == in6p->sin6_family &&
|
|
srv6->sin6_port == in6p->sin6_port &&
|
|
#ifdef HAVE_SIN6_SCOPE_ID
|
|
(srv6->sin6_scope_id == 0 ||
|
|
srv6->sin6_scope_id == in6p->sin6_scope_id) &&
|
|
#endif
|
|
(IN6_IS_ADDR_UNSPECIFIED(&srv6->sin6_addr) ||
|
|
IN6_ARE_ADDR_EQUAL(&srv6->sin6_addr, &in6p->sin6_addr)))
|
|
return (1);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* int
|
|
* res_nameinquery(name, type, class, buf, eom)
|
|
* look for (name,type,class) in the query section of packet (buf,eom)
|
|
* requires:
|
|
* buf + HFIXEDSZ <= eom
|
|
* returns:
|
|
* -1 : format error
|
|
* 0 : not found
|
|
* >0 : found
|
|
* author:
|
|
* paul vixie, 29may94
|
|
*/
|
|
int
|
|
res_nameinquery(const char *name, int type, int class,
|
|
const u_char *buf, const u_char *eom)
|
|
{
|
|
const u_char *cp = buf + HFIXEDSZ;
|
|
int qdcount = ntohs(((const HEADER*)(const void *)buf)->qdcount);
|
|
|
|
while (qdcount-- > 0) {
|
|
char tname[MAXDNAME+1];
|
|
int n, ttype, tclass;
|
|
|
|
n = dn_expand(buf, eom, cp, tname, sizeof tname);
|
|
if (n < 0)
|
|
return (-1);
|
|
cp += n;
|
|
if (cp + 2 * INT16SZ > eom)
|
|
return (-1);
|
|
ttype = ns_get16(cp); cp += INT16SZ;
|
|
tclass = ns_get16(cp); cp += INT16SZ;
|
|
if (ttype == type && tclass == class &&
|
|
ns_samename(tname, name) == 1)
|
|
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(const u_char *buf1, const u_char *eom1,
|
|
const u_char *buf2, const u_char *eom2)
|
|
{
|
|
const u_char *cp = buf1 + HFIXEDSZ;
|
|
int qdcount = ntohs(((const HEADER*)(const void *)buf1)->qdcount);
|
|
|
|
if (buf1 + HFIXEDSZ > eom1 || buf2 + HFIXEDSZ > eom2)
|
|
return (-1);
|
|
|
|
/*
|
|
* Only header section present in replies to
|
|
* dynamic update packets.
|
|
*/
|
|
if ((((const HEADER *)(const void *)buf1)->opcode == ns_o_update) &&
|
|
(((const HEADER *)(const void *)buf2)->opcode == ns_o_update))
|
|
return (1);
|
|
|
|
if (qdcount != ntohs(((const HEADER*)(const void *)buf2)->qdcount))
|
|
return (0);
|
|
while (qdcount-- > 0) {
|
|
char tname[MAXDNAME+1];
|
|
int n, ttype, tclass;
|
|
|
|
n = dn_expand(buf1, eom1, cp, tname, sizeof tname);
|
|
if (n < 0)
|
|
return (-1);
|
|
cp += n;
|
|
if (cp + 2 * INT16SZ > eom1)
|
|
return (-1);
|
|
ttype = ns_get16(cp); cp += INT16SZ;
|
|
tclass = ns_get16(cp); cp += INT16SZ;
|
|
if (!res_nameinquery(tname, ttype, tclass, buf2, eom2))
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
|
|
int
|
|
res_nsend(res_state statp,
|
|
const u_char *buf, int buflen, u_char *ans, int anssiz)
|
|
{
|
|
int gotsomewhere, terrno, try, v_circuit, resplen, ns, n;
|
|
char abuf[NI_MAXHOST];
|
|
#if USE_RESOLV_CACHE
|
|
struct resolv_cache* cache;
|
|
ResolvCacheStatus cache_status = RESOLV_CACHE_UNSUPPORTED;
|
|
#endif
|
|
|
|
if (statp->nscount == 0) {
|
|
errno = ESRCH;
|
|
return (-1);
|
|
}
|
|
if (anssiz < HFIXEDSZ) {
|
|
errno = EINVAL;
|
|
return (-1);
|
|
}
|
|
DprintQ((statp->options & RES_DEBUG) || (statp->pfcode & RES_PRF_QUERY),
|
|
(stdout, ";; res_send()\n"), buf, buflen);
|
|
v_circuit = (statp->options & RES_USEVC) || buflen > PACKETSZ;
|
|
gotsomewhere = 0;
|
|
terrno = ETIMEDOUT;
|
|
|
|
#if USE_RESOLV_CACHE
|
|
cache = __get_res_cache();
|
|
if (cache != NULL) {
|
|
int anslen = 0;
|
|
cache_status = _resolv_cache_lookup(
|
|
cache, buf, buflen,
|
|
ans, anssiz, &anslen);
|
|
|
|
if (cache_status == RESOLV_CACHE_FOUND) {
|
|
return anslen;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* If the ns_addr_list in the resolver context has changed, then
|
|
* invalidate our cached copy and the associated timing data.
|
|
*/
|
|
if (EXT(statp).nscount != 0) {
|
|
int needclose = 0;
|
|
struct sockaddr_storage peer;
|
|
socklen_t peerlen;
|
|
|
|
if (EXT(statp).nscount != statp->nscount)
|
|
needclose++;
|
|
else
|
|
for (ns = 0; ns < statp->nscount; ns++) {
|
|
if (statp->nsaddr_list[ns].sin_family &&
|
|
!sock_eq((struct sockaddr *)(void *)&statp->nsaddr_list[ns],
|
|
(struct sockaddr *)(void *)&EXT(statp).ext->nsaddrs[ns])) {
|
|
needclose++;
|
|
break;
|
|
}
|
|
|
|
if (EXT(statp).nssocks[ns] == -1)
|
|
continue;
|
|
peerlen = sizeof(peer);
|
|
if (getpeername(EXT(statp).nssocks[ns],
|
|
(struct sockaddr *)(void *)&peer, &peerlen) < 0) {
|
|
needclose++;
|
|
break;
|
|
}
|
|
if (!sock_eq((struct sockaddr *)(void *)&peer,
|
|
get_nsaddr(statp, (size_t)ns))) {
|
|
needclose++;
|
|
break;
|
|
}
|
|
}
|
|
if (needclose) {
|
|
res_nclose(statp);
|
|
EXT(statp).nscount = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Maybe initialize our private copy of the ns_addr_list.
|
|
*/
|
|
if (EXT(statp).nscount == 0) {
|
|
for (ns = 0; ns < statp->nscount; ns++) {
|
|
EXT(statp).nstimes[ns] = RES_MAXTIME;
|
|
EXT(statp).nssocks[ns] = -1;
|
|
if (!statp->nsaddr_list[ns].sin_family)
|
|
continue;
|
|
EXT(statp).ext->nsaddrs[ns].sin =
|
|
statp->nsaddr_list[ns];
|
|
}
|
|
EXT(statp).nscount = statp->nscount;
|
|
}
|
|
|
|
/*
|
|
* Some resolvers want to even out the load on their nameservers.
|
|
* Note that RES_BLAST overrides RES_ROTATE.
|
|
*/
|
|
if ((statp->options & RES_ROTATE) != 0U &&
|
|
(statp->options & RES_BLAST) == 0U) {
|
|
union res_sockaddr_union inu;
|
|
struct sockaddr_in ina;
|
|
int lastns = statp->nscount - 1;
|
|
int fd;
|
|
u_int16_t nstime;
|
|
|
|
if (EXT(statp).ext != NULL)
|
|
inu = EXT(statp).ext->nsaddrs[0];
|
|
ina = statp->nsaddr_list[0];
|
|
fd = EXT(statp).nssocks[0];
|
|
nstime = EXT(statp).nstimes[0];
|
|
for (ns = 0; ns < lastns; ns++) {
|
|
if (EXT(statp).ext != NULL)
|
|
EXT(statp).ext->nsaddrs[ns] =
|
|
EXT(statp).ext->nsaddrs[ns + 1];
|
|
statp->nsaddr_list[ns] = statp->nsaddr_list[ns + 1];
|
|
EXT(statp).nssocks[ns] = EXT(statp).nssocks[ns + 1];
|
|
EXT(statp).nstimes[ns] = EXT(statp).nstimes[ns + 1];
|
|
}
|
|
if (EXT(statp).ext != NULL)
|
|
EXT(statp).ext->nsaddrs[lastns] = inu;
|
|
statp->nsaddr_list[lastns] = ina;
|
|
EXT(statp).nssocks[lastns] = fd;
|
|
EXT(statp).nstimes[lastns] = nstime;
|
|
}
|
|
|
|
/*
|
|
* Send request, RETRY times, or until successful.
|
|
*/
|
|
for (try = 0; try < statp->retry; try++) {
|
|
for (ns = 0; ns < statp->nscount; ns++) {
|
|
struct sockaddr *nsap;
|
|
int nsaplen;
|
|
nsap = get_nsaddr(statp, (size_t)ns);
|
|
nsaplen = get_salen(nsap);
|
|
statp->_flags &= ~RES_F_LASTMASK;
|
|
statp->_flags |= (ns << RES_F_LASTSHIFT);
|
|
same_ns:
|
|
if (statp->qhook) {
|
|
int done = 0, loops = 0;
|
|
|
|
do {
|
|
res_sendhookact act;
|
|
|
|
act = (*statp->qhook)(&nsap, &buf, &buflen,
|
|
ans, anssiz, &resplen);
|
|
switch (act) {
|
|
case res_goahead:
|
|
done = 1;
|
|
break;
|
|
case res_nextns:
|
|
res_nclose(statp);
|
|
goto next_ns;
|
|
case res_done:
|
|
return (resplen);
|
|
case res_modified:
|
|
/* give the hook another try */
|
|
if (++loops < 42) /*doug adams*/
|
|
break;
|
|
/*FALLTHROUGH*/
|
|
case res_error:
|
|
/*FALLTHROUGH*/
|
|
default:
|
|
goto fail;
|
|
}
|
|
} while (!done);
|
|
}
|
|
|
|
Dprint(((statp->options & RES_DEBUG) &&
|
|
getnameinfo(nsap, (socklen_t)nsaplen, abuf, sizeof(abuf),
|
|
NULL, 0, niflags) == 0),
|
|
(stdout, ";; Querying server (# %d) address = %s\n",
|
|
ns + 1, abuf));
|
|
|
|
|
|
if (v_circuit) {
|
|
/* Use VC; at most one attempt per server. */
|
|
try = statp->retry;
|
|
n = send_vc(statp, buf, buflen, ans, anssiz, &terrno,
|
|
ns);
|
|
if (n < 0)
|
|
goto fail;
|
|
if (n == 0)
|
|
goto next_ns;
|
|
resplen = n;
|
|
} else {
|
|
/* Use datagrams. */
|
|
n = send_dg(statp, buf, buflen, ans, anssiz, &terrno,
|
|
ns, &v_circuit, &gotsomewhere);
|
|
if (n < 0)
|
|
goto fail;
|
|
if (n == 0)
|
|
goto next_ns;
|
|
if (v_circuit)
|
|
goto same_ns;
|
|
resplen = n;
|
|
}
|
|
|
|
Dprint((statp->options & RES_DEBUG) ||
|
|
((statp->pfcode & RES_PRF_REPLY) &&
|
|
(statp->pfcode & RES_PRF_HEAD1)),
|
|
(stdout, ";; got answer:\n"));
|
|
|
|
DprintQ((statp->options & RES_DEBUG) ||
|
|
(statp->pfcode & RES_PRF_REPLY),
|
|
(stdout, "%s", ""),
|
|
ans, (resplen > anssiz) ? anssiz : resplen);
|
|
|
|
#if USE_RESOLV_CACHE
|
|
if (cache_status == RESOLV_CACHE_NOTFOUND) {
|
|
_resolv_cache_add(cache, buf, buflen,
|
|
ans, resplen);
|
|
}
|
|
#endif
|
|
/*
|
|
* If we have temporarily opened a virtual circuit,
|
|
* or if we haven't been asked to keep a socket open,
|
|
* close the socket.
|
|
*/
|
|
if ((v_circuit && (statp->options & RES_USEVC) == 0U) ||
|
|
(statp->options & RES_STAYOPEN) == 0U) {
|
|
res_nclose(statp);
|
|
}
|
|
if (statp->rhook) {
|
|
int done = 0, loops = 0;
|
|
|
|
do {
|
|
res_sendhookact act;
|
|
|
|
act = (*statp->rhook)(nsap, buf, buflen,
|
|
ans, anssiz, &resplen);
|
|
switch (act) {
|
|
case res_goahead:
|
|
case res_done:
|
|
done = 1;
|
|
break;
|
|
case res_nextns:
|
|
res_nclose(statp);
|
|
goto next_ns;
|
|
case res_modified:
|
|
/* give the hook another try */
|
|
if (++loops < 42) /*doug adams*/
|
|
break;
|
|
/*FALLTHROUGH*/
|
|
case res_error:
|
|
/*FALLTHROUGH*/
|
|
default:
|
|
goto fail;
|
|
}
|
|
} while (!done);
|
|
|
|
}
|
|
return (resplen);
|
|
next_ns: ;
|
|
} /*foreach ns*/
|
|
} /*foreach retry*/
|
|
res_nclose(statp);
|
|
if (!v_circuit) {
|
|
if (!gotsomewhere)
|
|
errno = ECONNREFUSED; /* no nameservers found */
|
|
else
|
|
errno = ETIMEDOUT; /* no answer obtained */
|
|
} else
|
|
errno = terrno;
|
|
return (-1);
|
|
fail:
|
|
res_nclose(statp);
|
|
return (-1);
|
|
}
|
|
|
|
/* Private */
|
|
|
|
static int
|
|
get_salen(sa)
|
|
const struct sockaddr *sa;
|
|
{
|
|
|
|
#ifdef HAVE_SA_LEN
|
|
/* There are people do not set sa_len. Be forgiving to them. */
|
|
if (sa->sa_len)
|
|
return (sa->sa_len);
|
|
#endif
|
|
|
|
if (sa->sa_family == AF_INET)
|
|
return (sizeof(struct sockaddr_in));
|
|
else if (sa->sa_family == AF_INET6)
|
|
return (sizeof(struct sockaddr_in6));
|
|
else
|
|
return (0); /* unknown, die on connect */
|
|
}
|
|
|
|
/*
|
|
* pick appropriate nsaddr_list for use. see res_init() for initialization.
|
|
*/
|
|
static struct sockaddr *
|
|
get_nsaddr(statp, n)
|
|
res_state statp;
|
|
size_t n;
|
|
{
|
|
|
|
if (!statp->nsaddr_list[n].sin_family && EXT(statp).ext) {
|
|
/*
|
|
* - EXT(statp).ext->nsaddrs[n] holds an address that is larger
|
|
* than struct sockaddr, and
|
|
* - user code did not update statp->nsaddr_list[n].
|
|
*/
|
|
return (struct sockaddr *)(void *)&EXT(statp).ext->nsaddrs[n];
|
|
} else {
|
|
/*
|
|
* - user code updated statp->nsaddr_list[n], or
|
|
* - statp->nsaddr_list[n] has the same content as
|
|
* EXT(statp).ext->nsaddrs[n].
|
|
*/
|
|
return (struct sockaddr *)(void *)&statp->nsaddr_list[n];
|
|
}
|
|
}
|
|
|
|
static int
|
|
send_vc(res_state statp,
|
|
const u_char *buf, int buflen, u_char *ans, int anssiz,
|
|
int *terrno, int ns)
|
|
{
|
|
const HEADER *hp = (const HEADER *)(const void *)buf;
|
|
HEADER *anhp = (HEADER *)(void *)ans;
|
|
struct sockaddr *nsap;
|
|
int nsaplen;
|
|
int truncating, connreset, resplen, n;
|
|
struct iovec iov[2];
|
|
u_short len;
|
|
u_char *cp;
|
|
void *tmp;
|
|
|
|
nsap = get_nsaddr(statp, (size_t)ns);
|
|
nsaplen = get_salen(nsap);
|
|
|
|
connreset = 0;
|
|
same_ns:
|
|
truncating = 0;
|
|
|
|
/* Are we still talking to whom we want to talk to? */
|
|
if (statp->_vcsock >= 0 && (statp->_flags & RES_F_VC) != 0) {
|
|
struct sockaddr_storage peer;
|
|
socklen_t size = sizeof peer;
|
|
|
|
if (getpeername(statp->_vcsock,
|
|
(struct sockaddr *)(void *)&peer, &size) < 0 ||
|
|
!sock_eq((struct sockaddr *)(void *)&peer, nsap)) {
|
|
res_nclose(statp);
|
|
statp->_flags &= ~RES_F_VC;
|
|
}
|
|
}
|
|
|
|
if (statp->_vcsock < 0 || (statp->_flags & RES_F_VC) == 0) {
|
|
if (statp->_vcsock >= 0)
|
|
res_nclose(statp);
|
|
|
|
statp->_vcsock = socket(nsap->sa_family, SOCK_STREAM, 0);
|
|
if (statp->_vcsock > highestFD) {
|
|
res_nclose(statp);
|
|
errno = ENOTSOCK;
|
|
}
|
|
if (statp->_vcsock < 0) {
|
|
switch (errno) {
|
|
case EPROTONOSUPPORT:
|
|
#ifdef EPFNOSUPPORT
|
|
case EPFNOSUPPORT:
|
|
#endif
|
|
case EAFNOSUPPORT:
|
|
Perror(statp, stderr, "socket(vc)", errno);
|
|
return (0);
|
|
default:
|
|
*terrno = errno;
|
|
Perror(statp, stderr, "socket(vc)", errno);
|
|
return (-1);
|
|
}
|
|
}
|
|
errno = 0;
|
|
if (random_bind(statp->_vcsock,nsap->sa_family) < 0) {
|
|
*terrno = errno;
|
|
Aerror(statp, stderr, "bind/vc", errno, nsap,
|
|
nsaplen);
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
if (connect(statp->_vcsock, nsap, (socklen_t)nsaplen) < 0) {
|
|
*terrno = errno;
|
|
Aerror(statp, stderr, "connect/vc", errno, nsap,
|
|
nsaplen);
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
statp->_flags |= RES_F_VC;
|
|
}
|
|
|
|
/*
|
|
* Send length & message
|
|
*/
|
|
ns_put16((u_short)buflen, (u_char*)(void *)&len);
|
|
iov[0] = evConsIovec(&len, INT16SZ);
|
|
DE_CONST(buf, tmp);
|
|
iov[1] = evConsIovec(tmp, (size_t)buflen);
|
|
if (writev(statp->_vcsock, iov, 2) != (INT16SZ + buflen)) {
|
|
*terrno = errno;
|
|
Perror(statp, stderr, "write failed", errno);
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
/*
|
|
* Receive length & response
|
|
*/
|
|
read_len:
|
|
cp = ans;
|
|
len = INT16SZ;
|
|
while ((n = read(statp->_vcsock, (char *)cp, (size_t)len)) > 0) {
|
|
cp += n;
|
|
if ((len -= n) == 0)
|
|
break;
|
|
}
|
|
if (n <= 0) {
|
|
*terrno = errno;
|
|
Perror(statp, stderr, "read failed", errno);
|
|
res_nclose(statp);
|
|
/*
|
|
* 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 (*terrno == ECONNRESET && !connreset) {
|
|
connreset = 1;
|
|
res_nclose(statp);
|
|
goto same_ns;
|
|
}
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
resplen = ns_get16(ans);
|
|
if (resplen > anssiz) {
|
|
Dprint(statp->options & RES_DEBUG,
|
|
(stdout, ";; response truncated\n")
|
|
);
|
|
truncating = 1;
|
|
len = anssiz;
|
|
} else
|
|
len = resplen;
|
|
if (len < HFIXEDSZ) {
|
|
/*
|
|
* Undersized message.
|
|
*/
|
|
Dprint(statp->options & RES_DEBUG,
|
|
(stdout, ";; undersized: %d\n", len));
|
|
*terrno = EMSGSIZE;
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
cp = ans;
|
|
while (len != 0 && (n = read(statp->_vcsock, (char *)cp, (size_t)len)) > 0){
|
|
cp += n;
|
|
len -= n;
|
|
}
|
|
if (n <= 0) {
|
|
*terrno = errno;
|
|
Perror(statp, stderr, "read(vc)", errno);
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
if (truncating) {
|
|
/*
|
|
* Flush rest of answer so connection stays in synch.
|
|
*/
|
|
anhp->tc = 1;
|
|
len = resplen - anssiz;
|
|
while (len != 0) {
|
|
char junk[PACKETSZ];
|
|
|
|
n = read(statp->_vcsock, junk,
|
|
(len > sizeof junk) ? sizeof junk : len);
|
|
if (n > 0)
|
|
len -= n;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
* If 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, then drop the packet and
|
|
* wait for the correct one.
|
|
*/
|
|
if (hp->id != anhp->id) {
|
|
DprintQ((statp->options & RES_DEBUG) ||
|
|
(statp->pfcode & RES_PRF_REPLY),
|
|
(stdout, ";; old answer (unexpected):\n"),
|
|
ans, (resplen > anssiz) ? anssiz: resplen);
|
|
goto read_len;
|
|
}
|
|
|
|
/*
|
|
* All is well, or the error is fatal. Signal that the
|
|
* next nameserver ought not be tried.
|
|
*/
|
|
return (resplen);
|
|
}
|
|
|
|
static int
|
|
send_dg(res_state statp,
|
|
const u_char *buf, int buflen, u_char *ans, int anssiz,
|
|
int *terrno, int ns, int *v_circuit, int *gotsomewhere)
|
|
{
|
|
const HEADER *hp = (const HEADER *)(const void *)buf;
|
|
HEADER *anhp = (HEADER *)(void *)ans;
|
|
const struct sockaddr *nsap;
|
|
int nsaplen;
|
|
struct timespec now, timeout, finish;
|
|
fd_set dsmask;
|
|
struct sockaddr_storage from;
|
|
socklen_t fromlen;
|
|
int resplen, seconds, n, s;
|
|
|
|
nsap = get_nsaddr(statp, (size_t)ns);
|
|
nsaplen = get_salen(nsap);
|
|
if (EXT(statp).nssocks[ns] == -1) {
|
|
EXT(statp).nssocks[ns] = socket(nsap->sa_family, SOCK_DGRAM, 0);
|
|
if (EXT(statp).nssocks[ns] > highestFD) {
|
|
res_nclose(statp);
|
|
errno = ENOTSOCK;
|
|
}
|
|
if (EXT(statp).nssocks[ns] < 0) {
|
|
switch (errno) {
|
|
case EPROTONOSUPPORT:
|
|
#ifdef EPFNOSUPPORT
|
|
case EPFNOSUPPORT:
|
|
#endif
|
|
case EAFNOSUPPORT:
|
|
Perror(statp, stderr, "socket(dg)", errno);
|
|
return (0);
|
|
default:
|
|
*terrno = errno;
|
|
Perror(statp, stderr, "socket(dg)", errno);
|
|
return (-1);
|
|
}
|
|
}
|
|
#ifndef CANNOT_CONNECT_DGRAM
|
|
/*
|
|
* 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 (random_bind(EXT(statp).nssocks[ns], nsap->sa_family) < 0) {
|
|
Aerror(statp, stderr, "bind(dg)", errno, nsap,
|
|
nsaplen);
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
if (connect(EXT(statp).nssocks[ns], nsap, (socklen_t)nsaplen) < 0) {
|
|
Aerror(statp, stderr, "connect(dg)", errno, nsap,
|
|
nsaplen);
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
#endif /* !CANNOT_CONNECT_DGRAM */
|
|
Dprint(statp->options & RES_DEBUG,
|
|
(stdout, ";; new DG socket\n"))
|
|
}
|
|
s = EXT(statp).nssocks[ns];
|
|
#ifndef CANNOT_CONNECT_DGRAM
|
|
if (send(s, (const char*)buf, (size_t)buflen, 0) != buflen) {
|
|
Perror(statp, stderr, "send", errno);
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
#else /* !CANNOT_CONNECT_DGRAM */
|
|
if (sendto(s, (const char*)buf, buflen, 0, nsap, nsaplen) != buflen)
|
|
{
|
|
Aerror(statp, stderr, "sendto", errno, nsap, nsaplen);
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
#endif /* !CANNOT_CONNECT_DGRAM */
|
|
|
|
/*
|
|
* Wait for reply.
|
|
*/
|
|
seconds = (statp->retrans << ns);
|
|
if (ns > 0)
|
|
seconds /= statp->nscount;
|
|
if (seconds <= 0)
|
|
seconds = 1;
|
|
now = evNowTime();
|
|
timeout = evConsTime((long)seconds, 0L);
|
|
finish = evAddTime(now, timeout);
|
|
goto nonow;
|
|
wait:
|
|
now = evNowTime();
|
|
nonow:
|
|
FD_ZERO(&dsmask);
|
|
FD_SET(s, &dsmask);
|
|
if (evCmpTime(finish, now) > 0)
|
|
timeout = evSubTime(finish, now);
|
|
else
|
|
timeout = evConsTime(0L, 0L);
|
|
n = pselect(s + 1, &dsmask, NULL, NULL, &timeout, NULL);
|
|
if (n == 0) {
|
|
Dprint(statp->options & RES_DEBUG, (stdout, ";; timeout\n"));
|
|
*gotsomewhere = 1;
|
|
return (0);
|
|
}
|
|
if (n < 0) {
|
|
if (errno == EINTR)
|
|
goto wait;
|
|
Perror(statp, stderr, "select", errno);
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
errno = 0;
|
|
fromlen = sizeof(from);
|
|
resplen = recvfrom(s, (char*)ans, (size_t)anssiz,0,
|
|
(struct sockaddr *)(void *)&from, &fromlen);
|
|
if (resplen <= 0) {
|
|
Perror(statp, stderr, "recvfrom", errno);
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
*gotsomewhere = 1;
|
|
if (resplen < HFIXEDSZ) {
|
|
/*
|
|
* Undersized message.
|
|
*/
|
|
Dprint(statp->options & RES_DEBUG,
|
|
(stdout, ";; undersized: %d\n",
|
|
resplen));
|
|
*terrno = EMSGSIZE;
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
if (hp->id != anhp->id) {
|
|
/*
|
|
* response from old query, ignore it.
|
|
* XXX - potential security hazard could
|
|
* be detected here.
|
|
*/
|
|
DprintQ((statp->options & RES_DEBUG) ||
|
|
(statp->pfcode & RES_PRF_REPLY),
|
|
(stdout, ";; old answer:\n"),
|
|
ans, (resplen > anssiz) ? anssiz : resplen);
|
|
goto wait;
|
|
}
|
|
if (!(statp->options & RES_INSECURE1) &&
|
|
!res_ourserver_p(statp, (struct sockaddr *)(void *)&from)) {
|
|
/*
|
|
* response from wrong server? ignore it.
|
|
* XXX - potential security hazard could
|
|
* be detected here.
|
|
*/
|
|
DprintQ((statp->options & RES_DEBUG) ||
|
|
(statp->pfcode & RES_PRF_REPLY),
|
|
(stdout, ";; not our server:\n"),
|
|
ans, (resplen > anssiz) ? anssiz : resplen);
|
|
goto wait;
|
|
}
|
|
#ifdef RES_USE_EDNS0
|
|
if (anhp->rcode == FORMERR && (statp->options & RES_USE_EDNS0) != 0U) {
|
|
/*
|
|
* Do not retry if the server do not understand EDNS0.
|
|
* The case has to be captured here, as FORMERR packet do not
|
|
* carry query section, hence res_queriesmatch() returns 0.
|
|
*/
|
|
DprintQ(statp->options & RES_DEBUG,
|
|
(stdout, "server rejected query with EDNS0:\n"),
|
|
ans, (resplen > anssiz) ? anssiz : resplen);
|
|
/* record the error */
|
|
statp->_flags |= RES_F_EDNS0ERR;
|
|
res_nclose(statp);
|
|
return (0);
|
|
}
|
|
#endif
|
|
if (!(statp->options & RES_INSECURE2) &&
|
|
!res_queriesmatch(buf, buf + buflen,
|
|
ans, ans + anssiz)) {
|
|
/*
|
|
* response contains wrong query? ignore it.
|
|
* XXX - potential security hazard could
|
|
* be detected here.
|
|
*/
|
|
DprintQ((statp->options & RES_DEBUG) ||
|
|
(statp->pfcode & RES_PRF_REPLY),
|
|
(stdout, ";; wrong query name:\n"),
|
|
ans, (resplen > anssiz) ? anssiz : resplen);
|
|
goto wait;
|
|
}
|
|
if (anhp->rcode == SERVFAIL ||
|
|
anhp->rcode == NOTIMP ||
|
|
anhp->rcode == REFUSED) {
|
|
DprintQ(statp->options & RES_DEBUG,
|
|
(stdout, "server rejected query:\n"),
|
|
ans, (resplen > anssiz) ? anssiz : resplen);
|
|
res_nclose(statp);
|
|
/* don't retry if called from dig */
|
|
if (!statp->pfcode)
|
|
return (0);
|
|
}
|
|
if (!(statp->options & RES_IGNTC) && anhp->tc) {
|
|
/*
|
|
* To get the rest of answer,
|
|
* use TCP with same server.
|
|
*/
|
|
Dprint(statp->options & RES_DEBUG,
|
|
(stdout, ";; truncated answer\n"));
|
|
*v_circuit = 1;
|
|
res_nclose(statp);
|
|
return (1);
|
|
}
|
|
/*
|
|
* All is well, or the error is fatal. Signal that the
|
|
* next nameserver ought not be tried.
|
|
*/
|
|
return (resplen);
|
|
}
|
|
|
|
static void
|
|
Aerror(const res_state statp, FILE *file, const char *string, int error,
|
|
const struct sockaddr *address, int alen)
|
|
{
|
|
int save = errno;
|
|
char hbuf[NI_MAXHOST];
|
|
char sbuf[NI_MAXSERV];
|
|
|
|
alen = alen;
|
|
|
|
if ((statp->options & RES_DEBUG) != 0U) {
|
|
if (getnameinfo(address, (socklen_t)alen, hbuf, sizeof(hbuf),
|
|
sbuf, sizeof(sbuf), niflags)) {
|
|
strncpy(hbuf, "?", sizeof(hbuf) - 1);
|
|
hbuf[sizeof(hbuf) - 1] = '\0';
|
|
strncpy(sbuf, "?", sizeof(sbuf) - 1);
|
|
sbuf[sizeof(sbuf) - 1] = '\0';
|
|
}
|
|
fprintf(file, "res_send: %s ([%s].%s): %s\n",
|
|
string, hbuf, sbuf, strerror(error));
|
|
}
|
|
errno = save;
|
|
}
|
|
|
|
static void
|
|
Perror(const res_state statp, FILE *file, const char *string, int error) {
|
|
int save = errno;
|
|
|
|
if ((statp->options & RES_DEBUG) != 0U)
|
|
fprintf(file, "res_send: %s: %s\n",
|
|
string, strerror(error));
|
|
errno = save;
|
|
}
|
|
|
|
static int
|
|
sock_eq(struct sockaddr *a, struct sockaddr *b) {
|
|
struct sockaddr_in *a4, *b4;
|
|
struct sockaddr_in6 *a6, *b6;
|
|
|
|
if (a->sa_family != b->sa_family)
|
|
return 0;
|
|
switch (a->sa_family) {
|
|
case AF_INET:
|
|
a4 = (struct sockaddr_in *)(void *)a;
|
|
b4 = (struct sockaddr_in *)(void *)b;
|
|
return a4->sin_port == b4->sin_port &&
|
|
a4->sin_addr.s_addr == b4->sin_addr.s_addr;
|
|
case AF_INET6:
|
|
a6 = (struct sockaddr_in6 *)(void *)a;
|
|
b6 = (struct sockaddr_in6 *)(void *)b;
|
|
return a6->sin6_port == b6->sin6_port &&
|
|
#ifdef HAVE_SIN6_SCOPE_ID
|
|
a6->sin6_scope_id == b6->sin6_scope_id &&
|
|
#endif
|
|
IN6_ARE_ADDR_EQUAL(&a6->sin6_addr, &b6->sin6_addr);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#ifdef NEED_PSELECT
|
|
/* XXX needs to move to the porting library. */
|
|
static int
|
|
pselect(int nfds, void *rfds, void *wfds, void *efds,
|
|
struct timespec *tsp, const sigset_t *sigmask)
|
|
{
|
|
struct timeval tv, *tvp;
|
|
sigset_t sigs;
|
|
int n;
|
|
|
|
if (tsp) {
|
|
tvp = &tv;
|
|
tv = evTimeVal(*tsp);
|
|
} else
|
|
tvp = NULL;
|
|
if (sigmask)
|
|
sigprocmask(SIG_SETMASK, sigmask, &sigs);
|
|
n = select(nfds, rfds, wfds, efds, tvp);
|
|
if (sigmask)
|
|
sigprocmask(SIG_SETMASK, &sigs, NULL);
|
|
if (tsp)
|
|
*tsp = evTimeSpec(tv);
|
|
return (n);
|
|
}
|
|
#endif
|