#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;
char *rhs;
int classes[2];
};
#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);
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) {
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;
}
void
hesiod_end(void *context)
{
struct hesiod_p *ctx = (struct hesiod_p *) context;
free(ctx->rhs);
if (ctx->lhs)
free(ctx->lhs);
free(ctx);
}
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;
}
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;
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;
}
strcat(bindname, ".");
strcat(bindname, type);
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);
if (rhs_list)
hesiod_free_list(context, rhs_list);
ret = strdup(bindname);
if (!ret)
errno = ENOMEM;
return ret;
}
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;
}
void
hesiod_free_list(void *context, char **list)
{
char **p;
if (list == NULL)
return;
for (p = list; *p; p++)
free(*p);
free(list);
}
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;
ctx->classes[0] = C_IN;
ctx->classes[1] = C_HS;
fp = fopen(filename, "re");
if (!fp) {
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;
}
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;
if ((_res.options & RES_INIT) == 0 && res_init() == -1)
return NULL;
n = res_mkquery(QUERY, name, qclass, T_TXT, NULL, 0,
NULL, qbuf, PACKETSZ);
if (n < 0)
return NULL;
n = res_send(qbuf, n, abuf, MAX_HESRESP);
if (n < 0 || n > MAX_HESRESP) {
errno = ECONNREFUSED;
return NULL;
}
hp = (HEADER *) (void *) abuf;
ancount = ntohs(hp->ancount);
qdcount = ntohs(hp->qdcount);
p = abuf + sizeof(HEADER);
eom = abuf + n;
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;
}
list = malloc((ancount + 1) * sizeof(char *));
if (!list) {
errno = ENOMEM;
return NULL;
}
j = 0;
for (i = 0; i < ancount; i++) {
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;
}
if (class != qclass || type != T_TXT) {
p += len;
continue;
}
list[j] = malloc((size_t)len);
if (!list[j]) {
errno = ENOMEM;
break;
}
dst = list[j++];
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 (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;
}
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;
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:
errval = HES_ER_CONFIG;
break;
}
}