/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* lib/krb5/os/dnsglue.c */ /* * Copyright 2004, 2009 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, 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. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * 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. */ #include "autoconf.h" #ifdef KRB5_DNS_LOOKUP #include "dnsglue.h" /* * Only use res_ninit() if there's also a res_ndestroy(), to avoid * memory leaks (Linux & Solaris) and outright corruption (AIX 4.x, * 5.x). While we're at it, make sure res_nsearch() is there too. * * In any case, it is probable that platforms having broken * res_ninit() will have thread safety hacks for res_init() and _res. */ #if HAVE_RES_NINIT && HAVE_RES_NDESTROY && HAVE_RES_NSEARCH #define USE_RES_NINIT 1 #endif /* * Opaque handle */ struct krb5int_dns_state { int nclass; int ntype; void *ansp; int anslen; int ansmax; #if HAVE_NS_INITPARSE int cur_ans; ns_msg msg; #else unsigned char *ptr; unsigned short nanswers; #endif }; #if !HAVE_NS_INITPARSE static int initparse(struct krb5int_dns_state *); #endif /* * krb5int_dns_init() * * Initialize an opaque handle. Do name lookup and initial parsing of * reply, skipping question section. Prepare to iterate over answer * section. Returns -1 on error, 0 on success. */ int krb5int_dns_init(struct krb5int_dns_state **dsp, char *host, int nclass, int ntype) { #if USE_RES_NINIT struct __res_state statbuf; #endif struct krb5int_dns_state *ds; int len, ret; size_t nextincr, maxincr; unsigned char *p; *dsp = ds = malloc(sizeof(*ds)); if (ds == NULL) return -1; ret = -1; ds->nclass = nclass; ds->ntype = ntype; ds->ansp = NULL; ds->anslen = 0; ds->ansmax = 0; nextincr = 2048; maxincr = INT_MAX; #if HAVE_NS_INITPARSE ds->cur_ans = 0; #endif #if USE_RES_NINIT memset(&statbuf, 0, sizeof(statbuf)); ret = res_ninit(&statbuf); #else ret = res_init(); #endif if (ret < 0) return -1; do { p = (ds->ansp == NULL) ? malloc(nextincr) : realloc(ds->ansp, nextincr); if (p == NULL) { ret = -1; goto errout; } ds->ansp = p; ds->ansmax = nextincr; #if USE_RES_NINIT len = res_nsearch(&statbuf, host, ds->nclass, ds->ntype, ds->ansp, ds->ansmax); #else len = res_search(host, ds->nclass, ds->ntype, ds->ansp, ds->ansmax); #endif if ((size_t) len > maxincr) { ret = -1; goto errout; } while (nextincr < (size_t) len) nextincr *= 2; if (len < 0 || nextincr > maxincr) { ret = -1; goto errout; } } while (len > ds->ansmax); ds->anslen = len; #if HAVE_NS_INITPARSE ret = ns_initparse(ds->ansp, ds->anslen, &ds->msg); #else ret = initparse(ds); #endif if (ret < 0) goto errout; ret = 0; errout: #if USE_RES_NINIT res_ndestroy(&statbuf); #endif if (ret < 0) { if (ds->ansp != NULL) { free(ds->ansp); ds->ansp = NULL; } } return ret; } #if HAVE_NS_INITPARSE /* * krb5int_dns_nextans - get next matching answer record * * Sets pp to NULL if no more records. Returns -1 on error, 0 on * success. */ int krb5int_dns_nextans(struct krb5int_dns_state *ds, const unsigned char **pp, int *lenp) { int len; ns_rr rr; *pp = NULL; *lenp = 0; while (ds->cur_ans < ns_msg_count(ds->msg, ns_s_an)) { len = ns_parserr(&ds->msg, ns_s_an, ds->cur_ans, &rr); if (len < 0) return -1; ds->cur_ans++; if (ds->nclass == (int)ns_rr_class(rr) && ds->ntype == (int)ns_rr_type(rr)) { *pp = ns_rr_rdata(rr); *lenp = ns_rr_rdlen(rr); return 0; } } return 0; } #endif /* * krb5int_dns_expand - wrapper for dn_expand() */ int krb5int_dns_expand(struct krb5int_dns_state *ds, const unsigned char *p, char *buf, int len) { #if HAVE_NS_NAME_UNCOMPRESS return ns_name_uncompress(ds->ansp, (unsigned char *)ds->ansp + ds->anslen, p, buf, (size_t)len); #else return dn_expand(ds->ansp, (unsigned char *)ds->ansp + ds->anslen, p, buf, len); #endif } /* * Free stuff. */ void krb5int_dns_fini(struct krb5int_dns_state *ds) { if (ds == NULL) return; if (ds->ansp != NULL) free(ds->ansp); free(ds); } /* * Compat routines for BIND 4 */ #if !HAVE_NS_INITPARSE /* * initparse * * Skip header and question section of reply. Set a pointer to the * beginning of the answer section, and prepare to iterate over * answer records. */ static int initparse(struct krb5int_dns_state *ds) { HEADER *hdr; unsigned char *p; unsigned short nqueries, nanswers; int len; #if !HAVE_DN_SKIPNAME char host[MAXDNAME]; #endif if ((size_t) ds->anslen < sizeof(HEADER)) return -1; hdr = (HEADER *)ds->ansp; p = ds->ansp; nqueries = ntohs((unsigned short)hdr->qdcount); nanswers = ntohs((unsigned short)hdr->ancount); p += sizeof(HEADER); /* * Skip query records. */ while (nqueries--) { #if HAVE_DN_SKIPNAME len = dn_skipname(p, (unsigned char *)ds->ansp + ds->anslen); #else len = dn_expand(ds->ansp, (unsigned char *)ds->ansp + ds->anslen, p, host, sizeof(host)); #endif if (len < 0 || !INCR_OK(ds->ansp, ds->anslen, p, len + 4)) return -1; p += len + 4; } ds->ptr = p; ds->nanswers = nanswers; return 0; } /* * krb5int_dns_nextans() - get next answer record * * Sets pp to NULL if no more records. */ int krb5int_dns_nextans(struct krb5int_dns_state *ds, const unsigned char **pp, int *lenp) { int len; unsigned char *p; unsigned short ntype, nclass, rdlen; #if !HAVE_DN_SKIPNAME char host[MAXDNAME]; #endif *pp = NULL; *lenp = 0; p = ds->ptr; while (ds->nanswers--) { #if HAVE_DN_SKIPNAME len = dn_skipname(p, (unsigned char *)ds->ansp + ds->anslen); #else len = dn_expand(ds->ansp, (unsigned char *)ds->ansp + ds->anslen, p, host, sizeof(host)); #endif if (len < 0 || !INCR_OK(ds->ansp, ds->anslen, p, len)) return -1; p += len; SAFE_GETUINT16(ds->ansp, ds->anslen, p, 2, ntype, out); /* Also skip 4 bytes of TTL */ SAFE_GETUINT16(ds->ansp, ds->anslen, p, 6, nclass, out); SAFE_GETUINT16(ds->ansp, ds->anslen, p, 2, rdlen, out); if (!INCR_OK(ds->ansp, ds->anslen, p, rdlen)) return -1; if (rdlen > INT_MAX) return -1; if (nclass == ds->nclass && ntype == ds->ntype) { *pp = p; *lenp = rdlen; ds->ptr = p + rdlen; return 0; } p += rdlen; } return 0; out: return -1; } #endif /* * Try to look up a TXT record pointing to a Kerberos realm */ krb5_error_code krb5_try_realm_txt_rr(const char *prefix, const char *name, char **realm) { krb5_error_code retval = KRB5_ERR_HOST_REALM_UNKNOWN; const unsigned char *p, *base; char host[MAXDNAME]; int ret, rdlen, len; struct krb5int_dns_state *ds = NULL; struct k5buf buf; /* * Form our query, and send it via DNS */ k5_buf_init_fixed(&buf, host, sizeof(host)); if (name == NULL || name[0] == '\0') { k5_buf_add(&buf, prefix); } else { k5_buf_add_fmt(&buf, "%s.%s", prefix, name); /* Realm names don't (normally) end with ".", but if the query doesn't end with "." and doesn't get an answer as is, the resolv code will try appending the local domain. Since the realm names are absolutes, let's stop that. But only if a name has been specified. If we are performing a search on the prefix alone then the intention is to allow the local domain or domain search lists to be expanded. */ len = k5_buf_len(&buf); if (len > 0 && host[len - 1] != '.') k5_buf_add(&buf, "."); } if (k5_buf_data(&buf) == NULL) return KRB5_ERR_HOST_REALM_UNKNOWN; ret = krb5int_dns_init(&ds, host, C_IN, T_TXT); if (ret < 0) goto errout; ret = krb5int_dns_nextans(ds, &base, &rdlen); if (ret < 0 || base == NULL) goto errout; p = base; if (!INCR_OK(base, rdlen, p, 1)) goto errout; len = *p++; *realm = malloc((size_t)len + 1); if (*realm == NULL) { retval = ENOMEM; goto errout; } strncpy(*realm, (const char *)p, (size_t)len); (*realm)[len] = '\0'; /* Avoid a common error. */ if ( (*realm)[len-1] == '.' ) (*realm)[len-1] = '\0'; retval = 0; errout: if (ds != NULL) { krb5int_dns_fini(ds); ds = NULL; } return retval; } #endif /* KRB5_DNS_LOOKUP */