root/lib/libc/net/hesiod.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;
        }
}