7daefc5a5a
If an argument of a ctype function is outside "unsigned char" and if it is not EOF, the behavior is undefined. The isascii(3) is the sole exception of above and it was used to be used to check a value was valid for other ctype functions in ancient systems. On modern systems, the ctype functions take all values of "unsigned char", and this check is obsolete and even wrong for non-ASCII systems. However, we leave the isascii() untouched for now, so as not to change the current behavior.
626 lines
13 KiB
C
626 lines
13 KiB
C
/* $NetBSD: hesiod.c,v 1.14 2000/07/07 08:03:39 itohy Exp $ */
|
|
|
|
/* Copyright (c) 1996 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 INTERNET SOFTWARE CONSORTIUM DISCLAIMS
|
|
* ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
|
|
* CONSORTIUM 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 1996 by the Massachusetts Institute of Technology.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this
|
|
* software and its documentation for any purpose and without
|
|
* fee is hereby granted, provided that the above copyright
|
|
* notice appear in all copies and that both that copyright
|
|
* notice and this permission notice appear in supporting
|
|
* documentation, and that the name of M.I.T. not be used in
|
|
* advertising or publicity pertaining to distribution of the
|
|
* software without specific, written prior permission.
|
|
* M.I.T. makes no representations about the suitability of
|
|
* this software for any purpose. It is provided "as is"
|
|
* without express or implied warranty.
|
|
*/
|
|
|
|
/* This file is part of the hesiod library. It implements the core
|
|
* portion of the hesiod resolver.
|
|
*
|
|
* This file is loosely based on an interim version of hesiod.c from
|
|
* the BIND IRS library, which was in turn based on an earlier version
|
|
* of this file. Extensive changes have been made on each step of the
|
|
* path.
|
|
*
|
|
* This implementation is not truly thread-safe at the moment because
|
|
* it uses res_send() and accesses _res.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
|
|
#if defined(LIBC_SCCS) && !defined(lint)
|
|
__IDSTRING(rcsid_hesiod_c,
|
|
"#Id: hesiod.c,v 1.18.2.1 1997/01/03 20:48:20 ghudson Exp #");
|
|
__IDSTRING(rcsid_hesiod_p_h,
|
|
"#Id: hesiod_p.h,v 1.1 1996/12/08 21:39:37 ghudson Exp #");
|
|
__IDSTRING(rcsid_hescompat_c,
|
|
"#Id: hescompat.c,v 1.1.2.1 1996/12/16 08:37:45 ghudson Exp #");
|
|
__RCSID("$NetBSD: hesiod.c,v 1.14 2000/07/07 08:03:39 itohy Exp $");
|
|
#endif /* LIBC_SCCS and not lint */
|
|
|
|
#include "namespace.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/nameser.h>
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <hesiod.h>
|
|
#include <resolv.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef __weak_alias
|
|
__weak_alias(hesiod_init,_hesiod_init)
|
|
__weak_alias(hesiod_end,_hesiod_end)
|
|
__weak_alias(hesiod_to_bind,_hesiod_to_bind)
|
|
__weak_alias(hesiod_resolve,_hesiod_resolve)
|
|
__weak_alias(hesiod_free_list,_hesiod_free_list)
|
|
__weak_alias(hes_init,_hes_init)
|
|
__weak_alias(hes_to_bind,_hes_to_bind)
|
|
__weak_alias(hes_resolve,_hes_resolve)
|
|
__weak_alias(hes_error,_hes_error)
|
|
__weak_alias(hes_free,_hes_free)
|
|
#endif
|
|
|
|
struct hesiod_p {
|
|
char *lhs; /* normally ".ns" */
|
|
char *rhs; /* AKA the default hesiod domain */
|
|
int classes[2]; /* The class search order. */
|
|
};
|
|
|
|
#define MAX_HESRESP 1024
|
|
|
|
static int read_config_file __P((struct hesiod_p *, const char *));
|
|
static char **get_txt_records __P((int, const char *));
|
|
static int init_context __P((void));
|
|
static void translate_errors __P((void));
|
|
|
|
|
|
/*
|
|
* hesiod_init --
|
|
* initialize a hesiod_p.
|
|
*/
|
|
int
|
|
hesiod_init(context)
|
|
void **context;
|
|
{
|
|
struct hesiod_p *ctx;
|
|
const char *p, *configname;
|
|
int serrno;
|
|
|
|
_DIAGASSERT(context != NULL);
|
|
|
|
ctx = malloc(sizeof(struct hesiod_p));
|
|
if (ctx) {
|
|
*context = ctx;
|
|
configname = getenv("HESIOD_CONFIG");
|
|
if (!configname)
|
|
configname = _PATH_HESIOD_CONF;
|
|
if (read_config_file(ctx, configname) >= 0) {
|
|
/*
|
|
* The default rhs can be overridden by an
|
|
* environment variable.
|
|
*/
|
|
p = getenv("HES_DOMAIN");
|
|
if (p) {
|
|
if (ctx->rhs)
|
|
free(ctx->rhs);
|
|
ctx->rhs = malloc(strlen(p) + 2);
|
|
if (ctx->rhs) {
|
|
*ctx->rhs = '.';
|
|
strcpy(ctx->rhs + 1,
|
|
(*p == '.') ? p + 1 : p);
|
|
return 0;
|
|
} else
|
|
errno = ENOMEM;
|
|
} else
|
|
return 0;
|
|
}
|
|
} else
|
|
errno = ENOMEM;
|
|
|
|
serrno = errno;
|
|
if (ctx->lhs)
|
|
free(ctx->lhs);
|
|
if (ctx->rhs)
|
|
free(ctx->rhs);
|
|
if (ctx)
|
|
free(ctx);
|
|
errno = serrno;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* hesiod_end --
|
|
* Deallocates the hesiod_p.
|
|
*/
|
|
void
|
|
hesiod_end(context)
|
|
void *context;
|
|
{
|
|
struct hesiod_p *ctx = (struct hesiod_p *) context;
|
|
|
|
_DIAGASSERT(context != NULL);
|
|
|
|
free(ctx->rhs);
|
|
if (ctx->lhs)
|
|
free(ctx->lhs);
|
|
free(ctx);
|
|
}
|
|
|
|
/*
|
|
* hesiod_to_bind --
|
|
* takes a hesiod (name, type) and returns a DNS
|
|
* name which is to be resolved.
|
|
*/
|
|
char *
|
|
hesiod_to_bind(void *context, const char *name, const char *type)
|
|
{
|
|
struct hesiod_p *ctx = (struct hesiod_p *) context;
|
|
char bindname[MAXDNAME], *p, *ret, **rhs_list = NULL;
|
|
const char *rhs;
|
|
int len;
|
|
|
|
_DIAGASSERT(context != NULL);
|
|
_DIAGASSERT(name != NULL);
|
|
_DIAGASSERT(type != NULL);
|
|
|
|
strcpy(bindname, name);
|
|
|
|
/*
|
|
* Find the right right hand side to use, possibly
|
|
* truncating bindname.
|
|
*/
|
|
p = strchr(bindname, '@');
|
|
if (p) {
|
|
*p++ = 0;
|
|
if (strchr(p, '.'))
|
|
rhs = name + (p - bindname);
|
|
else {
|
|
rhs_list = hesiod_resolve(context, p, "rhs-extension");
|
|
if (rhs_list)
|
|
rhs = *rhs_list;
|
|
else {
|
|
errno = ENOENT;
|
|
return NULL;
|
|
}
|
|
}
|
|
} else
|
|
rhs = ctx->rhs;
|
|
|
|
/* See if we have enough room. */
|
|
len = strlen(bindname) + 1 + strlen(type);
|
|
if (ctx->lhs)
|
|
len += strlen(ctx->lhs) + ((ctx->lhs[0] != '.') ? 1 : 0);
|
|
len += strlen(rhs) + ((rhs[0] != '.') ? 1 : 0);
|
|
if (len > sizeof(bindname) - 1) {
|
|
if (rhs_list)
|
|
hesiod_free_list(context, rhs_list);
|
|
errno = EMSGSIZE;
|
|
return NULL;
|
|
}
|
|
/* Put together the rest of the domain. */
|
|
strcat(bindname, ".");
|
|
strcat(bindname, type);
|
|
/* Only append lhs if it isn't empty. */
|
|
if (ctx->lhs && ctx->lhs[0] != '\0' ) {
|
|
if (ctx->lhs[0] != '.')
|
|
strcat(bindname, ".");
|
|
strcat(bindname, ctx->lhs);
|
|
}
|
|
if (rhs[0] != '.')
|
|
strcat(bindname, ".");
|
|
strcat(bindname, rhs);
|
|
|
|
/* rhs_list is no longer needed, since we're done with rhs. */
|
|
if (rhs_list)
|
|
hesiod_free_list(context, rhs_list);
|
|
|
|
/* Make a copy of the result and return it to the caller. */
|
|
ret = strdup(bindname);
|
|
if (!ret)
|
|
errno = ENOMEM;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* hesiod_resolve --
|
|
* Given a hesiod name and type, return an array of strings returned
|
|
* by the resolver.
|
|
*/
|
|
char **
|
|
hesiod_resolve(context, name, type)
|
|
void *context;
|
|
const char *name;
|
|
const char *type;
|
|
{
|
|
struct hesiod_p *ctx = (struct hesiod_p *) context;
|
|
char *bindname, **retvec;
|
|
|
|
_DIAGASSERT(context != NULL);
|
|
_DIAGASSERT(name != NULL);
|
|
_DIAGASSERT(type != NULL);
|
|
|
|
bindname = hesiod_to_bind(context, name, type);
|
|
if (!bindname)
|
|
return NULL;
|
|
|
|
retvec = get_txt_records(ctx->classes[0], bindname);
|
|
if (retvec == NULL && errno == ENOENT && ctx->classes[1])
|
|
retvec = get_txt_records(ctx->classes[1], bindname);
|
|
|
|
free(bindname);
|
|
return retvec;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
void
|
|
hesiod_free_list(context, list)
|
|
void *context;
|
|
char **list;
|
|
{
|
|
char **p;
|
|
|
|
_DIAGASSERT(context != NULL);
|
|
|
|
if (list == NULL)
|
|
return;
|
|
for (p = list; *p; p++)
|
|
free(*p);
|
|
free(list);
|
|
}
|
|
|
|
|
|
/* read_config_file --
|
|
* Parse the /etc/hesiod.conf file. Returns 0 on success,
|
|
* -1 on failure. On failure, it might leave values in ctx->lhs
|
|
* or ctx->rhs which need to be freed by the caller.
|
|
*/
|
|
static int
|
|
read_config_file(ctx, filename)
|
|
struct hesiod_p *ctx;
|
|
const char *filename;
|
|
{
|
|
char *key, *data, *p, **which;
|
|
char buf[MAXDNAME + 7];
|
|
int n;
|
|
FILE *fp;
|
|
|
|
_DIAGASSERT(ctx != NULL);
|
|
_DIAGASSERT(filename != NULL);
|
|
|
|
/* Set default query classes. */
|
|
ctx->classes[0] = C_IN;
|
|
ctx->classes[1] = C_HS;
|
|
|
|
/* Try to open the configuration file. */
|
|
fp = fopen(filename, "r");
|
|
if (!fp) {
|
|
/* Use compiled in default domain names. */
|
|
ctx->lhs = strdup(DEF_LHS);
|
|
ctx->rhs = strdup(DEF_RHS);
|
|
if (ctx->lhs && ctx->rhs)
|
|
return 0;
|
|
else {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
ctx->lhs = NULL;
|
|
ctx->rhs = NULL;
|
|
while (fgets(buf, sizeof(buf), fp) != NULL) {
|
|
p = buf;
|
|
if (*p == '#' || *p == '\n' || *p == '\r')
|
|
continue;
|
|
while (*p == ' ' || *p == '\t')
|
|
p++;
|
|
key = p;
|
|
while (*p != ' ' && *p != '\t' && *p != '=')
|
|
p++;
|
|
*p++ = 0;
|
|
|
|
while (isspace((u_char) *p) || *p == '=')
|
|
p++;
|
|
data = p;
|
|
while (!isspace((u_char) *p))
|
|
p++;
|
|
*p = 0;
|
|
|
|
if (strcasecmp(key, "lhs") == 0 ||
|
|
strcasecmp(key, "rhs") == 0) {
|
|
which = (strcasecmp(key, "lhs") == 0)
|
|
? &ctx->lhs : &ctx->rhs;
|
|
*which = strdup(data);
|
|
if (!*which) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (strcasecmp(key, "classes") == 0) {
|
|
n = 0;
|
|
while (*data && n < 2) {
|
|
p = data;
|
|
while (*p && *p != ',')
|
|
p++;
|
|
if (*p)
|
|
*p++ = 0;
|
|
if (strcasecmp(data, "IN") == 0)
|
|
ctx->classes[n++] = C_IN;
|
|
else
|
|
if (strcasecmp(data, "HS") == 0)
|
|
ctx->classes[n++] =
|
|
C_HS;
|
|
data = p;
|
|
}
|
|
while (n < 2)
|
|
ctx->classes[n++] = 0;
|
|
}
|
|
}
|
|
}
|
|
fclose(fp);
|
|
|
|
if (!ctx->rhs || ctx->classes[0] == 0 ||
|
|
ctx->classes[0] == ctx->classes[1]) {
|
|
errno = ENOEXEC;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* get_txt_records --
|
|
* Given a DNS class and a DNS name, do a lookup for TXT records, and
|
|
* return a list of them.
|
|
*/
|
|
static char **
|
|
get_txt_records(qclass, name)
|
|
int qclass;
|
|
const char *name;
|
|
{
|
|
HEADER *hp;
|
|
unsigned char qbuf[PACKETSZ], abuf[MAX_HESRESP], *p, *eom, *eor;
|
|
char *dst, **list;
|
|
int ancount, qdcount, i, j, n, skip, type, class, len;
|
|
|
|
_DIAGASSERT(name != NULL);
|
|
|
|
/* Make sure the resolver is initialized. */
|
|
if ((_res.options & RES_INIT) == 0 && res_init() == -1)
|
|
return NULL;
|
|
|
|
/* Construct the query. */
|
|
n = res_mkquery(QUERY, name, qclass, T_TXT, NULL, 0,
|
|
NULL, qbuf, PACKETSZ);
|
|
if (n < 0) {
|
|
errno = EMSGSIZE;
|
|
return NULL;
|
|
}
|
|
|
|
/* Send the query. */
|
|
n = res_send(qbuf, n, abuf, MAX_HESRESP);
|
|
if (n < 0) {
|
|
errno = ECONNREFUSED;
|
|
return NULL;
|
|
}
|
|
/* Parse the header of the result. */
|
|
hp = (HEADER *) (void *) abuf;
|
|
ancount = ntohs(hp->ancount);
|
|
qdcount = ntohs(hp->qdcount);
|
|
p = abuf + sizeof(HEADER);
|
|
eom = abuf + n;
|
|
|
|
/*
|
|
* Skip questions, trying to get to the answer section
|
|
* which follows.
|
|
*/
|
|
for (i = 0; i < qdcount; i++) {
|
|
skip = dn_skipname(p, eom);
|
|
if (skip < 0 || p + skip + QFIXEDSZ > eom) {
|
|
errno = EMSGSIZE;
|
|
return NULL;
|
|
}
|
|
p += skip + QFIXEDSZ;
|
|
}
|
|
|
|
/* Allocate space for the text record answers. */
|
|
list = malloc((ancount + 1) * sizeof(char *));
|
|
if (!list) {
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
/* Parse the answers. */
|
|
j = 0;
|
|
for (i = 0; i < ancount; i++) {
|
|
/* Parse the header of this answer. */
|
|
skip = dn_skipname(p, eom);
|
|
if (skip < 0 || p + skip + 10 > eom)
|
|
break;
|
|
type = p[skip + 0] << 8 | p[skip + 1];
|
|
class = p[skip + 2] << 8 | p[skip + 3];
|
|
len = p[skip + 8] << 8 | p[skip + 9];
|
|
p += skip + 10;
|
|
if (p + len > eom) {
|
|
errno = EMSGSIZE;
|
|
break;
|
|
}
|
|
/* Skip entries of the wrong class and type. */
|
|
if (class != qclass || type != T_TXT) {
|
|
p += len;
|
|
continue;
|
|
}
|
|
/* Allocate space for this answer. */
|
|
list[j] = malloc((size_t)len);
|
|
if (!list[j]) {
|
|
errno = ENOMEM;
|
|
break;
|
|
}
|
|
dst = list[j++];
|
|
|
|
/* Copy answer data into the allocated area. */
|
|
eor = p + len;
|
|
while (p < eor) {
|
|
n = (unsigned char) *p++;
|
|
if (p + n > eor) {
|
|
errno = EMSGSIZE;
|
|
break;
|
|
}
|
|
memcpy(dst, p, (size_t)n);
|
|
p += n;
|
|
dst += n;
|
|
}
|
|
if (p < eor) {
|
|
errno = EMSGSIZE;
|
|
break;
|
|
}
|
|
*dst = 0;
|
|
}
|
|
|
|
/*
|
|
* If we didn't terminate the loop normally, something
|
|
* went wrong.
|
|
*/
|
|
if (i < ancount) {
|
|
for (i = 0; i < j; i++)
|
|
free(list[i]);
|
|
free(list);
|
|
return NULL;
|
|
}
|
|
if (j == 0) {
|
|
errno = ENOENT;
|
|
free(list);
|
|
return NULL;
|
|
}
|
|
list[j] = NULL;
|
|
return list;
|
|
}
|
|
|
|
/*
|
|
* COMPATIBILITY FUNCTIONS
|
|
*/
|
|
|
|
static int inited = 0;
|
|
static void *context;
|
|
static int errval = HES_ER_UNINIT;
|
|
|
|
int
|
|
hes_init()
|
|
{
|
|
init_context();
|
|
return errval;
|
|
}
|
|
|
|
char *
|
|
hes_to_bind(name, type)
|
|
const char *name;
|
|
const char *type;
|
|
{
|
|
static char *bindname;
|
|
|
|
_DIAGASSERT(name != NULL);
|
|
_DIAGASSERT(type != NULL);
|
|
|
|
if (init_context() < 0)
|
|
return NULL;
|
|
if (bindname)
|
|
free(bindname);
|
|
bindname = hesiod_to_bind(context, name, type);
|
|
if (!bindname)
|
|
translate_errors();
|
|
return bindname;
|
|
}
|
|
|
|
char **
|
|
hes_resolve(name, type)
|
|
const char *name;
|
|
const char *type;
|
|
{
|
|
static char **list;
|
|
|
|
_DIAGASSERT(name != NULL);
|
|
_DIAGASSERT(type != NULL);
|
|
|
|
if (init_context() < 0)
|
|
return NULL;
|
|
|
|
/*
|
|
* In the old Hesiod interface, the caller was responsible for
|
|
* freeing the returned strings but not the vector of strings itself.
|
|
*/
|
|
if (list)
|
|
free(list);
|
|
|
|
list = hesiod_resolve(context, name, type);
|
|
if (!list)
|
|
translate_errors();
|
|
return list;
|
|
}
|
|
|
|
int
|
|
hes_error()
|
|
{
|
|
return errval;
|
|
}
|
|
|
|
void
|
|
hes_free(hp)
|
|
char **hp;
|
|
{
|
|
hesiod_free_list(context, hp);
|
|
}
|
|
|
|
static int
|
|
init_context()
|
|
{
|
|
if (!inited) {
|
|
inited = 1;
|
|
if (hesiod_init(&context) < 0) {
|
|
errval = HES_ER_CONFIG;
|
|
return -1;
|
|
}
|
|
errval = HES_ER_OK;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
translate_errors()
|
|
{
|
|
switch (errno) {
|
|
case ENOENT:
|
|
errval = HES_ER_NOTFOUND;
|
|
break;
|
|
case ECONNREFUSED:
|
|
case EMSGSIZE:
|
|
errval = HES_ER_NET;
|
|
break;
|
|
case EFAULT:
|
|
case ENOMEM:
|
|
default:
|
|
/* Not a good match, but the best we can do. */
|
|
errval = HES_ER_CONFIG;
|
|
break;
|
|
}
|
|
}
|