/* -*- 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.
*
*/
/*
* Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
*/
#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 == ns_rr_class(rr)
&& ds->ntype == 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;
/* Solaris Kerberos - resync */
#if 0
if (rdlen > INT_MAX)
return -1;
#endif
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
*/
krb5int_buf_init_fixed(&buf, host, sizeof(host));
if (name == NULL || name[0] == '\0') {
krb5int_buf_add(&buf, prefix);
} else {
krb5int_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 = krb5int_buf_len(&buf);
if (len > 0 && host[len - 1] != '.')
krb5int_buf_add(&buf, ".");
}
if (krb5int_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 */