diff --git a/SOURCES/Add-a-hash-table-implementation-to-libkrb5support.patch b/SOURCES/Add-a-hash-table-implementation-to-libkrb5support.patch
new file mode 100644
index 0000000..21ce64d
--- /dev/null
+++ b/SOURCES/Add-a-hash-table-implementation-to-libkrb5support.patch
@@ -0,0 +1,657 @@
+From b3f5d3b0542ae314acfb94e1bc5a8f22201b8ac3 Mon Sep 17 00:00:00 2001
+From: Greg Hudson <ghudson@mit.edu>
+Date: Sat, 4 Aug 2018 20:11:09 -0400
+Subject: [PATCH] Add a hash table implementation to libkrb5support
+
+(cherry picked from commit 09e814fe47f5ceeb35bee15ced6e346db8a5e81d)
+[rharwood@redhat.com: gitignore, no utf16]
+---
+ src/include/k5-hashtab.h                      |  79 ++++++
+ src/util/support/Makefile.in                  |  15 +-
+ src/util/support/deps                         |  11 +
+ src/util/support/hashtab.c                    | 243 ++++++++++++++++++
+ src/util/support/libkrb5support-fixed.exports |   5 +
+ src/util/support/t_hashtab.c                  | 176 +++++++++++++
+ 6 files changed, 526 insertions(+), 3 deletions(-)
+ create mode 100644 src/include/k5-hashtab.h
+ create mode 100644 src/util/support/hashtab.c
+ create mode 100644 src/util/support/t_hashtab.c
+
+diff --git a/src/include/k5-hashtab.h b/src/include/k5-hashtab.h
+new file mode 100644
+index 000000000..dc0ef3613
+--- /dev/null
++++ b/src/include/k5-hashtab.h
+@@ -0,0 +1,79 @@
++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
++/* include/k5-hash.h - hash table interface definitions */
++/*
++ * Copyright (C) 2018 by the Massachusetts Institute of Technology.
++ * All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * * Redistributions of source code must retain the above copyright
++ *   notice, this list of conditions and the following disclaimer.
++ *
++ * * Redistributions in binary form must reproduce the above copyright
++ *   notice, this list of conditions and the following disclaimer in
++ *   the documentation and/or other materials provided with the
++ *   distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
++ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
++ * OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++/*
++ * This file contains declarations for a simple hash table using siphash.  Some
++ * limitations which might need to be addressed in the future:
++ *
++ * - The table does not manage caller memory.  This limitation could be
++ *   addressed by adding an optional free callback to k5_hashtab_create(), to
++ *   be called by k5_hashtab_free() and k5_hashtab_remove().
++ *
++ * - There is no way to iterate over a hash table.
++ *
++ * - k5_hashtab_add() does not check for duplicate entries.
++ */
++
++#ifndef K5_HASH_H
++#define K5_HASH_H
++
++#define K5_HASH_SEED_LEN 16
++
++struct k5_hashtab;
++
++/*
++ * Create a new hash table in *ht_out.  seed must point to random bytes if keys
++ * might be under the control of an attacker; otherwise it may be NULL.
++ * initial_buckets controls the initial allocation of hash buckets; pass zero
++ * to use a default value.  The number of hash buckets will be doubled as the
++ * number of entries increases.  Return 0 on success, ENOMEM on failure.
++ */
++int k5_hashtab_create(const uint8_t seed[K5_HASH_SEED_LEN],
++                      size_t initial_buckets, struct k5_hashtab **ht_out);
++
++/* Release the memory used by a hash table.  Keys and values are the caller's
++ * responsibility. */
++void k5_hashtab_free(struct k5_hashtab *ht);
++
++/* Add an entry to a hash table.  key and val must remain valid until the entry
++ * is removed or the hash table is freed.  The caller must avoid duplicates. */
++int k5_hashtab_add(struct k5_hashtab *ht, const void *key, size_t klen,
++                   void *val);
++
++/* Remove an entry from a hash table by key.  Does not free key or the
++ * associated value.  Return 1 if the key was found and removed, 0 if not. */
++int k5_hashtab_remove(struct k5_hashtab *ht, const void *key, size_t klen);
++
++/* Retrieve a value from a hash table by key. */
++void *k5_hashtab_get(struct k5_hashtab *ht, const void *key, size_t klen);
++
++#endif /* K5_HASH_H */
+diff --git a/src/util/support/Makefile.in b/src/util/support/Makefile.in
+index b3576f0b7..12797068f 100644
+--- a/src/util/support/Makefile.in
++++ b/src/util/support/Makefile.in
+@@ -83,6 +83,7 @@ STLIBOBJS= \
+ 	base64.o \
+ 	json.o \
+ 	hex.o \
++	hashtab.o \
+ 	bcmp.o \
+ 	strerror_r.o \
+ 	dir_filenames.o \
+@@ -110,6 +111,7 @@ LIBOBJS= \
+ 	$(OUTPRE)base64.$(OBJEXT) \
+ 	$(OUTPRE)json.$(OBJEXT) \
+ 	$(OUTPRE)hex.$(OBJEXT) \
++	$(OUTPRE)hashtab.$(OBJEXT) \
+ 	$(OUTPRE)bcmp.$(OBJEXT) \
+ 	$(OUTPRE)strerror_r.$(OBJEXT) \
+ 	$(OUTPRE)dir_filenames.$(OBJEXT) \
+@@ -142,11 +144,13 @@ SRCS=\
+ 	$(srcdir)/t_path.c \
+ 	$(srcdir)/t_json.c \
+ 	$(srcdir)/t_hex.c \
++	$(srcdir)/t_hashtab.c \
+ 	$(srcdir)/zap.c \
+ 	$(srcdir)/path.c \
+ 	$(srcdir)/base64.c \
+ 	$(srcdir)/json.c \
+ 	$(srcdir)/hex.c \
++	$(srcdir)/hashtab.c \
+ 	$(srcdir)/bcmp.c \
+ 	$(srcdir)/strerror_r.c \
+ 	$(srcdir)/dir_filenames.c \
+@@ -225,13 +229,17 @@ t_json: $(T_JSON_OBJS)
+ t_hex: t_hex.o hex.o
+ 	$(CC_LINK) -o $@ t_hex.o hex.o
+ 
++t_hashtab: t_hashtab.o
++	$(CC_LINK) -o $@ t_hashtab.o
++
+ t_unal: t_unal.o
+ 	$(CC_LINK) -o t_unal t_unal.o
+ 
+ t_utf8: t_utf8.o utf8.o
+ 	$(CC_LINK) -o t_utf8 t_utf8.o utf8.o
+ 
+-TEST_PROGS= t_k5buf t_path t_path_win t_base64 t_json t_hex t_unal t_utf8
++TEST_PROGS= t_k5buf t_path t_path_win t_base64 t_json t_hex t_hashtab t_unal \
++	t_utf8
+ 
+ check-unix: $(TEST_PROGS)
+ 	./t_k5buf
+@@ -240,14 +248,15 @@ check-unix: $(TEST_PROGS)
+ 	./t_base64
+ 	./t_json
+ 	./t_hex
++	./t_hashtab
+ 	./t_unal
+ 	./t_utf8
+ 
+ clean:
+ 	$(RM) t_k5buf.o t_k5buf t_unal.o t_unal path_win.o path_win
+ 	$(RM) t_path_win.o t_path_win t_path.o t_path t_base64.o t_base64
+-	$(RM) t_json.o t_json t_hex.o t_hex libkrb5support.exports
+-	$(RM) t_utf8.o t_utf8
++	$(RM) t_json.o t_json t_hex.o t_hex t_hashtab.o t_hashtab
++	$(RM) t_utf8.o t_utf8 libkrb5support.exports
+ 
+ @lib_frag@
+ @libobj_frag@
+diff --git a/src/util/support/deps b/src/util/support/deps
+index 551843357..d43baea13 100644
+--- a/src/util/support/deps
++++ b/src/util/support/deps
+@@ -65,6 +65,10 @@ t_json.so t_json.po $(OUTPRE)t_json.$(OBJEXT): $(top_srcdir)/include/k5-json.h \
+ t_hex.so t_hex.po $(OUTPRE)t_hex.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+   $(top_srcdir)/include/k5-hex.h $(top_srcdir)/include/k5-platform.h \
+   $(top_srcdir)/include/k5-thread.h t_hex.c
++t_hashtab.so t_hashtab.po $(OUTPRE)t_hashtab.$(OBJEXT): \
++  $(BUILDTOP)/include/autoconf.h $(top_srcdir)/include/k5-hashtab.h \
++  $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-queue.h \
++  $(top_srcdir)/include/k5-thread.h hashtab.c t_hashtab.c
+ zap.so zap.po $(OUTPRE)zap.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+   $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-thread.h \
+   zap.c
+@@ -81,12 +85,19 @@ json.so json.po $(OUTPRE)json.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ hex.so hex.po $(OUTPRE)hex.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+   $(top_srcdir)/include/k5-hex.h $(top_srcdir)/include/k5-platform.h \
+   $(top_srcdir)/include/k5-thread.h hex.c
++hashtab.so hashtab.po $(OUTPRE)hashtab.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
++  $(top_srcdir)/include/k5-hashtab.h $(top_srcdir)/include/k5-platform.h \
++  $(top_srcdir)/include/k5-queue.h $(top_srcdir)/include/k5-thread.h \
++  hashtab.c
+ bcmp.so bcmp.po $(OUTPRE)bcmp.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+   $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-thread.h \
+   bcmp.c
+ strerror_r.so strerror_r.po $(OUTPRE)strerror_r.$(OBJEXT): \
+   $(BUILDTOP)/include/autoconf.h $(top_srcdir)/include/k5-platform.h \
+   $(top_srcdir)/include/k5-thread.h strerror_r.c
++dir_filenames.so dir_filenames.po $(OUTPRE)dir_filenames.$(OBJEXT): \
++  $(BUILDTOP)/include/autoconf.h $(top_srcdir)/include/k5-platform.h \
++  $(top_srcdir)/include/k5-thread.h dir_filenames.c
+ t_utf8.so t_utf8.po $(OUTPRE)t_utf8.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+   $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-thread.h \
+   $(top_srcdir)/include/k5-utf8.h t_utf8.c
+diff --git a/src/util/support/hashtab.c b/src/util/support/hashtab.c
+new file mode 100644
+index 000000000..e04e491b2
+--- /dev/null
++++ b/src/util/support/hashtab.c
+@@ -0,0 +1,243 @@
++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
++/* util/support/hash.c - hash table implementation */
++/*
++ * Copyright (C) 2018 by the Massachusetts Institute of Technology.
++ * All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * * Redistributions of source code must retain the above copyright
++ *   notice, this list of conditions and the following disclaimer.
++ *
++ * * Redistributions in binary form must reproduce the above copyright
++ *   notice, this list of conditions and the following disclaimer in
++ *   the documentation and/or other materials provided with the
++ *   distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
++ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
++ * OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include "k5-platform.h"
++#include "k5-hashtab.h"
++#include "k5-queue.h"
++
++struct entry {
++    const void *key;
++    size_t klen;
++    void *val;
++    K5_SLIST_ENTRY(entry) next;
++};
++
++struct k5_hashtab {
++    uint64_t k0;
++    uint64_t k1;
++    size_t nbuckets;
++    size_t nentries;
++    K5_SLIST_HEAD(bucket_list, entry) *buckets;
++};
++
++/* Return x rotated to the left by r bits. */
++static inline uint64_t
++rotl64(uint64_t x, int r)
++{
++    return (x << r) | (x >> (64 - r));
++}
++
++static inline void
++sipround(uint64_t *v0, uint64_t *v1, uint64_t *v2, uint64_t *v3)
++{
++    *v0 += *v1;
++    *v2 += *v3;
++    *v1 = rotl64(*v1, 13) ^ *v0;
++    *v3 = rotl64(*v3, 16) ^ *v2;
++    *v0 = rotl64(*v0, 32);
++    *v2 += *v1;
++    *v0 += *v3;
++    *v1 = rotl64(*v1, 17) ^ *v2;
++    *v3 = rotl64(*v3, 21) ^ *v0;
++    *v2 = rotl64(*v2, 32);
++}
++
++/* SipHash-2-4 from https://131002.net/siphash/siphash.pdf (Jean-Philippe
++ * Aumasson and Daniel J. Bernstein) */
++static uint64_t
++siphash24(const uint8_t *data, size_t len, uint64_t k0, uint64_t k1)
++{
++    uint64_t v0 = k0 ^ 0x736F6D6570736575;
++    uint64_t v1 = k1 ^ 0x646F72616E646F6D;
++    uint64_t v2 = k0 ^ 0x6C7967656E657261;
++    uint64_t v3 = k1 ^ 0x7465646279746573;
++    uint64_t mi;
++    const uint8_t *p, *end = data + (len - len % 8);
++    uint8_t last[8] = { 0 };
++
++    /* Process each full 8-byte chunk of data. */
++    for (p = data; p < end; p += 8) {
++        mi = load_64_le(p);
++        v3 ^= mi;
++        sipround(&v0, &v1, &v2, &v3);
++        sipround(&v0, &v1, &v2, &v3);
++        v0 ^= mi;
++    }
++
++    /* Process the last 0-7 bytes followed by the length mod 256. */
++    memcpy(last, end, len % 8);
++    last[7] = len & 0xFF;
++    mi = load_64_le(last);
++    v3 ^= mi;
++    sipround(&v0, &v1, &v2, &v3);
++    sipround(&v0, &v1, &v2, &v3);
++    v0 ^= mi;
++
++    /* Finalize. */
++    v2 ^= 0xFF;
++    sipround(&v0, &v1, &v2, &v3);
++    sipround(&v0, &v1, &v2, &v3);
++    sipround(&v0, &v1, &v2, &v3);
++    sipround(&v0, &v1, &v2, &v3);
++    return v0 ^ v1 ^ v2 ^ v3;
++}
++
++int
++k5_hashtab_create(const uint8_t seed[K5_HASH_SEED_LEN], size_t initial_buckets,
++                  struct k5_hashtab **ht_out)
++{
++    struct k5_hashtab *ht;
++
++    *ht_out = NULL;
++
++    ht = malloc(sizeof(*ht));
++    if (ht == NULL)
++        return ENOMEM;
++
++    if (seed != NULL) {
++        ht->k0 = load_64_le(seed);
++        ht->k1 = load_64_le(seed + 8);
++    } else {
++        ht->k0 = ht->k1 = 0;
++    }
++    ht->nbuckets = (initial_buckets > 0) ? initial_buckets : 64;
++    ht->nentries = 0;
++    ht->buckets = calloc(ht->nbuckets, sizeof(*ht->buckets));
++    if (ht->buckets == NULL) {
++        free(ht);
++        return ENOMEM;
++    }
++
++    *ht_out = ht;
++    return 0;
++}
++
++void
++k5_hashtab_free(struct k5_hashtab *ht)
++{
++    size_t i;
++    struct entry *ent;
++
++    for (i = 0; i < ht->nbuckets; i++) {
++        while (!K5_SLIST_EMPTY(&ht->buckets[i])) {
++            ent = K5_SLIST_FIRST(&ht->buckets[i]);
++            K5_SLIST_REMOVE_HEAD(&ht->buckets[i], next);
++            free(ent);
++        }
++    }
++    free(ht->buckets);
++    free(ht);
++}
++
++static int
++resize_table(struct k5_hashtab *ht)
++{
++    size_t i, j, newsize = ht->nbuckets * 2;
++    struct bucket_list *newbuckets;
++    struct entry *ent;
++
++    newbuckets = calloc(newsize, sizeof(*newbuckets));
++    if (newbuckets == NULL)
++        return ENOMEM;
++
++    /* Rehash all the entries into the new buckets. */
++    for (i = 0; i < ht->nbuckets; i++) {
++        while (!K5_SLIST_EMPTY(&ht->buckets[i])) {
++            ent = K5_SLIST_FIRST(&ht->buckets[i]);
++            j = siphash24(ent->key, ent->klen, ht->k0, ht->k1) % newsize;
++            K5_SLIST_REMOVE_HEAD(&ht->buckets[i], next);
++            K5_SLIST_INSERT_HEAD(&newbuckets[j], ent, next);
++        }
++    }
++
++    free(ht->buckets);
++    ht->buckets = newbuckets;
++    ht->nbuckets = newsize;
++    return 0;
++}
++
++int
++k5_hashtab_add(struct k5_hashtab *ht, const void *key, size_t klen, void *val)
++{
++    size_t i;
++    struct entry *ent;
++
++    if (ht->nentries == ht->nbuckets) {
++        if (resize_table(ht) != 0)
++            return ENOMEM;
++    }
++
++    ent = malloc(sizeof(*ent));
++    if (ent == NULL)
++        return ENOMEM;
++    ent->key = key;
++    ent->klen = klen;
++    ent->val = val;
++
++    i = siphash24(key, klen, ht->k0, ht->k1) % ht->nbuckets;
++    K5_SLIST_INSERT_HEAD(&ht->buckets[i], ent, next);
++
++    ht->nentries++;
++    return 0;
++}
++
++int
++k5_hashtab_remove(struct k5_hashtab *ht, const void *key, size_t klen)
++{
++    size_t i;
++    struct entry *ent;
++
++    i = siphash24(key, klen, ht->k0, ht->k1) % ht->nbuckets;
++    K5_SLIST_FOREACH(ent, &ht->buckets[i], next) {
++        if (ent->klen == klen && memcmp(ent->key, key, klen) == 0) {
++            K5_SLIST_REMOVE(&ht->buckets[i], ent, entry, next);
++            free(ent);
++            ht->nentries--;
++            return 1;
++        }
++    }
++    return 0;
++}
++
++void *
++k5_hashtab_get(struct k5_hashtab *ht, const void *key, size_t klen)
++{
++    size_t i;
++    struct entry *ent;
++
++    i = siphash24(key, klen, ht->k0, ht->k1) % ht->nbuckets;
++    K5_SLIST_FOREACH(ent, &ht->buckets[i], next) {
++        if (ent->klen == klen && memcmp(ent->key, key, klen) == 0)
++            return ent->val;
++    }
++    return NULL;
++}
+diff --git a/src/util/support/libkrb5support-fixed.exports b/src/util/support/libkrb5support-fixed.exports
+index 6193d7331..c63c5fbc3 100644
+--- a/src/util/support/libkrb5support-fixed.exports
++++ b/src/util/support/libkrb5support-fixed.exports
+@@ -16,6 +16,11 @@ k5_get_error
+ k5_free_error
+ k5_clear_error
+ k5_set_error_info_callout_fn
++k5_hashtab_add
++k5_hashtab_create
++k5_hashtab_free
++k5_hashtab_get
++k5_hashtab_remove
+ k5_hex_decode
+ k5_hex_encode
+ k5_json_array_add
+diff --git a/src/util/support/t_hashtab.c b/src/util/support/t_hashtab.c
+new file mode 100644
+index 000000000..f51abc4f1
+--- /dev/null
++++ b/src/util/support/t_hashtab.c
+@@ -0,0 +1,176 @@
++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
++/* util/support/t_hash.c - tests for hash table code */
++/*
++ * Copyright (C) 2018 by the Massachusetts Institute of Technology.
++ * All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * * Redistributions of source code must retain the above copyright
++ *   notice, this list of conditions and the following disclaimer.
++ *
++ * * Redistributions in binary form must reproduce the above copyright
++ *   notice, this list of conditions and the following disclaimer in
++ *   the documentation and/or other materials provided with the
++ *   distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
++ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
++ * OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++/* hash.c has no linker dependencies, so we can simply include its source code
++ * to test its static functions and look inside its structures. */
++#include "hashtab.c"
++
++/* These match the sip64 test vectors in the reference C implementation of
++ * siphash at https://github.com/veorq/SipHash */
++const uint64_t vectors[64] = {
++    0x726FDB47DD0E0E31,
++    0x74F839C593DC67FD,
++    0x0D6C8009D9A94F5A,
++    0x85676696D7FB7E2D,
++    0xCF2794E0277187B7,
++    0x18765564CD99A68D,
++    0xCBC9466E58FEE3CE,
++    0xAB0200F58B01D137,
++    0x93F5F5799A932462,
++    0x9E0082DF0BA9E4B0,
++    0x7A5DBBC594DDB9F3,
++    0xF4B32F46226BADA7,
++    0x751E8FBC860EE5FB,
++    0x14EA5627C0843D90,
++    0xF723CA908E7AF2EE,
++    0xA129CA6149BE45E5,
++    0x3F2ACC7F57C29BDB,
++    0x699AE9F52CBE4794,
++    0x4BC1B3F0968DD39C,
++    0xBB6DC91DA77961BD,
++    0xBED65CF21AA2EE98,
++    0xD0F2CBB02E3B67C7,
++    0x93536795E3A33E88,
++    0xA80C038CCD5CCEC8,
++    0xB8AD50C6F649AF94,
++    0xBCE192DE8A85B8EA,
++    0x17D835B85BBB15F3,
++    0x2F2E6163076BCFAD,
++    0xDE4DAAACA71DC9A5,
++    0xA6A2506687956571,
++    0xAD87A3535C49EF28,
++    0x32D892FAD841C342,
++    0x7127512F72F27CCE,
++    0xA7F32346F95978E3,
++    0x12E0B01ABB051238,
++    0x15E034D40FA197AE,
++    0x314DFFBE0815A3B4,
++    0x027990F029623981,
++    0xCADCD4E59EF40C4D,
++    0x9ABFD8766A33735C,
++    0x0E3EA96B5304A7D0,
++    0xAD0C42D6FC585992,
++    0x187306C89BC215A9,
++    0xD4A60ABCF3792B95,
++    0xF935451DE4F21DF2,
++    0xA9538F0419755787,
++    0xDB9ACDDFF56CA510,
++    0xD06C98CD5C0975EB,
++    0xE612A3CB9ECBA951,
++    0xC766E62CFCADAF96,
++    0xEE64435A9752FE72,
++    0xA192D576B245165A,
++    0x0A8787BF8ECB74B2,
++    0x81B3E73D20B49B6F,
++    0x7FA8220BA3B2ECEA,
++    0x245731C13CA42499,
++    0xB78DBFAF3A8D83BD,
++    0xEA1AD565322A1A0B,
++    0x60E61C23A3795013,
++    0x6606D7E446282B93,
++    0x6CA4ECB15C5F91E1,
++    0x9F626DA15C9625F3,
++    0xE51B38608EF25F57,
++    0x958A324CEB064572
++};
++
++static void
++test_siphash()
++{
++    uint8_t seq[64];
++    uint64_t k0, k1, hval;
++    size_t i;
++
++    for (i = 0; i < sizeof(seq); i++)
++        seq[i] = i;
++    k0 = load_64_le(seq);
++    k1 = load_64_le(seq + 8);
++
++    for (i = 0; i < sizeof(seq); i++) {
++        hval = siphash24(seq, i, k0, k1);
++        assert(hval == vectors[i]);
++    }
++}
++
++static void
++test_hashtab()
++{
++    int st;
++    struct k5_hashtab *ht;
++    size_t i;
++    char zeros[100] = { 0 };
++
++    st = k5_hashtab_create(NULL, 4, &ht);
++    assert(st == 0 && ht != NULL && ht->nentries == 0);
++
++    st = k5_hashtab_add(ht, "abc", 3, &st);
++    assert(st == 0 && ht->nentries == 1);
++    assert(k5_hashtab_get(ht, "abc", 3) == &st);
++    assert(k5_hashtab_get(ht, "bcde", 4) == NULL);
++
++    st = k5_hashtab_add(ht, "bcde", 4, &ht);
++    assert(st == 0 && ht->nentries == 2);
++    assert(k5_hashtab_get(ht, "abc", 3) == &st);
++    assert(k5_hashtab_get(ht, "bcde", 4) == &ht);
++
++    k5_hashtab_remove(ht, "abc", 3);
++    assert(ht->nentries == 1);
++    assert(k5_hashtab_get(ht, "abc", 3) == NULL);
++    assert(k5_hashtab_get(ht, "bcde", 4) == &ht);
++
++    k5_hashtab_remove(ht, "bcde", 4);
++    assert(ht->nentries == 0);
++    assert(k5_hashtab_get(ht, "abc", 3) == NULL);
++    assert(k5_hashtab_get(ht, "bcde", 4) == NULL);
++
++    for (i = 0; i < sizeof(zeros); i++) {
++        st = k5_hashtab_add(ht, zeros, i, zeros + i);
++        assert(st == 0 && ht->nentries == i + 1 && ht->nbuckets >= i + 1);
++    }
++    for (i = 0; i < sizeof(zeros); i++) {
++        assert(k5_hashtab_get(ht, zeros, i) == zeros + i);
++        k5_hashtab_remove(ht, zeros, i);
++        assert(ht->nentries == sizeof(zeros) - i - 1);
++        if (i > 0)
++            assert(k5_hashtab_get(ht, zeros, i - 1) == NULL);
++    }
++
++    k5_hashtab_free(ht);
++}
++
++int
++main()
++{
++    test_siphash();
++    test_hashtab();
++    return 0;
++}
diff --git a/SOURCES/Add-libkrb5support-hex-functions-and-tests.patch b/SOURCES/Add-libkrb5support-hex-functions-and-tests.patch
new file mode 100644
index 0000000..becd51e
--- /dev/null
+++ b/SOURCES/Add-libkrb5support-hex-functions-and-tests.patch
@@ -0,0 +1,480 @@
+From 868cbb573b512eac4561046fe7540c37406637fb Mon Sep 17 00:00:00 2001
+From: Greg Hudson <ghudson@mit.edu>
+Date: Mon, 19 Feb 2018 00:51:44 -0500
+Subject: [PATCH] Add libkrb5support hex functions and tests
+
+(cherry picked from commit 720dea558da0062d3cea4385327161e62cf09a5e)
+[rharwood@redhat.com: gitignore, backport around no utf16]
+---
+ src/include/k5-hex.h                          |  53 ++++++
+ src/util/support/Makefile.in                  |  13 +-
+ src/util/support/deps                         |   6 +
+ src/util/support/hex.c                        | 116 ++++++++++++
+ src/util/support/libkrb5support-fixed.exports |   2 +
+ src/util/support/t_hex.c                      | 169 ++++++++++++++++++
+ 6 files changed, 357 insertions(+), 2 deletions(-)
+ create mode 100644 src/include/k5-hex.h
+ create mode 100644 src/util/support/hex.c
+ create mode 100644 src/util/support/t_hex.c
+
+diff --git a/src/include/k5-hex.h b/src/include/k5-hex.h
+new file mode 100644
+index 000000000..75bd2cb19
+--- /dev/null
++++ b/src/include/k5-hex.h
+@@ -0,0 +1,53 @@
++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
++/* include/k5-hex.h - libkrb5support hex encoding/decoding declarations */
++/*
++ * Copyright (C) 2018 by the Massachusetts Institute of Technology.
++ * All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * * Redistributions of source code must retain the above copyright
++ *   notice, this list of conditions and the following disclaimer.
++ *
++ * * Redistributions in binary form must reproduce the above copyright
++ *   notice, this list of conditions and the following disclaimer in
++ *   the documentation and/or other materials provided with the
++ *   distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
++ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
++ * OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#ifndef K5_HEX_H
++#define K5_HEX_H
++
++#include "k5-platform.h"
++
++/*
++ * Encode len bytes in hex, placing the result in allocated storage in
++ * *hex_out.  Use uppercase hex digits if uppercase is non-zero.  Return 0 on
++ * success, ENOMEM on error.
++ */
++int k5_hex_encode(const void *bytes, size_t len, int uppercase,
++                  char **hex_out);
++
++/*
++ * Decode hex bytes, placing the result in allocated storage in *bytes_out and
++ * *len_out.  Null-terminate the result (primarily for decoding passwords in
++ * libkdb_ldap).  Return 0 on success, ENOMEM or EINVAL on error.
++ */
++int k5_hex_decode(const char *hex, uint8_t **bytes_out, size_t *len_out);
++
++#endif /* K5_HEX_H */
+diff --git a/src/util/support/Makefile.in b/src/util/support/Makefile.in
+index 9326742d7..b3576f0b7 100644
+--- a/src/util/support/Makefile.in
++++ b/src/util/support/Makefile.in
+@@ -82,6 +82,7 @@ STLIBOBJS= \
+ 	path.o \
+ 	base64.o \
+ 	json.o \
++	hex.o \
+ 	bcmp.o \
+ 	strerror_r.o \
+ 	dir_filenames.o \
+@@ -108,6 +109,7 @@ LIBOBJS= \
+ 	$(OUTPRE)path.$(OBJEXT) \
+ 	$(OUTPRE)base64.$(OBJEXT) \
+ 	$(OUTPRE)json.$(OBJEXT) \
++	$(OUTPRE)hex.$(OBJEXT) \
+ 	$(OUTPRE)bcmp.$(OBJEXT) \
+ 	$(OUTPRE)strerror_r.$(OBJEXT) \
+ 	$(OUTPRE)dir_filenames.$(OBJEXT) \
+@@ -139,10 +141,12 @@ SRCS=\
+ 	$(srcdir)/t_unal.c \
+ 	$(srcdir)/t_path.c \
+ 	$(srcdir)/t_json.c \
++	$(srcdir)/t_hex.c \
+ 	$(srcdir)/zap.c \
+ 	$(srcdir)/path.c \
+ 	$(srcdir)/base64.c \
+ 	$(srcdir)/json.c \
++	$(srcdir)/hex.c \
+ 	$(srcdir)/bcmp.c \
+ 	$(srcdir)/strerror_r.c \
+ 	$(srcdir)/dir_filenames.c \
+@@ -218,13 +222,16 @@ T_JSON_OBJS= t_json.o json.o base64.o k5buf.o $(PRINTF_ST_OBJ)
+ t_json: $(T_JSON_OBJS)
+ 	$(CC_LINK) -o $@ $(T_JSON_OBJS)
+ 
++t_hex: t_hex.o hex.o
++	$(CC_LINK) -o $@ t_hex.o hex.o
++
+ t_unal: t_unal.o
+ 	$(CC_LINK) -o t_unal t_unal.o
+ 
+ t_utf8: t_utf8.o utf8.o
+ 	$(CC_LINK) -o t_utf8 t_utf8.o utf8.o
+ 
+-TEST_PROGS= t_k5buf t_path t_path_win t_base64 t_json t_unal t_utf8
++TEST_PROGS= t_k5buf t_path t_path_win t_base64 t_json t_hex t_unal t_utf8
+ 
+ check-unix: $(TEST_PROGS)
+ 	./t_k5buf
+@@ -232,13 +239,15 @@ check-unix: $(TEST_PROGS)
+ 	./t_path_win
+ 	./t_base64
+ 	./t_json
++	./t_hex
+ 	./t_unal
+ 	./t_utf8
+ 
+ clean:
+ 	$(RM) t_k5buf.o t_k5buf t_unal.o t_unal path_win.o path_win
+ 	$(RM) t_path_win.o t_path_win t_path.o t_path t_base64.o t_base64
+-	$(RM) t_json.o t_json libkrb5support.exports t_utf8.o t_utf8
++	$(RM) t_json.o t_json t_hex.o t_hex libkrb5support.exports
++	$(RM) t_utf8.o t_utf8
+ 
+ @lib_frag@
+ @libobj_frag@
+diff --git a/src/util/support/deps b/src/util/support/deps
+index 4dff014f4..551843357 100644
+--- a/src/util/support/deps
++++ b/src/util/support/deps
+@@ -62,6 +62,9 @@ t_path.so t_path.po $(OUTPRE)t_path.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+   t_path.c
+ t_json.so t_json.po $(OUTPRE)t_json.$(OBJEXT): $(top_srcdir)/include/k5-json.h \
+   t_json.c
++t_hex.so t_hex.po $(OUTPRE)t_hex.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
++  $(top_srcdir)/include/k5-hex.h $(top_srcdir)/include/k5-platform.h \
++  $(top_srcdir)/include/k5-thread.h t_hex.c
+ zap.so zap.po $(OUTPRE)zap.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+   $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-thread.h \
+   zap.c
+@@ -75,6 +78,9 @@ json.so json.po $(OUTPRE)json.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+   $(top_srcdir)/include/k5-base64.h $(top_srcdir)/include/k5-buf.h \
+   $(top_srcdir)/include/k5-json.h $(top_srcdir)/include/k5-platform.h \
+   $(top_srcdir)/include/k5-thread.h json.c
++hex.so hex.po $(OUTPRE)hex.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
++  $(top_srcdir)/include/k5-hex.h $(top_srcdir)/include/k5-platform.h \
++  $(top_srcdir)/include/k5-thread.h hex.c
+ bcmp.so bcmp.po $(OUTPRE)bcmp.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+   $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-thread.h \
+   bcmp.c
+diff --git a/src/util/support/hex.c b/src/util/support/hex.c
+new file mode 100644
+index 000000000..4407ff9ff
+--- /dev/null
++++ b/src/util/support/hex.c
+@@ -0,0 +1,116 @@
++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
++/* util/support/hex.c - hex encoding/decoding implementation */
++/*
++ * Copyright (C) 2018 by the Massachusetts Institute of Technology.
++ * All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * * Redistributions of source code must retain the above copyright
++ *   notice, this list of conditions and the following disclaimer.
++ *
++ * * Redistributions in binary form must reproduce the above copyright
++ *   notice, this list of conditions and the following disclaimer in
++ *   the documentation and/or other materials provided with the
++ *   distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
++ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
++ * OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include <k5-platform.h>
++#include <k5-hex.h>
++#include <ctype.h>
++
++static inline char
++hex_digit(uint8_t bval, int uppercase)
++{
++    assert(bval >= 0 && bval <= 0xF);
++    if (bval < 10)
++        return '0' + bval;
++    else if (uppercase)
++        return 'A' + (bval - 10);
++    else
++        return 'a' + (bval - 10);
++}
++
++int
++k5_hex_encode(const void *bytes, size_t len, int uppercase, char **hex_out)
++{
++    size_t i;
++    const uint8_t *p = bytes;
++    char *hex;
++
++    *hex_out = NULL;
++
++    hex = malloc(len * 2 + 1);
++    if (hex == NULL)
++        return ENOMEM;
++
++    for (i = 0; i < len; i++) {
++        hex[i * 2] = hex_digit(p[i] >> 4, uppercase);
++        hex[i * 2 + 1] = hex_digit(p[i] & 0xF, uppercase);
++    }
++    hex[len * 2] = '\0';
++
++    *hex_out = hex;
++    return 0;
++}
++
++/* Decode a hex digit.  Return 0-15 on success, -1 on invalid input. */
++static inline int
++decode_hexchar(unsigned char c)
++{
++    if (isdigit(c))
++        return c - '0';
++    if (c >= 'A' && c <= 'F')
++        return c - 'A' + 10;
++    if (c >= 'a' && c <= 'f')
++        return c - 'a' + 10;
++    return -1;
++}
++
++int
++k5_hex_decode(const char *hex, uint8_t **bytes_out, size_t *len_out)
++{
++    size_t hexlen, i;
++    int h1, h2;
++    uint8_t *bytes;
++
++    *bytes_out = NULL;
++    *len_out = 0;
++
++    hexlen = strlen(hex);
++    if (hexlen % 2 != 0)
++        return EINVAL;
++    bytes = malloc(hexlen / 2 + 1);
++    if (bytes == NULL)
++        return ENOMEM;
++
++    for (i = 0; i < hexlen / 2; i++) {
++        h1 = decode_hexchar(hex[i * 2]);
++        h2 = decode_hexchar(hex[i * 2 + 1]);
++        if (h1 == -1 || h2 == -1) {
++            free(bytes);
++            return EINVAL;
++        }
++        bytes[i] = h1 * 16 + h2;
++    }
++    bytes[i] = 0;
++
++    *bytes_out = bytes;
++    *len_out = hexlen / 2;
++    return 0;
++}
+diff --git a/src/util/support/libkrb5support-fixed.exports b/src/util/support/libkrb5support-fixed.exports
+index 2cdcddfe0..6193d7331 100644
+--- a/src/util/support/libkrb5support-fixed.exports
++++ b/src/util/support/libkrb5support-fixed.exports
+@@ -16,6 +16,8 @@ k5_get_error
+ k5_free_error
+ k5_clear_error
+ k5_set_error_info_callout_fn
++k5_hex_decode
++k5_hex_encode
+ k5_json_array_add
+ k5_json_array_create
+ k5_json_array_fmt
+diff --git a/src/util/support/t_hex.c b/src/util/support/t_hex.c
+new file mode 100644
+index 000000000..a586a1bc8
+--- /dev/null
++++ b/src/util/support/t_hex.c
+@@ -0,0 +1,169 @@
++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
++/* util/support/t_hex.c - Test hex encoding and decoding */
++/*
++ * Copyright (C) 2018 by the Massachusetts Institute of Technology.
++ * All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * * Redistributions of source code must retain the above copyright
++ *   notice, this list of conditions and the following disclaimer.
++ *
++ * * Redistributions in binary form must reproduce the above copyright
++ *   notice, this list of conditions and the following disclaimer in
++ *   the documentation and/or other materials provided with the
++ *   distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
++ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
++ * OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include <k5-platform.h>
++#include <k5-hex.h>
++
++struct {
++    const char *hex;
++    const char *binary;
++    size_t binary_len;
++    int uppercase;
++} tests[] = {
++    /* Invalid hex strings */
++    { "1" },
++    { "123" },
++    { "0/" },
++    { "/0" },
++    { "0:" },
++    { ":0" },
++    { "0@" },
++    { "@0" },
++    { "0G" },
++    { "G0" },
++    { "0`" },
++    { "`0" },
++    { "0g" },
++    { "g0" },
++    { " 00 " },
++    { "0\x01" },
++
++    { "", "", 0 },
++    { "00", "\x00", 1 },
++    { "01", "\x01", 1 },
++    { "10", "\x10", 1 },
++    { "01ff", "\x01\xFF", 2 },
++    { "A0B0C0", "\xA0\xB0\xC0", 3, 1 },
++    { "1a2b3c4d5e6f", "\x1A\x2B\x3C\x4D\x5E\x6F", 6 },
++    { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
++      "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
++      "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 32 },
++
++    /* All byte values, lowercase */
++    { "0001020304050607", "\x00\x01\x02\x03\x04\x05\x06\x07", 8 },
++    { "08090a0b0c0d0e0f", "\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", 8 },
++    { "1011121314151617", "\x10\x11\x12\x13\x14\x15\x16\x17", 8 },
++    { "18191a1b1c1d1e1f", "\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F", 8 },
++    { "2021222324252627", "\x20\x21\x22\x23\x24\x25\x26\x27", 8 },
++    { "28292a2b2c2d2e2f", "\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F", 8 },
++    { "3031323334353637", "\x30\x31\x32\x33\x34\x35\x36\x37", 8 },
++    { "38393a3b3c3d3e3f", "\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F", 8 },
++    { "4041424344454647", "\x40\x41\x42\x43\x44\x45\x46\x47", 8 },
++    { "48494a4b4c4d4e4f", "\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F", 8 },
++    { "5051525354555657", "\x50\x51\x52\x53\x54\x55\x56\x57", 8 },
++    { "58595a5b5c5d5e5f", "\x58\x59\x5A\x5B\x5C\x5D\x5E\x5F", 8 },
++    { "6061626364656667", "\x60\x61\x62\x63\x64\x65\x66\x67", 8 },
++    { "68696a6b6c6d6e6f", "\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F", 8 },
++    { "7071727374757677", "\x70\x71\x72\x73\x74\x75\x76\x77", 8 },
++    { "78797a7b7c7d7e7f", "\x78\x79\x7A\x7B\x7C\x7D\x7E\x7F", 8 },
++    { "8081828384858687", "\x80\x81\x82\x83\x84\x85\x86\x87", 8 },
++    { "88898a8b8c8d8e8f", "\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F", 8 },
++    { "9091929394959697", "\x90\x91\x92\x93\x94\x95\x96\x97", 8 },
++    { "98999a9b9c9d9e9f", "\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F", 8 },
++    { "a0a1a2a3a4a5a6a7", "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7", 8 },
++    { "a8a9aaabacadaeaf", "\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF", 8 },
++    { "b0b1b2b3b4b5b6b7", "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7", 8 },
++    { "b8b9babbbcbdbebf", "\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF", 8 },
++    { "c0c1c2c3c4c5c6c7", "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7", 8 },
++    { "c8c9cacbcccdcecf", "\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF", 8 },
++    { "d0d1d2d3d4d5d6d7", "\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7", 8 },
++    { "d8d9dadbdcdddedf", "\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF", 8 },
++    { "e0e1e2e3e4e5e6e7", "\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7", 8 },
++    { "e8e9eaebecedeeef", "\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF", 8 },
++    { "f0f1f2f3f4f5f6f7", "\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7", 8 },
++    { "f8f9fafbfcfdfeff", "\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF", 8 },
++
++    /* All byte values, uppercase */
++    { "0001020304050607", "\x00\x01\x02\x03\x04\x05\x06\x07", 8, 1 },
++    { "08090A0B0C0D0E0F", "\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", 8, 1 },
++    { "1011121314151617", "\x10\x11\x12\x13\x14\x15\x16\x17", 8, 1 },
++    { "18191A1B1C1D1E1F", "\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F", 8, 1 },
++    { "2021222324252627", "\x20\x21\x22\x23\x24\x25\x26\x27", 8, 1 },
++    { "28292A2B2C2D2E2F", "\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F", 8, 1 },
++    { "3031323334353637", "\x30\x31\x32\x33\x34\x35\x36\x37", 8, 1 },
++    { "38393A3B3C3D3E3F", "\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F", 8, 1 },
++    { "4041424344454647", "\x40\x41\x42\x43\x44\x45\x46\x47", 8, 1 },
++    { "48494A4B4C4D4E4F", "\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F", 8, 1 },
++    { "5051525354555657", "\x50\x51\x52\x53\x54\x55\x56\x57", 8, 1 },
++    { "58595A5B5C5D5E5F", "\x58\x59\x5A\x5B\x5C\x5D\x5E\x5F", 8, 1 },
++    { "6061626364656667", "\x60\x61\x62\x63\x64\x65\x66\x67", 8, 1 },
++    { "68696A6B6C6D6E6F", "\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F", 8, 1 },
++    { "7071727374757677", "\x70\x71\x72\x73\x74\x75\x76\x77", 8, 1 },
++    { "78797A7B7C7D7E7F", "\x78\x79\x7A\x7B\x7C\x7D\x7E\x7F", 8, 1 },
++    { "8081828384858687", "\x80\x81\x82\x83\x84\x85\x86\x87", 8, 1 },
++    { "88898A8B8C8D8E8F", "\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F", 8, 1 },
++    { "9091929394959697", "\x90\x91\x92\x93\x94\x95\x96\x97", 8, 1 },
++    { "98999A9B9C9D9E9F", "\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F", 8, 1 },
++    { "A0A1A2A3A4A5A6A7", "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7", 8, 1 },
++    { "A8A9AAABACADAEAF", "\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF", 8, 1 },
++    { "B0B1B2B3B4B5B6B7", "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7", 8, 1 },
++    { "B8B9BABBBCBDBEBF", "\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF", 8, 1 },
++    { "C0C1C2C3C4C5C6C7", "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7", 8, 1 },
++    { "C8C9CACBCCCDCECF", "\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF", 8, 1 },
++    { "D0D1D2D3D4D5D6D7", "\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7", 8, 1 },
++    { "D8D9DADBDCDDDEDF", "\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF", 8, 1 },
++    { "E0E1E2E3E4E5E6E7", "\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7", 8, 1 },
++    { "E8E9EAEBECEDEEEF", "\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF", 8, 1 },
++    { "F0F1F2F3F4F5F6F7", "\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7", 8, 1 },
++    { "F8F9FAFBFCFDFEFF", "\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF", 8, 1 },
++};
++
++int main()
++{
++    size_t i;
++    char *hex;
++    int ret;
++    uint8_t *bytes;
++    size_t len;
++
++    for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
++        if (tests[i].binary == NULL) {
++            ret = k5_hex_decode(tests[i].hex, &bytes, &len);
++            assert(ret == EINVAL && bytes == NULL && len == 0);
++            continue;
++        }
++
++        ret = k5_hex_decode(tests[i].hex, &bytes, &len);
++        assert(ret == 0);
++        assert(len == tests[i].binary_len);
++        assert(memcmp(bytes, tests[i].binary, len) == 0);
++        assert(bytes[len] == 0);
++        free(bytes);
++
++        ret = k5_hex_encode((uint8_t *)tests[i].binary, tests[i].binary_len,
++                            tests[i].uppercase, &hex);
++        assert(ret == 0);
++        assert(strcmp(tests[i].hex, hex) == 0);
++        free(hex);
++    }
++    return 0;
++}
diff --git a/SOURCES/Don-t-include-all-MEMORY-ccaches-in-collection.patch b/SOURCES/Don-t-include-all-MEMORY-ccaches-in-collection.patch
new file mode 100644
index 0000000..4adb9e8
--- /dev/null
+++ b/SOURCES/Don-t-include-all-MEMORY-ccaches-in-collection.patch
@@ -0,0 +1,91 @@
+From 763420ead602d5b17b27f6bad07fdb1cc2f61119 Mon Sep 17 00:00:00 2001
+From: Greg Hudson <ghudson@mit.edu>
+Date: Wed, 1 Aug 2018 15:53:12 -0400
+Subject: [PATCH] Don't include all MEMORY ccaches in collection
+
+In the MEMORY ccache implementation, only yield a cache in the
+per-type cursor if it is the context default cache, matching the
+behavior of FILE after commit 45360c9688ca963f75a2480f2cf818424fc3dc7b
+(ticket 6955).
+
+ticket: 8720 (new)
+(cherry picked from commit 49bb627fed70c5258c151c5135ac3d95ed1ee55d)
+---
+ src/lib/krb5/ccache/cc_memory.c | 25 ++++++++++---------------
+ src/lib/krb5/ccache/t_cccol.py  |  7 ++++---
+ 2 files changed, 14 insertions(+), 18 deletions(-)
+
+diff --git a/src/lib/krb5/ccache/cc_memory.c b/src/lib/krb5/ccache/cc_memory.c
+index 8cdaff7fb..cfd5c6389 100644
+--- a/src/lib/krb5/ccache/cc_memory.c
++++ b/src/lib/krb5/ccache/cc_memory.c
+@@ -132,7 +132,7 @@ struct mcc_cursor {
+ 
+ /* Iterator over memory caches.  */
+ struct krb5_mcc_ptcursor_data {
+-    struct krb5_mcc_list_node *cur;
++    krb5_boolean first;
+ };
+ 
+ k5_cc_mutex krb5int_mcc_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER;
+@@ -693,9 +693,7 @@ krb5_mcc_ptcursor_new(
+         return ENOMEM;
+     }
+     n->data = cdata;
+-    k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
+-    cdata->cur = mcc_head;
+-    k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
++    cdata->first = TRUE;
+     *cursor = n;
+     return 0;
+ }
+@@ -707,22 +705,19 @@ krb5_mcc_ptcursor_next(
+     krb5_ccache *ccache)
+ {
+     struct krb5_mcc_ptcursor_data *cdata = NULL;
++    const char *defname;
+ 
+     *ccache = NULL;
+     cdata = cursor->data;
+-    if (cdata->cur == NULL)
++    if (!cdata->first)
++        return 0;
++    cdata->first = FALSE;
++
++    defname = krb5_cc_default_name(context);
++    if (defname == NULL || strncmp(defname, "MEMORY:", 7) != 0)
+         return 0;
+ 
+-    *ccache = malloc(sizeof(**ccache));
+-    if (*ccache == NULL)
+-        return ENOMEM;
+-
+-    (*ccache)->ops = &krb5_mcc_ops;
+-    (*ccache)->data = cdata->cur->cache;
+-    k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
+-    cdata->cur = cdata->cur->next;
+-    k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+-    return 0;
++    return krb5_cc_resolve(context, defname, ccache);
+ }
+ 
+ static krb5_error_code KRB5_CALLCONV
+diff --git a/src/lib/krb5/ccache/t_cccol.py b/src/lib/krb5/ccache/t_cccol.py
+index f7f178564..c6d5f514d 100755
+--- a/src/lib/krb5/ccache/t_cccol.py
++++ b/src/lib/krb5/ccache/t_cccol.py
+@@ -97,10 +97,11 @@ if test_keyring:
+ 
+ mfoo = 'MEMORY:foo'
+ mbar = 'MEMORY:bar'
+-cursor_test('filemem', [fccname, mfoo, mbar], [fccname, mfoo, mbar])
+-cursor_test('dirmem', [dccname, mfoo], [duser, dalice, dbob, mfoo])
++cursor_test('filemem', [fccname, mfoo], [fccname])
++cursor_test('dirmem', [dccname, mfoo], [duser, dalice, dbob])
++cursor_test('mem', [mfoo, mbar], [mfoo])
+ if test_keyring:
+-    cursor_test('keyringmem', [krccname, mfoo], [kruser, kralice, krbob, mfoo])
++    cursor_test('keyringmem', [krccname, mfoo], [kruser, kralice, krbob])
+ 
+ # Test krb5_cccol_have_content.
+ realm.run(['./t_cccursor', dccname, 'CONTENT'])
diff --git a/SOURCES/Fix-bugs-with-concurrent-use-of-MEMORY-ccaches.patch b/SOURCES/Fix-bugs-with-concurrent-use-of-MEMORY-ccaches.patch
new file mode 100644
index 0000000..26399e2
--- /dev/null
+++ b/SOURCES/Fix-bugs-with-concurrent-use-of-MEMORY-ccaches.patch
@@ -0,0 +1,396 @@
+From c0873e9b9de0570c97e88598f17b72bf51fd7f5d Mon Sep 17 00:00:00 2001
+From: Greg Hudson <ghudson@mit.edu>
+Date: Sun, 1 Jul 2018 00:12:25 -0400
+Subject: [PATCH] Fix bugs with concurrent use of MEMORY ccaches
+
+A memory ccache iterator stores an alias into the cache object's
+linked list of credentials.  If the cache is reinitialized while the
+iterator is active, the alias becomes invalid.  Also, multiple handles
+referencing the same memory ccache all use aliases to the same data
+object; if one of the handles is destroyed, the other contains a
+dangling pointer.
+
+Fix the first issue by adding a generation counter to the cache and to
+cursors, incremented each time the cache is initialized or destroyed.
+Check the generation on each cursor step and end the iteration if the
+list was invalidated.  Fix the second issue by adding a reference
+count to the cache object, counting one reference for the table slot
+and one for each open handle.  Empty the cache object on each destroy
+operation, but only release the object when the last handle to it is
+destroyed or closed.
+
+Add regression tests for the two issues to t_cc.c.
+
+The first issue was reported by Sorin Manolache.
+
+ticket: 8202
+tags: pullup
+target_version: 1.16-next
+target_version: 1.15-next
+
+(cherry picked from commit 146dadec8fe7ccc4149eb2e3f577cc320aee6efb)
+---
+ src/lib/krb5/ccache/cc_memory.c | 164 ++++++++++++++++++++------------
+ src/lib/krb5/ccache/t_cc.c      |  51 ++++++++++
+ 2 files changed, 154 insertions(+), 61 deletions(-)
+
+diff --git a/src/lib/krb5/ccache/cc_memory.c b/src/lib/krb5/ccache/cc_memory.c
+index c5425eb3a..8cdaff7fb 100644
+--- a/src/lib/krb5/ccache/cc_memory.c
++++ b/src/lib/krb5/ccache/cc_memory.c
+@@ -102,18 +102,20 @@ extern krb5_error_code krb5_change_cache (void);
+ typedef struct _krb5_mcc_link {
+     struct _krb5_mcc_link *next;
+     krb5_creds *creds;
+-} krb5_mcc_link, *krb5_mcc_cursor;
++} krb5_mcc_link;
+ 
+ /* Per-cache data header.  */
+ typedef struct _krb5_mcc_data {
+     char *name;
+     k5_cc_mutex lock;
+     krb5_principal prin;
+-    krb5_mcc_cursor link;
++    krb5_mcc_link *link;
+     krb5_timestamp changetime;
+     /* Time offsets for clock-skewed clients.  */
+     krb5_int32 time_offset;
+     krb5_int32 usec_offset;
++    int refcount;               /* One for the table slot, one per handle */
++    int generation;             /* Incremented at each initialize */
+ } krb5_mcc_data;
+ 
+ /* List of memory caches.  */
+@@ -122,6 +124,12 @@ typedef struct krb5_mcc_list_node {
+     krb5_mcc_data *cache;
+ } krb5_mcc_list_node;
+ 
++/* Iterator over credentials in a memory cache. */
++struct mcc_cursor {
++    int generation;
++    krb5_mcc_link *next_link;
++};
++
+ /* Iterator over memory caches.  */
+ struct krb5_mcc_ptcursor_data {
+     struct krb5_mcc_list_node *cur;
+@@ -132,7 +140,23 @@ static krb5_mcc_list_node *mcc_head = 0;
+ 
+ static void update_mcc_change_time(krb5_mcc_data *);
+ 
+-static void krb5_mcc_free (krb5_context context, krb5_ccache id);
++/* Remove creds from d, invalidate any existing cursors, and unset the client
++ * principal.  The caller is responsible for locking. */
++static void
++empty_mcc_cache(krb5_context context, krb5_mcc_data *d)
++{
++    krb5_mcc_link *curr, *next;
++
++    for (curr = d->link; curr != NULL; curr = next) {
++        next = curr->next;
++        krb5_free_creds(context, curr->creds);
++        free(curr);
++    }
++    d->link = NULL;
++    d->generation++;
++    krb5_free_principal(context, d->prin);
++    d->prin = NULL;
++}
+ 
+ /*
+  * Modifies:
+@@ -150,16 +174,12 @@ krb5_mcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
+ {
+     krb5_os_context os_ctx = &context->os_context;
+     krb5_error_code ret;
+-    krb5_mcc_data *d;
++    krb5_mcc_data *d = id->data;
+ 
+-    d = (krb5_mcc_data *)id->data;
+     k5_cc_mutex_lock(context, &d->lock);
++    empty_mcc_cache(context, d);
+ 
+-    krb5_mcc_free(context, id);
+-
+-    d = (krb5_mcc_data *)id->data;
+-    ret = krb5_copy_principal(context, princ,
+-                              &d->prin);
++    ret = krb5_copy_principal(context, princ, &d->prin);
+     update_mcc_change_time(d);
+ 
+     if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
+@@ -185,61 +205,59 @@ krb5_mcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
+ krb5_error_code KRB5_CALLCONV
+ krb5_mcc_close(krb5_context context, krb5_ccache id)
+ {
++    krb5_mcc_data *d = id->data;
++    int count;
++
+     free(id);
+-    return KRB5_OK;
+-}
+-
+-static void
+-krb5_mcc_free(krb5_context context, krb5_ccache id)
+-{
+-    krb5_mcc_cursor curr,next;
+-    krb5_mcc_data *d;
+-
+-    d = (krb5_mcc_data *) id->data;
+-    for (curr = d->link; curr;) {
+-        krb5_free_creds(context, curr->creds);
+-        next = curr->next;
+-        free(curr);
+-        curr = next;
++    k5_cc_mutex_lock(context, &d->lock);
++    count = --d->refcount;
++    k5_cc_mutex_unlock(context, &d->lock);
++    if (count == 0) {
++        /* This is the last active handle referencing d and d has been removed
++         * from the table, so we can release it. */
++        empty_mcc_cache(context, d);
++        free(d->name);
++        k5_cc_mutex_destroy(&d->lock);
++        free(d);
+     }
+-    d->link = NULL;
+-    krb5_free_principal(context, d->prin);
++    return KRB5_OK;
+ }
+ 
+ /*
+  * Effects:
+  * Destroys the contents of id. id is invalid after call.
+- *
+- * Errors:
+- * system errors (locks related)
+  */
+ krb5_error_code KRB5_CALLCONV
+ krb5_mcc_destroy(krb5_context context, krb5_ccache id)
+ {
+     krb5_mcc_list_node **curr, *node;
+-    krb5_mcc_data *d;
++    krb5_mcc_data *d = id->data;
++    krb5_boolean removed_from_table = FALSE;
+ 
+     k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
+ 
+-    d = (krb5_mcc_data *)id->data;
+     for (curr = &mcc_head; *curr; curr = &(*curr)->next) {
+         if ((*curr)->cache == d) {
+             node = *curr;
+             *curr = node->next;
+             free(node);
++            removed_from_table = TRUE;
+             break;
+         }
+     }
+     k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+ 
++    /* Empty the cache and remove the reference for the table slot.  There will
++     * always be at least one reference left for the handle being destroyed. */
+     k5_cc_mutex_lock(context, &d->lock);
+-
+-    krb5_mcc_free(context, id);
+-    free(d->name);
++    empty_mcc_cache(context, d);
++    if (removed_from_table)
++        d->refcount--;
+     k5_cc_mutex_unlock(context, &d->lock);
+-    k5_cc_mutex_destroy(&d->lock);
+-    free(d);
+-    free(id);
++
++    /* Invalidate the handle, possibly removing the last reference to d and
++     * freeing it. */
++    krb5_mcc_close(context, id);
+ 
+     krb5_change_cache ();
+     return KRB5_OK;
+@@ -279,9 +297,12 @@ krb5_mcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
+     for (ptr = mcc_head; ptr; ptr=ptr->next)
+         if (!strcmp(ptr->cache->name, residual))
+             break;
+-    if (ptr)
++    if (ptr != NULL) {
+         d = ptr->cache;
+-    else {
++        k5_cc_mutex_lock(context, &d->lock);
++        d->refcount++;
++        k5_cc_mutex_unlock(context, &d->lock);
++    } else {
+         err = new_mcc_data(residual, &d);
+         if (err) {
+             k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+@@ -326,14 +347,18 @@ krb5_error_code KRB5_CALLCONV
+ krb5_mcc_start_seq_get(krb5_context context, krb5_ccache id,
+                        krb5_cc_cursor *cursor)
+ {
+-    krb5_mcc_cursor mcursor;
++    struct mcc_cursor *mcursor;
+     krb5_mcc_data *d;
+ 
++    mcursor = malloc(sizeof(*mcursor));
++    if (mcursor == NULL)
++        return KRB5_CC_NOMEM;
+     d = id->data;
+     k5_cc_mutex_lock(context, &d->lock);
+-    mcursor = d->link;
++    mcursor->generation = d->generation;
++    mcursor->next_link = d->link;
+     k5_cc_mutex_unlock(context, &d->lock);
+-    *cursor = (krb5_cc_cursor) mcursor;
++    *cursor = mcursor;
+     return KRB5_OK;
+ }
+ 
+@@ -361,23 +386,34 @@ krb5_error_code KRB5_CALLCONV
+ krb5_mcc_next_cred(krb5_context context, krb5_ccache id,
+                    krb5_cc_cursor *cursor, krb5_creds *creds)
+ {
+-    krb5_mcc_cursor mcursor;
++    struct mcc_cursor *mcursor;
+     krb5_error_code retval;
++    krb5_mcc_data *d = id->data;
+ 
+-    /* Once the node in the linked list is created, it's never
+-       modified, so we don't need to worry about locking here.  (Note
+-       that we don't support _remove_cred.)  */
+-    mcursor = (krb5_mcc_cursor) *cursor;
+-    if (mcursor == NULL)
+-        return KRB5_CC_END;
+     memset(creds, 0, sizeof(krb5_creds));
+-    if (mcursor->creds) {
+-        retval = k5_copy_creds_contents(context, mcursor->creds, creds);
+-        if (retval)
+-            return retval;
++    mcursor = *cursor;
++    if (mcursor->next_link == NULL)
++        return KRB5_CC_END;
++
++    /*
++     * Check the cursor generation against the cache generation in case the
++     * cache has been reinitialized or destroyed, freeing the pointer in the
++     * cursor.  Keep the cache locked while we copy the creds and advance the
++     * pointer, in case another thread reinitializes the cache after we check
++     * the generation.
++     */
++    k5_cc_mutex_lock(context, &d->lock);
++    if (mcursor->generation != d->generation) {
++        k5_cc_mutex_unlock(context, &d->lock);
++        return KRB5_CC_END;
+     }
+-    *cursor = (krb5_cc_cursor)mcursor->next;
+-    return KRB5_OK;
++
++    retval = k5_copy_creds_contents(context, mcursor->next_link->creds, creds);
++    if (retval == 0)
++        mcursor->next_link = mcursor->next_link->next;
++
++    k5_cc_mutex_unlock(context, &d->lock);
++    return retval;
+ }
+ 
+ /*
+@@ -396,14 +432,18 @@ krb5_mcc_next_cred(krb5_context context, krb5_ccache id,
+ krb5_error_code KRB5_CALLCONV
+ krb5_mcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+ {
+-    *cursor = 0L;
++    free(*cursor);
++    *cursor = NULL;
+     return KRB5_OK;
+ }
+ 
+-/* Utility routine: Creates the back-end data for a memory cache, and
+-   threads it into the global linked list.
+-
+-   Call with the global list lock held.  */
++/*
++ * Utility routine: Creates the back-end data for a memory cache, and threads
++ * it into the global linked list.  Give the new object two references, one for
++ * the table slot and one for the caller's handle.
++ *
++ * Call with the global list lock held.
++ */
+ static krb5_error_code
+ new_mcc_data (const char *name, krb5_mcc_data **dataptr)
+ {
+@@ -432,6 +472,8 @@ new_mcc_data (const char *name, krb5_mcc_data **dataptr)
+     d->changetime = 0;
+     d->time_offset = 0;
+     d->usec_offset = 0;
++    d->refcount = 2;
++    d->generation = 0;
+     update_mcc_change_time(d);
+ 
+     n = malloc(sizeof(krb5_mcc_list_node));
+diff --git a/src/lib/krb5/ccache/t_cc.c b/src/lib/krb5/ccache/t_cc.c
+index 6069cabd3..cd4569c4c 100644
+--- a/src/lib/krb5/ccache/t_cc.c
++++ b/src/lib/krb5/ccache/t_cc.c
+@@ -386,6 +386,55 @@ test_misc(krb5_context context)
+     krb5_cc_dfl_ops = ops_save;
+ 
+ }
++
++/*
++ * Regression tests for #8202.  Because memory ccaches share objects between
++ * different handles to the same cache and between iterators and caches,
++ * historically there have been some bugs when those objects are released.
++ */
++static void
++test_memory_concurrent(krb5_context context)
++{
++    krb5_error_code kret;
++    krb5_ccache id1, id2;
++    krb5_cc_cursor cursor;
++    krb5_creds creds;
++
++    /* Create two handles to the same memory ccache and destroy them. */
++    kret = krb5_cc_resolve(context, "MEMORY:x", &id1);
++    CHECK(kret, "resolve 1");
++    kret = krb5_cc_resolve(context, "MEMORY:x", &id2);
++    CHECK(kret, "resolve 2");
++    kret = krb5_cc_destroy(context, id1);
++    CHECK(kret, "destroy 1");
++    kret = krb5_cc_destroy(context, id2);
++    CHECK(kret, "destroy 2");
++
++    kret = init_test_cred(context);
++    CHECK(kret, "init_creds");
++
++    /* Reinitialize the cache after creating an iterator for it, and verify
++     * that the iterator ends gracefully. */
++    kret = krb5_cc_resolve(context, "MEMORY:x", &id1);
++    CHECK(kret, "resolve");
++    kret = krb5_cc_initialize(context, id1, test_creds.client);
++    CHECK(kret, "initialize");
++    kret = krb5_cc_store_cred(context, id1, &test_creds);
++    CHECK(kret, "store");
++    kret = krb5_cc_start_seq_get(context, id1, &cursor);
++    CHECK(kret, "start_seq_get");
++    kret = krb5_cc_initialize(context, id1, test_creds.client);
++    CHECK(kret, "initialize again");
++    kret = krb5_cc_next_cred(context, id1, &cursor, &creds);
++    CHECK_BOOL(kret != KRB5_CC_END, "iterator should end", "next_cred");
++    kret = krb5_cc_end_seq_get(context, id1, &cursor);
++    CHECK(kret, "end_seq_get");
++    kret = krb5_cc_destroy(context, id1);
++    CHECK(kret, "destroy");
++
++    free_test_cred(context);
++}
++
+ extern const krb5_cc_ops krb5_mcc_ops;
+ extern const krb5_cc_ops krb5_fcc_ops;
+ 
+@@ -434,6 +483,8 @@ main(void)
+     do_test(context, "MEMORY:");
+     do_test(context, "FILE:");
+ 
++    test_memory_concurrent(context);
++
+     krb5_free_context(context);
+     return 0;
+ }
diff --git a/SOURCES/Use-a-hash-table-for-MEMORY-ccache-resolution.patch b/SOURCES/Use-a-hash-table-for-MEMORY-ccache-resolution.patch
new file mode 100644
index 0000000..77ccf6a
--- /dev/null
+++ b/SOURCES/Use-a-hash-table-for-MEMORY-ccache-resolution.patch
@@ -0,0 +1,185 @@
+From 779a298a583d64cb9a200cc35a4def3a120e03f7 Mon Sep 17 00:00:00 2001
+From: Greg Hudson <ghudson@mit.edu>
+Date: Sat, 4 Aug 2018 23:55:18 -0400
+Subject: [PATCH] Use a hash table for MEMORY ccache resolution
+
+In cc_memory.c, replace the linked list of caches with a hash table,
+for better performance with large numbers of memory caches.
+
+ticket: 8722 (new)
+(cherry picked from commit 088ba228acce4fd55bbb7c30122fe2703b8beeb8)
+---
+ src/lib/krb5/ccache/cc_memory.c | 77 +++++++++++++++------------------
+ 1 file changed, 34 insertions(+), 43 deletions(-)
+
+diff --git a/src/lib/krb5/ccache/cc_memory.c b/src/lib/krb5/ccache/cc_memory.c
+index cfd5c6389..114ef6913 100644
+--- a/src/lib/krb5/ccache/cc_memory.c
++++ b/src/lib/krb5/ccache/cc_memory.c
+@@ -26,6 +26,7 @@
+ 
+ #include "cc-int.h"
+ #include "../krb/int-proto.h"
++#include "k5-hashtab.h"
+ #include <errno.h>
+ 
+ static krb5_error_code KRB5_CALLCONV krb5_mcc_close
+@@ -118,12 +119,6 @@ typedef struct _krb5_mcc_data {
+     int generation;             /* Incremented at each initialize */
+ } krb5_mcc_data;
+ 
+-/* List of memory caches.  */
+-typedef struct krb5_mcc_list_node {
+-    struct krb5_mcc_list_node *next;
+-    krb5_mcc_data *cache;
+-} krb5_mcc_list_node;
+-
+ /* Iterator over credentials in a memory cache. */
+ struct mcc_cursor {
+     int generation;
+@@ -136,10 +131,27 @@ struct krb5_mcc_ptcursor_data {
+ };
+ 
+ k5_cc_mutex krb5int_mcc_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER;
+-static krb5_mcc_list_node *mcc_head = 0;
++static struct k5_hashtab *mcc_hashtab = NULL;
+ 
+ static void update_mcc_change_time(krb5_mcc_data *);
+ 
++/* Ensure that mcc_hashtab is initialized.  Call with krb5int_mcc_mutex
++ * locked. */
++static krb5_error_code
++init_table(krb5_context context)
++{
++    krb5_error_code ret;
++    uint8_t seed[K5_HASH_SEED_LEN];
++    krb5_data d = make_data(seed, sizeof(seed));
++
++    if (mcc_hashtab != NULL)
++        return 0;
++    ret = krb5_c_random_make_octets(context, &d);
++    if (ret)
++        return ret;
++    return k5_hashtab_create(seed, 64, &mcc_hashtab);
++}
++
+ /* Remove creds from d, invalidate any existing cursors, and unset the client
+  * principal.  The caller is responsible for locking. */
+ static void
+@@ -230,21 +242,13 @@ krb5_mcc_close(krb5_context context, krb5_ccache id)
+ krb5_error_code KRB5_CALLCONV
+ krb5_mcc_destroy(krb5_context context, krb5_ccache id)
+ {
+-    krb5_mcc_list_node **curr, *node;
+     krb5_mcc_data *d = id->data;
+     krb5_boolean removed_from_table = FALSE;
+ 
++    /* Remove this node from the table if it is still present. */
+     k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
+-
+-    for (curr = &mcc_head; *curr; curr = &(*curr)->next) {
+-        if ((*curr)->cache == d) {
+-            node = *curr;
+-            *curr = node->next;
+-            free(node);
+-            removed_from_table = TRUE;
+-            break;
+-        }
+-    }
++    if (k5_hashtab_remove(mcc_hashtab, d->name, strlen(d->name)))
++        removed_from_table = TRUE;
+     k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+ 
+     /* Empty the cache and remove the reference for the table slot.  There will
+@@ -289,16 +293,13 @@ krb5_mcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
+ {
+     krb5_os_context os_ctx = &context->os_context;
+     krb5_ccache lid;
+-    krb5_mcc_list_node *ptr;
+     krb5_error_code err;
+     krb5_mcc_data *d;
+ 
+     k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
+-    for (ptr = mcc_head; ptr; ptr=ptr->next)
+-        if (!strcmp(ptr->cache->name, residual))
+-            break;
+-    if (ptr != NULL) {
+-        d = ptr->cache;
++    init_table(context);
++    d = k5_hashtab_get(mcc_hashtab, residual, strlen(residual));
++    if (d != NULL) {
+         k5_cc_mutex_lock(context, &d->lock);
+         d->refcount++;
+         k5_cc_mutex_unlock(context, &d->lock);
+@@ -438,18 +439,17 @@ krb5_mcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *curso
+ }
+ 
+ /*
+- * Utility routine: Creates the back-end data for a memory cache, and threads
+- * it into the global linked list.  Give the new object two references, one for
+- * the table slot and one for the caller's handle.
++ * Utility routine: Creates the back-end data for a memory cache, and adds it
++ * to the global table.  Give the new object two references, one for the table
++ * slot and one for the caller's handle.
+  *
+- * Call with the global list lock held.
++ * Call with the global table lock held.
+  */
+ static krb5_error_code
+ new_mcc_data (const char *name, krb5_mcc_data **dataptr)
+ {
+     krb5_error_code err;
+     krb5_mcc_data *d;
+-    krb5_mcc_list_node *n;
+ 
+     d = malloc(sizeof(krb5_mcc_data));
+     if (d == NULL)
+@@ -476,18 +476,13 @@ new_mcc_data (const char *name, krb5_mcc_data **dataptr)
+     d->generation = 0;
+     update_mcc_change_time(d);
+ 
+-    n = malloc(sizeof(krb5_mcc_list_node));
+-    if (n == NULL) {
++    if (k5_hashtab_add(mcc_hashtab, d->name, strlen(d->name), d) != 0) {
+         free(d->name);
+         k5_cc_mutex_destroy(&d->lock);
+         free(d);
+         return KRB5_CC_NOMEM;
+     }
+ 
+-    n->cache = d;
+-    n->next = mcc_head;
+-    mcc_head = n;
+-
+     *dataptr = d;
+     return 0;
+ }
+@@ -522,11 +517,10 @@ krb5_mcc_generate_new (krb5_context context, krb5_ccache *id)
+     lid->ops = &krb5_mcc_ops;
+ 
+     k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
++    init_table(context);
+ 
+     /* Check for uniqueness with mutex locked to avoid race conditions */
+     while (1) {
+-        krb5_mcc_list_node *ptr;
+-
+         err = krb5int_random_string (context, uniquename, sizeof (uniquename));
+         if (err) {
+             k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+@@ -534,12 +528,9 @@ krb5_mcc_generate_new (krb5_context context, krb5_ccache *id)
+             return err;
+         }
+ 
+-        for (ptr = mcc_head; ptr; ptr=ptr->next) {
+-            if (!strcmp(ptr->cache->name, uniquename)) {
+-                break;  /* got a match, loop again */
+-            }
+-        }
+-        if (!ptr) break; /* got to the end without finding a match */
++        if (k5_hashtab_get(mcc_hashtab, uniquename,
++                           strlen(uniquename)) == NULL)
++            break;
+     }
+ 
+     err = new_mcc_data(uniquename, &d);
diff --git a/SPECS/krb5.spec b/SPECS/krb5.spec
index 9e84678..bc5177a 100644
--- a/SPECS/krb5.spec
+++ b/SPECS/krb5.spec
@@ -12,7 +12,7 @@
 Summary: The Kerberos network authentication system
 Name: krb5
 Version: 1.15.1
-Release: 34%{?dist}
+Release: 37%{?dist}
 
 # - Maybe we should explode from the now-available-to-everybody tarball instead?
 # http://web.mit.edu/kerberos/dist/krb5/1.13/krb5-1.13.2-signed.tar
@@ -121,7 +121,12 @@ Patch206: Add-test-cases-for-preauth-fallback-behavior.patch
 Patch207: Include-preauth-name-in-trace-output-if-possible.patch
 Patch208: Add-vector-support-to-k5_sha256.patch
 Patch209: Use-SHA-256-instead-of-MD5-for-audit-ticket-IDs.patch
-Patch210: In-FIPS-mode-add-plaintext-fallback-for-RC4-usages-a.patch
+Patch211: In-FIPS-mode-add-plaintext-fallback-for-RC4-usages-a.patch
+Patch212: Fix-bugs-with-concurrent-use-of-MEMORY-ccaches.patch
+Patch213: Don-t-include-all-MEMORY-ccaches-in-collection.patch
+Patch214: Add-libkrb5support-hex-functions-and-tests.patch
+Patch215: Add-a-hash-table-implementation-to-libkrb5support.patch
+Patch216: Use-a-hash-table-for-MEMORY-ccache-resolution.patch
 
 License: MIT
 URL: http://web.mit.edu/kerberos/www/
@@ -376,7 +381,12 @@ ONLY by kerberos itself. Do not depend on this package.
 %patch207 -p1 -b .Include-preauth-name-in-trace-output-if-possible
 %patch208 -p1 -b .Add-vector-support-to-k5_sha256
 %patch209 -p1 -b .Use-SHA-256-instead-of-MD5-for-audit-ticket-IDs
-%patch210 -p1 -b .In-FIPS-mode-add-plaintext-fallback-for-RC4-usages-a
+%patch211 -p1 -b .In-FIPS-mode-add-plaintext-fallback-for-RC4-usages-a
+%patch212 -p1 -b .Fix-bugs-with-concurrent-use-of-MEMORY-ccaches
+%patch213 -p1 -b .Don-t-include-all-MEMORY-ccaches-in-collection
+%patch214 -p1 -b .Add-libkrb5support-hex-functions-and-tests
+%patch215 -p1 -b .Add-a-hash-table-implementation-to-libkrb5support
+%patch216 -p1 -b .Use-a-hash-table-for-MEMORY-ccache-resolution
 
 ln NOTICE LICENSE
 
@@ -460,7 +470,7 @@ CPPFLAGS="`echo $DEFINES $INCLUDES`"
 	--with-dirsrv-account-locking \
 %endif
 	--enable-pkinit \
-	--with-crypto-impl=openssl \
+	--with-crypto-impl=builtin \
 	--with-pkinit-crypto-impl=openssl \
 	--with-tls-impl=openssl \
 	--with-system-verto \
@@ -882,6 +892,18 @@ exit 0
 %{_libdir}/libkadm5srv_mit.so.*
 
 %changelog
+* Tue Dec 18 2018 Robbie Harwood <rharwood@redhat.com> - 1.15.1-37
+- Bring back builtin crypto (openssl broke too many FIPS setups)
+- Resolves: #1657890
+
+* Mon Dec 17 2018 Robbie Harwood <rharwood@redhat.com> - 1.15.1-36
+- Clean up MEMORY ccache behavior to match upstream more closely
+- Resolves: #1657890
+
+* Tue Dec 11 2018 Robbie Harwood <rharwood@redhat.com> - 1.15.1-35
+- Fix bugs with concurrent use of MEMORY ccaches
+- Resolves: #1657890
+
 * Wed Aug 01 2018 Robbie Harwood <rharwood@redhat.com> - 1.15.1-34
 - In FIPS mode, add plaintext fallback for RC4 usages and taint
 - Resolves: #1570600