diff options
Diffstat (limited to 'src/lib/krb5/os/dnsglue.c')
| -rw-r--r-- | src/lib/krb5/os/dnsglue.c | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/src/lib/krb5/os/dnsglue.c b/src/lib/krb5/os/dnsglue.c new file mode 100644 index 000000000000..1a259b399eba --- /dev/null +++ b/src/lib/krb5/os/dnsglue.c @@ -0,0 +1,432 @@ +/* -*- 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" +#ifdef __APPLE__ +#include <dns.h> +#endif + +/* + * 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. + */ + +/* + * 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 + +/* + * Define macros to use the best available DNS search functions. INIT_HANDLE() + * returns true if handle initialization is successful, false if it is not. + * SEARCH() returns the length of the response or -1 on error. + * DECLARE_HANDLE() must be used last in the declaration list since it may + * evaluate to nothing. + */ + +#if defined(__APPLE__) + +/* Use the OS X interfaces dns_open, dns_search, and dns_free. */ +#define DECLARE_HANDLE(h) dns_handle_t h +#define INIT_HANDLE(h) ((h = dns_open(NULL)) != NULL) +#define SEARCH(h, n, c, t, a, l) dns_search(h, n, c, t, a, l, NULL, NULL) +#define DESTROY_HANDLE(h) dns_free(h) + +#elif HAVE_RES_NINIT && HAVE_RES_NSEARCH + +/* Use res_ninit, res_nsearch, and res_ndestroy or res_nclose. */ +#define DECLARE_HANDLE(h) struct __res_state h +#define INIT_HANDLE(h) (memset(&h, 0, sizeof(h)), res_ninit(&h) == 0) +#define SEARCH(h, n, c, t, a, l) res_nsearch(&h, n, c, t, a, l) +#if HAVE_RES_NDESTROY +#define DESTROY_HANDLE(h) res_ndestroy(&h) +#else +#define DESTROY_HANDLE(h) res_nclose(&h) +#endif + +#else + +/* Use res_init and res_search. */ +#define DECLARE_HANDLE(h) +#define INIT_HANDLE(h) (res_init() == 0) +#define SEARCH(h, n, c, t, a, l) res_search(n, c, t, a, l) +#define DESTROY_HANDLE(h) + +#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) +{ + struct krb5int_dns_state *ds; + int len, ret; + size_t nextincr, maxincr; + unsigned char *p; + DECLARE_HANDLE(h); + + *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 = 4096; + maxincr = INT_MAX; + +#if HAVE_NS_INITPARSE + ds->cur_ans = 0; +#endif + + if (!INIT_HANDLE(h)) + 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; + + len = SEARCH(h, host, ds->nclass, ds->ntype, ds->ansp, ds->ansmax); + 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: + DESTROY_HANDLE(h); + 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 +k5_try_realm_txt_rr(krb5_context context, 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. + */ + + if (buf.len > 0 && host[buf.len - 1] != '.') + k5_buf_add(&buf, "."); + } + if (k5_buf_status(&buf) != 0) + return KRB5_ERR_HOST_REALM_UNKNOWN; + ret = krb5int_dns_init(&ds, host, C_IN, T_TXT); + if (ret < 0) { + TRACE_TXT_LOOKUP_NOTFOUND(context, host); + 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; + TRACE_TXT_LOOKUP_SUCCESS(context, host, *realm); + +errout: + if (ds != NULL) { + krb5int_dns_fini(ds); + ds = NULL; + } + return retval; +} + +#endif /* KRB5_DNS_LOOKUP */ |
