freebsd-src/lib/libc/net/hesiod.c
Warner Losh dc36d6f9bb lib: Remove ancient SCCS tags.
Remove ancient SCCS tags from the tree, automated scripting, with two
minor fixup to keep things compiling. All the common forms in the tree
were removed with a perl script.

Sponsored by:		Netflix
2023-11-26 22:23:28 -07:00

555 lines
12 KiB
C

/* $NetBSD: hesiod.c,v 1.9 1999/02/11 06:16:38 simonb 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/param.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <ctype.h>
#include <errno.h>
#include <hesiod.h>
#include <resolv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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(struct hesiod_p *, const char *);
static char **get_txt_records(int, const char *);
static int init_context(void);
static void translate_errors(void);
/*
* hesiod_init --
* initialize a hesiod_p.
*/
int
hesiod_init(void **context)
{
struct hesiod_p *ctx;
const char *p, *configname;
ctx = malloc(sizeof(struct hesiod_p));
if (ctx) {
*context = ctx;
configname = secure_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 = secure_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;
if (ctx->lhs)
free(ctx->lhs);
if (ctx->rhs)
free(ctx->rhs);
if (ctx)
free(ctx);
return -1;
}
/*
* hesiod_end --
* Deallocates the hesiod_p.
*/
void
hesiod_end(void *context)
{
struct hesiod_p *ctx = (struct hesiod_p *) context;
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;
if (strlcpy(bindname, name, sizeof(bindname)) >= sizeof(bindname)) {
errno = EMSGSIZE;
return NULL;
}
/*
* 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(void *context, const char *name, const char *type)
{
struct hesiod_p *ctx = (struct hesiod_p *) context;
char *bindname, **retvec;
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(void *context, char **list)
{
char **p;
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(struct hesiod_p *ctx, const char *filename)
{
char *key, *data, *p, **which;
char buf[MAXDNAME + 7];
int n;
FILE *fp;
/* Set default query classes. */
ctx->classes[0] = C_IN;
ctx->classes[1] = C_HS;
/* Try to open the configuration file. */
fp = fopen(filename, "re");
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(*p) || *p == '=')
p++;
data = p;
while (!isspace(*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) {
fclose(fp);
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(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;
/* 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)
return NULL;
/* Send the query. */
n = res_send(qbuf, n, abuf, MAX_HESRESP);
if (n < 0 || n > MAX_HESRESP) {
errno = ECONNREFUSED; /* XXX */
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(void)
{
init_context();
return errval;
}
char *
hes_to_bind(const char *name, const char *type)
{
static char *bindname;
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(const char *name, const char *type)
{
static char **list;
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(void)
{
return errval;
}
void
hes_free(char **hp)
{
hesiod_free_list(context, hp);
}
static int
init_context(void)
{
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(void)
{
switch (errno) {
case ENOENT:
errval = HES_ER_NOTFOUND;
break;
case ECONNREFUSED:
case EMSGSIZE:
errval = HES_ER_NET;
break;
case ENOMEM:
default:
/* Not a good match, but the best we can do. */
errval = HES_ER_CONFIG;
break;
}
}