508 lines
11 KiB
C
508 lines
11 KiB
C
#if defined(LIBC_SCCS) && !defined(lint)
|
|
static const char rcsid[] = "$BINDId: hesiod.c,v 1.21 2000/02/28 14:51:08 vixie Exp $";
|
|
#endif
|
|
|
|
/*
|
|
* 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 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.
|
|
*/
|
|
|
|
/*
|
|
* This file is primarily maintained by <tytso@mit.edu> and <ghudson@mit.edu>.
|
|
*/
|
|
|
|
/*
|
|
* hesiod.c --- the core portion of the hesiod resolver.
|
|
*
|
|
* This file is derived from the hesiod library from Project Athena;
|
|
* It has been extensively rewritten by Theodore Ts'o to have a more
|
|
* thread-safe interface.
|
|
*/
|
|
|
|
/* Imports */
|
|
|
|
#include <sys/types.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/nameser.h>
|
|
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#include <resolv.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "hesiod.h"
|
|
#include "hesiod_p.h"
|
|
#undef DEF_RHS
|
|
|
|
#define _PATH_HESIOD_CONF "/etc/hesiod.conf"
|
|
|
|
/* Forward */
|
|
|
|
int hesiod_init(void **context);
|
|
void hesiod_end(void *context);
|
|
char * hesiod_to_bind(void *context, const char *name,
|
|
const char *type);
|
|
char ** hesiod_resolve(void *context, const char *name,
|
|
const char *type);
|
|
void hesiod_free_list(void *context, char **list);
|
|
|
|
static int parse_config_file(struct hesiod_p *ctx, const char *filename);
|
|
static char ** get_txt_records(struct hesiod_p *ctx, int class,
|
|
const char *name);
|
|
static int init(struct hesiod_p *ctx);
|
|
|
|
/* Public */
|
|
|
|
/*
|
|
* This function is called to initialize a hesiod_p.
|
|
*/
|
|
int
|
|
hesiod_init(void **context) {
|
|
struct hesiod_p *ctx;
|
|
const char *configname;
|
|
char *cp;
|
|
|
|
ctx = malloc(sizeof(struct hesiod_p));
|
|
if (ctx == 0)
|
|
return (-1);
|
|
|
|
ctx->LHS = NULL;
|
|
ctx->RHS = NULL;
|
|
ctx->res = NULL;
|
|
/* Set default query classes. */
|
|
ctx->classes[0] = C_IN;
|
|
ctx->classes[1] = C_HS;
|
|
|
|
configname = __secure_getenv("HESIOD_CONFIG");
|
|
if (!configname)
|
|
configname = _PATH_HESIOD_CONF;
|
|
if (parse_config_file(ctx, configname) < 0) {
|
|
#ifdef DEF_RHS
|
|
/*
|
|
* Use compiled in defaults.
|
|
*/
|
|
ctx->LHS = malloc(strlen(DEF_LHS)+1);
|
|
ctx->RHS = malloc(strlen(DEF_RHS)+1);
|
|
if (ctx->LHS == 0 || ctx->RHS == 0)
|
|
goto cleanup;
|
|
strcpy(ctx->LHS, DEF_LHS);
|
|
strcpy(ctx->RHS, DEF_RHS);
|
|
#else
|
|
goto cleanup;
|
|
#endif
|
|
}
|
|
/*
|
|
* The default RHS can be overridden by an environment
|
|
* variable.
|
|
*/
|
|
if ((cp = __secure_getenv("HES_DOMAIN")) != NULL) {
|
|
free(ctx->RHS);
|
|
ctx->RHS = malloc(strlen(cp)+2);
|
|
if (!ctx->RHS)
|
|
goto cleanup;
|
|
if (cp[0] == '.')
|
|
strcpy(ctx->RHS, cp);
|
|
else {
|
|
ctx->RHS[0] = '.';
|
|
strcpy(ctx->RHS + 1, cp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If there is no default hesiod realm set, we return an
|
|
* error.
|
|
*/
|
|
if (!ctx->RHS) {
|
|
__set_errno(ENOEXEC);
|
|
goto cleanup;
|
|
}
|
|
|
|
#if 0
|
|
if (res_ninit(ctx->res) < 0)
|
|
goto cleanup;
|
|
#endif
|
|
|
|
*context = ctx;
|
|
return (0);
|
|
|
|
cleanup:
|
|
hesiod_end(ctx);
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* This function deallocates the hesiod_p
|
|
*/
|
|
void
|
|
hesiod_end(void *context) {
|
|
struct hesiod_p *ctx = (struct hesiod_p *) context;
|
|
int save_errno = errno;
|
|
|
|
if (ctx->res)
|
|
res_nclose(ctx->res);
|
|
free(ctx->RHS);
|
|
free(ctx->LHS);
|
|
if (ctx->res && ctx->free_res)
|
|
(*ctx->free_res)(ctx->res);
|
|
free(ctx);
|
|
__set_errno(save_errno);
|
|
}
|
|
|
|
/*
|
|
* This function 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;
|
|
char **rhs_list = NULL;
|
|
const char *RHS, *cp;
|
|
char *endp;
|
|
|
|
/* Decide what our RHS is, and set cp to the end of the actual name. */
|
|
if ((cp = strchr(name, '@')) != NULL) {
|
|
if (strchr(cp + 1, '.'))
|
|
RHS = cp + 1;
|
|
else if ((rhs_list = hesiod_resolve(context, cp + 1,
|
|
"rhs-extension")) != NULL)
|
|
RHS = *rhs_list;
|
|
else {
|
|
__set_errno(ENOENT);
|
|
return (NULL);
|
|
}
|
|
} else {
|
|
RHS = ctx->RHS;
|
|
cp = name + strlen(name);
|
|
}
|
|
|
|
/*
|
|
* Allocate the space we need, including up to three periods and
|
|
* the terminating NUL.
|
|
*/
|
|
if ((bindname = malloc((cp - name) + strlen(type) + strlen(RHS) +
|
|
(ctx->LHS ? strlen(ctx->LHS) : 0) + 4)) == NULL) {
|
|
if (rhs_list)
|
|
hesiod_free_list(context, rhs_list);
|
|
return NULL;
|
|
}
|
|
|
|
/* Now put together the DNS name. */
|
|
endp = (char *) __mempcpy (bindname, name, cp - name);
|
|
*endp++ = '.';
|
|
endp = (char *) __stpcpy (endp, type);
|
|
if (ctx->LHS) {
|
|
if (ctx->LHS[0] != '.')
|
|
*endp++ = '.';
|
|
endp = __stpcpy (endp, ctx->LHS);
|
|
}
|
|
if (RHS[0] != '.')
|
|
*endp++ = '.';
|
|
strcpy (endp, RHS);
|
|
|
|
if (rhs_list)
|
|
hesiod_free_list(context, rhs_list);
|
|
|
|
return (bindname);
|
|
}
|
|
|
|
/*
|
|
* This is the core function. Given a hesiod (name, type), it
|
|
* returns 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 = hesiod_to_bind(context, name, type);
|
|
char **retvec;
|
|
|
|
if (bindname == NULL)
|
|
return (NULL);
|
|
if (init(ctx) == -1) {
|
|
free(bindname);
|
|
return (NULL);
|
|
}
|
|
|
|
retvec = get_txt_records(ctx, ctx->classes[0], bindname);
|
|
|
|
if (retvec == NULL && (errno == ENOENT || errno == ECONNREFUSED) && ctx->classes[1])
|
|
retvec = get_txt_records(ctx, ctx->classes[1], bindname);
|
|
|
|
|
|
free(bindname);
|
|
return (retvec);
|
|
}
|
|
|
|
void
|
|
hesiod_free_list(void *context, char **list) {
|
|
char **p;
|
|
|
|
for (p = list; *p; p++)
|
|
free(*p);
|
|
free(list);
|
|
}
|
|
|
|
/*
|
|
* This function parses the /etc/hesiod.conf file
|
|
*/
|
|
static int
|
|
parse_config_file(struct hesiod_p *ctx, const char *filename) {
|
|
char buf[MAXDNAME+7];
|
|
FILE *fp;
|
|
|
|
/*
|
|
* Clear the existing configuration variable, just in case
|
|
* they're set.
|
|
*/
|
|
free(ctx->RHS);
|
|
free(ctx->LHS);
|
|
ctx->RHS = ctx->LHS = 0;
|
|
/* Set default query classes. */
|
|
ctx->classes[0] = C_IN;
|
|
ctx->classes[1] = C_HS;
|
|
|
|
/*
|
|
* Now open and parse the file...
|
|
*/
|
|
if (!(fp = fopen(filename, "r")))
|
|
return (-1);
|
|
|
|
while (fgets(buf, sizeof(buf), fp) != NULL) {
|
|
char *key, *data, *cp, **cpp;
|
|
|
|
cp = buf;
|
|
if (*cp == '#' || *cp == '\n' || *cp == '\r')
|
|
continue;
|
|
while(*cp == ' ' || *cp == '\t')
|
|
cp++;
|
|
key = cp;
|
|
while(*cp != ' ' && *cp != '\t' && *cp != '=')
|
|
cp++;
|
|
*cp++ = '\0';
|
|
|
|
while(*cp == ' ' || *cp == '\t' || *cp == '=')
|
|
cp++;
|
|
data = cp;
|
|
while(*cp != ' ' && *cp != '\n' && *cp != '\r')
|
|
cp++;
|
|
*cp++ = '\0';
|
|
|
|
cpp = NULL;
|
|
if (strcasecmp(key, "lhs") == 0)
|
|
cpp = &ctx->LHS;
|
|
else if (strcasecmp(key, "rhs") == 0)
|
|
cpp = &ctx->RHS;
|
|
if (cpp) {
|
|
*cpp = strdup(data);
|
|
if (!*cpp)
|
|
goto cleanup;
|
|
} else if (strcasecmp(key, "classes") == 0) {
|
|
int n = 0;
|
|
while (*data && n < 2) {
|
|
cp = strchrnul(data, ',');
|
|
if (*cp != '\0')
|
|
*cp++ = '\0';
|
|
if (strcasecmp(data, "IN") == 0)
|
|
ctx->classes[n++] = C_IN;
|
|
else if (strcasecmp(data, "HS") == 0)
|
|
ctx->classes[n++] = C_HS;
|
|
data = cp;
|
|
}
|
|
if (n == 0) {
|
|
/* Restore the default. Better than
|
|
nother at all. */
|
|
ctx->classes[0] = C_IN;
|
|
ctx->classes[1] = C_HS;
|
|
} else if (n == 1
|
|
|| ctx->classes[0] == ctx->classes[1])
|
|
ctx->classes[1] = 0;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
return (0);
|
|
|
|
cleanup:
|
|
fclose(fp);
|
|
free(ctx->RHS);
|
|
free(ctx->LHS);
|
|
ctx->RHS = ctx->LHS = 0;
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* 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(struct hesiod_p *ctx, int class, const char *name) {
|
|
struct {
|
|
int type; /* RR type */
|
|
int class; /* RR class */
|
|
int dlen; /* len of data section */
|
|
u_char *data; /* pointer to data */
|
|
} rr;
|
|
HEADER *hp;
|
|
u_char qbuf[MAX_HESRESP], abuf[MAX_HESRESP];
|
|
u_char *cp, *erdata, *eom;
|
|
char *dst, *edst, **list;
|
|
int ancount, qdcount;
|
|
int i, j, n, skip;
|
|
|
|
/*
|
|
* Construct the query and send it.
|
|
*/
|
|
n = res_nmkquery(ctx->res, QUERY, name, class, T_TXT, NULL, 0,
|
|
NULL, qbuf, MAX_HESRESP);
|
|
if (n < 0) {
|
|
__set_errno(EMSGSIZE);
|
|
return (NULL);
|
|
}
|
|
n = res_nsend(ctx->res, qbuf, n, abuf, MAX_HESRESP);
|
|
if (n < 0) {
|
|
__set_errno(ECONNREFUSED);
|
|
return (NULL);
|
|
}
|
|
if (n < HFIXEDSZ) {
|
|
__set_errno(EMSGSIZE);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* OK, parse the result.
|
|
*/
|
|
hp = (HEADER *) abuf;
|
|
ancount = ntohs(hp->ancount);
|
|
qdcount = ntohs(hp->qdcount);
|
|
cp = abuf + sizeof(HEADER);
|
|
eom = abuf + n;
|
|
|
|
/* Skip query, trying to get to the answer section which follows. */
|
|
for (i = 0; i < qdcount; i++) {
|
|
skip = dn_skipname(cp, eom);
|
|
if (skip < 0 || cp + skip + QFIXEDSZ > eom) {
|
|
__set_errno(EMSGSIZE);
|
|
return (NULL);
|
|
}
|
|
cp += skip + QFIXEDSZ;
|
|
}
|
|
|
|
list = malloc((ancount + 1) * sizeof(char *));
|
|
if (!list)
|
|
return (NULL);
|
|
j = 0;
|
|
for (i = 0; i < ancount; i++) {
|
|
skip = dn_skipname(cp, eom);
|
|
if (skip < 0) {
|
|
__set_errno(EMSGSIZE);
|
|
goto cleanup;
|
|
}
|
|
cp += skip;
|
|
if (cp + 3 * INT16SZ + INT32SZ > eom) {
|
|
__set_errno(EMSGSIZE);
|
|
goto cleanup;
|
|
}
|
|
rr.type = ns_get16(cp);
|
|
cp += INT16SZ;
|
|
rr.class = ns_get16(cp);
|
|
cp += INT16SZ + INT32SZ; /* skip the ttl, too */
|
|
rr.dlen = ns_get16(cp);
|
|
cp += INT16SZ;
|
|
if (cp + rr.dlen > eom) {
|
|
__set_errno(EMSGSIZE);
|
|
goto cleanup;
|
|
}
|
|
rr.data = cp;
|
|
cp += rr.dlen;
|
|
if (rr.class != class || rr.type != T_TXT)
|
|
continue;
|
|
if (!(list[j] = malloc(rr.dlen)))
|
|
goto cleanup;
|
|
dst = list[j++];
|
|
edst = dst + rr.dlen;
|
|
erdata = rr.data + rr.dlen;
|
|
cp = rr.data;
|
|
while (cp < erdata) {
|
|
n = (unsigned char) *cp++;
|
|
if (cp + n > eom || dst + n > edst) {
|
|
__set_errno(EMSGSIZE);
|
|
goto cleanup;
|
|
}
|
|
memcpy(dst, cp, n);
|
|
cp += n;
|
|
dst += n;
|
|
}
|
|
if (cp != erdata) {
|
|
__set_errno(EMSGSIZE);
|
|
goto cleanup;
|
|
}
|
|
*dst = '\0';
|
|
}
|
|
list[j] = NULL;
|
|
if (j == 0) {
|
|
__set_errno(ENOENT);
|
|
goto cleanup;
|
|
}
|
|
return (list);
|
|
|
|
cleanup:
|
|
for (i = 0; i < j; i++)
|
|
free(list[i]);
|
|
free(list);
|
|
return (NULL);
|
|
}
|
|
|
|
struct __res_state *
|
|
__hesiod_res_get(void *context) {
|
|
struct hesiod_p *ctx = context;
|
|
|
|
if (!ctx->res) {
|
|
struct __res_state *res;
|
|
res = (struct __res_state *)calloc(1, sizeof *res);
|
|
if (res == NULL)
|
|
return (NULL);
|
|
__hesiod_res_set(ctx, res, free);
|
|
}
|
|
|
|
return (ctx->res);
|
|
}
|
|
|
|
void
|
|
__hesiod_res_set(void *context, struct __res_state *res,
|
|
void (*free_res)(void *)) {
|
|
struct hesiod_p *ctx = context;
|
|
|
|
if (ctx->res && ctx->free_res) {
|
|
res_nclose(ctx->res);
|
|
(*ctx->free_res)(ctx->res);
|
|
}
|
|
|
|
ctx->res = res;
|
|
ctx->free_res = free_res;
|
|
}
|
|
|
|
static int
|
|
init(struct hesiod_p *ctx) {
|
|
|
|
if (!ctx->res && !__hesiod_res_get(ctx))
|
|
return (-1);
|
|
|
|
if (__res_maybe_init (ctx->res, 0) == -1)
|
|
return (-1);
|
|
|
|
return (0);
|
|
}
|