/* $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 #if 0 static char *orig_rcsid = "$NetBSD: hesiod.c,v 1.9 1999/02/11 06:16:38 simonb Exp $"; #endif #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include 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(context) void **context; { struct hesiod_p *ctx; const char *p, *configname; ctx = malloc(sizeof(struct hesiod_p)); if (ctx) { *context = ctx; if (!issetugid()) configname = getenv("HESIOD_CONFIG"); else configname = NULL; if (!configname) configname = _PATH_HESIOD_CONF; if (read_config_file(ctx, configname) >= 0) { /* * The default rhs can be overridden by an * environment variable. */ if (!issetugid()) p = getenv("HES_DOMAIN"); else p = NULL; 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(context) 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(context, name, type) 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(context, 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(ctx, filename) 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(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; /* 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() { init_context(); return errval; } char * hes_to_bind(name, type) 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(name, type) 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() { 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 ENOMEM: default: /* Not a good match, but the best we can do. */ errval = HES_ER_CONFIG; break; } }