Commit bf9b852c authored by Evan Hunt's avatar Evan Hunt

2929. [bug] Improved handling of GSS security contexts:

			 - added LRU expiration for generated TSIGs
			 - added the ability to use a non-default realm
                         - added new "realm" keyword in nsupdate
			 - limited lifetime of generated keys to 1 hour
			   or the lifetime of the context (whichever is
			   smaller)
			[RT #19737]
parent 385c6ae1
2929. [bug] Improved handling of GSS security contexts:
- added LRU expiration for generated TSIGs
- added the ability to use a non-default realm
- added new "realm" keyword in nsupdate
- limited lifetime of generated keys to 1 hour
or the lifetime of the context (whichever is
smaller)
[RT #19737]
2928. [bug] Be more selective about the non-authoritative
answer we apply change 2748 to. [RT #21594]
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: nsupdate.c,v 1.178 2010/05/18 06:18:23 marka Exp $ */
/* $Id: nsupdate.c,v 1.179 2010/07/09 05:13:14 each Exp $ */
/*! \file */
......@@ -195,6 +195,7 @@ ddebug(const char *format, ...) ISC_FORMAT_PRINTF(1, 2);
#ifdef GSSAPI
static dns_fixedname_t fkname;
static isc_sockaddr_t *kserver = NULL;
static char *realm = NULL;
static char servicename[DNS_NAME_FORMATSIZE];
static dns_name_t *keyname;
typedef struct nsu_gssinfo {
......@@ -548,7 +549,8 @@ setup_keystr(void) {
debug("keycreate");
result = dns_tsigkey_create(keyname, hmacname, secret, secretlen,
ISC_TRUE, NULL, 0, 0, mctx, NULL, &tsigkey);
ISC_FALSE, NULL, 0, 0, mctx, NULL,
&tsigkey);
if (result != ISC_R_SUCCESS)
fprintf(stderr, "could not create key from %s: %s\n",
keystr, dns_result_totext(result));
......@@ -1462,7 +1464,7 @@ evaluate_key(char *cmdline) {
if (tsigkey != NULL)
dns_tsigkey_detach(&tsigkey);
result = dns_tsigkey_create(keyname, hmacname, secret, secretlen,
ISC_TRUE, NULL, 0, 0, mctx, NULL,
ISC_FALSE, NULL, 0, 0, mctx, NULL,
&tsigkey);
isc_mem_free(mctx, secret);
if (result != ISC_R_SUCCESS) {
......@@ -1500,6 +1502,31 @@ evaluate_zone(char *cmdline) {
return (STATUS_MORE);
}
static isc_uint16_t
evaluate_realm(char *cmdline) {
#ifdef GSSAPI
char *word;
char buf[1024];
word = nsu_strsep(&cmdline, " \t\r\n");
if (*word == 0) {
if (realm != NULL)
isc_mem_free(mctx, realm);
realm = NULL;
return (STATUS_MORE);
}
snprintf(buf, sizeof(buf), "@%s", word);
realm = isc_mem_strdup(mctx, buf);
if (realm == NULL)
fatal("out of memory");
return (STATUS_MORE);
#else
UNUSED(cmdline);
return (STATUS_SYNTAX);
#endif
}
static isc_uint16_t
evaluate_ttl(char *cmdline) {
char *word;
......@@ -1891,6 +1918,8 @@ get_next_command(void) {
usegsstsig = ISC_FALSE;
return (evaluate_key(cmdline));
}
if (strcasecmp(word, "realm") == 0)
return (evaluate_realm(cmdline));
if (strcasecmp(word, "gsstsig") == 0) {
#ifdef GSSAPI
usegsstsig = ISC_TRUE;
......@@ -2423,7 +2452,7 @@ start_gssrequest(dns_name_t *master)
servname = dns_fixedname_name(&fname);
result = isc_string_printf(servicename, sizeof(servicename),
"DNS/%s", namestr);
"DNS/%s%s", namestr, realm ? realm : "");
if (result != ISC_R_SUCCESS)
fatal("isc_string_printf(servicename) failed: %s",
isc_result_totext(result));
......@@ -2461,7 +2490,6 @@ start_gssrequest(dns_name_t *master)
isc_result_totext(result));
/* Build first request. */
context = GSS_C_NO_CONTEXT;
result = dns_tkey_buildgssquery(rmsg, keyname, servname, NULL, 0,
&context, use_win2k_gsstsig);
......@@ -2763,6 +2791,10 @@ cleanup(void) {
isc_mem_put(mctx, kserver, sizeof(isc_sockaddr_t));
kserver = NULL;
}
if (realm != NULL) {
isc_mem_free(mctx, realm);
realm = NULL;
}
#endif
ddebug("Shutting down task manager");
......
......@@ -18,7 +18,7 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
<!-- $Id: nsupdate.docbook,v 1.42 2009/12/16 07:13:02 each Exp $ -->
<!-- $Id: nsupdate.docbook,v 1.43 2010/07/09 05:13:14 each Exp $ -->
<refentry id="man.nsupdate">
<refentryinfo>
<date>Aug 25, 2009</date>
......@@ -382,6 +382,45 @@
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>gsstsig</command>
</term>
<listitem>
<para>
Use GSS-TSIG to sign the updated. This is equivalent to
specifying <option>-g</option> on the commandline.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>oldgsstsig</command>
</term>
<listitem>
<para>
Use the Windows 2000 version of GSS-TSIG to sign the updated.
This is equivalent to specifying <option>-o</option> on the
commandline.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>realm</command>
<arg choice="req"><optional>realm_name</optional></arg>
</term>
<listitem>
<para>
When using GSS-TSIG use <parameter>realm_name</parameter> rather
than the default realm in <filename>krb5.conf</filename>. If no
realm is specified the saved realm is cleared.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>prereq nxdomain</command>
......
......@@ -18,7 +18,7 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
<!-- File: $Id: Bv9ARM-book.xml,v 1.459 2010/06/25 03:24:05 marka Exp $ -->
<!-- File: $Id: Bv9ARM-book.xml,v 1.460 2010/07/09 05:13:15 each Exp $ -->
<book xmlns:xi="http://www.w3.org/2001/XInclude">
<title>BIND 9 Administrator Reference Manual</title>
......@@ -5243,7 +5243,7 @@ badresp:1,adberr:0,findfail:0,valfail:0]
the server can acquire through the default system
key file, normally <filename>/etc/krb5.keytab</filename>.
Normally this principal is of the form
"<userinput>dns/</userinput><varname>server.domain</varname>".
"<userinput>DNS/</userinput><varname>server.domain</varname>".
To use GSS-TSIG, <command>tkey-domain</command>
must also be set.
</para>
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: gssapictx.c,v 1.17 2010/06/03 02:27:11 marka Exp $ */
/* $Id: gssapictx.c,v 1.18 2010/07/09 05:13:15 each Exp $ */
#include <config.h>
......@@ -132,7 +132,7 @@ name_to_gbuffer(dns_name_t *name, isc_buffer_t *buffer,
namep = &tname;
}
result = dns_name_totext(namep, ISC_FALSE, buffer);
result = dns_name_toprincipal(namep, buffer);
isc_buffer_putuint8(buffer, 0);
isc_buffer_usedregion(buffer, &r);
REGION_TO_GBUFFER(r, *gbuffer);
......@@ -336,12 +336,15 @@ dst_gssapi_identitymatchesrealmkrb5(dns_name_t *signer, dns_name_t *name,
char rbuf[DNS_NAME_FORMATSIZE];
char *sname;
char *rname;
isc_buffer_t buffer;
/*
* It is far, far easier to write the names we are looking at into
* a string, and do string operations on them.
*/
dns_name_format(signer, sbuf, sizeof(sbuf));
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));
......@@ -351,7 +354,7 @@ dst_gssapi_identitymatchesrealmkrb5(dns_name_t *signer, dns_name_t *name,
* does not exist, we don't have something we like, so we fail our
* compare.
*/
rname = strstr(sbuf, "\\@");
rname = strchr(sbuf, '@');
if (rname == NULL)
return (isc_boolean_false);
*rname = '\0';
......@@ -405,12 +408,15 @@ dst_gssapi_identitymatchesrealmms(dns_name_t *signer, dns_name_t *name,
char *sname;
char *nname;
char *rname;
isc_buffer_t buffer;
/*
* It is far, far easier to write the names we are looking at into
* a string, and do string operations on them.
*/
dns_name_format(signer, sbuf, sizeof(sbuf));
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));
......@@ -420,17 +426,17 @@ dst_gssapi_identitymatchesrealmms(dns_name_t *signer, dns_name_t *name,
* does not exist, we don't have something we like, so we fail our
* compare.
*/
rname = strstr(sbuf, "\\@");
rname = strchr(sbuf, '@');
if (rname == NULL)
return (isc_boolean_false);
sname = strstr(sbuf, "\\$");
sname = strchr(sbuf, '$');
if (sname == NULL)
return (isc_boolean_false);
/*
* Verify that the $ and @ follow one another.
*/
if (rname - sname != 2)
if (rname - sname != 1)
return (isc_boolean_false);
/*
......@@ -442,8 +448,7 @@ dst_gssapi_identitymatchesrealmms(dns_name_t *signer, dns_name_t *name,
* machinename$@EXAMPLE.COM
* format.
*/
*rname = '\0';
rname += 2;
rname++;
*sname = '\0';
sname = sbuf;
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: name.h,v 1.133 2009/12/24 00:35:46 each Exp $ */
/* $Id: name.h,v 1.134 2010/07/09 05:13:15 each Exp $ */
#ifndef DNS_NAME_H
#define DNS_NAME_H 1
......@@ -802,9 +802,18 @@ dns_name_fromtext(dns_name_t *name, isc_buffer_t *source,
*\li #ISC_R_UNEXPECTEDEND
*/
#define DNS_NAME_OMITFINALDOT 0x01U
#define DNS_NAME_MASTERFILE 0x02U /* escape $ and @ */
isc_result_t
dns_name_toprincipal(dns_name_t *name, isc_buffer_t *target);
isc_result_t
dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
isc_buffer_t *target);
isc_result_t
dns_name_totext2(dns_name_t *name, unsigned int options, isc_buffer_t *target);
/*%<
* Convert 'name' into text format, storing the result in 'target'.
*
......@@ -812,6 +821,12 @@ dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
*\li If 'omit_final_dot' is true, then the final '.' in absolute
* names other than the root name will be omitted.
*
*\li If DNS_NAME_OMITFINALDOT is set in options, then the final '.'
* in absolute names other than the root name will be omitted.
*
*\li If DNS_NAME_MASTERFILE is set in options, '$' and '@' will also
* be escaped.
*
*\li If dns_name_countlabels == 0, the name will be "@", representing the
* current origin as described by RFC1035.
*
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: tsig.h,v 1.53 2009/06/11 23:47:55 tbox Exp $ */
/* $Id: tsig.h,v 1.54 2010/07/09 05:13:15 each Exp $ */
#ifndef DNS_TSIG_H
#define DNS_TSIG_H 1
......@@ -62,6 +62,13 @@ struct dns_tsig_keyring {
unsigned int writecount;
isc_rwlock_t lock;
isc_mem_t *mctx;
/*
* LRU list of generated key along with a count of the keys on the
* list and a maximum size.
*/
unsigned int generated;
unsigned int maxgenerated;
ISC_LIST(dns_tsigkey_t) lru;
};
struct dns_tsigkey {
......@@ -77,6 +84,7 @@ struct dns_tsigkey {
isc_stdtime_t expire; /*%< end of validity period */
dns_tsig_keyring_t *ring; /*%< the enclosing keyring */
isc_refcount_t refs; /*%< reference counter */
ISC_LINK(dns_tsigkey_t) link;
};
#define dns_tsigkey_identity(tsigkey) \
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: name.c,v 1.171 2010/05/12 23:51:13 tbox Exp $ */
/* $Id: name.c,v 1.172 2010/07/09 05:13:15 each Exp $ */
/*! \file */
......@@ -1323,6 +1323,21 @@ totext_filter_proc_key_init(void) {
isc_result_t
dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
isc_buffer_t *target)
{
unsigned int options = DNS_NAME_MASTERFILE;
if (omit_final_dot)
options |= DNS_NAME_OMITFINALDOT;
return (dns_name_totext2(name, options, target));
}
isc_result_t
dns_name_toprincipal(dns_name_t *name, isc_buffer_t *target) {
return (dns_name_totext2(name, DNS_NAME_OMITFINALDOT, target));
}
isc_result_t
dns_name_totext2(dns_name_t *name, unsigned int options, isc_buffer_t *target)
{
unsigned char *ndata;
char *tdata;
......@@ -1337,6 +1352,8 @@ dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
dns_name_totextfilter_t totext_filter_proc = NULL;
isc_result_t result;
#endif
isc_boolean_t omit_final_dot =
ISC_TF(options & DNS_NAME_OMITFINALDOT);
/*
* This function assumes the name is in proper uncompressed
......@@ -1412,15 +1429,17 @@ dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
while (count > 0) {
c = *ndata;
switch (c) {
/* Special modifiers in zone files. */
case 0x40: /* '@' */
case 0x24: /* '$' */
if ((options & DNS_NAME_MASTERFILE) == 0)
goto no_escape;
case 0x22: /* '"' */
case 0x28: /* '(' */
case 0x29: /* ')' */
case 0x2E: /* '.' */
case 0x3B: /* ';' */
case 0x5C: /* '\\' */
/* Special modifiers in zone files. */
case 0x40: /* '@' */
case 0x24: /* '$' */
if (trem < 2)
return (ISC_R_NOSPACE);
*tdata++ = '\\';
......@@ -1430,6 +1449,7 @@ dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
trem -= 2;
nlen--;
break;
no_escape:
default:
if (c > 0x20 && c < 0x7f) {
if (trem == 0)
......
......@@ -16,7 +16,7 @@
*/
/*
* $Id: tkey.c,v 1.92 2009/09/02 23:48:02 tbox Exp $
* $Id: tkey.c,v 1.93 2010/07/09 05:13:15 each Exp $
*/
/*! \file */
#include <config.h>
......@@ -456,18 +456,15 @@ process_gsstkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name,
if (result == ISC_R_SUCCESS)
gss_ctx = dst_key_getgssctx(tsigkey->key);
dns_fixedname_init(&principal);
result = dst_gssapi_acceptctx(tctx->gsscred, &intoken,
&outtoken, &gss_ctx,
dns_fixedname_name(&principal),
tctx->mctx);
if (tsigkey != NULL)
dns_tsigkey_detach(&tsigkey);
if (result == DNS_R_INVALIDTKEY) {
if (tsigkey != NULL)
dns_tsigkey_detach(&tsigkey);
tkeyout->error = dns_tsigerror_badkey;
tkey_log("process_gsstkey(): dns_tsigerror_badkey"); /* XXXSRA */
return (ISC_R_SUCCESS);
......@@ -478,20 +475,38 @@ process_gsstkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name,
* XXXDCL Section 4.1.3: Limit GSS_S_CONTINUE_NEEDED to 10 times.
*/
isc_stdtime_get(&now);
if (tsigkey == NULL) {
#ifdef GSSAPI
OM_uint32 gret, minor, lifetime;
#endif
isc_uint32_t expire;
RETERR(dst_key_fromgssapi(name, gss_ctx, msg->mctx, &dstkey));
/*
* Limit keys to 1 hour or the context's lifetime whichever
* is smaller.
*/
expire = now + 3600;
#ifdef GSSAPI
gret = gss_context_time(&minor, gss_ctx, &lifetime);
if (gret == GSS_S_COMPLETE && now + lifetime < expire)
expire = now + lifetime;
#endif
RETERR(dns_tsigkey_createfromkey(name, &tkeyin->algorithm,
dstkey, ISC_TRUE,
dns_fixedname_name(&principal),
tkeyin->inception,
tkeyin->expire,
ring->mctx, ring, NULL));
now, expire, ring->mctx, ring,
NULL));
tkeyout->inception = now;
tkeyout->expire = expire;
} else {
tkeyout->inception = tsigkey->inception;
tkeyout->expire = tkeyout->expire;
dns_tsigkey_detach(&tsigkey);
}
isc_stdtime_get(&now);
tkeyout->inception = tkeyin->inception;
tkeyout->expire = tkeyin->expire;
if (outtoken) {
tkeyout->key = isc_mem_get(tkeyout->mctx,
isc_buffer_usedlength(outtoken));
......@@ -520,6 +535,9 @@ process_gsstkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name,
return (ISC_R_SUCCESS);
failure:
if (tsigkey != NULL)
dns_tsigkey_detach(&tsigkey);
if (dstkey != NULL)
dst_key_free(&dstkey);
......@@ -1364,10 +1382,10 @@ dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg,
if (win2k == ISC_TRUE)
RETERR(find_tkey(qmsg, &tkeyname, &qtkeyrdata,
DNS_SECTION_ANSWER));
DNS_SECTION_ANSWER));
else
RETERR(find_tkey(qmsg, &tkeyname, &qtkeyrdata,
DNS_SECTION_ADDITIONAL));
DNS_SECTION_ADDITIONAL));
RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL));
......
......@@ -16,7 +16,7 @@
*/
/*
* $Id: tsig.c,v 1.140 2010/03/12 23:51:11 tbox Exp $
* $Id: tsig.c,v 1.141 2010/07/09 05:13:15 each Exp $
*/
/*! \file */
#include <config.h>
......@@ -26,6 +26,7 @@
#include <isc/mem.h>
#include <isc/print.h>
#include <isc/refcount.h>
#include <isc/serial.h>
#include <isc/string.h> /* Required for HP/UX (and others?) */
#include <isc/util.h>
#include <isc/time.h>
......@@ -47,6 +48,10 @@
#define TSIG_MAGIC ISC_MAGIC('T', 'S', 'I', 'G')
#define VALID_TSIG_KEY(x) ISC_MAGIC_VALID(x, TSIG_MAGIC)
#ifndef DNS_TSIG_MAXGENERATEDKEYS
#define DNS_TSIG_MAXGENERATEDKEYS 4096
#endif
#define is_response(msg) (msg->flags & DNS_MESSAGEFLAG_QR)
#define algname_is_allocated(algname) \
((algname) != dns_tsig_hmacmd5_name && \
......@@ -215,6 +220,31 @@ tsig_log(dns_tsigkey_t *key, int level, const char *fmt, ...) {
level, "tsig key '%s': %s", namestr, message);
}
static void
remove_fromring(dns_tsigkey_t *tkey) {
if (tkey->generated) {
ISC_LIST_UNLINK(tkey->ring->lru, tkey, link);
tkey->ring->generated--;
}
(void)dns_rbt_deletename(tkey->ring->keys, &tkey->name, ISC_FALSE);
}
static void
adjust_lru(dns_tsigkey_t *tkey) {
if (tkey->generated) {
RWLOCK(&tkey->ring->lock, isc_rwlocktype_write);
/*
* We may have been removed from the LRU list between
* removing the read lock and aquiring the write lock.
*/
if (ISC_LINK_LINKED(tkey, link)) {
ISC_LIST_UNLINK(tkey->ring->lru, tkey, link);
ISC_LIST_APPEND(tkey->ring->lru, tkey, link);
}
RWUNLOCK(&tkey->ring->lock, isc_rwlocktype_write);
}
}
/*
* A supplemental routine just to add a key to ring. Note that reference
* counter should be counted separately because we may be adding the key
......@@ -241,6 +271,15 @@ keyring_add(dns_tsig_keyring_t *ring, dns_name_t *name,
}
result = dns_rbt_addname(ring->keys, name, tkey);
if (tkey->generated) {
/*
* Add the new key to the LRU list and remove the least
* recently used key if there are too many keys on the list.
*/
ISC_LIST_INITANDAPPEND(ring->lru, tkey, link);
if (ring->generated++ > ring->maxgenerated)
remove_fromring(ISC_LIST_HEAD(ring->lru));
}
RWUNLOCK(&ring->lock, isc_rwlocktype_write);
return (result);
......@@ -470,9 +509,7 @@ cleanup_ring(dns_tsig_keyring_t *ring)
tsig_log(tkey, 2, "tsig expire: deleting");
/* delete the key */
dns_rbtnodechain_invalidate(&chain);
(void)dns_rbt_deletename(ring->keys,
&tkey->name,
ISC_FALSE);
remove_fromring(tkey);
goto again;
}
}
......@@ -482,7 +519,6 @@ cleanup_ring(dns_tsig_keyring_t *ring)
dns_rbtnodechain_invalidate(&chain);
return;
}
}
}
......@@ -647,7 +683,7 @@ dns_tsigkey_setdeleted(dns_tsigkey_t *key) {
REQUIRE(key->ring != NULL);
RWLOCK(&key->ring->lock, isc_rwlocktype_write);
(void)dns_rbt_deletename(key->ring->keys, &key->name, ISC_FALSE);
remove_fromring(key);
RWUNLOCK(&key->ring->lock, isc_rwlocktype_write);
}
......@@ -1490,19 +1526,30 @@ dns_tsigkey_find(dns_tsigkey_t **tsigkey, dns_name_t *name,
RWUNLOCK(&ring->lock, isc_rwlocktype_read);
return (ISC_R_NOTFOUND);
}
if (key->inception != key->expire && key->expire < now) {
if (key->inception != key->expire && isc_serial_lt(key->expire, now)) {
/*
* The key has expired.
*/
RWUNLOCK(&ring->lock, isc_rwlocktype_read);
RWLOCK(&ring->lock, isc_rwlocktype_write);
(void)dns_rbt_deletename(ring->keys, name, ISC_FALSE);
remove_fromring(key);
RWUNLOCK(&ring->lock, isc_rwlocktype_write);
return (ISC_R_NOTFOUND);
}
#if 0
/*
* MPAXXX We really should look at the inception time.
*/
if (key->inception != key->expire &&
isc_serial_lt(key->inception, now)) {