]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
2929. [bug] Improved handling of GSS security contexts:
authorEvan Hunt <each@isc.org>
Fri, 9 Jul 2010 05:16:10 +0000 (05:16 +0000)
committerEvan Hunt <each@isc.org>
Fri, 9 Jul 2010 05:16:10 +0000 (05:16 +0000)
 - 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]

CHANGES
bin/nsupdate/nsupdate.c
bin/nsupdate/nsupdate.docbook
doc/arm/Bv9ARM-book.xml
lib/dns/gssapictx.c
lib/dns/include/dns/name.h
lib/dns/include/dns/tsig.h
lib/dns/name.c
lib/dns/tkey.c
lib/dns/tsig.c

diff --git a/CHANGES b/CHANGES
index f7e937815f3735e66e5a54b2e933389ef2541df8..010b2108c7b030df0a539efd154006b62a3e695e 100644 (file)
--- 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]
+
 2925.  [bug]           Named failed to accept uncachable negative responses
                        from insecure zones. [RT# 21555]
 
index aaca279f07c08865efcf05c87be51a1d62125ff3..e98ee8d5c354f99ea6ac7b94a21b4bd1cf79dad1 100644 (file)
@@ -15,7 +15,7 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: nsupdate.c,v 1.154.56.10 2010/05/18 06:25:19 marka Exp $ */
+/* $Id: nsupdate.c,v 1.154.56.11 2010/07/09 05:16:00 each Exp $ */
 
 /*! \file */
 
@@ -183,6 +183,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 {
@@ -537,7 +538,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));
@@ -1348,7 +1350,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) {
@@ -1360,6 +1362,31 @@ evaluate_key(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_zone(char *cmdline) {
        char *word;
@@ -1736,6 +1763,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;
@@ -2268,7 +2297,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));
@@ -2308,7 +2337,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);
@@ -2611,6 +2639,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");
index cf893fd4ba4bae532f03fc16daeac67a6b0e0141..3eca3dafa4d7df6550d35b94b3cef274279fd4ad 100644 (file)
@@ -18,7 +18,7 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id: nsupdate.docbook,v 1.29.56.4 2009/01/22 23:46:35 tbox Exp $ -->
+<!-- $Id: nsupdate.docbook,v 1.29.56.5 2010/07/09 05:16:01 each Exp $ -->
 <refentry>
   <refentryinfo>
     <date>Jun 30, 2000</date>
           </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>
index bf4309140bc91bf10ce4aa1c9084b17828f10f2d..82e8923d1f4388d588e4c1d4f3beb1393640a71e 100644 (file)
@@ -35,7 +35,7 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- File: $Id: Bv9ARM-book.xml,v 1.340.24.51 2010/02/25 10:31:03 marka Exp $ -->
+<!-- File: $Id: Bv9ARM-book.xml,v 1.340.24.52 2010/07/09 05:16:01 each Exp $ -->
 <book xmlns:xi="http://www.w3.org/2001/XInclude">
   <title>BIND 9 Administrator Reference Manual</title>
 
@@ -4974,7 +4974,7 @@ category notify { null; };
                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>
index 7376756e19b19fb7c7105517a3f13eae779aa9b9..d4efe59c2e47517ae49fa8f5d3a0aad2b595eb44 100644 (file)
@@ -15,7 +15,7 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: gssapictx.c,v 1.8.128.7 2010/06/03 02:39:05 marka Exp $ */
+/* $Id: gssapictx.c,v 1.8.128.8 2010/07/09 05:16:03 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;
 
index 65eb6a05fb846e36eeb4719807ee778ba62cfe71..a123f983e6198b10dbd99aa41197176ab099927c 100644 (file)
@@ -15,7 +15,7 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: name.h,v 1.126.128.3 2009/12/24 00:34:43 each Exp $ */
+/* $Id: name.h,v 1.126.128.4 2010/07/09 05:16:06 each Exp $ */
 
 #ifndef DNS_NAME_H
 #define DNS_NAME_H 1
@@ -796,9 +796,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'.
  *
@@ -806,6 +815,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.
  *
index e8c0e2caff1849d2716907501132dba5eef5e533..6569e3982ac74fc4068c89f5522cd2a7ed1cd460 100644 (file)
@@ -15,7 +15,7 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: tsig.h,v 1.51 2007/06/19 23:47:17 tbox Exp $ */
+/* $Id: tsig.h,v 1.51.128.1 2010/07/09 05:16:10 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) \
index 520f0b3a76aeb3bfd5ff93dc78c37c58d1e62332..9607829623e9879eba7114fae925f32eadb70186 100644 (file)
@@ -15,7 +15,7 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: name.c,v 1.163.128.4 2010/05/12 23:47:25 tbox Exp $ */
+/* $Id: name.c,v 1.163.128.5 2010/07/09 05:16:03 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)
index f94b3ae0b22a4730f62e64ba4596625ab0e77c95..2d700a43f551383bcc4fa352e5e0d90a7021dc7e 100644 (file)
@@ -16,7 +16,7 @@
  */
 
 /*
- * $Id: tkey.c,v 1.87.2.3 2008/04/03 00:47:46 marka Exp $
+ * $Id: tkey.c,v 1.87.2.4 2010/07/09 05:16:03 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);
 
@@ -1365,10 +1383,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));
 
index 6e09ca1e3e2cdfc352e7f8a356248f3ea7037540..676a4a5d86619a05eeac129a86bc1d4897f42e9c 100644 (file)
@@ -16,7 +16,7 @@
  */
 
 /*
- * $Id: tsig.c,v 1.131.2.5 2010/03/12 23:47:22 tbox Exp $
+ * $Id: tsig.c,v 1.131.2.6 2010/07/09 05:16:03 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>
 #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 && \
@@ -86,6 +91,31 @@ static dns_name_t gsstsig = {
 };
 LIBDNS_EXTERNAL_DATA dns_name_t *dns_tsig_gssapi_name = &gsstsig;
 
+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);
+       }
+}
+
 /*
  * Since Microsoft doesn't follow its own standard, we will use this
  * alternate name as a second guess.
@@ -358,11 +388,24 @@ dns_tsigkey_createfromkey(dns_name_t *name, dns_name_t *algorithm,
                        cleanup_ring(ring);
                        ring->writecount = 0;
                }
+
                ret = dns_rbt_addname(ring->keys, name, tkey);
                if (ret != ISC_R_SUCCESS) {
                        RWUNLOCK(&ring->lock, isc_rwlocktype_write);
                        goto cleanup_refs;
                }
+
+               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);
        }
 
@@ -452,9 +495,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;
                        }
                }
@@ -464,7 +505,6 @@ cleanup_ring(dns_tsig_keyring_t *ring)
                        dns_rbtnodechain_invalidate(&chain);
                        return;
                }
-
        }
 }
 
@@ -629,7 +669,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);
 }
 
@@ -1472,19 +1512,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);
 }
@@ -1530,6 +1581,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;