#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <isc/buffer.h>
#include <isc/dir.h>
#include <isc/entropy.h>
#include <isc/lex.h>
#include <isc/mem.h>
#include <isc/once.h>
#include <isc/print.h>
#include <isc/platform.h>
#include <isc/random.h>
#include <isc/string.h>
#include <isc/time.h>
#include <isc/util.h>
#include <dns/fixedname.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/result.h>
#include <dns/types.h>
#include <dns/keyvalues.h>
#include <dns/log.h>
#include <dst/gssapi.h>
#include <dst/result.h>
#include "dst_internal.h"
#if defined(GSSAPI) && defined(USE_ISC_SPNEGO)
#include "spnego.h"
#define gss_accept_sec_context gss_accept_sec_context_spnego
#define gss_init_sec_context gss_init_sec_context_spnego
#endif
#if defined(GSSAPI)
#include ISC_PLATFORM_KRB5HEADER
static unsigned char krb5_mech_oid_bytes[] = {
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02
};
#ifndef USE_ISC_SPNEGO
static unsigned char spnego_mech_oid_bytes[] = {
0x2b, 0x06, 0x01, 0x05, 0x05, 0x02
};
#endif
static gss_OID_desc mech_oid_set_array[] = {
{ sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes },
#ifndef USE_ISC_SPNEGO
{ sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes },
#endif
};
static gss_OID_set_desc mech_oid_set = {
sizeof(mech_oid_set_array) / sizeof(*mech_oid_set_array),
mech_oid_set_array
};
#endif
#define REGION_TO_GBUFFER(r, gb) \
do { \
(gb).length = (r).length; \
(gb).value = (r).base; \
} while (0)
#define GBUFFER_TO_REGION(gb, r) \
do { \
(r).length = (gb).length; \
(r).base = (gb).value; \
} while (0)
#define RETERR(x) do { \
result = (x); \
if (result != ISC_R_SUCCESS) \
goto out; \
} while (0)
#ifdef GSSAPI
static inline void
name_to_gbuffer(dns_name_t *name, isc_buffer_t *buffer,
gss_buffer_desc *gbuffer)
{
dns_name_t tname, *namep;
isc_region_t r;
isc_result_t result;
if (!dns_name_isabsolute(name))
namep = name;
else
{
unsigned int labels;
dns_name_init(&tname, NULL);
labels = dns_name_countlabels(name);
dns_name_getlabelsequence(name, 0, labels - 1, &tname);
namep = &tname;
}
result = dns_name_toprincipal(namep, buffer);
isc_buffer_putuint8(buffer, 0);
isc_buffer_usedregion(buffer, &r);
REGION_TO_GBUFFER(r, *gbuffer);
}
static void
log_cred(const gss_cred_id_t cred) {
OM_uint32 gret, minor, lifetime;
gss_name_t gname;
gss_buffer_desc gbuffer;
gss_cred_usage_t usage;
const char *usage_text;
char buf[1024];
gret = gss_inquire_cred(&minor, cred, &gname, &lifetime, &usage, NULL);
if (gret != GSS_S_COMPLETE) {
gss_log(3, "failed gss_inquire_cred: %s",
gss_error_tostring(gret, minor, buf, sizeof(buf)));
return;
}
gret = gss_display_name(&minor, gname, &gbuffer, NULL);
if (gret != GSS_S_COMPLETE)
gss_log(3, "failed gss_display_name: %s",
gss_error_tostring(gret, minor, buf, sizeof(buf)));
else {
switch (usage) {
case GSS_C_BOTH:
usage_text = "GSS_C_BOTH";
break;
case GSS_C_INITIATE:
usage_text = "GSS_C_INITIATE";
break;
case GSS_C_ACCEPT:
usage_text = "GSS_C_ACCEPT";
break;
default:
usage_text = "???";
}
gss_log(3, "gss cred: \"%s\", %s, %lu", (char *)gbuffer.value,
usage_text, (unsigned long)lifetime);
}
if (gret == GSS_S_COMPLETE) {
if (gbuffer.length != 0) {
gret = gss_release_buffer(&minor, &gbuffer);
if (gret != GSS_S_COMPLETE)
gss_log(3, "failed gss_release_buffer: %s",
gss_error_tostring(gret, minor, buf,
sizeof(buf)));
}
}
gret = gss_release_name(&minor, &gname);
if (gret != GSS_S_COMPLETE)
gss_log(3, "failed gss_release_name: %s",
gss_error_tostring(gret, minor, buf, sizeof(buf)));
}
#endif
#ifdef GSSAPI
static void
dst_gssapi_check_config(const char *gss_name) {
const char *p;
krb5_context krb5_ctx;
char *krb5_realm = NULL;
if (strncasecmp(gss_name, "DNS/", 4) != 0) {
gss_log(ISC_LOG_ERROR, "tkey-gssapi-credential (%s) "
"should start with 'DNS/'", gss_name);
return;
}
if (krb5_init_context(&krb5_ctx) != 0) {
gss_log(ISC_LOG_ERROR, "Unable to initialise krb5 context");
return;
}
if (krb5_get_default_realm(krb5_ctx, &krb5_realm) != 0) {
gss_log(ISC_LOG_ERROR, "Unable to get krb5 default realm");
krb5_free_context(krb5_ctx);
return;
}
p = strchr(gss_name, '/');
if (p == NULL) {
gss_log(ISC_LOG_ERROR, "badly formatted "
"tkey-gssapi-credentials (%s)", gss_name);
krb5_free_context(krb5_ctx);
return;
}
if (strcasecmp(p + 1, krb5_realm) != 0) {
gss_log(ISC_LOG_ERROR, "default realm from krb5.conf (%s) "
"does not match tkey-gssapi-credential (%s)",
krb5_realm, gss_name);
krb5_free_context(krb5_ctx);
return;
}
krb5_free_context(krb5_ctx);
}
#endif
isc_result_t
dst_gssapi_acquirecred(dns_name_t *name, isc_boolean_t initiate,
gss_cred_id_t *cred)
{
#ifdef GSSAPI
isc_buffer_t namebuf;
gss_name_t gname;
gss_buffer_desc gnamebuf;
unsigned char array[DNS_NAME_MAXTEXT + 1];
OM_uint32 gret, minor;
gss_OID_set mechs;
OM_uint32 lifetime;
gss_cred_usage_t usage;
char buf[1024];
REQUIRE(cred != NULL && *cred == NULL);
if (name != NULL) {
isc_buffer_init(&namebuf, array, sizeof(array));
name_to_gbuffer(name, &namebuf, &gnamebuf);
gret = gss_import_name(&minor, &gnamebuf,
GSS_C_NO_OID, &gname);
if (gret != GSS_S_COMPLETE) {
dst_gssapi_check_config((char *)array);
gss_log(3, "failed gss_import_name: %s",
gss_error_tostring(gret, minor, buf,
sizeof(buf)));
return (ISC_R_FAILURE);
}
} else
gname = NULL;
if (gname != NULL)
gss_log(3, "acquiring credentials for %s",
(char *)gnamebuf.value);
else {
gss_log(3, "acquiring credentials for ?");
}
if (initiate)
usage = GSS_C_INITIATE;
else
usage = GSS_C_ACCEPT;
gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE,
&mech_oid_set,
usage, cred, &mechs, &lifetime);
if (gret != GSS_S_COMPLETE) {
gss_log(3, "failed to acquire %s credentials for %s: %s",
initiate ? "initiate" : "accept",
(char *)gnamebuf.value,
gss_error_tostring(gret, minor, buf, sizeof(buf)));
dst_gssapi_check_config((char *)array);
return (ISC_R_FAILURE);
}
gss_log(4, "acquired %s credentials for %s",
initiate ? "initiate" : "accept",
(char *)gnamebuf.value);
log_cred(*cred);
return (ISC_R_SUCCESS);
#else
UNUSED(name);
UNUSED(initiate);
UNUSED(cred);
return (ISC_R_NOTIMPLEMENTED);
#endif
}
isc_boolean_t
dst_gssapi_identitymatchesrealmkrb5(dns_name_t *signer, dns_name_t *name,
dns_name_t *realm)
{
#ifdef GSSAPI
char sbuf[DNS_NAME_FORMATSIZE];
char nbuf[DNS_NAME_FORMATSIZE];
char rbuf[DNS_NAME_FORMATSIZE];
char *sname;
char *rname;
isc_buffer_t buffer;
isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
dns_name_toprincipal(signer, &buffer);
isc_buffer_putuint8(&buffer, 0);
if (name != NULL)
dns_name_format(name, nbuf, sizeof(nbuf));
dns_name_format(realm, rbuf, sizeof(rbuf));
rname = strchr(sbuf, '@');
if (rname == NULL)
return (isc_boolean_false);
*rname = '\0';
rname++;
sname = strchr(sbuf, '/');
if (sname == NULL)
return (isc_boolean_false);
*sname = '\0';
sname++;
if (strcmp(sbuf, "host") != 0)
return (isc_boolean_false);
if (name != NULL) {
if ((strcasecmp(sname, nbuf) == 0)
&& (strcmp(rname, rbuf) == 0))
return (isc_boolean_true);
} else {
if (strcmp(rname, rbuf) == 0)
return (isc_boolean_true);
}
return (isc_boolean_false);
#else
UNUSED(signer);
UNUSED(name);
UNUSED(realm);
return (isc_boolean_false);
#endif
}
isc_boolean_t
dst_gssapi_identitymatchesrealmms(dns_name_t *signer, dns_name_t *name,
dns_name_t *realm)
{
#ifdef GSSAPI
char sbuf[DNS_NAME_FORMATSIZE];
char nbuf[DNS_NAME_FORMATSIZE];
char rbuf[DNS_NAME_FORMATSIZE];
char *sname;
char *nname;
char *rname;
isc_buffer_t buffer;
isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
dns_name_toprincipal(signer, &buffer);
isc_buffer_putuint8(&buffer, 0);
if (name != NULL)
dns_name_format(name, nbuf, sizeof(nbuf));
dns_name_format(realm, rbuf, sizeof(rbuf));
rname = strchr(sbuf, '@');
if (rname == NULL)
return (isc_boolean_false);
sname = strchr(sbuf, '$');
if (sname == NULL)
return (isc_boolean_false);
if (rname - sname != 1)
return (isc_boolean_false);
rname++;
*sname = '\0';
sname = sbuf;
if (name != NULL) {
nname = strchr(nbuf, '.');
if (nname == NULL)
return (isc_boolean_false);
*nname++ = '\0';
}
if (name != NULL) {
if ((strcasecmp(sname, nbuf) == 0)
&& (strcmp(rname, rbuf) == 0)
&& (strcasecmp(nname, rbuf) == 0))
return (isc_boolean_true);
} else {
if (strcmp(rname, rbuf) == 0)
return (isc_boolean_true);
}
return (isc_boolean_false);
#else
UNUSED(signer);
UNUSED(name);
UNUSED(realm);
return (isc_boolean_false);
#endif
}
isc_result_t
dst_gssapi_releasecred(gss_cred_id_t *cred) {
#ifdef GSSAPI
OM_uint32 gret, minor;
char buf[1024];
REQUIRE(cred != NULL && *cred != NULL);
gret = gss_release_cred(&minor, cred);
if (gret != GSS_S_COMPLETE) {
gss_log(3, "failed releasing credential: %s",
gss_error_tostring(gret, minor, buf, sizeof(buf)));
}
*cred = NULL;
return(ISC_R_SUCCESS);
#else
UNUSED(cred);
return (ISC_R_NOTIMPLEMENTED);
#endif
}
isc_result_t
dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken,
isc_buffer_t *outtoken, gss_ctx_id_t *gssctx)
{
#ifdef GSSAPI
isc_region_t r;
isc_buffer_t namebuf;
gss_name_t gname;
OM_uint32 gret, minor, ret_flags, flags;
gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
isc_result_t result;
gss_buffer_desc gnamebuf;
unsigned char array[DNS_NAME_MAXTEXT + 1];
char buf[1024];
REQUIRE(gssctx != NULL);
isc_buffer_init(&namebuf, array, sizeof(array));
name_to_gbuffer(name, &namebuf, &gnamebuf);
gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
if (gret != GSS_S_COMPLETE) {
result = ISC_R_FAILURE;
goto out;
}
if (intoken != NULL) {
REGION_TO_GBUFFER(*intoken, gintoken);
gintokenp = &gintoken;
} else {
gintokenp = NULL;
}
flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_DELEG_FLAG |
GSS_C_INTEG_FLAG;
gret = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, gssctx,
gname, GSS_SPNEGO_MECHANISM, flags,
0, NULL, gintokenp,
NULL, &gouttoken, &ret_flags, NULL);
if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
gss_log(3, "Failure initiating security context");
gss_log(3, "%s", gss_error_tostring(gret, minor,
buf, sizeof(buf)));
result = ISC_R_FAILURE;
goto out;
}
if (gouttoken.length != 0) {
GBUFFER_TO_REGION(gouttoken, r);
RETERR(isc_buffer_copyregion(outtoken, &r));
(void)gss_release_buffer(&minor, &gouttoken);
}
(void)gss_release_name(&minor, &gname);
if (gret == GSS_S_COMPLETE)
result = ISC_R_SUCCESS;
else
result = DNS_R_CONTINUE;
out:
return (result);
#else
UNUSED(name);
UNUSED(intoken);
UNUSED(outtoken);
UNUSED(gssctx);
return (ISC_R_NOTIMPLEMENTED);
#endif
}
isc_result_t
dst_gssapi_acceptctx(gss_cred_id_t cred,
isc_region_t *intoken, isc_buffer_t **outtoken,
gss_ctx_id_t *ctxout, dns_name_t *principal,
isc_mem_t *mctx)
{
#ifdef GSSAPI
isc_region_t r;
isc_buffer_t namebuf;
gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
gouttoken = GSS_C_EMPTY_BUFFER;
OM_uint32 gret, minor;
gss_ctx_id_t context = GSS_C_NO_CONTEXT;
gss_name_t gname = NULL;
isc_result_t result;
char buf[1024];
REQUIRE(outtoken != NULL && *outtoken == NULL);
log_cred(cred);
REGION_TO_GBUFFER(*intoken, gintoken);
if (*ctxout == NULL)
context = GSS_C_NO_CONTEXT;
else
context = *ctxout;
gret = gss_accept_sec_context(&minor, &context, cred, &gintoken,
GSS_C_NO_CHANNEL_BINDINGS, &gname,
NULL, &gouttoken, NULL, NULL, NULL);
result = ISC_R_FAILURE;
switch (gret) {
case GSS_S_COMPLETE:
result = ISC_R_SUCCESS;
break;
case GSS_S_CONTINUE_NEEDED:
result = DNS_R_CONTINUE;
break;
case GSS_S_DEFECTIVE_TOKEN:
case GSS_S_DEFECTIVE_CREDENTIAL:
case GSS_S_BAD_SIG:
case GSS_S_DUPLICATE_TOKEN:
case GSS_S_OLD_TOKEN:
case GSS_S_NO_CRED:
case GSS_S_CREDENTIALS_EXPIRED:
case GSS_S_BAD_BINDINGS:
case GSS_S_NO_CONTEXT:
case GSS_S_BAD_MECH:
case GSS_S_FAILURE:
result = DNS_R_INVALIDTKEY;
default:
gss_log(3, "failed gss_accept_sec_context: %s",
gss_error_tostring(gret, minor, buf, sizeof(buf)));
return (result);
}
if (gouttoken.length > 0) {
RETERR(isc_buffer_allocate(mctx, outtoken, gouttoken.length));
GBUFFER_TO_REGION(gouttoken, r);
RETERR(isc_buffer_copyregion(*outtoken, &r));
(void)gss_release_buffer(&minor, &gouttoken);
}
if (gret == GSS_S_COMPLETE) {
gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
if (gret != GSS_S_COMPLETE) {
gss_log(3, "failed gss_display_name: %s",
gss_error_tostring(gret, minor,
buf, sizeof(buf)));
RETERR(ISC_R_FAILURE);
}
if (gnamebuf.length > 0 &&
((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
gnamebuf.length--;
gss_log(3, "gss-api source name (accept) is %.*s",
(int)gnamebuf.length, (char *)gnamebuf.value);
GBUFFER_TO_REGION(gnamebuf, r);
isc_buffer_init(&namebuf, r.base, r.length);
isc_buffer_add(&namebuf, r.length);
RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname,
0, NULL));
if (gnamebuf.length != 0) {
gret = gss_release_buffer(&minor, &gnamebuf);
if (gret != GSS_S_COMPLETE)
gss_log(3, "failed gss_release_buffer: %s",
gss_error_tostring(gret, minor, buf,
sizeof(buf)));
}
}
*ctxout = context;
out:
if (gname != NULL) {
gret = gss_release_name(&minor, &gname);
if (gret != GSS_S_COMPLETE)
gss_log(3, "failed gss_release_name: %s",
gss_error_tostring(gret, minor, buf,
sizeof(buf)));
}
return (result);
#else
UNUSED(cred);
UNUSED(intoken);
UNUSED(outtoken);
UNUSED(ctxout);
UNUSED(principal);
UNUSED(mctx);
return (ISC_R_NOTIMPLEMENTED);
#endif
}
isc_result_t
dst_gssapi_deletectx(isc_mem_t *mctx, gss_ctx_id_t *gssctx)
{
#ifdef GSSAPI
OM_uint32 gret, minor;
char buf[1024];
UNUSED(mctx);
REQUIRE(gssctx != NULL && *gssctx != NULL);
gret = gss_delete_sec_context(&minor, gssctx, GSS_C_NO_BUFFER);
if (gret != GSS_S_COMPLETE) {
gss_log(3, "Failure deleting security context %s",
gss_error_tostring(gret, minor, buf, sizeof(buf)));
}
return(ISC_R_SUCCESS);
#else
UNUSED(mctx);
UNUSED(gssctx);
return (ISC_R_NOTIMPLEMENTED);
#endif
}
char *
gss_error_tostring(isc_uint32_t major, isc_uint32_t minor,
char *buf, size_t buflen) {
#ifdef GSSAPI
gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
msg_major = GSS_C_EMPTY_BUFFER;
OM_uint32 msg_ctx, minor_stat;
msg_ctx = 0;
(void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
GSS_C_NULL_OID, &msg_ctx, &msg_major);
msg_ctx = 0;
(void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
GSS_C_NULL_OID, &msg_ctx, &msg_minor);
snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
(char *)msg_major.value, (char *)msg_minor.value);
if (msg_major.length != 0)
(void)gss_release_buffer(&minor_stat, &msg_major);
if (msg_minor.length != 0)
(void)gss_release_buffer(&minor_stat, &msg_minor);
return(buf);
#else
snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.",
major, minor);
return (buf);
#endif
}
void
gss_log(int level, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_TKEY, ISC_LOG_DEBUG(level), fmt, ap);
va_end(ap);
}