diff --git a/CHANGES b/CHANGES index f88798aec4..68a2301a95 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,12 @@ +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] diff --git a/bin/nsupdate/nsupdate.c b/bin/nsupdate/nsupdate.c index f24504ba7c..0651bf85a9 100644 --- a/bin/nsupdate/nsupdate.c +++ b/bin/nsupdate/nsupdate.c @@ -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"); diff --git a/bin/nsupdate/nsupdate.docbook b/bin/nsupdate/nsupdate.docbook index 39714b3a8e..aa7137770a 100644 --- a/bin/nsupdate/nsupdate.docbook +++ b/bin/nsupdate/nsupdate.docbook @@ -18,7 +18,7 @@ - PERFORMANCE OF THIS SOFTWARE. --> - + Aug 25, 2009 @@ -382,6 +382,45 @@ + + + gsstsig + + + + Use GSS-TSIG to sign the updated. This is equivalent to + specifying on the commandline. + + + + + + + oldgsstsig + + + + Use the Windows 2000 version of GSS-TSIG to sign the updated. + This is equivalent to specifying on the + commandline. + + + + + + + realm + realm_name + + + + When using GSS-TSIG use realm_name rather + than the default realm in krb5.conf. If no + realm is specified the saved realm is cleared. + + + + prereq nxdomain diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index b454873ea7..6d4fcb5e69 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -18,7 +18,7 @@ - PERFORMANCE OF THIS SOFTWARE. --> - + BIND 9 Administrator Reference Manual @@ -5243,7 +5243,7 @@ badresp:1,adberr:0,findfail:0,valfail:0] the server can acquire through the default system key file, normally /etc/krb5.keytab. Normally this principal is of the form - "dns/server.domain". + "DNS/server.domain". To use GSS-TSIG, tkey-domain must also be set. diff --git a/lib/dns/gssapictx.c b/lib/dns/gssapictx.c index 00597924eb..3a25e09c9b 100644 --- a/lib/dns/gssapictx.c +++ b/lib/dns/gssapictx.c @@ -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 @@ -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; diff --git a/lib/dns/include/dns/name.h b/lib/dns/include/dns/name.h index 734d1b0bed..04a5d8393a 100644 --- a/lib/dns/include/dns/name.h +++ b/lib/dns/include/dns/name.h @@ -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. * diff --git a/lib/dns/include/dns/tsig.h b/lib/dns/include/dns/tsig.h index b4770b4ed1..1fbfccaa95 100644 --- a/lib/dns/include/dns/tsig.h +++ b/lib/dns/include/dns/tsig.h @@ -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) \ diff --git a/lib/dns/name.c b/lib/dns/name.c index 9863b182af..8442acef3c 100644 --- a/lib/dns/name.c +++ b/lib/dns/name.c @@ -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) diff --git a/lib/dns/tkey.c b/lib/dns/tkey.c index 02f93debd6..98d2af4c91 100644 --- a/lib/dns/tkey.c +++ b/lib/dns/tkey.c @@ -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 @@ -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)); diff --git a/lib/dns/tsig.c b/lib/dns/tsig.c index f3df5fdc58..bc6454560e 100644 --- a/lib/dns/tsig.c +++ b/lib/dns/tsig.c @@ -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 @@ -26,6 +26,7 @@ #include #include #include +#include #include /* Required for HP/UX (and others?) */ #include #include @@ -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)) { + RWUNLOCK(&ring->lock, isc_rwlocktype_read); + adjust_lru(key); + return (ISC_R_NOTFOUND); + } +#endif isc_refcount_increment(&key->refs, NULL); RWUNLOCK(&ring->lock, isc_rwlocktype_read); + adjust_lru(key); *tsigkey = key; return (ISC_R_SUCCESS); } @@ -1548,6 +1595,9 @@ dns_tsigkeyring_create(isc_mem_t *mctx, dns_tsig_keyring_t **ringp) { ring->writecount = 0; ring->mctx = NULL; + ring->generated = 0; + ring->maxgenerated = DNS_TSIG_MAXGENERATEDKEYS; + ISC_LIST_INIT(ring->lru); isc_mem_attach(mctx, &ring->mctx); *ringp = ring;