mariadb/mit-pthreads/net/res_internal.c
bk@work.mysql.com f4c589ff6c Import changeset
2000-07-31 21:29:14 +02:00

576 lines
16 KiB
C

/*
* Copyright (c) 1985 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.
*/
#if defined(LIBC_SCCS) && !defined(lint)
/*static char *sccsid = "from: @(#)res_internal.c 6.22 (Berkeley) 3/19/91";*/
static char *rcsid = "$Id$";
#endif /* LIBC_SCCS and not lint */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <resolv.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include "res_internal.h"
#define DEFAULT_RETRIES 4
pthread_mutex_t host_iterate_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t init_once = PTHREAD_ONCE_INIT;
static pthread_key_t key;
static int init_status;
static void _res_init_global(void);
static void set_options(const char *options, const char *source);
static pthread_ipaddr_type net_mask(struct in_addr in);
static int qcomp(const void *arg1, const void *arg2);
static struct __res_state start;
/* We want to define _res for partial binary compatibility with libraries. */
#undef _res
struct __res_state _res = {
RES_TIMEOUT, /* retransmition time interval */
4, /* number of times to retransmit */
RES_DEFAULT, /* options flags */
1, /* number of name servers */
};
struct hostent *_res_parse_answer(querybuf *answer, int anslen, int iquery,
struct hostent *result, char *buf,
int bufsize, int *errval)
{
struct res_data *data = _res_init();
register HEADER *hp;
register u_char *cp;
register int n;
u_char *eom;
char *aliases[__NETDB_MAXALIASES], *addrs[__NETDB_MAXADDRS];
char *bp = buf, **ap = aliases, **hap = addrs;
int type, class, ancount, qdcount, getclass = C_ANY, iquery_done = 0;
eom = answer->buf + anslen;
/*
* find first satisfactory answer
*/
hp = &answer->hdr;
ancount = ntohs(hp->ancount);
qdcount = ntohs(hp->qdcount);
bp = buf;
cp = answer->buf + sizeof(HEADER);
/* Read in the hostname if this is an address lookup. */
if (qdcount) {
if (iquery) {
if ((n = dn_expand((u_char *) answer->buf,
(u_char *) eom, (u_char *) cp, (u_char *) bp,
bufsize - (bp - buf))) < 0) {
*errval = NO_RECOVERY;
return ((struct hostent *) NULL);
}
cp += n + QFIXEDSZ;
result->h_name = bp;
bp += strlen(bp) + 1;
} else {
cp += __dn_skipname(cp, eom) + QFIXEDSZ;
}
while (--qdcount > 0)
cp += __dn_skipname(cp, eom) + QFIXEDSZ;
} else if (iquery) {
*errval = (hp->aa) ? HOST_NOT_FOUND : TRY_AGAIN;
return ((struct hostent *) NULL);
}
/* Read in the answers. */
*ap = NULL;
*hap = NULL;
while (--ancount >= 0 && cp < eom) {
if ((n = dn_expand((u_char *) answer->buf, (u_char *) eom,
(u_char *) cp, (u_char *) bp,
bufsize - (bp - buf))) < 0)
break;
cp += n;
type = _getshort(cp);
cp += sizeof(u_short);
class = _getshort(cp);
cp += sizeof(u_short) + sizeof(pthread_ipaddr_type);
n = _getshort(cp);
cp += sizeof(u_short);
if (type == T_CNAME) {
cp += n;
if (ap >= aliases + __NETDB_MAXALIASES - 1)
continue;
*ap++ = bp;
bp += strlen(bp) + 1;
continue;
}
if (iquery && type == T_PTR) {
if ((n = dn_expand((u_char *) answer->buf, (u_char *) eom,
(u_char *) cp, (u_char *) bp,
bufsize - (bp - buf))) < 0)
break;
cp += n;
result->h_name = bp;
bp += strlen(bp) + 1;
iquery_done = 1;
break;
}
if (iquery || type != T_A) {
#ifdef DEBUG_RESOLVER
if (data->state.options & RES_DEBUG)
printf("unexpected answer type %d, size %d\n",
type, n);
#endif
cp += n;
continue;
}
if (hap > addrs) {
if (n != result->h_length) {
cp += n;
continue;
}
if (class != getclass) {
cp += n;
continue;
}
} else {
result->h_length = n;
getclass = class;
result->h_addrtype = (class == C_IN) ? AF_INET : AF_UNSPEC;
if (!iquery) {
result->h_name = bp;
bp += strlen(bp) + 1;
}
}
bp = ALIGN(bp, pthread_ipaddr_type);
if (bp + n >= buf + bufsize) {
errno = ERANGE;
return NULL;
}
memcpy(bp, cp, n);
cp += n;
if (hap >= addrs + __NETDB_MAXADDRS - 1)
continue;
*hap++ = bp;
bp += n;
cp += n;
}
if (hap > addrs || iquery_done) {
*ap++ = NULL;
*hap++ = NULL;
if (data->state.nsort)
qsort(addrs, hap - addrs, sizeof(struct in_addr), qcomp);
if (SP(bp, char *, (hap - addrs) + (ap - aliases)) > buf + bufsize) {
errno = ERANGE;
return NULL;
}
result->h_addr_list = (char **) ALIGN(bp, char *);
memcpy(result->h_addr_list, addrs, (hap - addrs) * sizeof(char *));
result->h_aliases = result->h_addr_list + (hap - addrs);
memcpy(result->h_aliases, aliases, (ap - aliases) * sizeof(char *));
return result;
} else {
*errval = TRY_AGAIN;
return NULL;
}
}
/* Performs global initialization. */
struct res_data *_res_init()
{
struct res_data *data;
/* Make sure the global initializations have been done. */
pthread_once(&init_once, _res_init_global);
if (init_status < 0)
return NULL;
/* Initialize thread-specific data for this thread if it hasn't
* been done already. */
data = (struct res_data *) pthread_getspecific(key);
if (!data) {
data = (struct res_data *) malloc(sizeof(struct res_data));
if (data == NULL)
return NULL;
if (pthread_setspecific(key, data) < 0) {
free(data);
return NULL;
}
data->buf = NULL;
data->state = start;
data->errval = NO_RECOVERY;
data->sock = -1;
}
return data;
}
static void _res_init_global()
{
int result;
char line[BUFSIZ], buf[BUFSIZ], *domain, *p, *net;
int i, localdomain_set = 0, num_servers = 0, num_sorts = 0;
FILE *fp;
struct in_addr addr;
/* Assume an error state until we finish. */
init_status = -1;
/* Initialize the key for thread-specific data. */
result = pthread_key_create(&key, free);
if (result < 0)
return;
/* Initialize starting state. */
start.retrans = RES_TIMEOUT;
start.retry = DEFAULT_RETRIES;
start.options = RES_DEFAULT;
start.id = 0;
start.nscount = 1;
start.nsaddr.sin_addr.s_addr = INADDR_ANY;
start.nsaddr.sin_family = AF_INET;
start.nsaddr.sin_port = htons(NAMESERVER_PORT);
start.nscount = 1;
start.ndots = 1;
start.pfcode = 0;
strncpy(start.lookups, "f", sizeof(start.lookups));
/* Look for a LOCALDOMAIN definition. */
domain = getenv("LOCALDOMAIN");
if (domain != NULL) {
strncpy(start.defdname, domain, sizeof(start.defdname));
domain = start.defdname;
localdomain_set = 1;
/* Construct a search path from the LOCALDOMAIN value, which is
* a space-separated list of strings. For backwards-compatibility,
* a newline terminates the list. */
i = 0;
while (*domain && i < MAXDNSRCH) {
start.dnsrch[i] = domain;
while (*domain && !isspace(*domain))
domain++;
if (!*domain || *domain == '\n') {
*domain = 0;
break;
}
*domain++ = 0;
while (isspace(*domain))
domain++;
i++;
}
}
/* Look for a config file and read it in. */
fp = fopen(_PATH_RESCONF, "r");
if (fp != NULL) {
strncpy(start.lookups, "bf", sizeof(start.lookups));
/* Read in the configuration file. */
while (fgets(line, sizeof(line), fp)) {
/* Ignore blank lines and comments. */
if (*line == ';' || *line == '#' || !*line)
continue;
if (strncmp(line, "domain", 6) == 0) {
/* Read in the default domain, and initialize a one-
* element search path. Skip the domain line if we
* already got one from the LOCALDOMAIN environment
* variable. */
if (localdomain_set)
continue;
/* Look for the next word in the line. */
p = line + 6;
while (*p == ' ' || *p == '\t')
p++;
if (!*p || *p == '\n')
continue;
/* Copy in the domain, and null-terminate it at the
* first tab or newline. */
strncpy(start.defdname, p, sizeof(start.defdname) - 1);
p = strpbrk(start.defdname, "\t\n");
if (p)
*p = 0;
start.dnsrch[0] = start.defdname;
start.dnsrch[1] = NULL;
} else if (strncmp(line, "lookup", 6) == 0) {
/* Get a list of lookup types. */
memset(start.lookups, 0, sizeof(start.lookups));
/* Find the next word in the line. */
p = line + 6;
while (isspace(*p))
p++;
i = 0;
while (*p && i < MAXDNSLUS) {
/* Add a lookup type. */
if (*p == 'y' || *p == 'b' || *p == 'f')
start.lookups[i++] = *p;
/* Find the next word. */
while (*p && !isspace(*p))
p++;
while (isspace(*p))
p++;
}
} else if (strncmp(line, "search", 6) == 0) {
/* Read in a space-separated list of domains to search
* when a name is not fully-qualified. Skip this line
* if the LOCALDOMAIN environment variable was set. */
if (localdomain_set)
continue;
/* Look for the next word on the line. */
p = line + 6;
while (*p == ' ' || *p == '\t')
p++;
if (!*p || *p == '\n')
continue;
/* Copy the rest of the line into start.defdname. */
strncpy(start.defdname, p, sizeof(start.defdname) - 1);
domain = start.defdname;
p = strchr(domain, '\n');
if (*p)
*p = 0;
/* Construct a search path from the line, which is a
* space-separated list of strings. */
i = 0;
while (*domain && i < MAXDNSRCH) {
start.dnsrch[i] = domain;
while (*domain && !isspace(*domain))
domain++;
if (!*domain || *domain == '\n') {
*domain = 0;
break;
}
*domain++ = 0;
while (isspace(*domain))
domain++;
i++;
}
} else if (strncmp(line, "nameserver", 10) == 0) {
/* Add an address to the list of name servers we can
* connect to. */
/* Look for the next word in the line. */
p = line + 10;
while (*p == ' ' || *p == '\t')
p++;
if (*p && *p != '\n' && inet_aton(p, &addr)) {
start.nsaddr_list[num_servers].sin_addr = addr;
start.nsaddr_list[num_servers].sin_family = AF_INET;
start.nsaddr_list[num_servers].sin_port =
htons(NAMESERVER_PORT);
if (++num_servers >= MAXNS)
break;
}
} else if (strncmp(line, "sortlist", 8) == 0) {
p = line + 8;
while (num_sorts < MAXRESOLVSORT) {
/* Find the next word in the line. */
p = line + 8;
while (*p == ' ' || *p == '\t')
p++;
/* Read in an IP address and netmask. */
if (sscanf(p, "%[0-9./]s", buf) != 1)
break;
net = strchr(buf, '/');
if (net)
*net = 0;
/* Translate the address into an IP address
* and netmask. */
if (inet_aton(buf, &addr)) {
start.sort_list[num_sorts].addr = addr;
if (net && inet_aton(net + 1, &addr)) {
start.sort_list[num_sorts].mask = addr.s_addr;
} else {
start.sort_list[num_sorts].mask =
net_mask(start.sort_list[num_sorts].addr);
}
num_sorts++;
}
/* Skip past this word. */
if (net)
*net = '/';
p += strlen(buf);
}
}
}
fclose(fp);
}
/* If we don't have a default domain, strip off the first
* component of this machine's domain name, and make a one-
* element search path consisting of the default domain. */
if (*start.defdname == 0) {
if (gethostname(buf, sizeof(start.defdname) - 1) == 0) {
p = strchr(buf, '.');
if (p)
strcpy(start.defdname, p + 1);
}
start.dnsrch[0] = start.defdname;
start.dnsrch[1] = NULL;
}
p = getenv("RES_OPTIONS");
if (p)
set_options(p, "env");
start.options |= RES_INIT;
_res = start;
init_status = 0;
}
static void set_options(const char *options, const char *source)
{
const char *p = options;
int i;
while (*p) {
/* Skip leading and inner runs of spaces. */
while (*p == ' ' || *p == '\t')
p++;
/* Search for and process individual options. */
if (strncmp(p, "ndots:", 6) == 0) {
i = atoi(p + 6);
start.ndots = (i <= RES_MAXNDOTS) ? i : RES_MAXNDOTS;
} else if (!strncmp(p, "debug", 5))
start.options |= RES_DEBUG;
else if (!strncmp(p, "usevc", 5))
start.options |= RES_USEVC;
else if (!strncmp(p, "stayopen", 8))
start.options |= RES_STAYOPEN;
/* Skip to next run of spaces */
while (*p && *p != ' ' && *p != '\t')
p++;
}
}
static pthread_ipaddr_type net_mask(struct in_addr in)
{
pthread_ipaddr_type i = ntohl(in.s_addr);
if (IN_CLASSA(i))
return htonl(IN_CLASSA_NET);
if (IN_CLASSB(i))
return htonl(IN_CLASSB_NET);
return htonl(IN_CLASSC_NET);
}
/* Get the error value for this thread, or NO_RECOVERY if none has been
* successfully set. The screw case to worry about here is if
* __res_init() fails for a resolver routine because it can't allocate
* or set the thread-specific data, and then __res_init() succeeds here.
* Because __res_init() sets errval to NO_RECOVERY after a successful
* initialization, we return NO_RECOVERY in that case, which is correct. */
int _res_get_error()
{
struct res_data *data;
data = _res_init();
return (data) ? data->errval : NO_RECOVERY;
}
struct __res_state *_res_status()
{
struct res_data *data;
data = _res_init();
return (data) ? &data->state : NULL;
}
static int qcomp(const void *arg1, const void *arg2)
{
const struct in_addr **a1 = (const struct in_addr **) arg1;
const struct in_addr **a2 = (const struct in_addr **) arg2;
struct __res_state *state = _res_status();
int pos1, pos2;
for (pos1 = 0; pos1 < state->nsort; pos1++) {
if (state->sort_list[pos1].addr.s_addr ==
((*a1)->s_addr & state->sort_list[pos1].mask))
break;
}
for (pos2 = 0; pos2 < state->nsort; pos2++) {
if (state->sort_list[pos2].addr.s_addr ==
((*a2)->s_addr & state->sort_list[pos2].mask))
break;
}
return pos1 - pos2;
}
/*
* This routine is for closing the socket if a virtual circuit is used and
* the program wants to close it. We don't use this routine, but libc
* might reference it.
*
* This routine is not expected to be user visible.
*/
void _res_close()
{
struct res_data *data;
data = _res_init();
if (data && data->sock != -1) {
(void) close(data->sock);
data->sock = -1;
}
}