diff --git a/SOURCES/0062-Ticket-49330-Improve-ndn-cache-performance-1.3.6.patch b/SOURCES/0062-Ticket-49330-Improve-ndn-cache-performance-1.3.6.patch
new file mode 100644
index 0000000..9b21655
--- /dev/null
+++ b/SOURCES/0062-Ticket-49330-Improve-ndn-cache-performance-1.3.6.patch
@@ -0,0 +1,1041 @@
+From 2975f68e139169ee2d2259cfbbb2a15b54dc3724 Mon Sep 17 00:00:00 2001
+From: William Brown <firstyear@redhat.com>
+Date: Wed, 26 Jul 2017 11:01:49 +1000
+Subject: [PATCH] Ticket 49330 - Improve ndn cache performance 1.3.6
+
+Backport from 1.3.7 master.
+
+Bug Description:  Normalised DN's are a costly process to update
+and maintain. As a result, a normalised DN cache was created. Yet
+it was never able to perform well. In some datasets with large sets
+of dn attr types, the NDN cache actively hurt performance.
+
+The issue stemmed from 3 major issues in the design of the NDN
+cache.
+
+First, it is a global cache which means it exists behind
+a rwlock. This causes delay as threads wait behind the lock
+to access or update the cache (especially on a miss).
+
+Second, the cache was limited to 4073 buckets. Despite the fact
+that a prime number on a hash causes a skew in distribution,
+this was in an NSPR hash - which does not grow dynamically,
+rather devolving a bucket to a linked list. AS a result, once you
+passed ~3000 your lookup performance would degrade rapidly to O(1)
+
+Finally, the cache's lru policy did not evict least used - it
+evicted the 10,000 least used. So if you tuned your cache
+to match the NSPR map, every inclusion that would trigger a
+delete of old values would effectively empty your cache. ON bigger
+set sizes, this has to walk the map (at O(1)) to clean 10,000
+elements.
+
+Premature optimisation strikes again ....
+
+Fix Description:  Throw it out. Rewrite. We now use a hash
+algo that has proper distribution across a set. The hash
+sizes slots to a power of two. Finally, each thread has
+a private cache rather than shared which completely eliminates
+a lock contention and even NUMA performance issues.
+
+Interestingly this fix should have improvements for DB
+imports, memberof and refint performance and more.
+
+Some testing has shown in simple search workloads a 10%
+improvement in throughput, and on complex searches a 47x
+improvement.
+
+https://pagure.io/389-ds-base/issue/49330
+
+Author: wibrown
+
+Review by: lkrispen, tbordaz
+---
+ ldap/servers/slapd/back-ldbm/monitor.c |  11 +-
+ ldap/servers/slapd/dn.c                | 809 +++++++++++++++++++++------------
+ ldap/servers/slapd/slapi-private.h     |   2 +-
+ 3 files changed, 527 insertions(+), 295 deletions(-)
+
+diff --git a/ldap/servers/slapd/back-ldbm/monitor.c b/ldap/servers/slapd/back-ldbm/monitor.c
+index c58b069..aa7d709 100644
+--- a/ldap/servers/slapd/back-ldbm/monitor.c
++++ b/ldap/servers/slapd/back-ldbm/monitor.c
+@@ -43,6 +43,9 @@ int ldbm_back_monitor_instance_search(Slapi_PBlock *pb, Slapi_Entry *e,
+     PRUint64 hits, tries;
+     long nentries, maxentries, count;
+     size_t size, maxsize;
++    size_t thread_size;
++    size_t evicts;
++    size_t slots;
+ /* NPCTE fix for bugid 544365, esc 0. <P.R> <04-Jul-2001> */
+     struct stat astat;
+ /* end of NPCTE fix for bugid 544365 */
+@@ -118,7 +121,7 @@ int ldbm_back_monitor_instance_search(Slapi_PBlock *pb, Slapi_Entry *e,
+     }
+     /* normalized dn cache stats */
+     if(ndn_cache_started()){
+-        ndn_cache_get_stats(&hits, &tries, &size, &maxsize, &count);
++        ndn_cache_get_stats(&hits, &tries, &size, &maxsize, &thread_size, &evicts, &slots, &count);
+         sprintf(buf, "%" PRIu64, tries);
+         MSET("normalizedDnCacheTries");
+         sprintf(buf, "%" PRIu64, hits);
+@@ -127,6 +130,8 @@ int ldbm_back_monitor_instance_search(Slapi_PBlock *pb, Slapi_Entry *e,
+         MSET("normalizedDnCacheMisses");
+         sprintf(buf, "%lu", (unsigned long)(100.0*(double)hits / (double)(tries > 0 ? tries : 1)));
+         MSET("normalizedDnCacheHitRatio");
++        sprintf(buf, "%"PRIu64, evicts);
++        MSET("NormalizedDnCacheEvictions");
+         sprintf(buf, "%lu", (long unsigned int)size);
+         MSET("currentNormalizedDnCacheSize");
+         if(maxsize == 0){
+@@ -135,6 +140,10 @@ int ldbm_back_monitor_instance_search(Slapi_PBlock *pb, Slapi_Entry *e,
+         	sprintf(buf, "%lu", (long unsigned int)maxsize);
+         }
+         MSET("maxNormalizedDnCacheSize");
++        sprintf(buf, "%"PRIu64, thread_size);
++        MSET("NormalizedDnCacheThreadSize");
++        sprintf(buf, "%"PRIu64, slots);
++        MSET("NormalizedDnCacheThreadSlots");
+         sprintf(buf, "%ld", count);
+         MSET("currentNormalizedDnCacheCount");
+     }
+diff --git a/ldap/servers/slapd/dn.c b/ldap/servers/slapd/dn.c
+index fa3909f..9cb3e7b 100644
+--- a/ldap/servers/slapd/dn.c
++++ b/ldap/servers/slapd/dn.c
+@@ -22,6 +22,24 @@
+ #include "slap.h"
+ #include <plhash.h>
+ 
++#include <inttypes.h>
++#include <stddef.h> /* for size_t */
++
++#if defined(HAVE_SYS_ENDIAN_H)
++#include <sys/endian.h>
++#elif defined(HAVE_ENDIAN_H)
++#include <endian.h>
++#else
++#error platform header for endian detection not found.
++#endif
++
++/* See: http://sourceforge.net/p/predef/wiki/Endianness/ */
++#if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN
++#define _le64toh(x) ((uint64_t)(x))
++#else
++#define _le64toh(x) le64toh(x)
++#endif
++
+ #undef SDN_DEBUG
+ 
+ static void add_rdn_av( char *avstart, char *avend, int *rdn_av_countp,
+@@ -33,52 +51,89 @@ static void rdn_av_swap( struct berval *av1, struct berval *av2, int escape );
+ static int does_cn_uses_dn_syntax_in_dns(char *type, char *dn);
+ 
+ /* normalized dn cache related definitions*/
+-struct
+-ndn_cache_lru
+-{
+-    struct ndn_cache_lru *prev;
+-    struct ndn_cache_lru *next;
+-    char *key;
+-};
+-
+-struct
+-ndn_cache_ctx
+-{
+-    struct ndn_cache_lru *head;
+-    struct ndn_cache_lru *tail;
++struct ndn_cache_stats {
+     Slapi_Counter *cache_hits;
+     Slapi_Counter *cache_tries;
+-    Slapi_Counter *cache_misses;
+-    size_t cache_size;
+-    size_t cache_max_size;
+-    long cache_count;
++    Slapi_Counter *cache_count;
++    Slapi_Counter *cache_size;
++    Slapi_Counter *cache_evicts;
++    size_t max_size;
++    size_t thread_max_size;
++    size_t slots;
+ };
+ 
+-struct
+-ndn_hash_val
+-{
++struct ndn_cache_value {
++    size_t size;
++    size_t slot;
++    char *dn;
+     char *ndn;
+-    size_t len;
+-    int size;
+-    struct ndn_cache_lru *lru_node; /* used to speed up lru shuffling */
++    struct ndn_cache_value *next;
++    struct ndn_cache_value *prev;
++    struct ndn_cache_value *child;
++};
++
++/*
++ * This uses a similar alloc trick to IDList to keep
++ * The amount of derefs small.
++ */
++struct ndn_cache {
++    /*
++     * We keep per thread stats and flush them occasionally
++     */
++    size_t max_size;
++    /* Need to track this because we need to provide diffs to counter */
++    size_t last_count;
++    size_t count;
++    /* Number of ops */
++    size_t tries;
++    /* hit vs miss. in theroy miss == tries - hits.*/
++    size_t hits;
++    /* How many values we kicked out */
++    size_t evicts;
++    /* Need to track this because we need to provide diffs to counter */
++    size_t last_size;
++    size_t size;
++
++    size_t slots;
++    /*
++     * This is used by siphash to prevent hash bugket attacks
++     */
++    char key[16];
++
++    struct ndn_cache_value *head;
++    struct ndn_cache_value *tail;
++    struct ndn_cache_value *table[1];
+ };
+ 
+-#define NDN_FLUSH_COUNT 10000 /* number of DN's to remove when cache fills up */
+-#define NDN_MIN_COUNT 1000 /* the minimum number of DN's to keep in the cache */
+-#define NDN_CACHE_BUCKETS 2053 /* prime number */
++/*
++ * This means we need 1 MB minimum per thread
++ * 
++ */
++#define NDN_CACHE_MINIMUM_CAPACITY 1048576
++/*
++ * This helps us define the number of hashtable slots
++ * to create. We assume an average DN is 64 chars long
++ * This way we end up we a ht entry of:
++ * 8 bytes: from the table pointing to us.
++ * 8 bytes: next ptr
++ * 8 bytes: prev ptr
++ * 8 bytes + 64: dn
++ * 8 bytes + 64: ndn itself.
++ * This gives us 168 bytes. In theory this means
++ * 6241 entries, but we have to clamp this to a power of
++ * two, so we have 8192 slots. In reality, dns may be
++ * shorter *and* the dn may be the same as the ndn
++ * so we *may* store more ndns that this. Again, a good reason
++ * to round the ht size up!
++ */
++#define NDN_ENTRY_AVG_SIZE 168
++/*
++ * After how many operations do we sync our per-thread stats.
++ */
++#define NDN_STAT_COMMIT_FREQUENCY 256
+ 
+-static PLHashNumber ndn_hash_string(const void *key);
+ static int ndn_cache_lookup(char *dn, size_t dn_len, char **result, char **udn, int *rc);
+-static void ndn_cache_update_lru(struct ndn_cache_lru **node);
+ static void ndn_cache_add(char *dn, size_t dn_len, char *ndn, size_t ndn_len);
+-static void ndn_cache_delete(char *dn);
+-static void ndn_cache_flush(void);
+-static void ndn_cache_free(void);
+-static int ndn_started = 0;
+-static PRLock *lru_lock = NULL;
+-static Slapi_RWLock *ndn_cache_lock = NULL;
+-static struct ndn_cache_ctx *ndn_cache = NULL;
+-static PLHashTable *ndn_cache_hashtable = NULL;
+ 
+ #define ISBLANK(c)	((c) == ' ')
+ #define ISBLANKSTR(s)	(((*(s)) == '2') && (*((s)+1) == '0'))
+@@ -2768,166 +2823,408 @@ slapi_sdn_get_size(const Slapi_DN *sdn)
+  *
+  */
+ 
++/* <MIT License>
++ Copyright (c) 2013  Marek Majkowski <marek@popcount.org>
++
++ Permission is hereby granted, free of charge, to any person obtaining a copy
++ of this software and associated documentation files (the "Software"), to deal
++ in the Software without restriction, including without limitation the rights
++ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++ copies of the Software, and to permit persons to whom the Software is
++ furnished to do so, subject to the following conditions:
++
++ The above copyright notice and this permission notice shall be included in
++ all copies or substantial portions of the Software.
++
++ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
++ THE SOFTWARE.
++ </MIT License>
++
++ Original location:
++    https://github.com/majek/csiphash/
++
++ Solution inspired by code from:
++    Samuel Neves (supercop/crypto_auth/siphash24/little)
++    djb (supercop/crypto_auth/siphash24/little2)
++    Jean-Philippe Aumasson (https://131002.net/siphash/siphash24.c)
++*/
++
++#define ROTATE(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
++
++#define HALF_ROUND(a, b, c, d, s, t) \
++    a += b;                          \
++    c += d;                          \
++    b = ROTATE(b, s) ^ a;            \
++    d = ROTATE(d, t) ^ c;            \
++    a = ROTATE(a, 32);
++
++#define ROUND(v0, v1, v2, v3)           \
++    HALF_ROUND(v0, v1, v2, v3, 13, 16); \
++    HALF_ROUND(v2, v1, v0, v3, 17, 21)
++
++#define cROUND(v0, v1, v2, v3) \
++    ROUND(v0, v1, v2, v3)
++
++#define dROUND(v0, v1, v2, v3) \
++    ROUND(v0, v1, v2, v3);     \
++    ROUND(v0, v1, v2, v3);     \
++    ROUND(v0, v1, v2, v3)
++
++
++static uint64_t
++sds_siphash13(const void *src, size_t src_sz, const char key[16])
++{
++    const uint64_t *_key = (uint64_t *)key;
++    uint64_t k0 = _le64toh(_key[0]);
++    uint64_t k1 = _le64toh(_key[1]);
++    uint64_t b = (uint64_t)src_sz << 56;
++    const uint64_t *in = (uint64_t *)src;
++
++    uint64_t v0 = k0 ^ 0x736f6d6570736575ULL;
++    uint64_t v1 = k1 ^ 0x646f72616e646f6dULL;
++    uint64_t v2 = k0 ^ 0x6c7967656e657261ULL;
++    uint64_t v3 = k1 ^ 0x7465646279746573ULL;
++
++    while (src_sz >= 8) {
++        uint64_t mi = _le64toh(*in);
++        in += 1;
++        src_sz -= 8;
++        v3 ^= mi;
++        // cround
++        cROUND(v0, v1, v2, v3);
++        v0 ^= mi;
++    }
++
++    uint64_t t = 0;
++    uint8_t *pt = (uint8_t *)&t;
++    uint8_t *m = (uint8_t *)in;
++
++    switch (src_sz) {
++    case 7:
++        pt[6] = m[6]; /* FALLTHRU */
++    case 6:
++        pt[5] = m[5]; /* FALLTHRU */
++    case 5:
++        pt[4] = m[4]; /* FALLTHRU */
++    case 4:
++        *((uint32_t *)&pt[0]) = *((uint32_t *)&m[0]);
++        break;
++    case 3:
++        pt[2] = m[2]; /* FALLTHRU */
++    case 2:
++        pt[1] = m[1]; /* FALLTHRU */
++    case 1:
++        pt[0] = m[0]; /* FALLTHRU */
++    }
++    b |= _le64toh(t);
++
++    v3 ^= b;
++    // cround
++    cROUND(v0, v1, v2, v3);
++    v0 ^= b;
++    v2 ^= 0xff;
++    // dround
++    dROUND(v0, v1, v2, v3);
++    return (v0 ^ v1) ^ (v2 ^ v3);
++}
++
++static pthread_key_t ndn_cache_key;
++static pthread_once_t ndn_cache_key_once = PTHREAD_ONCE_INIT;
++static struct ndn_cache_stats t_cache_stats = {0};
+ /*
+- *  Hashing function using Bernstein's method
++ * WARNING: For some reason we try to use the NDN cache *before*
++ * we have a chance to configure it. As a result, we need to rely
++ * on a trick in the way we start, that we start in one thread
++ * so we can manipulate ints as though they were atomics, then
++ * we start in *one* thread, so it's set, then when threads
++ * fork the get barriers, so we can go from there. However we *CANNOT*
++ * change this at runtime without expensive atomics per op, so lets
++ * not bother until we improve libglobs to be COW.
+  */
+-static PLHashNumber
+-ndn_hash_string(const void *key)
+-{
+-    PLHashNumber hash = 5381;
+-    unsigned char *x = (unsigned char *)key;
+-    int c;
++static int32_t ndn_enabled = 0;
++
++static struct ndn_cache *
++ndn_thread_cache_create(size_t thread_max_size, size_t slots) {
++    size_t t_cache_size = sizeof(struct ndn_cache) + (slots * sizeof(struct ndn_cache_value *));
++    struct ndn_cache *t_cache = (struct ndn_cache *)slapi_ch_calloc(1, t_cache_size);
++
++    t_cache->max_size = thread_max_size;
++    t_cache->slots = slots;
+ 
+-    while ((c = *x++)){
+-        hash = ((hash << 5) + hash) ^ c;
++    return t_cache;
++}
++
++static void
++ndn_thread_cache_commit_status(struct ndn_cache *t_cache) {
++    /*
++     * Every so often we commit these atomically. We do this infrequently
++     * to avoid the costly atomics.
++     */
++    if (t_cache->tries % NDN_STAT_COMMIT_FREQUENCY == 0) {
++        /* We can just add tries and hits. */
++        slapi_counter_add(t_cache_stats.cache_evicts, t_cache->evicts);
++        slapi_counter_add(t_cache_stats.cache_tries, t_cache->tries);
++        slapi_counter_add(t_cache_stats.cache_hits, t_cache->hits);
++        t_cache->hits = 0;
++        t_cache->tries = 0;
++        t_cache->evicts = 0;
++        /* Count and size need diff */
++        int64_t diff = (t_cache->size - t_cache->last_size);
++        if (diff > 0) {
++            // We have more ....
++            slapi_counter_add(t_cache_stats.cache_size, (uint64_t)diff);
++        } else if (diff < 0) {
++            slapi_counter_subtract(t_cache_stats.cache_size, (uint64_t)llabs(diff));
++        }
++        t_cache->last_size = t_cache->size;
++
++        diff = (t_cache->count - t_cache->last_count);
++        if (diff > 0) {
++            // We have more ....
++            slapi_counter_add(t_cache_stats.cache_count, (uint64_t)diff);
++        } else if (diff < 0) {
++            slapi_counter_subtract(t_cache_stats.cache_count, (uint64_t)llabs(diff));
++        }
++        t_cache->last_count = t_cache->count;
++
++    }
++}
++
++static void
++ndn_thread_cache_value_destroy(struct ndn_cache *t_cache, struct ndn_cache_value *v) {
++    /* Update stats */
++    t_cache->size = t_cache->size - v->size;
++    t_cache->count--;
++    t_cache->evicts++;
++
++    if (v == t_cache->head) {
++        t_cache->head = v->prev;
++    }
++    if (v == t_cache->tail) {
++        t_cache->tail = v->next;
++    }
++
++    /* Cut the node out. */
++    if (v->next != NULL) {
++        v->next->prev = v->prev;
++    }
++    if (v->prev != NULL) {
++        v->prev->next = v->next;
++    }
++    /* Set the pointer in the table to NULL */
++    /* Now see if we were in a list */
++    struct ndn_cache_value *slot_node = t_cache->table[v->slot];
++    if (slot_node == v) {
++        t_cache->table[v->slot] = v->child;
++    } else {
++        struct ndn_cache_value *former_slot_node = NULL;
++        do {
++            former_slot_node = slot_node;
++            slot_node = slot_node->child;
++        } while(slot_node != v);
++        /* Okay, now slot_node is us, and former is our parent */
++        former_slot_node->child = v->child;
++    }
++
++    slapi_ch_free((void **)&(v->dn));
++    slapi_ch_free((void **)&(v->ndn));
++    slapi_ch_free((void **)&v);
++}
++
++static void
++ndn_thread_cache_destroy(void *v_cache) {
++    struct ndn_cache *t_cache = (struct ndn_cache *)v_cache;
++    /*
++     * FREE ALL THE NODES!!!
++     */
++    struct ndn_cache_value *node = t_cache->tail;
++    struct ndn_cache_value *next_node = NULL;
++    while (node) {
++        next_node = node->next;
++        ndn_thread_cache_value_destroy(t_cache, node);
++        node = next_node;
++    }
++    slapi_ch_free((void **)&t_cache);
++}
++
++static void
++ndn_cache_key_init() {
++    if (pthread_key_create(&ndn_cache_key, ndn_thread_cache_destroy) != 0) {
++        /* Log a scary warning? */
++        slapi_log_err(SLAPI_LOG_ERR, "ndn_cache_init", "Failed to create pthread key, aborting.\n");
+     }
+-    return hash;
+ }
+ 
+ void
+ ndn_cache_init()
+ {
+-    if(!config_get_ndn_cache_enabled() || ndn_started){
++    ndn_enabled = config_get_ndn_cache_enabled();
++    if (ndn_enabled == 0) {
++        /*
++         * Don't configure the keys or anything, need a restart
++         * to enable. We'll just never use ndn cache in this
++         * run.
++         */
+         return;
+     }
+-    ndn_cache_hashtable = PL_NewHashTable( NDN_CACHE_BUCKETS, ndn_hash_string, PL_CompareStrings, PL_CompareValues, 0, 0);
+-    ndn_cache = (struct ndn_cache_ctx *)slapi_ch_malloc(sizeof(struct ndn_cache_ctx));
+-    ndn_cache->cache_max_size = config_get_ndn_cache_size();
+-    ndn_cache->cache_hits = slapi_counter_new();
+-    ndn_cache->cache_tries = slapi_counter_new();
+-    ndn_cache->cache_misses = slapi_counter_new();
+-    ndn_cache->cache_count = 0;
+-    ndn_cache->cache_size = sizeof(struct ndn_cache_ctx) + sizeof(PLHashTable) + sizeof(PLHashTable);
+-    ndn_cache->head = NULL;
+-    ndn_cache->tail = NULL;
+-    ndn_started = 1;
+-    if ( NULL == ( lru_lock = PR_NewLock()) ||  NULL == ( ndn_cache_lock = slapi_new_rwlock())) {
+-        ndn_cache_destroy();
+-        slapi_log_err(SLAPI_LOG_ERR, "ndn_cache_init", "Failed to create locks.  Disabling cache.\n" );
++
++    /* Create the pthread key */
++    (void)pthread_once(&ndn_cache_key_once, ndn_cache_key_init);
++
++    /* Create the global stats. */
++    t_cache_stats.max_size = config_get_ndn_cache_size();
++    t_cache_stats.cache_evicts = slapi_counter_new();
++    t_cache_stats.cache_tries = slapi_counter_new();
++    t_cache_stats.cache_hits = slapi_counter_new();
++    t_cache_stats.cache_count = slapi_counter_new();
++    t_cache_stats.cache_size = slapi_counter_new();
++    /* Get thread numbers and calc the per thread size */
++    int32_t maxthreads = (int32_t)config_get_threadnumber();
++    size_t tentative_size = t_cache_stats.max_size / maxthreads;
++    if (tentative_size < NDN_CACHE_MINIMUM_CAPACITY) {
++        tentative_size = NDN_CACHE_MINIMUM_CAPACITY;
++        t_cache_stats.max_size = NDN_CACHE_MINIMUM_CAPACITY * maxthreads;
++    }
++    t_cache_stats.thread_max_size = tentative_size;
++
++    /*
++     * Slots *must* be a power of two, even if the number of entries
++     * we store will be *less* than this.
++     */
++    size_t possible_elements = tentative_size / NDN_ENTRY_AVG_SIZE;
++    /*
++     * So this is like 1048576 / 168, so we get 6241. Now we need to
++     * shift this to get the number of bits.
++     */
++    size_t shifts = 0;
++    while (possible_elements > 0) {
++        shifts++;
++        possible_elements = possible_elements >> 1;
+     }
++    /*
++     * So now we can use this to make the slot count.
++     */
++    t_cache_stats.slots = 1 << shifts;
++    /* Done? */
++    return;
+ }
+ 
+ void
+ ndn_cache_destroy()
+ {
+-    if(!ndn_started){
++    if (ndn_enabled == 0) {
+         return;
+     }
+-    if(lru_lock){
+-        PR_DestroyLock(lru_lock);
+-        lru_lock = NULL;
+-    }
+-    if(ndn_cache_lock){
+-        slapi_destroy_rwlock(ndn_cache_lock);
+-        ndn_cache_lock = NULL;
+-    }
+-    if(ndn_cache_hashtable){
+-        ndn_cache_free();
+-        PL_HashTableDestroy(ndn_cache_hashtable);
+-        ndn_cache_hashtable = NULL;
+-    }
+-    config_set_ndn_cache_enabled(CONFIG_NDN_CACHE, "off", NULL, 1 );
+-    slapi_counter_destroy(&ndn_cache->cache_hits);
+-    slapi_counter_destroy(&ndn_cache->cache_tries);
+-    slapi_counter_destroy(&ndn_cache->cache_misses);
+-    slapi_ch_free((void **)&ndn_cache);
+-
+-    ndn_started = 0;
++    slapi_counter_destroy(&(t_cache_stats.cache_tries));
++    slapi_counter_destroy(&(t_cache_stats.cache_hits));
++    slapi_counter_destroy(&(t_cache_stats.cache_count));
++    slapi_counter_destroy(&(t_cache_stats.cache_size));
++    slapi_counter_destroy(&(t_cache_stats.cache_evicts));
+ }
+ 
+ int
+ ndn_cache_started()
+ {
+-    return ndn_started;
++    return ndn_enabled;
+ }
+ 
+ /*
+  *  Look up this dn in the ndn cache
+  */
+ static int
+-ndn_cache_lookup(char *dn, size_t dn_len, char **result, char **udn, int *rc)
++ndn_cache_lookup(char *dn, size_t dn_len, char **ndn, char **udn, int *rc)
+ {
+-    struct ndn_hash_val *ndn_ht_val = NULL;
+-    char *ndn, *key;
+-    int rv = 0;
+-
+-    if(NULL == udn){
+-        return rv;
++    if (ndn_enabled == 0 || NULL == udn) {
++        return 0;
+     }
+     *udn = NULL;
+-    if(ndn_started == 0){
+-        return rv;
+-    }
+-    if(dn_len == 0){
+-        *result = dn;
++
++    if (dn_len == 0) {
++        *ndn = dn;
+         *rc = 0;
+         return 1;
+     }
+-    slapi_counter_increment(ndn_cache->cache_tries);
+-    slapi_rwlock_rdlock(ndn_cache_lock);
+-    ndn_ht_val = (struct ndn_hash_val *)PL_HashTableLookupConst(ndn_cache_hashtable, dn);
+-    if(ndn_ht_val){
+-        ndn_cache_update_lru(&ndn_ht_val->lru_node);
+-        slapi_counter_increment(ndn_cache->cache_hits);
+-        if ((ndn_ht_val->len != dn_len) || 
+-            /* even if the lengths match, dn may not be normalized yet.
+-             * (e.g., 'cn="o=ABC",o=XYZ' vs. 'cn=o\3DABC,o=XYZ') */
+-            (memcmp(dn, ndn_ht_val->ndn, dn_len))){
+-            *rc = 1; /* free result */
+-            ndn = slapi_ch_malloc(ndn_ht_val->len + 1);
+-            memcpy(ndn, ndn_ht_val->ndn, ndn_ht_val->len);
+-            ndn[ndn_ht_val->len] = '\0';
+-            *result = ndn;
+-        } else {
+-            /* the dn was already normalized, just return the dn as the result */
+-            *result = dn;
+-            *rc = 0;
+-        }
+-        rv = 1;
+-    } else {
+-        /* copy/preserve the udn, so we can use it as the key when we add dn's to the hashtable */
+-        key = slapi_ch_malloc(dn_len + 1);
+-        memcpy(key, dn, dn_len);
+-        key[dn_len] = '\0';
+-        *udn = key;
++
++    struct ndn_cache *t_cache = pthread_getspecific(ndn_cache_key);
++    if (t_cache == NULL) {
++        t_cache = ndn_thread_cache_create(t_cache_stats.thread_max_size, t_cache_stats.slots);
++        pthread_setspecific(ndn_cache_key, t_cache);
++        /* If we have no cache, we can't look up ... */
++        return 0;
+     }
+-    slapi_rwlock_unlock(ndn_cache_lock);
+ 
+-    return rv;
+-}
++    t_cache->tries++;
+ 
+-/*
+- *  Move this lru node to the top of the list
+- */
+-static void
+-ndn_cache_update_lru(struct ndn_cache_lru **node)
+-{
+-    struct ndn_cache_lru *prev, *next, *curr_node = *node;
++    /*
++     * Hash our DN ...
++     */
++    uint64_t dn_hash = sds_siphash13(dn, dn_len, t_cache->key);
++    /* Where should it be? */
++    size_t expect_slot = dn_hash % t_cache->slots;
+ 
+-    if(curr_node == NULL){
+-        return;
+-    }
+-    PR_Lock(lru_lock);
+-    if(curr_node->prev == NULL){
+-        /* already the top node */
+-        PR_Unlock(lru_lock);
+-        return;
+-    }
+-    prev = curr_node->prev;
+-    next = curr_node->next;
+-    if(next){
+-        next->prev = prev;
+-        prev->next = next;
+-    } else {
+-        /* this was the tail, so reset the tail */
+-        ndn_cache->tail = prev;
+-        prev->next = NULL;
++    /*
++     * Is it there?
++     */
++    if (t_cache->table[expect_slot] != NULL) {
++        /*
++         * Check it really matches, could be collision.
++         */
++        struct ndn_cache_value *node = t_cache->table[expect_slot];
++        while (node != NULL) {
++            if (strcmp(dn, node->dn) == 0) {
++                /*
++                 * Update LRU
++                 * Are we already the tail? If so, we can just skip.
++                 * remember, this means in a set of 1, we will always be tail
++                 */
++                if (t_cache->tail != node) {
++                    /*
++                     * Okay, we are *not* the tail. We could be anywhere between
++                     * tail -> ... -> x -> head
++                     * or even, we are the head ourself.
++                     */
++                    if (t_cache->head == node) {
++                        /* We are the head, update head to our predecessor */
++                        t_cache->head = node->prev;
++                        /* Remember, the head has no next. */
++                        t_cache->head->next = NULL;
++                    } else {
++                        /* Right, we aren't the head, so we have a next node. */
++                        node->next->prev = node->prev;
++                    }
++                    /* Because we must be in the middle somewhere, we can assume next and prev exist. */
++                    node->prev->next = node->next;
++                    /*
++                     * Tail can't be NULL if we have a value in the cache, so we can
++                     * just deref this.
++                     */
++                    node->next = t_cache->tail;
++                    t_cache->tail->prev = node;
++                    t_cache->tail = node;
++                    node->prev = NULL;
++                }
++                /* Update that we have a hit.*/
++                t_cache->hits++;
++                /* Cope the NDN to the caller. */
++                *ndn = slapi_ch_strdup(node->ndn);
++                /* Indicate to the caller to free this. */
++                *rc = 1;
++                ndn_thread_cache_commit_status(t_cache);
++                return 1;
++            }
++            node = node->child;
++        }
+     }
+-    curr_node->prev = NULL;
+-    curr_node->next = ndn_cache->head;
+-    ndn_cache->head->prev = curr_node;
+-    ndn_cache->head = curr_node;
+-    PR_Unlock(lru_lock);
++    /* If we miss, we need to duplicate dn to udn here. */
++    *udn = slapi_ch_strdup(dn);
++    *rc = 0;
++    ndn_thread_cache_commit_status(t_cache);
++    return 0;
+ }
+ 
+ /*
+@@ -2936,176 +3233,102 @@ ndn_cache_update_lru(struct ndn_cache_lru **node)
+ static void
+ ndn_cache_add(char *dn, size_t dn_len, char *ndn, size_t ndn_len)
+ {
+-    struct ndn_hash_val *ht_entry;
+-    struct ndn_cache_lru *new_node = NULL;
+-    PLHashEntry *he;
+-    int size;
+-
+-    if(ndn_started == 0 || dn_len == 0){
++    if (ndn_enabled == 0) {
+         return;
+     }
+-    if(strlen(ndn) > ndn_len){
++    if (dn_len == 0) {
++        return;
++    }
++    if (strlen(ndn) > ndn_len) {
+         /* we need to null terminate the ndn */
+         *(ndn + ndn_len) = '\0';
+     }
+     /*
+      *  Calculate the approximate memory footprint of the hash entry, key, and lru entry.
+      */
+-    size = (dn_len * 2) + ndn_len + sizeof(PLHashEntry) + sizeof(struct ndn_hash_val) + sizeof(struct ndn_cache_lru);
++    struct ndn_cache_value *new_value = (struct ndn_cache_value *)slapi_ch_calloc(1, sizeof(struct ndn_cache_value));
++    new_value->size = sizeof(struct ndn_cache_value) + dn_len + ndn_len;
++    /* DN is alloc for us */
++    new_value->dn = dn;
++    /* But we need to copy ndn */
++    new_value->ndn = slapi_ch_strdup(ndn);
++
+     /*
+-     *  Create our LRU node
++     * Get our local cache out.
+      */
+-    new_node = (struct ndn_cache_lru *)slapi_ch_malloc(sizeof(struct ndn_cache_lru));
+-    if(new_node == NULL){
+-        slapi_log_err(SLAPI_LOG_ERR, "ndn_cache_add", "Failed to allocate new lru node.\n");
+-        return;
++    struct ndn_cache *t_cache = pthread_getspecific(ndn_cache_key);
++    if (t_cache == NULL) {
++        t_cache = ndn_thread_cache_create(t_cache_stats.thread_max_size, t_cache_stats.slots);
++        pthread_setspecific(ndn_cache_key, t_cache);
+     }
+-    new_node->prev = NULL;
+-    new_node->key = dn; /* dn has already been allocated */
+     /*
+-     *  Its possible this dn was added to the hash by another thread.
++     * Hash the DN
+      */
+-    slapi_rwlock_wrlock(ndn_cache_lock);
+-    ht_entry = (struct ndn_hash_val *)PL_HashTableLookupConst(ndn_cache_hashtable, dn);
+-    if(ht_entry){
+-        /* already exists, free the node and return */
+-        slapi_rwlock_unlock(ndn_cache_lock);
+-        slapi_ch_free_string(&new_node->key);
+-        slapi_ch_free((void **)&new_node);
+-        return;
+-    }
++    uint64_t dn_hash = sds_siphash13(new_value->dn, dn_len, t_cache->key);
+     /*
+-     *  Create the hash entry
++     * Get the insert slot: This works because the number spaces of dn_hash is
++     * a 64bit int, and slots is a power of two. As a result, we end up with
++     * even distribution of the values.
+      */
+-    ht_entry = (struct ndn_hash_val *)slapi_ch_malloc(sizeof(struct ndn_hash_val));
+-    if(ht_entry == NULL){
+-        slapi_rwlock_unlock(ndn_cache_lock);
+-        slapi_log_err(SLAPI_LOG_ERR, "ndn_cache_add", "Failed to allocate new hash entry.\n");
+-        slapi_ch_free_string(&new_node->key);
+-        slapi_ch_free((void **)&new_node);
+-        return;
+-    }
+-    ht_entry->ndn = slapi_ch_malloc(ndn_len + 1);
+-    memcpy(ht_entry->ndn, ndn, ndn_len);
+-    ht_entry->ndn[ndn_len] = '\0';
+-    ht_entry->len = ndn_len;
+-    ht_entry->size = size;
+-    ht_entry->lru_node = new_node;
++    size_t insert_slot = dn_hash % t_cache->slots;
++    /* Track this for free */
++    new_value->slot = insert_slot;
++
+     /*
+-     *  Check if our cache is full
++     * Okay, check if we have space, else we need to trim nodes from
++     * the LRU
+      */
+-    PR_Lock(lru_lock); /* grab the lru lock now, as ndn_cache_flush needs it */
+-    if(ndn_cache->cache_max_size != 0 && ((ndn_cache->cache_size + size) > ndn_cache->cache_max_size)){
+-        ndn_cache_flush();
++    while (t_cache->head && (t_cache->size + new_value->size) > t_cache->max_size) {
++        struct ndn_cache_value *trim_node = t_cache->head;
++        ndn_thread_cache_value_destroy(t_cache, trim_node);
+     }
++
+     /*
+-     * Set the ndn cache lru nodes
++     * Add it!
+      */
+-    if(ndn_cache->head == NULL && ndn_cache->tail == NULL){
+-        /* this is the first node */
+-        ndn_cache->head = new_node;
+-        ndn_cache->tail = new_node;
+-        new_node->next = NULL;
++    if (t_cache->table[insert_slot] == NULL) {
++        t_cache->table[insert_slot] = new_value;
+     } else {
+-        new_node->next = ndn_cache->head;
+-        if(ndn_cache->head)
+-            ndn_cache->head->prev = new_node;
++        /*
++         * Hash collision! We need to replace the bucket then ....
++         * insert at the head of the slot to make this simpler.
++         */
++        new_value->child = t_cache->table[insert_slot];
++        t_cache->table[insert_slot] = new_value;
+     }
+-    ndn_cache->head = new_node;
+-    PR_Unlock(lru_lock);
++
+     /*
+-     *  Add the new object to the hashtable, and update our stats
++     * Finally, stick this onto the tail because it's the newest.
+      */
+-    he = PL_HashTableAdd(ndn_cache_hashtable, new_node->key, (void *)ht_entry);
+-    if(he == NULL){
+-        slapi_log_err(SLAPI_LOG_ERR, "ndn_cache_add", "Failed to add new entry to hash(%s)\n",dn);
+-    } else {
+-        ndn_cache->cache_count++;
+-        ndn_cache->cache_size += size;
++    if (t_cache->head == NULL) {
++        t_cache->head = new_value;
+     }
+-    slapi_rwlock_unlock(ndn_cache_lock);
+-}
+-
+-/*
+- *  cache is full, remove the least used dn's.  lru_lock/ndn_cache write lock are already taken
+- */
+-static void
+-ndn_cache_flush(void)
+-{
+-    struct ndn_cache_lru *node, *next, *flush_node;
+-    int i;
+-
+-    node = ndn_cache->tail;
+-    for(i = 0; node && i < NDN_FLUSH_COUNT && ndn_cache->cache_count > NDN_MIN_COUNT; i++){
+-        flush_node = node;
+-        /* update the lru */
+-        next = node->prev;
+-        next->next = NULL;
+-        ndn_cache->tail = next;
+-        node = next;
+-        /* now update the hash */
+-        ndn_cache->cache_count--;
+-        ndn_cache_delete(flush_node->key);
+-        slapi_ch_free_string(&flush_node->key);
+-        slapi_ch_free((void **)&flush_node);
++    if (t_cache->tail != NULL) {
++        new_value->next = t_cache->tail;
++        t_cache->tail->prev = new_value;
+     }
++    t_cache->tail = new_value;
+ 
+-    slapi_log_err(SLAPI_LOG_CACHE, "ndn_cache_flush","Flushed cache.\n");
+-}
+-
+-static void
+-ndn_cache_free(void)
+-{
+-    struct ndn_cache_lru *node, *next, *flush_node;
+-
+-    if(!ndn_cache){
+-        return;
+-    }
+-
+-    node = ndn_cache->tail;
+-    while(node && ndn_cache->cache_count){
+-        flush_node = node;
+-        /* update the lru */
+-        next = node->prev;
+-        if(next){
+-            next->next = NULL;
+-        }
+-        ndn_cache->tail = next;
+-        node = next;
+-        /* now update the hash */
+-        ndn_cache->cache_count--;
+-        ndn_cache_delete(flush_node->key);
+-        slapi_ch_free_string(&flush_node->key);
+-        slapi_ch_free((void **)&flush_node);
+-    }
+-}
+-
+-/* this is already "write" locked from ndn_cache_add */
+-static void
+-ndn_cache_delete(char *dn)
+-{
+-    struct ndn_hash_val *ht_entry;
++    /*
++     * And update the stats.
++     */
++    t_cache->size = t_cache->size + new_value->size;
++    t_cache->count++;
+ 
+-    ht_entry = (struct ndn_hash_val *)PL_HashTableLookupConst(ndn_cache_hashtable, dn);
+-    if(ht_entry){
+-        ndn_cache->cache_size -= ht_entry->size;
+-        slapi_ch_free_string(&ht_entry->ndn);
+-        slapi_ch_free((void **)&ht_entry);
+-        PL_HashTableRemove(ndn_cache_hashtable, dn);
+-    }
+ }
+ 
+ /* stats for monitor */
+ void
+-ndn_cache_get_stats(PRUint64 *hits, PRUint64 *tries, size_t *size, size_t *max_size, long *count)
+-{
+-    slapi_rwlock_rdlock(ndn_cache_lock);
+-    *hits = slapi_counter_get_value(ndn_cache->cache_hits);
+-    *tries = slapi_counter_get_value(ndn_cache->cache_tries);
+-    *size = ndn_cache->cache_size;
+-    *max_size = ndn_cache->cache_max_size;
+-    *count = ndn_cache->cache_count;
+-    slapi_rwlock_unlock(ndn_cache_lock);
++ndn_cache_get_stats(PRUint64 *hits, PRUint64 *tries, size_t *size, size_t *max_size, size_t *thread_size, size_t *evicts, size_t *slots, long *count)
++{
++    *max_size = t_cache_stats.max_size;
++    *thread_size = t_cache_stats.thread_max_size;
++    *slots = t_cache_stats.slots;
++    *evicts = slapi_counter_get_value(t_cache_stats.cache_evicts);
++    *hits = slapi_counter_get_value(t_cache_stats.cache_hits);
++    *tries = slapi_counter_get_value(t_cache_stats.cache_tries);
++    *size = slapi_counter_get_value(t_cache_stats.cache_size);
++    *count = slapi_counter_get_value(t_cache_stats.cache_count);
+ }
+ 
+ /* Common ancestor sdn is allocated.
+diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
+index 3910dbe..68b59f3 100644
+--- a/ldap/servers/slapd/slapi-private.h
++++ b/ldap/servers/slapd/slapi-private.h
+@@ -380,7 +380,7 @@ char *slapi_dn_normalize_case_original( char *dn );
+ void ndn_cache_init(void);
+ void ndn_cache_destroy(void);
+ int ndn_cache_started(void);
+-void ndn_cache_get_stats(PRUint64 *hits, PRUint64 *tries, size_t *size, size_t *max_size, long *count);
++void ndn_cache_get_stats(PRUint64 *hits, PRUint64 *tries, size_t *size, size_t *max_size, size_t *thread_size, size_t *evicts, size_t *slots, long *count);
+ #define NDN_DEFAULT_SIZE 20971520 /* 20mb - size of normalized dn cache */
+ 
+ /* filter.c */
+-- 
+2.9.4
+
diff --git a/SOURCES/0063-Ticket-49330-Add-endian-header-file-check-to-configu.patch b/SOURCES/0063-Ticket-49330-Add-endian-header-file-check-to-configu.patch
new file mode 100644
index 0000000..f1dce6f
--- /dev/null
+++ b/SOURCES/0063-Ticket-49330-Add-endian-header-file-check-to-configu.patch
@@ -0,0 +1,25 @@
+From c9817ebe42a97ac7df155582957927b4d1d08c5f Mon Sep 17 00:00:00 2001
+From: Mark Reynolds <mreynolds@redhat.com>
+Date: Mon, 18 Sep 2017 14:55:14 -0400
+Subject: [PATCH] Ticket 49330 - Add endian header file
+
+---
+ configure.ac | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/configure.ac b/configure.ac
+index 67217af..163db7d 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -39,7 +39,7 @@ AC_PROG_LIBTOOL
+ AC_HEADER_DIRENT
+ AC_HEADER_STDC
+ AC_HEADER_SYS_WAIT
+-AC_CHECK_HEADERS([arpa/inet.h errno.h fcntl.h malloc.h netdb.h netinet/in.h stdlib.h string.h strings.h sys/file.h sys/socket.h sys/time.h syslog.h unistd.h inttypes.h mntent.h sys/sysinfo.h])
++AC_CHECK_HEADERS([arpa/inet.h errno.h fcntl.h malloc.h netdb.h netinet/in.h stdlib.h string.h strings.h sys/file.h sys/socket.h sys/time.h syslog.h unistd.h mntent.h sys/sysinfo.h sys/endian.h endian.h])
+ 
+ # Checks for typedefs, structures, and compiler characteristics.
+ AC_HEADER_STAT
+-- 
+2.9.5
+
diff --git a/SOURCES/0064-Ticket-49257-only-register-modify-callbacks.patch b/SOURCES/0064-Ticket-49257-only-register-modify-callbacks.patch
new file mode 100644
index 0000000..5391136
--- /dev/null
+++ b/SOURCES/0064-Ticket-49257-only-register-modify-callbacks.patch
@@ -0,0 +1,87 @@
+From bbe3403a88f9adecbd5d4187ceeb080fb51d9d14 Mon Sep 17 00:00:00 2001
+From: Mark Reynolds <mreynolds@redhat.com>
+Date: Wed, 31 May 2017 11:15:13 -0400
+Subject: [PATCH] Ticket 49257 - only register modify callbacks
+
+Bug Description:  Regression.  In the previous fix we called
+                  ldbm_instance_config_load_dse_info() to register all
+                  the dse preop callbacks.  Previously this was only done
+                  when creating an instance.  It was not designed to be
+                  used outside of that context, and it caused error 53's
+                  when trying to add a backend after instance creation.
+
+Fix Description:  Just register the "modify" DSE preop callbacks.
+
+https://pagure.io/389-ds-base/issue/49257
+
+Reviewed by: ?
+
+(cherry picked from commit 75a32a8829297a5cab303590d049f581740cf87e)
+---
+ ldap/servers/slapd/back-ldbm/instance.c             | 12 +++---------
+ ldap/servers/slapd/back-ldbm/ldbm_config.h          |  2 +-
+ ldap/servers/slapd/back-ldbm/ldbm_instance_config.c | 13 +++++++++++++
+ 3 files changed, 17 insertions(+), 10 deletions(-)
+
+diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
+index 8b38644..f067d22 100644
+--- a/ldap/servers/slapd/back-ldbm/instance.c
++++ b/ldap/servers/slapd/back-ldbm/instance.c
+@@ -305,15 +305,9 @@ ldbm_instance_startall(struct ldbminfo *li)
+         if (rc1 != 0) {
+             rc = rc1;
+         } else {
+-            if(ldbm_instance_config_load_dse_info(inst) != 0){
+-                slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_startall",
+-                    "Loading database instance configuration failed for (%s)\n",
+-                    inst->inst_name);
+-                rc = -1;
+-            } else {
+-                vlv_init(inst);
+-                slapi_mtn_be_started(inst->inst_be);
+-            }
++            ldbm_instance_register_modify_callback(inst);
++            vlv_init(inst);
++            slapi_mtn_be_started(inst->inst_be);
+         }
+         inst_obj = objset_next_obj(li->li_instance_set, inst_obj);
+     }
+diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.h b/ldap/servers/slapd/back-ldbm/ldbm_config.h
+index ddec3a8..ea59739 100644
+--- a/ldap/servers/slapd/back-ldbm/ldbm_config.h
++++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h
+@@ -157,6 +157,6 @@ int
+ ldbm_instance_index_config_enable_index(ldbm_instance *inst, Slapi_Entry* e);
+ int ldbm_instance_create_default_user_indexes(ldbm_instance *inst);
+ void ldbm_config_destroy(struct ldbminfo *li);
+-
++void ldbm_instance_register_modify_callback(ldbm_instance *inst);
+ 
+ #endif /* _LDBM_CONFIG_H_ */
+diff --git a/ldap/servers/slapd/back-ldbm/ldbm_instance_config.c b/ldap/servers/slapd/back-ldbm/ldbm_instance_config.c
+index 49a6cac..8fb4119 100644
+--- a/ldap/servers/slapd/back-ldbm/ldbm_instance_config.c
++++ b/ldap/servers/slapd/back-ldbm/ldbm_instance_config.c
+@@ -554,6 +554,19 @@ static int ldbm_instance_deny_config(Slapi_PBlock *pb, Slapi_Entry *e,
+     return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ 
++void
++ldbm_instance_register_modify_callback(ldbm_instance *inst)
++{
++    struct ldbminfo *li = inst->inst_li;
++    char *dn = NULL;
++
++    dn = slapi_create_dn_string("cn=%s,cn=%s,cn=plugins,cn=config",
++                                inst->inst_name, li->li_plugin->plg_name);
++    slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, dn,
++        LDAP_SCOPE_BASE, "(objectclass=*)",
++        ldbm_instance_modify_config_entry_callback, (void *) inst);
++    slapi_ch_free_string(&dn);
++}
+ /* Reads in any config information held in the dse for the given
+  * entry.  Creates dse entries used to configure the given instance
+  * if they don't already exist.  Registers dse callback functions to
+-- 
+2.9.5
+
diff --git a/SOURCES/0065-Ticket-49291-slapi_search_internal_callback_pb-may-S.patch b/SOURCES/0065-Ticket-49291-slapi_search_internal_callback_pb-may-S.patch
new file mode 100644
index 0000000..219ba58
--- /dev/null
+++ b/SOURCES/0065-Ticket-49291-slapi_search_internal_callback_pb-may-S.patch
@@ -0,0 +1,46 @@
+From 28529671057c95327a35c326ee99fcafccad9de9 Mon Sep 17 00:00:00 2001
+From: Thierry Bordaz <tbordaz@redhat.com>
+Date: Wed, 14 Jun 2017 18:36:55 +0200
+Subject: [PATCH] Ticket 49291 - slapi_search_internal_callback_pb may SIGSEV
+ if related pblock has not operation set
+
+Bug Description:
+    if slapi_search_internal_set_pb is called with an invalid (NULL) base, the pblock should not
+    be used to call send_ldap_result. If it is, the send_ldap_result trying to derefence the
+    operation pointer will crash
+
+Fix Description:
+    Check that the operation is set before derefencing it
+
+https://pagure.io/389-ds-base/issue/49291
+
+Reviewed by: Mark Reynolds
+
+Platforms tested: F23
+
+Flag Day: no
+
+Doc impact: no
+---
+ ldap/servers/slapd/result.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
+index 56257c3..f3016ca 100644
+--- a/ldap/servers/slapd/result.c
++++ b/ldap/servers/slapd/result.c
+@@ -350,6 +350,11 @@ send_ldap_result_ext(
+ 	slapi_pblock_get (pb, SLAPI_BIND_METHOD, &bind_method);
+ 	slapi_pblock_get (pb, SLAPI_OPERATION, &operation);
+ 
++	if (operation == NULL) {
++		slapi_log_err(SLAPI_LOG_ERR, "send_ldap_result_ext", "No operation found: slapi_search_internal_set_pb was incomplete (invalid 'base' ?)\n");
++		return;
++	}
++
+ 	if (operation->o_status == SLAPI_OP_STATUS_RESULT_SENT) {
+ 		return; /* result already sent */
+ 	}
+-- 
+2.9.5
+
diff --git a/SOURCES/0066-Ticket-49370-local-password-policies-should-use-the-.patch b/SOURCES/0066-Ticket-49370-local-password-policies-should-use-the-.patch
new file mode 100644
index 0000000..2753f3b
--- /dev/null
+++ b/SOURCES/0066-Ticket-49370-local-password-policies-should-use-the-.patch
@@ -0,0 +1,51 @@
+From 1ec56936d29985a55f9529c1ea3e71056557b3ff Mon Sep 17 00:00:00 2001
+From: Mark Reynolds <mreynolds@redhat.com>
+Date: Fri, 1 Sep 2017 09:24:55 -0400
+Subject: [PATCH] Ticket 49370 - local password policies should use the same
+ defaults as the global policy
+
+Description:  When a local password policy (subtree/user) is created it does not use
+              the same defaults as the global policy.  This causes inconsistent behavior.
+
+https://pagure.io/389-ds-base/issue/49370
+
+Reviewed by: firstyear(Thanks!)
+---
+ ldap/servers/slapd/pw.c | 21 +++++++++++++++++++++
+ 1 file changed, 21 insertions(+)
+
+diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c
+index 378d148..19a863a 100644
+--- a/ldap/servers/slapd/pw.c
++++ b/ldap/servers/slapd/pw.c
+@@ -1768,6 +1768,27 @@ new_passwdPolicy(Slapi_PBlock *pb, const char *dn)
+ 				goto done;
+ 			}
+ 
++			/* Set the default values */
++			pwdpolicy->pw_mintokenlength = SLAPD_DEFAULT_PW_MINTOKENLENGTH;
++			pwdpolicy->pw_minlength = SLAPD_DEFAULT_PW_MINLENGTH;
++			pwdpolicy->pw_mindigits = SLAPD_DEFAULT_PW_MINDIGITS;
++			pwdpolicy->pw_minalphas = SLAPD_DEFAULT_PW_MINALPHAS;
++			pwdpolicy->pw_minuppers = SLAPD_DEFAULT_PW_MINUPPERS;
++			pwdpolicy->pw_minlowers = SLAPD_DEFAULT_PW_MINLOWERS;
++			pwdpolicy->pw_minspecials = SLAPD_DEFAULT_PW_MINSPECIALS;
++			pwdpolicy->pw_min8bit = SLAPD_DEFAULT_PW_MIN8BIT;
++			pwdpolicy->pw_maxrepeats = SLAPD_DEFAULT_PW_MAXREPEATS;
++			pwdpolicy->pw_mincategories = SLAPD_DEFAULT_PW_MINCATEGORIES;
++			pwdpolicy->pw_mintokenlength = SLAPD_DEFAULT_PW_MINTOKENLENGTH;
++			pwdpolicy->pw_maxage = SLAPD_DEFAULT_PW_MAXAGE;
++			pwdpolicy->pw_minage = SLAPD_DEFAULT_PW_MINAGE;
++			pwdpolicy->pw_warning = SLAPD_DEFAULT_PW_WARNING;
++			pwdpolicy->pw_inhistory = SLAPD_DEFAULT_PW_INHISTORY;
++			pwdpolicy->pw_maxfailure = SLAPD_DEFAULT_PW_MAXFAILURE;
++			pwdpolicy->pw_lockduration = SLAPD_DEFAULT_PW_LOCKDURATION;
++			pwdpolicy->pw_resetfailurecount = SLAPD_DEFAULT_PW_RESETFAILURECOUNT;
++			pwdpolicy->pw_gracelimit = SLAPD_DEFAULT_PW_GRACELIMIT;
++
+ 			/* set the default passwordLegacyPolicy setting */
+ 			pwdpolicy->pw_is_legacy = 1;
+ 
+-- 
+2.9.5
+
diff --git a/SOURCES/0067-Ticket-49380-Crash-when-adding-invalid-replication.patch b/SOURCES/0067-Ticket-49380-Crash-when-adding-invalid-replication.patch
new file mode 100644
index 0000000..cb988c3
--- /dev/null
+++ b/SOURCES/0067-Ticket-49380-Crash-when-adding-invalid-replication.patch
@@ -0,0 +1,55 @@
+From af59afa03296160577e419257772d5319796a992 Mon Sep 17 00:00:00 2001
+From: Mark Reynolds <mreynolds@redhat.com>
+Date: Thu, 14 Sep 2017 08:32:11 -0400
+Subject: [PATCH] Ticket 49380 - Crash when adding invalid replication 
+ agreement
+
+    Bug Description:  If you add a replication agreement with an invalid "replicaEnabled" value
+                      the server crashes when freeing the replica schedule.  This is because the
+                      schedule never gets allocated before the rror conidtion is hit, and then
+                      it get dereferenced.
+
+    Fix Description:  Check for a NULL schedule before trying to destroy it.
+
+    https://pagure.io/389-ds-base/issue/49380
+
+    Reviewed by: tbordaz(Thanks!)
+---
+ ldap/servers/plugins/replication/repl5_schedule.c | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/ldap/servers/plugins/replication/repl5_schedule.c b/ldap/servers/plugins/replication/repl5_schedule.c
+index 60ee6f2..4572e63 100644
+--- a/ldap/servers/plugins/replication/repl5_schedule.c
++++ b/ldap/servers/plugins/replication/repl5_schedule.c
+@@ -130,6 +130,10 @@ schedule_destroy(Schedule *s)
+ {
+ 	int i;
+ 
++	if (s == NULL) {
++		return;
++	}
++
+     /* unschedule update window event if exists */
+     unschedule_window_state_change_event (s);
+ 
+@@ -177,11 +181,15 @@ free_schedule_list(schedule_item **schedule_list)
+ int
+ schedule_set(Schedule *sch, Slapi_Attr *attr)
+ {
+-	int return_value;
++	int return_value = -1;
+ 	schedule_item *si = NULL;
+ 	schedule_item *new_schedule_list = NULL;
+ 	int valid = 1;
+ 	
++	if (sch == NULL) {
++		return return_value;
++	}
++
+ 	if (NULL != attr)
+ 	{
+ 		int ind;
+-- 
+2.9.5
+
diff --git a/SOURCES/0068-Ticket-49380-Add-CI-test.patch b/SOURCES/0068-Ticket-49380-Add-CI-test.patch
new file mode 100644
index 0000000..d59e7bf
--- /dev/null
+++ b/SOURCES/0068-Ticket-49380-Add-CI-test.patch
@@ -0,0 +1,81 @@
+From d336e3558655d44f8ba797392af882e33d492958 Mon Sep 17 00:00:00 2001
+From: Mark Reynolds <mreynolds@redhat.com>
+Date: Thu, 14 Sep 2017 14:15:25 -0400
+Subject: [PATCH] Ticket 49380 - Add CI test
+
+Description:  Add test to verify invalid agreement is rejected, and it
+              does not cause a crash
+
+https://pagure.io/389-ds-base/issue/49380
+
+Reviewed by: spichugi(Thanks!)
+
+(cherry picked from commit 02d76b61489f105f81d72d4f3848e2444463289b)
+---
+ .../tests/suites/replication/acceptance_test.py    | 43 ++++++++++++++++++++++
+ 1 file changed, 43 insertions(+)
+
+diff --git a/dirsrvtests/tests/suites/replication/acceptance_test.py b/dirsrvtests/tests/suites/replication/acceptance_test.py
+index e6f2ef7..2f8b180 100644
+--- a/dirsrvtests/tests/suites/replication/acceptance_test.py
++++ b/dirsrvtests/tests/suites/replication/acceptance_test.py
+@@ -3,6 +3,12 @@ from lib389.tasks import *
+ from lib389.utils import *
+ from lib389.topologies import topology_m4 as topo
+ 
++from lib389._constants import (BACKEND_NAME, DEFAULT_SUFFIX, LOG_REPLICA, REPLICA_RUV_FILTER,
++                               ReplicaRole, REPLICATION_BIND_DN, REPLICATION_BIND_PW,
++                               REPLICATION_BIND_METHOD, REPLICATION_TRANSPORT, defaultProperties,
++                               RA_NAME, RA_BINDDN, RA_BINDPW, RA_METHOD, RA_TRANSPORT_PROT,
++                               DN_DM, PASSWORD, LOG_DEFAULT, RA_ENABLED, RA_SCHEDULE)
++
+ TEST_ENTRY_NAME = 'mmrepl_test'
+ TEST_ENTRY_DN = 'uid={},{}'.format(TEST_ENTRY_NAME, DEFAULT_SUFFIX)
+ 
+@@ -193,6 +199,43 @@ def test_modrdn_entry(topo, test_entry, delold):
+         topo.ms["master1"].delete_s(newrdn_dn)
+ 
+ 
++def test_invalid_agmt(topo_m4):
++    """Test adding that an invalid agreement is properly rejected and does not crash the server
++
++    :id: 6c3b2a7e-edcd-4327-a003-6bd878ff722b
++    :setup: MMR with four masters
++    :steps:
++        1. Add invalid agreement (nsds5ReplicaEnabled set to invalid value)
++        2. Verify the server is still running
++    :expectedresults:
++        1. Invalid repl agreement should be rejected
++        2. Server should be still running
++    """
++    m1 = topo_m4.ms["master1"]
++
++    # Add invalid agreement (nsds5ReplicaEnabled set to invalid value)
++    AGMT_DN = 'cn=whatever,cn=replica,cn="dc=example,dc=com",cn=mapping tree,cn=config'
++    try:
++        invalid_props = {RA_ENABLED: 'True',  # Invalid value
++                         RA_SCHEDULE: '0001-2359 0123456'}
++        m1.agreement.create(suffix=DEFAULT_SUFFIX, host='localhost', port=389, properties=invalid_props)
++    except ldap.UNWILLING_TO_PERFORM:
++        m1.log.info('Invalid repl agreement correctly rejected')
++    except ldap.LDAPError as e:
++        m1.log.fatal('Got unexpected error adding invalid agreement: ' + str(e))
++        assert False
++    else:
++        m1.log.fatal('Invalid agreement was incorrectly accepted by the server')
++        assert False
++
++    # Verify the server is still running
++    try:
++        m1.simple_bind_s(DN_DM, PASSWORD)
++    except ldap.LDAPError as e:
++        m1.log.fatal('Failed to bind: ' + str(e))
++        assert False
++
++
+ if __name__ == '__main__':
+     # Run isolated
+     # -s for DEBUG mode
+-- 
+2.9.5
+
diff --git a/SOURCES/0069-Ticket-49327-password-expired-control-not-sent-durin.patch b/SOURCES/0069-Ticket-49327-password-expired-control-not-sent-durin.patch
new file mode 100644
index 0000000..e4b182a
--- /dev/null
+++ b/SOURCES/0069-Ticket-49327-password-expired-control-not-sent-durin.patch
@@ -0,0 +1,610 @@
+From 3ab8a78cd27cc8d2ad7a2b322a4fe73c43a3db08 Mon Sep 17 00:00:00 2001
+From: Mark Reynolds <mreynolds@redhat.com>
+Date: Thu, 14 Sep 2017 15:47:53 -0400
+Subject: [PATCH] Ticket 49327 - password expired control not sent during grace
+ logins
+
+Bug Description:  When a password is expired, but within the grace login limit,
+                  we should still send the expired control even though we allowed
+                  the bind.
+
+Fix Description:  new_new_passwd() returned a variety of result codes that required
+                  the caller to set the response controls.  This was hard to read and
+                  process.  Instead I added all the controls inside the function, and
+                  return success or failure to the caller.
+
+https://pagure.io/389-ds-base/issue/49327
+
+Reviewed by: gparente & tbordaz (Thanks!!)
+
+(cherry picked from commit fbd32c4e27af9f331ee3a42dec944895a6efe2ad)
+---
+ ldap/servers/plugins/replication/repl_extop.c |   5 +-
+ ldap/servers/slapd/bind.c                     |  18 +-
+ ldap/servers/slapd/proto-slap.h               |   3 +-
+ ldap/servers/slapd/pw_mgmt.c                  | 453 +++++++++++++-------------
+ ldap/servers/slapd/saslbind.c                 |  20 +-
+ 5 files changed, 238 insertions(+), 261 deletions(-)
+
+diff --git a/ldap/servers/plugins/replication/repl_extop.c b/ldap/servers/plugins/replication/repl_extop.c
+index a39d918..96ad7dd 100644
+--- a/ldap/servers/plugins/replication/repl_extop.c
++++ b/ldap/servers/plugins/replication/repl_extop.c
+@@ -1173,8 +1173,9 @@ send_response:
+ 			 * On the supplier, we need to close the connection so
+ 			 * that the RA will restart a new session in a clear state 
+ 			 */
+-			slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name, "multimaster_extop_StartNSDS50ReplicationRequest - "
+-				"already acquired replica: disconnect conn=%d\n", connid);
++			slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name, 
++			              "multimaster_extop_StartNSDS50ReplicationRequest - "
++			              "already acquired replica: disconnect conn=%" PRIu64 "\n", connid);
+ 			slapi_disconnect_server(conn);
+             
+ 		}
+diff --git a/ldap/servers/slapd/bind.c b/ldap/servers/slapd/bind.c
+index d6c7668..e6cad7f 100644
+--- a/ldap/servers/slapd/bind.c
++++ b/ldap/servers/slapd/bind.c
+@@ -673,8 +673,7 @@ do_bind( Slapi_PBlock *pb )
+             slapi_entry_free(referral);
+             goto free_and_return;
+         } else if (auto_bind || rc == SLAPI_BIND_SUCCESS || rc == SLAPI_BIND_ANONYMOUS) {
+-            long t;
+-            char* authtype = NULL;
++            char *authtype = NULL;
+             /* rc is SLAPI_BIND_SUCCESS or SLAPI_BIND_ANONYMOUS */
+             if(auto_bind) {
+                 rc = SLAPI_BIND_SUCCESS;
+@@ -761,19 +760,8 @@ do_bind( Slapi_PBlock *pb )
+                                          slapi_ch_strdup(slapi_sdn_get_ndn(sdn)),
+                                          NULL, NULL, NULL, bind_target_entry);
+                     if (!slapi_be_is_flag_set(be, SLAPI_BE_FLAG_REMOTE_DATA)) {
+-                        /* check if need new password before sending 
+-                           the bind success result */
+-                        myrc = need_new_pw(pb, &t, bind_target_entry, pw_response_requested);
+-                        switch (myrc) {
+-                        case 1:
+-                            (void)slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
+-                            break;
+-                        case 2:
+-                            (void)slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRING, t);
+-                            break;
+-                        default:
+-                            break;
+-                        }
++                        /* check if need new password before sending the bind success result */
++                        myrc = need_new_pw(pb, bind_target_entry, pw_response_requested);
+                     }
+                 }
+                 if (auth_response_requested) {
+diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
+index 9696ead..0ba61d7 100644
+--- a/ldap/servers/slapd/proto-slap.h
++++ b/ldap/servers/slapd/proto-slap.h
+@@ -972,7 +972,7 @@ int plugin_call_acl_verify_syntax ( Slapi_PBlock *pb, Slapi_Entry *e, char **err
+  * pw_mgmt.c
+  */
+ void pw_init( void );
+-int need_new_pw( Slapi_PBlock *pb, long *t,  Slapi_Entry *e, int pwresponse_req );
++int need_new_pw(Slapi_PBlock *pb, Slapi_Entry *e, int pwresponse_req);
+ int update_pw_info( Slapi_PBlock *pb , char *old_pw );
+ int check_pw_syntax( Slapi_PBlock *pb, const Slapi_DN *sdn, Slapi_Value **vals, 
+ 	char **old_pw, Slapi_Entry *e, int mod_op );
+@@ -982,7 +982,6 @@ void get_old_pw( Slapi_PBlock *pb, const Slapi_DN *sdn, char **old_pw);
+ int check_account_lock( Slapi_PBlock *pb, Slapi_Entry * bind_target_entry, int pwresponse_req, int account_inactivation_only /*no wire/no pw policy*/);
+ int check_pw_minage( Slapi_PBlock *pb, const Slapi_DN *sdn, struct berval **vals) ;
+ void add_password_attrs( Slapi_PBlock *pb, Operation *op, Slapi_Entry *e );
+-
+ int add_shadow_ext_password_attrs(Slapi_PBlock *pb, Slapi_Entry **e);
+ 
+ /*
+diff --git a/ldap/servers/slapd/pw_mgmt.c b/ldap/servers/slapd/pw_mgmt.c
+index 7252c08..b06e3f1 100644
+--- a/ldap/servers/slapd/pw_mgmt.c
++++ b/ldap/servers/slapd/pw_mgmt.c
+@@ -22,234 +22,239 @@
+ /* prototypes                                                               */
+ /****************************************************************************/
+ 
+-/* need_new_pw() is called when non rootdn bind operation succeeds with authentication */ 
++/*
++ * need_new_pw() is called when non rootdn bind operation succeeds with authentication
++ *
++ * Return  0 - password is okay
++ * Return -1 - password is expired, abort bind
++ */
+ int
+-need_new_pw( Slapi_PBlock *pb, long *t, Slapi_Entry *e, int pwresponse_req )
++need_new_pw(Slapi_PBlock *pb, Slapi_Entry *e, int pwresponse_req)
+ {
+-	time_t 		cur_time, pw_exp_date;
+-	Slapi_Mods smods;
+-	double		diff_t = 0;
+-	char 		*cur_time_str = NULL;
+-	char *passwordExpirationTime = NULL;
+-	char *timestring;
+-	char *dn;
+-	const Slapi_DN *sdn;
+-	passwdPolicy *pwpolicy = NULL;
+-	int	pwdGraceUserTime = 0;
+-	char graceUserTime[8];
+-
+-	if (NULL == e) {
+-		return (-1);
+-	}
+-	slapi_mods_init (&smods, 0);
+-	sdn = slapi_entry_get_sdn_const( e );
+-	dn = slapi_entry_get_ndn( e );
+-	pwpolicy = new_passwdPolicy(pb, dn);
+-
+-	/* after the user binds with authentication, clear the retry count */
+-	if ( pwpolicy->pw_lockout == 1)
+-	{
+-		if(slapi_entry_attr_get_int( e, "passwordRetryCount") > 0)
+-		{
+-			slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordRetryCount", "0");
+-		}
+-	}
+-
+-	cur_time = current_time();
+-
+-	/* get passwordExpirationTime attribute */
+-	passwordExpirationTime= slapi_entry_attr_get_charptr(e, "passwordExpirationTime");
+-
+-	if (passwordExpirationTime == NULL)
+-	{
+-		/* password expiration date is not set.
+-		 * This is ok for data that has been loaded via ldif2ldbm
+-		 * Set expiration time if needed,
+-		 * don't do further checking and return 0 */
+-		if (pwpolicy->pw_exp == 1) {
+-			pw_exp_date = time_plus_sec(cur_time, pwpolicy->pw_maxage);
+-
+-			timestring = format_genTime (pw_exp_date);
+-			slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring);
+-			slapi_ch_free_string(&timestring);
+-			slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpWarned", "0");
+-			
+-			pw_apply_mods(sdn, &smods);
+-		} else if (pwpolicy->pw_lockout == 1) {
+-			pw_apply_mods(sdn, &smods);
+-		}
+-		slapi_mods_done(&smods);
+-		return ( 0 );
+-	}
+-
+-	pw_exp_date = parse_genTime(passwordExpirationTime);
+-
+-	slapi_ch_free_string(&passwordExpirationTime);
+-
+-	/* Check if password has been reset */
+-	if ( pw_exp_date == NO_TIME ) {
+-
+-		/* check if changing password is required */  
+-		if ( pwpolicy->pw_must_change ) {
+-			/* set c_needpw for this connection to be true.  this client 
+-			   now can only change its own password */
+-			pb->pb_conn->c_needpw = 1;
+-			*t=0;
+-			/* We need to include "changeafterreset" error in
+-			 * passwordpolicy response control. So, we will not be
+-			 * done here. We remember this scenario by (c_needpw=1)
+-			 * and check it before sending the control from various
+-			 * places. We will also add LDAP_CONTROL_PWEXPIRED control
+-			 * as the return value used to be (1).
+-			 */
+-			goto skip;
+-		}
+-		/* Mark that first login occured */
+-		pw_exp_date = NOT_FIRST_TIME;
+-		timestring = format_genTime(pw_exp_date);
+-		slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring);
+-		slapi_ch_free_string(&timestring);
+-	}
++    time_t cur_time, pw_exp_date;
++    Slapi_Mods smods;
++    double diff_t = 0;
++    char *cur_time_str = NULL;
++    char *passwordExpirationTime = NULL;
++    char *timestring;
++    char *dn;
++    const Slapi_DN *sdn;
++    passwdPolicy *pwpolicy = NULL;
++    int pwdGraceUserTime = 0;
++    char graceUserTime[16] = {0};
++    Connection *pb_conn = NULL;
++    long t;
++
++    if (NULL == e) {
++        return (-1);
++    }
++    slapi_mods_init(&smods, 0);
++    sdn = slapi_entry_get_sdn_const(e);
++    dn = slapi_entry_get_ndn(e);
++    pwpolicy = new_passwdPolicy(pb, dn);
++
++    /* after the user binds with authentication, clear the retry count */
++    if (pwpolicy->pw_lockout == 1) {
++        if (slapi_entry_attr_get_int(e, "passwordRetryCount") > 0) {
++            slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordRetryCount", "0");
++        }
++    }
++
++    cur_time = current_time();
++
++    /* get passwordExpirationTime attribute */
++    passwordExpirationTime = slapi_entry_attr_get_charptr(e, "passwordExpirationTime");
++
++    if (passwordExpirationTime == NULL) {
++        /* password expiration date is not set.
++         * This is ok for data that has been loaded via ldif2ldbm
++         * Set expiration time if needed,
++         * don't do further checking and return 0 */
++        if (pwpolicy->pw_exp == 1) {
++            pw_exp_date = time_plus_sec(cur_time, pwpolicy->pw_maxage);
++
++            timestring = format_genTime(pw_exp_date);
++            slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring);
++            slapi_ch_free_string(&timestring);
++            slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpWarned", "0");
++
++            pw_apply_mods(sdn, &smods);
++        } else if (pwpolicy->pw_lockout == 1) {
++            pw_apply_mods(sdn, &smods);
++        }
++        slapi_mods_done(&smods);
++        return (0);
++    }
++
++    pw_exp_date = parse_genTime(passwordExpirationTime);
++
++    slapi_ch_free_string(&passwordExpirationTime);
++
++    slapi_pblock_get(pb, SLAPI_CONNECTION, &pb_conn);
++
++    /* Check if password has been reset */
++    if (pw_exp_date == NO_TIME) {
++
++        /* check if changing password is required */
++        if (pwpolicy->pw_must_change) {
++            /* set c_needpw for this connection to be true.  this client
++               now can only change its own password */
++            pb_conn->c_needpw = 1;
++            t = 0;
++            /* We need to include "changeafterreset" error in
++             * passwordpolicy response control. So, we will not be
++             * done here. We remember this scenario by (c_needpw=1)
++             * and check it before sending the control from various
++             * places. We will also add LDAP_CONTROL_PWEXPIRED control
++             * as the return value used to be (1).
++             */
++            goto skip;
++        }
++        /* Mark that first login occured */
++        pw_exp_date = NOT_FIRST_TIME;
++        timestring = format_genTime(pw_exp_date);
++        slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring);
++        slapi_ch_free_string(&timestring);
++    }
+ 
+ skip:
+-	/* if password never expires, don't need to go on; return 0 */
+-	if ( pwpolicy->pw_exp == 0 ) {
+-		/* check for "changeafterreset" condition */
+-		if (pb->pb_conn->c_needpw == 1) {
+-			if (pwresponse_req) {
+-				slapi_pwpolicy_make_response_control ( pb, -1, -1, LDAP_PWPOLICY_CHGAFTERRESET );
+-			} 
+-			slapi_add_pwd_control ( pb, LDAP_CONTROL_PWEXPIRED, 0);
+-		}
+-		pw_apply_mods(sdn, &smods);
+-		slapi_mods_done(&smods);
+-		return ( 0 );
+-	}
+-
+-	/* check if password expired.  If so, abort bind. */
+-	cur_time_str = format_genTime ( cur_time );
+-	if ((pw_exp_date != NO_TIME) && (pw_exp_date != NOT_FIRST_TIME) &&
+-	    (diff_t = difftime(pw_exp_date, parse_genTime(cur_time_str))) <= 0) {
+-		slapi_ch_free_string(&cur_time_str); /* only need this above */
+-		/* password has expired. Check the value of 
+-		 * passwordGraceUserTime and compare it
+-		 * against the value of passwordGraceLimit */
+-		pwdGraceUserTime = slapi_entry_attr_get_int( e, "passwordGraceUserTime");
+-		if ( pwpolicy->pw_gracelimit > pwdGraceUserTime ) {
+-			pwdGraceUserTime++;
+-			sprintf ( graceUserTime, "%d", pwdGraceUserTime );
+-			slapi_mods_add_string(&smods, LDAP_MOD_REPLACE,
+-				"passwordGraceUserTime", graceUserTime);
+-			pw_apply_mods(sdn, &smods);
+-			slapi_mods_done(&smods);
+-			if (pwresponse_req) {
+-				/* check for "changeafterreset" condition */
+-				if (pb->pb_conn->c_needpw == 1) {
+-					slapi_pwpolicy_make_response_control( pb, -1, 
+-						((pwpolicy->pw_gracelimit) - pwdGraceUserTime),
+-						LDAP_PWPOLICY_CHGAFTERRESET);
+-				} else {
+-					slapi_pwpolicy_make_response_control( pb, -1, 
+-						((pwpolicy->pw_gracelimit) - pwdGraceUserTime),
+-						-1);
+-				}
+-			}
+-			
+-			if (pb->pb_conn->c_needpw == 1) {
+-				slapi_add_pwd_control ( pb, LDAP_CONTROL_PWEXPIRED, 0);
+-			}
+-			return ( 0 );
+-		}
+-
+-		/* password expired and user exceeded limit of grace attemps.
+-		 * Send result and also the control */
+-		slapi_add_pwd_control ( pb, LDAP_CONTROL_PWEXPIRED, 0);
+-		if (pwresponse_req) {
+-			slapi_pwpolicy_make_response_control ( pb, -1, -1, LDAP_PWPOLICY_PWDEXPIRED );
+-		}
+-		slapi_send_ldap_result ( pb, LDAP_INVALID_CREDENTIALS, NULL,
+-			"password expired!", 0, NULL );
+-		
+-		/* abort bind */
+-		/* pass pb to do_unbind().  pb->pb_op->o_opid and 
+-		   pb->pb_op->o_tag are not right but I don't see 
+-		   do_unbind() checking for those.   We might need to 
+-		   create a pb for unbind operation.  Also do_unbind calls
+-		   pre and post ops.  Maybe we don't want to call them */
+-		if (pb->pb_conn && (LDAP_VERSION2 == pb->pb_conn->c_ldapversion)) {
+-			/* We close the connection only with LDAPv2 connections */
+-			disconnect_server( pb->pb_conn, pb->pb_op->o_connid,
+-				pb->pb_op->o_opid, SLAPD_DISCONNECT_UNBIND, 0);
+-		}
+-		/* Apply current modifications */
+-		pw_apply_mods(sdn, &smods);
+-		slapi_mods_done(&smods);
+-		return (-1);
+-	}
+-	slapi_ch_free((void **) &cur_time_str );
+-
+-	/* check if password is going to expire within "passwordWarning" */
+-	/* Note that if pw_exp_date is NO_TIME or NOT_FIRST_TIME,
+-	 * we must send warning first and this changes the expiration time.
+-	 * This is done just below since diff_t is 0 
+-	 */
+-	if ( diff_t <= pwpolicy->pw_warning ) {
+-		int pw_exp_warned = 0;
+-		
+-		pw_exp_warned = slapi_entry_attr_get_int( e, "passwordExpWarned");
+-		if ( !pw_exp_warned ){
+-			/* first time send out a warning */
+-			/* reset the expiration time to current + warning time 
+-			 * and set passwordExpWarned to true
+-			 */
+-			if (pb->pb_conn->c_needpw != 1) {
+-				pw_exp_date = time_plus_sec(cur_time, pwpolicy->pw_warning);
+-			}
+-			
+-			timestring = format_genTime(pw_exp_date);
+-			slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring);
+-			slapi_ch_free_string(&timestring);
+-
+-			slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpWarned", "1");
+-			
+-			*t = pwpolicy->pw_warning;
+-
+-		} else {
+-			*t = (long)diff_t; /* jcm: had to cast double to long */
+-		}
+-
+-		pw_apply_mods(sdn, &smods);
+-		slapi_mods_done(&smods);
+-		if (pwresponse_req) {
+-			/* check for "changeafterreset" condition */
+-			if (pb->pb_conn->c_needpw == 1) {
+-					slapi_pwpolicy_make_response_control( pb, *t, -1,
+-						LDAP_PWPOLICY_CHGAFTERRESET);
+-				} else {
+-					slapi_pwpolicy_make_response_control( pb, *t, -1,
+-						-1);
+-				}
+-		}
+-
+-		if (pb->pb_conn->c_needpw == 1) {
+-			slapi_add_pwd_control ( pb, LDAP_CONTROL_PWEXPIRED, 0);
+-		}
+-		return (2);
+-	} else {
+-		if (pwresponse_req && pwpolicy->pw_send_expiring) {
+-			slapi_pwpolicy_make_response_control( pb, diff_t, -1, -1);
+-			slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRING, diff_t);
+-		}
+-	}
+-
+-	pw_apply_mods(sdn, &smods);
+-	slapi_mods_done(&smods);
+-	/* Leftover from "changeafterreset" condition */
+-	if (pb->pb_conn->c_needpw == 1) {
+-		slapi_add_pwd_control ( pb, LDAP_CONTROL_PWEXPIRED, 0);
+-	}
+-	/* passes checking, return 0 */
+-	return( 0 );
++    /* if password never expires, don't need to go on; return 0 */
++    if (pwpolicy->pw_exp == 0) {
++        /* check for "changeafterreset" condition */
++        if (pb_conn->c_needpw == 1) {
++            if (pwresponse_req) {
++                slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_CHGAFTERRESET);
++            }
++            slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
++        }
++        pw_apply_mods(sdn, &smods);
++        slapi_mods_done(&smods);
++        return (0);
++    }
++
++    /* check if password expired.  If so, abort bind. */
++    cur_time_str = format_genTime(cur_time);
++    if ((pw_exp_date != NO_TIME) && (pw_exp_date != NOT_FIRST_TIME) &&
++        (diff_t = difftime(pw_exp_date, parse_genTime(cur_time_str))) <= 0) {
++        slapi_ch_free_string(&cur_time_str); /* only need this above */
++        /* password has expired. Check the value of
++         * passwordGraceUserTime and compare it
++         * against the value of passwordGraceLimit */
++        pwdGraceUserTime = slapi_entry_attr_get_int(e, "passwordGraceUserTime");
++        if (pwpolicy->pw_gracelimit > pwdGraceUserTime) {
++            pwdGraceUserTime++;
++            sprintf(graceUserTime, "%d", pwdGraceUserTime);
++            slapi_mods_add_string(&smods, LDAP_MOD_REPLACE,
++                                  "passwordGraceUserTime", graceUserTime);
++            pw_apply_mods(sdn, &smods);
++            slapi_mods_done(&smods);
++            if (pwresponse_req) {
++                /* check for "changeafterreset" condition */
++                if (pb_conn->c_needpw == 1) {
++                    slapi_pwpolicy_make_response_control(pb, -1,
++                                                         ((pwpolicy->pw_gracelimit) - pwdGraceUserTime),
++                                                         LDAP_PWPOLICY_CHGAFTERRESET);
++                } else {
++                    slapi_pwpolicy_make_response_control(pb, -1,
++                                                         ((pwpolicy->pw_gracelimit) - pwdGraceUserTime),
++                                                         -1);
++                }
++            }
++            slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
++            return (0);
++        }
++
++        /* password expired and user exceeded limit of grace attemps.
++         * Send result and also the control */
++        slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
++        if (pwresponse_req) {
++            slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_PWDEXPIRED);
++        }
++        slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, NULL,
++                               "password expired!", 0, NULL);
++
++        /* abort bind */
++        /* pass pb to do_unbind().  pb->pb_op->o_opid and
++           pb->pb_op->o_tag are not right but I don't see
++           do_unbind() checking for those.   We might need to
++           create a pb for unbind operation.  Also do_unbind calls
++           pre and post ops.  Maybe we don't want to call them */
++        if (pb_conn && (LDAP_VERSION2 == pb_conn->c_ldapversion)) {
++            Operation *pb_op = NULL;
++            slapi_pblock_get(pb, SLAPI_OPERATION, &pb_op);
++            /* We close the connection only with LDAPv2 connections */
++            disconnect_server(pb_conn, pb_op->o_connid,
++                              pb_op->o_opid, SLAPD_DISCONNECT_UNBIND, 0);
++        }
++        /* Apply current modifications */
++        pw_apply_mods(sdn, &smods);
++        slapi_mods_done(&smods);
++        return (-1);
++    }
++    slapi_ch_free((void **)&cur_time_str);
++
++    /* check if password is going to expire within "passwordWarning" */
++    /* Note that if pw_exp_date is NO_TIME or NOT_FIRST_TIME,
++     * we must send warning first and this changes the expiration time.
++     * This is done just below since diff_t is 0
++     */
++    if (diff_t <= pwpolicy->pw_warning) {
++        int pw_exp_warned = 0;
++
++        pw_exp_warned = slapi_entry_attr_get_int(e, "passwordExpWarned");
++        if (!pw_exp_warned) {
++            /* first time send out a warning */
++            /* reset the expiration time to current + warning time
++             * and set passwordExpWarned to true
++             */
++            if (pb_conn->c_needpw != 1) {
++                pw_exp_date = time_plus_sec(cur_time, pwpolicy->pw_warning);
++            }
++
++            timestring = format_genTime(pw_exp_date);
++            slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring);
++            slapi_ch_free_string(&timestring);
++
++            slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpWarned", "1");
++
++            t = pwpolicy->pw_warning;
++
++        } else {
++            t = (long)diff_t; /* jcm: had to cast double to long */
++        }
++
++        pw_apply_mods(sdn, &smods);
++        slapi_mods_done(&smods);
++        if (pwresponse_req) {
++            /* check for "changeafterreset" condition */
++            if (pb_conn->c_needpw == 1) {
++                slapi_pwpolicy_make_response_control(pb, t, -1, LDAP_PWPOLICY_CHGAFTERRESET);
++            } else {
++                slapi_pwpolicy_make_response_control(pb, t, -1, -1);
++            }
++        }
++
++        if (pb_conn->c_needpw == 1) {
++            slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
++        } else {
++            slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRING, t);
++        }
++        return (0);
++    } else {
++        if (pwresponse_req && pwpolicy->pw_send_expiring) {
++            slapi_pwpolicy_make_response_control(pb, diff_t, -1, -1);
++            slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRING, diff_t);
++        }
++    }
++
++    pw_apply_mods(sdn, &smods);
++    slapi_mods_done(&smods);
++    /* Leftover from "changeafterreset" condition */
++    if (pb_conn->c_needpw == 1) {
++        slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
++    }
++    /* passes checking, return 0 */
++    return (0);
+ }
+ 
+ /* Called once from main */
+diff --git a/ldap/servers/slapd/saslbind.c b/ldap/servers/slapd/saslbind.c
+index dd0c4fb..134f5aa 100644
+--- a/ldap/servers/slapd/saslbind.c
++++ b/ldap/servers/slapd/saslbind.c
+@@ -859,7 +859,6 @@ ids_sasl_mech_supported(Slapi_PBlock *pb, const char *mech)
+ void ids_sasl_check_bind(Slapi_PBlock *pb)
+ {
+     int rc, isroot;
+-    long t;
+     sasl_conn_t *sasl_conn;
+     struct propctx *propctx;
+     sasl_ssf_t *ssfp;
+@@ -1096,23 +1095,8 @@ sasl_check_result:
+         set_db_default_result_handlers(pb);
+ 
+         /* check password expiry */
+-        if (!isroot) {
+-            int pwrc;
+-
+-            pwrc = need_new_pw(pb, &t, bind_target_entry, pwresponse_requested);
+-            
+-            switch (pwrc) {
+-            case 1:
+-                slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
+-                break;
+-            case 2:
+-                slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRING, t);
+-                break;
+-            case -1:
+-                goto out;
+-            default:
+-                break;
+-            }
++        if (!isroot && need_new_pw(pb, bind_target_entry, pwresponse_requested) == -1) {
++            goto out;
+         }
+ 
+         /* attach the sasl data */
+-- 
+2.9.5
+
diff --git a/SOURCES/0070-Ticket-49379-Allowed-sasl-mapping-requires-restart.patch b/SOURCES/0070-Ticket-49379-Allowed-sasl-mapping-requires-restart.patch
new file mode 100644
index 0000000..c2e230b
--- /dev/null
+++ b/SOURCES/0070-Ticket-49379-Allowed-sasl-mapping-requires-restart.patch
@@ -0,0 +1,439 @@
+From 8a7b47602acc910d2f64439b81af3299b60359c8 Mon Sep 17 00:00:00 2001
+From: Mark Reynolds <mreynolds@redhat.com>
+Date: Mon, 18 Sep 2017 10:35:20 -0400
+Subject: [PATCH] Ticket 49379 - Allowed sasl mapping requires restart
+
+Bug Description:  If allowed sasl mechanisms are configured, and the server is
+                  restarted, trying to add new sasl mechanisms does not get applied
+                  until the server is restarted again. [1]
+
+                  We were also overwriting memory when we stripped the commas from
+                  the allowed machanism list.  THis lead to the allowed mechanisms
+                  to get truncated,and permanently lose certain mechs. [2]
+
+                  A crash with PLAIN sasl mechanism was also found. [3]
+
+Fix Description:  To address allowed sasl mechs, we no longer explicitly the mechanisms
+                  during the sasl_init at server startup.  Instead we check the allowed
+                  list ourselves during a bind. [1]
+
+                  When setting the allowed sasl mechs, make a copy of the value to
+                  apply the changes to(removing coamms), and do not change the original
+                  value as it's still being used. [2]
+
+                  The crash when using sasl PLAIN was due to unlocking a rwlock that
+                  was not locked. [3]
+
+https://pagure.io/389-ds-base/issue/49379
+
+Reviewed by: tbordaz(Thanks!)
+
+(cherry picked from commit c78f41db31752a99aadd6abcbf7a1d852a8e7931)
+---
+ dirsrvtests/tests/suites/sasl/allowed_mechs.py | 114 ++++++++++++++++++++++--
+ dirsrvtests/tests/suites/sasl/plain.py         |  10 ++-
+ ldap/servers/slapd/libglobs.c                  |  23 ++---
+ ldap/servers/slapd/saslbind.c                  | 116 +++++++++++++------------
+ 4 files changed, 187 insertions(+), 76 deletions(-)
+
+diff --git a/dirsrvtests/tests/suites/sasl/allowed_mechs.py b/dirsrvtests/tests/suites/sasl/allowed_mechs.py
+index 7958db4..5b1b92c 100644
+--- a/dirsrvtests/tests/suites/sasl/allowed_mechs.py
++++ b/dirsrvtests/tests/suites/sasl/allowed_mechs.py
+@@ -8,45 +8,141 @@
+ #
+ 
+ import pytest
+-import ldap
+-
+-import time
+-
++import os
+ from lib389.topologies import topology_st
+ 
++
+ def test_sasl_allowed_mechs(topology_st):
++    """Test the alloweed sasl mechanism feature
++
++    :ID: ab7d9f86-8cfe-48c3-8baa-739e599f006a
++    :feature: Allowed sasl mechanisms
++    :steps: 1.  Get the default list of mechanisms
++            2.  Set allowed mechanism PLAIN, and verify it's correctly listed
++            3.  Restart server, and verify list is still correct
++            4.  Test EXTERNAL is properly listed
++            5.  Add GSSAPI to the existing list, and verify it's correctly listed
++            6.  Restart server and verify list is still correct
++            7.  Add ANONYMOUS to the existing list, and veirfy it's correctly listed
++            8.  Restart server and verify list is still correct
++            9.  Remove GSSAPI and verify it's correctly listed
++            10. Restart server and verify list is still correct
++            11. Reset allowed list to nothing, verify "all" the mechanisms are returned
++            12. Restart server and verify list is still correct
++
++    :expectedresults: The supported mechanisms supported what is set for the allowed
++                      mechanisms
++    """
+     standalone = topology_st.standalone
+ 
+     # Get the supported mechs. This should contain PLAIN, GSSAPI, EXTERNAL at least
++    standalone.log.info("Test we have some of the default mechanisms")
+     orig_mechs = standalone.rootdse.supported_sasl()
+     print(orig_mechs)
+     assert('GSSAPI' in orig_mechs)
+     assert('PLAIN' in orig_mechs)
+     assert('EXTERNAL' in orig_mechs)
+ 
+-    # Now edit the supported mechs. CHeck them again.
++    # Now edit the supported mechs. Check them again.
++    standalone.log.info("Edit mechanisms to allow just PLAIN")
+     standalone.config.set('nsslapd-allowed-sasl-mechanisms', 'PLAIN')
++    limit_mechs = standalone.rootdse.supported_sasl()
++    assert('PLAIN' in limit_mechs)
++    assert('EXTERNAL' in limit_mechs)  # Should always be in the allowed list, even if not set.
++    assert('GSSAPI' not in limit_mechs)  # Should not be there!
+ 
++    # Restart the server a few times and make sure nothing changes
++    standalone.log.info("Restart server and make sure we still have correct allowed mechs")
++    standalone.restart()
++    standalone.restart()
+     limit_mechs = standalone.rootdse.supported_sasl()
+     assert('PLAIN' in limit_mechs)
+-    # Should always be in the allowed list, even if not set.
+     assert('EXTERNAL' in limit_mechs)
+-    # Should not be there!
+     assert('GSSAPI' not in limit_mechs)
+ 
++    # Set EXTERNAL, even though its always supported
++    standalone.log.info("Edit mechanisms to allow just PLAIN and EXTERNAL")
+     standalone.config.set('nsslapd-allowed-sasl-mechanisms', 'PLAIN, EXTERNAL')
++    limit_mechs = standalone.rootdse.supported_sasl()
++    assert('PLAIN' in limit_mechs)
++    assert('EXTERNAL' in limit_mechs)
++    assert('GSSAPI' not in limit_mechs)
++
++    # Now edit the supported mechs. Check them again.
++    standalone.log.info("Edit mechanisms to allow just PLAIN and GSSAPI")
++    standalone.config.set('nsslapd-allowed-sasl-mechanisms', 'PLAIN, GSSAPI')
++    limit_mechs = standalone.rootdse.supported_sasl()
++    assert('PLAIN' in limit_mechs)
++    assert('EXTERNAL' in limit_mechs)
++    assert('GSSAPI' in limit_mechs)
++    assert(len(limit_mechs) == 3)
++
++    # Restart server twice and make sure the allowed list is the same
++    standalone.restart()
++    standalone.restart()  # For ticket 49379 (test double restart)
++    limit_mechs = standalone.rootdse.supported_sasl()
++    assert('PLAIN' in limit_mechs)
++    assert('EXTERNAL' in limit_mechs)
++    assert('GSSAPI' in limit_mechs)
++    assert(len(limit_mechs) == 3)
++
++    # Add ANONYMOUS to the supported mechs and test again.
++    standalone.log.info("Edit mechanisms to allow just PLAIN, GSSAPI, and ANONYMOUS")
++    standalone.config.set('nsslapd-allowed-sasl-mechanisms', 'PLAIN, GSSAPI, ANONYMOUS')
++    limit_mechs = standalone.rootdse.supported_sasl()
++    assert('PLAIN' in limit_mechs)
++    assert('EXTERNAL' in limit_mechs)
++    assert('GSSAPI' in limit_mechs)
++    assert('ANONYMOUS' in limit_mechs)
++    assert(len(limit_mechs) == 4)
++
++    # Restart server and make sure the allowed list is the same
++    standalone.restart()
++    standalone.restart()  # For ticket 49379 (test double restart)
++    limit_mechs = standalone.rootdse.supported_sasl()
++    assert('PLAIN' in limit_mechs)
++    assert('EXTERNAL' in limit_mechs)
++    assert('GSSAPI' in limit_mechs)
++    assert('ANONYMOUS' in limit_mechs)
++    assert(len(limit_mechs) == 4)
+ 
++    # Remove GSSAPI
++    standalone.log.info("Edit mechanisms to allow just PLAIN and ANONYMOUS")
++    standalone.config.set('nsslapd-allowed-sasl-mechanisms', 'PLAIN, ANONYMOUS')
+     limit_mechs = standalone.rootdse.supported_sasl()
+     assert('PLAIN' in limit_mechs)
+     assert('EXTERNAL' in limit_mechs)
+-    # Should not be there!
+     assert('GSSAPI' not in limit_mechs)
++    assert('ANONYMOUS' in limit_mechs)
++    assert(len(limit_mechs) == 3)
++
++    # Restart server and make sure the allowed list is the same
++    standalone.restart()
++    limit_mechs = standalone.rootdse.supported_sasl()
++    assert('PLAIN' in limit_mechs)
++    assert('EXTERNAL' in limit_mechs)
++    assert('GSSAPI' not in limit_mechs)
++    assert('ANONYMOUS' in limit_mechs)
++    assert(len(limit_mechs) == 3)
+ 
+     # Do a config reset
++    standalone.log.info("Reset allowed mechaisms")
+     standalone.config.reset('nsslapd-allowed-sasl-mechanisms')
+ 
+     # check the supported list is the same as our first check.
++    standalone.log.info("Check that we have the original set of mechanisms")
+     final_mechs = standalone.rootdse.supported_sasl()
+-    print(final_mechs)
+     assert(set(final_mechs) == set(orig_mechs))
+ 
++    # Check it after a restart
++    standalone.log.info("Check that we have the original set of mechanisms after a restart")
++    standalone.restart()
++    final_mechs = standalone.rootdse.supported_sasl()
++    assert(set(final_mechs) == set(orig_mechs))
++
++
++if __name__ == '__main__':
++    # Run isolated
++    # -s for DEBUG mode
++    CURRENT_FILE = os.path.realpath(__file__)
++    pytest.main("-s %s" % CURRENT_FILE)
+diff --git a/dirsrvtests/tests/suites/sasl/plain.py b/dirsrvtests/tests/suites/sasl/plain.py
+index 91ccb02..6bf39a8 100644
+--- a/dirsrvtests/tests/suites/sasl/plain.py
++++ b/dirsrvtests/tests/suites/sasl/plain.py
+@@ -15,9 +15,11 @@ from lib389.topologies import topology_st
+ from lib389.utils import *
+ from lib389.sasl import PlainSASL
+ from lib389.idm.services import ServiceAccounts
++from lib389._constants import (SECUREPORT_STANDALONE1, DEFAULT_SUFFIX)
+ 
+ log = logging.getLogger(__name__)
+ 
++
+ def test_sasl_plain(topology_st):
+ 
+     standalone = topology_st.standalone
+@@ -38,7 +40,7 @@ def test_sasl_plain(topology_st):
+     standalone.rsa.create()
+     # Set the secure port and nsslapd-security
+     # Could this fail with selinux?
+-    standalone.config.set('nsslapd-secureport', '%s' % SECUREPORT_STANDALONE1 )
++    standalone.config.set('nsslapd-secureport', '%s' % SECUREPORT_STANDALONE1)
+     standalone.config.set('nsslapd-security', 'on')
+     # Do we need to restart to allow starttls?
+     standalone.restart()
+@@ -65,12 +67,14 @@ def test_sasl_plain(topology_st):
+     # I can not solve. I think it's leaking state across connections in start_tls_s?
+ 
+     # Check that it works with TLS
+-    conn = standalone.openConnection(saslmethod='PLAIN', sasltoken=auth_tokens, starttls=True, connOnly=True, certdir=standalone.get_cert_dir(), reqcert=ldap.OPT_X_TLS_NEVER)
++    conn = standalone.openConnection(saslmethod='PLAIN', sasltoken=auth_tokens, starttls=True, connOnly=True,
++                                    certdir=standalone.get_cert_dir(), reqcert=ldap.OPT_X_TLS_NEVER)
+     conn.close()
+ 
+     # Check that it correct fails our bind if we don't have the password.
+     auth_tokens = PlainSASL("dn:%s" % sa.dn, 'password-wrong')
+     with pytest.raises(ldap.INVALID_CREDENTIALS):
+-        standalone.openConnection(saslmethod='PLAIN', sasltoken=auth_tokens, starttls=False, connOnly=True, certdir=standalone.get_cert_dir(), reqcert=ldap.OPT_X_TLS_NEVER)
++        standalone.openConnection(saslmethod='PLAIN', sasltoken=auth_tokens, starttls=True, connOnly=True,
++                                  certdir=standalone.get_cert_dir(), reqcert=ldap.OPT_X_TLS_NEVER)
+ 
+     # Done!
+diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
+index bb51827..2fb4bab 100644
+--- a/ldap/servers/slapd/libglobs.c
++++ b/ldap/servers/slapd/libglobs.c
+@@ -7137,22 +7137,25 @@ config_set_allowed_sasl_mechs(const char *attrname, char *value, char *errorbuf,
+ 
+     /* During a reset, the value is "", so we have to handle this case. */
+     if (strcmp(value, "") != 0) {
+-        /* cyrus sasl doesn't like comma separated lists */
+-        remove_commas(value);
++        char *nval = slapi_ch_strdup(value);
+ 
+-        if(invalid_sasl_mech(value)){
+-            slapi_log_err(SLAPI_LOG_ERR,"config_set_allowed_sasl_mechs",
+-                    "Invalid value/character for sasl mechanism (%s).  Use ASCII "
+-                    "characters, upto 20 characters, that are upper-case letters, "
+-                    "digits, hyphens, or underscores\n", value);
++        /* cyrus sasl doesn't like comma separated lists */
++        remove_commas(nval);
++
++        if (invalid_sasl_mech(nval)) {
++            slapi_log_err(SLAPI_LOG_ERR, "config_set_allowed_sasl_mechs",
++                          "Invalid value/character for sasl mechanism (%s).  Use ASCII "
++                          "characters, upto 20 characters, that are upper-case letters, "
++                          "digits, hyphens, or underscores\n",
++                          nval);
++            slapi_ch_free_string(&nval);
+             return LDAP_UNWILLING_TO_PERFORM;
+         }
+-
+         CFG_LOCK_WRITE(slapdFrontendConfig);
+         slapi_ch_free_string(&slapdFrontendConfig->allowed_sasl_mechs);
+         slapi_ch_array_free(slapdFrontendConfig->allowed_sasl_mechs_array);
+-        slapdFrontendConfig->allowed_sasl_mechs = slapi_ch_strdup(value);
+-        slapdFrontendConfig->allowed_sasl_mechs_array = slapi_str2charray_ext(value, " ", 0);
++        slapdFrontendConfig->allowed_sasl_mechs = nval;
++        slapdFrontendConfig->allowed_sasl_mechs_array = slapi_str2charray_ext(nval, " ", 0);
+         CFG_UNLOCK_WRITE(slapdFrontendConfig);
+     } else {
+         /* If this value is "", we need to set the list to *all* possible mechs */
+diff --git a/ldap/servers/slapd/saslbind.c b/ldap/servers/slapd/saslbind.c
+index 134f5aa..03e2a97 100644
+--- a/ldap/servers/slapd/saslbind.c
++++ b/ldap/servers/slapd/saslbind.c
+@@ -169,8 +169,6 @@ static int ids_sasl_getopt(
+         }
+     } else if (strcasecmp(option, "auxprop_plugin") == 0) {
+         *result = "iDS";
+-    } else if (strcasecmp(option, "mech_list") == 0){
+-        *result = config_get_allowed_sasl_mechs();
+     }
+ 
+     if (*result) *len = strlen(*result);
+@@ -572,12 +570,8 @@ static int ids_sasl_userdb_checkpass(sasl_conn_t *conn, void *context, const cha
+         slapi_pblock_set(pb, SLAPI_BIND_METHOD, &method);
+         /* Feed it to pw_verify_be_dn */
+         bind_result = pw_verify_be_dn(pb, &referral);
+-        /* Now check the result, and unlock be if needed. */
+-        if (bind_result == SLAPI_BIND_SUCCESS || bind_result == SLAPI_BIND_ANONYMOUS) {
+-            Slapi_Backend *be = NULL;
+-            slapi_pblock_get(pb, SLAPI_BACKEND, &be);
+-            slapi_be_Unlock(be);
+-        } else if (bind_result == SLAPI_BIND_REFERRAL) {
++        /* Now check the result. */
++        if (bind_result == SLAPI_BIND_REFERRAL) {
+             /* If we have a referral do we ignore it for sasl? */
+             slapi_entry_free(referral);
+         }
+@@ -760,22 +754,25 @@ char **ids_sasl_listmech(Slapi_PBlock *pb)
+     sup_ret = slapi_get_supported_saslmechanisms_copy();
+ 
+     /* If we have a connection, get the provided list from SASL */
+-    if (pb->pb_conn != NULL) {
+-        sasl_conn = (sasl_conn_t*)pb->pb_conn->c_sasl_conn;
+-
+-        /* sasl library mechanisms are connection dependent */
+-        PR_EnterMonitor(pb->pb_conn->c_mutex);
+-        if (sasl_listmech(sasl_conn,
+-                          NULL,     /* username */
+-                          "", ",", "",
+-                          &str, NULL, NULL) == SASL_OK) {
+-            slapi_log_err(SLAPI_LOG_TRACE, "ids_sasl_listmech", "sasl library mechs: %s\n", str);
+-            /* merge into result set */
+-            dupstr = slapi_ch_strdup(str);
+-            others = slapi_str2charray_ext(dupstr, ",", 0 /* don't list duplicate mechanisms */);
+-            charray_merge(&sup_ret, others, 1);
+-            charray_free(others);
+-            slapi_ch_free((void**)&dupstr);
++    if (pb_conn != NULL) {
++        sasl_conn = (sasl_conn_t*)pb_conn->c_sasl_conn;
++        if (sasl_conn != NULL) {
++            /* sasl library mechanisms are connection dependent */
++            PR_EnterMonitor(pb_conn->c_mutex);
++            if (sasl_listmech(sasl_conn,
++                              NULL,     /* username */
++                              "", ",", "",
++                              &str, NULL, NULL) == SASL_OK) {
++                slapi_log_err(SLAPI_LOG_TRACE, "ids_sasl_listmech", "sasl library mechs: %s\n", str);
++                /* merge into result set */
++                dupstr = slapi_ch_strdup(str);
++                others = slapi_str2charray_ext(dupstr, ",", 0 /* don't list duplicate mechanisms */);
++
++                charray_merge(&sup_ret, others, 1);
++                charray_free(others);
++                slapi_ch_free((void**)&dupstr);
++            }
++            PR_ExitMonitor(pb_conn->c_mutex);
+         }
+         PR_ExitMonitor(pb->pb_conn->c_mutex);
+     }
+@@ -785,7 +782,7 @@ char **ids_sasl_listmech(Slapi_PBlock *pb)
+ 
+     /* Remove any content that isn't in the allowed list */
+     if (config_ret != NULL) {
+-        /* Get the set of supported mechs in the insection of the two */
++        /* Get the set of supported mechs in the intersection of the two */
+         ret = charray_intersection(sup_ret, config_ret);
+         charray_free(sup_ret);
+         charray_free(config_ret);
+@@ -816,41 +813,52 @@ char **ids_sasl_listmech(Slapi_PBlock *pb)
+ static int
+ ids_sasl_mech_supported(Slapi_PBlock *pb, const char *mech)
+ {
+-  int i, ret = 0;
+-  char **mechs;
+-  char *dupstr;
+-  const char *str;
+-  int sasl_result = 0;
+-  sasl_conn_t *sasl_conn = (sasl_conn_t *)pb->pb_conn->c_sasl_conn;
+-
+-  slapi_log_err(SLAPI_LOG_TRACE, "ids_sasl_mech_supported", "=>\n");
+-
+-
+-  /* sasl_listmech is not thread-safe - caller must lock pb_conn */
+-  sasl_result = sasl_listmech(sasl_conn, 
+-                    NULL,     /* username */
+-                    "", ",", "",
+-                    &str, NULL, NULL);
+-  if (sasl_result != SASL_OK) {
+-    return 0;
+-  }
++    int i, ret = 0;
++    char **mechs;
++    char **allowed_mechs = NULL;
++    char *dupstr;
++    const char *str;
++    int sasl_result = 0;
++    Connection *pb_conn = NULL;
++
++    slapi_pblock_get(pb, SLAPI_CONNECTION, &pb_conn);
++    sasl_conn_t *sasl_conn = (sasl_conn_t *)pb_conn->c_sasl_conn;
++    slapi_log_err(SLAPI_LOG_TRACE, "ids_sasl_mech_supported", "=>\n");
++
++    /* sasl_listmech is not thread-safe - caller must lock pb_conn */
++    sasl_result = sasl_listmech(sasl_conn,
++                                NULL, /* username */
++                                "", ",", "",
++                                &str, NULL, NULL);
++    if (sasl_result != SASL_OK) {
++        return 0;
++    }
+ 
+-  dupstr = slapi_ch_strdup(str);
+-  mechs = slapi_str2charray(dupstr, ",");
++    dupstr = slapi_ch_strdup(str);
++    mechs = slapi_str2charray(dupstr, ",");
++    allowed_mechs = config_get_allowed_sasl_mechs_array();
+ 
+-  for (i = 0; mechs[i] != NULL; i++) {
+-    if (strcasecmp(mech, mechs[i]) == 0) {
+-      ret = 1;
+-      break;
++    for (i = 0; mechs[i] != NULL; i++) {
++        if (strcasecmp(mech, mechs[i]) == 0) {
++            if (allowed_mechs) {
++                if (charray_inlist(allowed_mechs, (char *)mech) == 0) {
++                    ret = 1;
++                }
++                break;
++            } else {
++                ret = 1;
++                break;
++            }
++        }
+     }
+-  }
+ 
+-  charray_free(mechs);
+-  slapi_ch_free((void**)&dupstr);
++    charray_free(allowed_mechs);
++    charray_free(mechs);
++    slapi_ch_free((void **)&dupstr);
+ 
+-  slapi_log_err(SLAPI_LOG_TRACE, "ids_sasl_mech_supported", "<=\n");
++    slapi_log_err(SLAPI_LOG_TRACE, "ids_sasl_mech_supported", "<=\n");
+ 
+-  return ret;
++    return ret;
+ }
+ 
+ /*
+-- 
+2.9.5
+
diff --git a/SOURCES/0071-Fix-cherry-pick-error-from-sasl-mech-commit.patch b/SOURCES/0071-Fix-cherry-pick-error-from-sasl-mech-commit.patch
new file mode 100644
index 0000000..b32ef38
--- /dev/null
+++ b/SOURCES/0071-Fix-cherry-pick-error-from-sasl-mech-commit.patch
@@ -0,0 +1,31 @@
+From 4a51a17762fb4e7ce1beb0600916fed8b45a5483 Mon Sep 17 00:00:00 2001
+From: Mark Reynolds <mreynolds@redhat.com>
+Date: Mon, 18 Sep 2017 15:06:06 -0400
+Subject: [PATCH] Fix cherry-pick error from sasl mech commit
+
+---
+ ldap/servers/slapd/saslbind.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/ldap/servers/slapd/saslbind.c b/ldap/servers/slapd/saslbind.c
+index 03e2a97..8e94ee6 100644
+--- a/ldap/servers/slapd/saslbind.c
++++ b/ldap/servers/slapd/saslbind.c
+@@ -745,11 +745,14 @@ char **ids_sasl_listmech(Slapi_PBlock *pb)
+     const char *str;
+     char *dupstr;
+     sasl_conn_t *sasl_conn;
++    Connection *pb_conn = NULL;
+ 
+     slapi_log_err(SLAPI_LOG_TRACE, "ids_sasl_listmech", "=>\n");
+ 
+     PR_ASSERT(pb);
+ 
++    slapi_pblock_get(pb, SLAPI_CONNECTION, &pb_conn);
++
+     /* hard-wired mechanisms and slapi plugin registered mechanisms */
+     sup_ret = slapi_get_supported_saslmechanisms_copy();
+ 
+-- 
+2.9.5
+
diff --git a/SOURCES/0072-Ticket-49389-unable-to-retrieve-specific-cosAttribut.patch b/SOURCES/0072-Ticket-49389-unable-to-retrieve-specific-cosAttribut.patch
new file mode 100644
index 0000000..d97bb51
--- /dev/null
+++ b/SOURCES/0072-Ticket-49389-unable-to-retrieve-specific-cosAttribut.patch
@@ -0,0 +1,322 @@
+From 2741a6db134ad40662cfa0233c4542d2d4148997 Mon Sep 17 00:00:00 2001
+From: Mark Reynolds <mreynolds@redhat.com>
+Date: Tue, 3 Oct 2017 17:22:37 -0400
+Subject: [PATCH] Ticket 49389 - unable to retrieve specific cosAttribute when
+ subtree                password policy is configured
+
+Bug Description:   If indirect cos is being used and a subtree password
+                   policy is added, th orignal COS attributes aren't always
+                   returned.  The issue is that when the subtree password
+                   policy attribute was encountered during the virtual
+                   attribute processing it set a flag that said the attribute
+                   was operational (which is correct for the password policy
+                   attr: pwdpolicysubentry).
+
+                   However, this flag was accidentally carried over to the
+                   following virtual attributes that were being processed.
+                   Which caused those attributes to be seen as operational
+                   which is why it was no longer being returned to the client.
+
+Fix Description:   Reset the prop flags before processing the next COS attribute
+
+https://pagure.io/389-ds-base/issue/49389
+
+Reviewed by: firstyear(Thanks!)
+
+(cherry picked from commit 0953e6011368bc29300990e9493ac13e5aba9586)
+---
+ dirsrvtests/tests/suites/cos/__init__.py          |   0
+ dirsrvtests/tests/suites/cos/indirect_cos_test.py | 191 ++++++++++++++++++++++
+ ldap/servers/plugins/cos/cos_cache.c              |  68 ++++----
+ 3 files changed, 223 insertions(+), 36 deletions(-)
+ create mode 100644 dirsrvtests/tests/suites/cos/__init__.py
+ create mode 100644 dirsrvtests/tests/suites/cos/indirect_cos_test.py
+
+diff --git a/dirsrvtests/tests/suites/cos/__init__.py b/dirsrvtests/tests/suites/cos/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/dirsrvtests/tests/suites/cos/indirect_cos_test.py b/dirsrvtests/tests/suites/cos/indirect_cos_test.py
+new file mode 100644
+index 0000000..1aac6b8
+--- /dev/null
++++ b/dirsrvtests/tests/suites/cos/indirect_cos_test.py
+@@ -0,0 +1,191 @@
++import logging
++import pytest
++import os
++import ldap
++import time
++import subprocess
++
++from lib389 import Entry
++from lib389.idm.user import UserAccounts
++from lib389.topologies import topology_st as topo
++from lib389._constants import (DEFAULT_SUFFIX, DN_DM, PASSWORD, HOST_STANDALONE,
++                               SERVERID_STANDALONE, PORT_STANDALONE)
++
++
++DEBUGGING = os.getenv("DEBUGGING", default=False)
++if DEBUGGING:
++    logging.getLogger(__name__).setLevel(logging.DEBUG)
++else:
++    logging.getLogger(__name__).setLevel(logging.INFO)
++log = logging.getLogger(__name__)
++
++TEST_USER_DN = "uid=test_user,ou=people,dc=example,dc=com"
++OU_PEOPLE = 'ou=people,{}'.format(DEFAULT_SUFFIX)
++
++PW_POLICY_CONT_PEOPLE = 'cn="cn=nsPwPolicyEntry,' \
++                        'ou=people,dc=example,dc=com",' \
++                        'cn=nsPwPolicyContainer,ou=people,dc=example,dc=com'
++
++PW_POLICY_CONT_PEOPLE2 = 'cn="cn=nsPwPolicyEntry,' \
++                        'dc=example,dc=com",' \
++                        'cn=nsPwPolicyContainerdc=example,dc=com'
++
++
++def check_user(inst):
++    """Search the test user and make sure it has the execpted attrs
++    """
++    try:
++        entries = inst.search_s('dc=example,dc=com', ldap.SCOPE_SUBTREE, "uid=test_user")
++        log.debug('user: \n' + str(entries[0]))
++        assert entries[0].hasAttr('ou'), "Entry is missing ou cos attribute"
++        assert entries[0].hasAttr('x-department'), "Entry is missing description cos attribute"
++        assert entries[0].hasAttr('x-en-ou'), "Entry is missing givenname cos attribute"
++    except ldap.LDAPError as e:
++        log.fatal('Failed to search for user: ' + str(e))
++        raise e
++
++
++def setup_subtree_policy(topo):
++    """Set up subtree password policy
++    """
++    try:
++        topo.standalone.modify_s("cn=config", [(ldap.MOD_REPLACE,
++                                                'nsslapd-pwpolicy-local',
++                                                'on')])
++    except ldap.LDAPError as e:
++        log.error('Failed to set fine-grained policy: error {}'.format(
++            e.message['desc']))
++        raise e
++
++    log.info('Create password policy for subtree {}'.format(OU_PEOPLE))
++    try:
++        subprocess.call(['%s/ns-newpwpolicy.pl' % topo.standalone.get_sbin_dir(),
++                         '-D', DN_DM, '-w', PASSWORD,
++                         '-p', str(PORT_STANDALONE), '-h', HOST_STANDALONE,
++                         '-S', DEFAULT_SUFFIX, '-Z', SERVERID_STANDALONE])
++    except subprocess.CalledProcessError as e:
++        log.error('Failed to create pw policy policy for {}: error {}'.format(
++            OU_PEOPLE, e.message['desc']))
++        raise e
++
++    log.info('Add pwdpolicysubentry attribute to {}'.format(OU_PEOPLE))
++    try:
++        topo.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_REPLACE,
++                                                   'pwdpolicysubentry',
++                                                   PW_POLICY_CONT_PEOPLE2)])
++    except ldap.LDAPError as e:
++        log.error('Failed to pwdpolicysubentry pw policy '
++                  'policy for {}: error {}'.format(OU_PEOPLE, e.message['desc']))
++        raise e
++    time.sleep(1)
++
++
++def setup_indirect_cos(topo):
++    """Setup indirect COS definition and template
++    """
++    cosDef = Entry(('cn=cosDefinition,dc=example,dc=com',
++                    {'objectclass': ['top', 'ldapsubentry',
++                                     'cossuperdefinition',
++                                     'cosIndirectDefinition'],
++                     'cosAttribute': ['ou merge-schemes',
++                                      'x-department merge-schemes',
++                                      'x-en-ou merge-schemes'],
++                     'cosIndirectSpecifier': 'seeAlso',
++                     'cn': 'cosDefinition'}))
++
++    cosTemplate = Entry(('cn=cosTemplate,dc=example,dc=com',
++                         {'objectclass': ['top',
++                                          'extensibleObject',
++                                          'cosTemplate'],
++                          'ou': 'My COS Org',
++                          'x-department': 'My COS x-department',
++                          'x-en-ou': 'my COS x-en-ou',
++                          'cn': 'cosTemplate'}))
++    try:
++        topo.standalone.add_s(cosDef)
++        topo.standalone.add_s(cosTemplate)
++    except ldap.LDAPError as e:
++        log.fatal('Failed to add cos: error ' + str(e))
++        raise e
++    time.sleep(1)
++
++
++@pytest.fixture(scope="module")
++def setup(topo, request):
++    """Add schema, and test user
++    """
++    log.info('Add custom schema...')
++    try:
++        ATTR_1 = ("( 1.3.6.1.4.1.409.389.2.189 NAME 'x-department' " +
++                  "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'user defined' )")
++        ATTR_2 = ("( 1.3.6.1.4.1.409.389.2.187 NAME 'x-en-ou' " +
++                  "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'user defined' )")
++        OC = ("( xPerson-oid NAME 'xPerson' DESC '' SUP person STRUCTURAL MAY " +
++              "( x-department $ x-en-ou ) X-ORIGIN 'user defined' )")
++        topo.standalone.modify_s("cn=schema", [(ldap.MOD_ADD, 'attributeTypes', ATTR_1),
++                                               (ldap.MOD_ADD, 'attributeTypes', ATTR_2),
++                                               (ldap.MOD_ADD, 'objectClasses', OC)])
++    except ldap.LDAPError as e:
++        log.fatal('Failed to add custom schema')
++        raise e
++    time.sleep(1)
++
++    log.info('Add test user...')
++    users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
++
++    user_properties = {
++        'uid': 'test_user',
++        'cn': 'test user',
++        'sn': 'user',
++        'uidNumber': '1000',
++        'gidNumber': '2000',
++        'homeDirectory': '/home/test_user',
++        'seeAlso': 'cn=cosTemplate,dc=example,dc=com'
++    }
++    users.create(properties=user_properties)
++    try:
++        topo.standalone.modify_s(TEST_USER_DN, [(ldap.MOD_ADD,
++                                                 'objectclass',
++                                                 'xPerson')])
++    except ldap.LDAPError as e:
++        log.fatal('Failed to add objectclass to user')
++        raise e
++
++    # Setup COS
++    log.info("Setup indirect COS...")
++    setup_indirect_cos(topo)
++
++
++def test_indirect_cos(topo, setup):
++    """Test indirect cos
++
++    :id: 890d5929-7d52-4a56-956e-129611b4649a
++    :setup: standalone
++    :steps:
++        1. Test cos is working for test user
++        2. Add subtree password policy
++        3. Test cos is working for test user
++    :expectedresults:
++        1. User has expected cos attrs
++        2. Substree password policy setup is successful
++        3  User still has expected cos attrs
++    """
++
++    # Step 1 - Search user and see if the COS attrs are included
++    log.info('Checking user...')
++    check_user(topo.standalone)
++
++    # Step 2 - Add subtree password policy (Second COS - operational attribute)
++    setup_subtree_policy(topo)
++
++    # Step 3 - Check user again now hat we have a mix of vattrs
++    log.info('Checking user...')
++    check_user(topo.standalone)
++
++
++if __name__ == '__main__':
++    # Run isolated
++    # -s for DEBUG mode
++    CURRENT_FILE = os.path.realpath(__file__)
++    pytest.main("-s %s" % CURRENT_FILE)
++
+diff --git a/ldap/servers/plugins/cos/cos_cache.c b/ldap/servers/plugins/cos/cos_cache.c
+index 66c6c7f..87d4890 100644
+--- a/ldap/servers/plugins/cos/cos_cache.c
++++ b/ldap/servers/plugins/cos/cos_cache.c
+@@ -2190,48 +2190,44 @@ bail:
+ static int cos_cache_vattr_types(vattr_sp_handle *handle,Slapi_Entry *e,
+ 							vattr_type_list_context *type_context,int flags)
+ {
+-	int ret = 0;
+-	int index = 0;
+-	cosCache *pCache;
+-	char *lastattr = "thisisfakeforcos";
+-	int props = 0;
+-
+-	slapi_log_err(SLAPI_LOG_TRACE, COS_PLUGIN_SUBSYSTEM, "--> cos_cache_vattr_types\n");
+-	
+-	if(cos_cache_getref((cos_cache **)&pCache) < 1)
+-	{
+-		/* problems we are hosed */
+-		slapi_log_err(SLAPI_LOG_PLUGIN, COS_PLUGIN_SUBSYSTEM, "cos_cache_vattr_types - Failed to get class of service reference\n");
+-		goto bail;
+-	}
+-
+-	while(index < pCache->attrCount )
+-	{
+-		if(slapi_utf8casecmp(
+-				(unsigned char *)pCache->ppAttrIndex[index]->pAttrName, 
+-				(unsigned char *)lastattr))
+-		{
+-			lastattr = pCache->ppAttrIndex[index]->pAttrName;
++    int ret = 0;
++    int index = 0;
++    cosCache *pCache;
++    char *lastattr = "thisisfakeforcos";
+ 
+-			if(1 == cos_cache_query_attr(pCache, NULL, e, lastattr, NULL, NULL,
+-											 NULL, &props, NULL))
+-			{
+-				/* entry contains this attr */
+-				vattr_type_thang thang = {0};
++    slapi_log_err(SLAPI_LOG_TRACE, COS_PLUGIN_SUBSYSTEM, "--> cos_cache_vattr_types\n");
+ 
+-				thang.type_name = lastattr;
+-				thang.type_flags = props;
++    if (cos_cache_getref((cos_cache **)&pCache) < 1) {
++        /* problems we are hosed */
++        slapi_log_err(SLAPI_LOG_PLUGIN, COS_PLUGIN_SUBSYSTEM, "cos_cache_vattr_types - Failed to get class of service reference\n");
++        goto bail;
++    }
+ 
+-				slapi_vattrspi_add_type(type_context,&thang,0);
+-			}
+-		}
+-		index++;
+-	}
+-	cos_cache_release(pCache);
++    while (index < pCache->attrCount) {
++        int props = 0;
++        if (slapi_utf8casecmp(
++                (unsigned char *)pCache->ppAttrIndex[index]->pAttrName,
++                (unsigned char *)lastattr)) {
++            lastattr = pCache->ppAttrIndex[index]->pAttrName;
++
++            if (1 == cos_cache_query_attr(pCache, NULL, e, lastattr, NULL, NULL,
++                                          NULL, &props, NULL)) {
++                /* entry contains this attr */
++                vattr_type_thang thang = {0};
++
++                thang.type_name = lastattr;
++                thang.type_flags = props;
++
++                slapi_vattrspi_add_type(type_context, &thang, 0);
++            }
++        }
++        index++;
++    }
++    cos_cache_release(pCache);
+ 
+ bail:
+ 
+-slapi_log_err(SLAPI_LOG_TRACE, COS_PLUGIN_SUBSYSTEM, "<-- cos_cache_vattr_types\n");
++    slapi_log_err(SLAPI_LOG_TRACE, COS_PLUGIN_SUBSYSTEM, "<-- cos_cache_vattr_types\n");
+ 	return ret;
+ }
+ 
+-- 
+2.9.5
+
diff --git a/SPECS/389-ds-base.spec b/SPECS/389-ds-base.spec
index fd533d0..2547084 100644
--- a/SPECS/389-ds-base.spec
+++ b/SPECS/389-ds-base.spec
@@ -30,7 +30,7 @@
 Summary:          389 Directory Server (base)
 Name:             389-ds-base
 Version:          1.3.6.1
-Release:          %{?relprefix}19%{?prerel}%{?dist}
+Release:          %{?relprefix}21%{?prerel}%{?dist}
 License:          GPLv3+
 URL:              https://www.port389.org/
 Group:            System Environment/Daemons
@@ -195,7 +195,17 @@ Patch58:          0058-Ticket-49336-SECURITY-Locked-account-provides-differ.patc
 Patch59:          0059-Ticket-49298-force-sync-on-shutdown.patch
 Patch60:          0060-Ticket-49334-fix-backup-restore-if-changelog-exists.patch
 Patch61:          0061-Ticket-49356-mapping-tree-crash-can-occur-during-tot.patch
-
+Patch62:          0062-Ticket-49330-Improve-ndn-cache-performance-1.3.6.patch
+Patch63:          0063-Ticket-49330-Add-endian-header-file-check-to-configu.patch
+Patch64:          0064-Ticket-49257-only-register-modify-callbacks.patch
+Patch65:          0065-Ticket-49291-slapi_search_internal_callback_pb-may-S.patch
+Patch66:          0066-Ticket-49370-local-password-policies-should-use-the-.patch
+Patch67:          0067-Ticket-49380-Crash-when-adding-invalid-replication.patch
+Patch68:          0068-Ticket-49380-Add-CI-test.patch
+Patch69:          0069-Ticket-49327-password-expired-control-not-sent-durin.patch
+Patch70:          0070-Ticket-49379-Allowed-sasl-mapping-requires-restart.patch
+Patch71:          0071-Fix-cherry-pick-error-from-sasl-mech-commit.patch
+Patch72:          0072-Ticket-49389-unable-to-retrieve-specific-cosAttribut.patch
 
 %description
 389 Directory Server is an LDAPv3 compliant server.  The base package includes
@@ -327,6 +337,17 @@ cp %{SOURCE2} README.devel
 %patch59 -p1
 %patch60 -p1
 %patch61 -p1
+%patch62 -p1
+%patch63 -p1
+%patch64 -p1
+%patch65 -p1
+%patch66 -p1
+%patch67 -p1
+%patch68 -p1
+%patch69 -p1
+%patch70 -p1
+%patch71 -p1
+%patch72 -p1
 %build
 
 OPENLDAP_FLAG="--with-openldap"
@@ -457,9 +478,9 @@ echo remove pid files . . . >> $output 2>&1 || :
 echo upgrading instances . . . >> $output 2>&1 || :
 DEBUGPOSTSETUPOPT=`/usr/bin/echo $DEBUGPOSTSETUP | /usr/bin/sed -e "s/[^d]//g"`
 if [ -n "$DEBUGPOSTSETUPOPT" ] ; then
-    %{_sbindir}/setup-ds.pl -l $output2 -$DEBUGPOSTSETUPOPT -u -s General.UpdateMode=offline >> $output 2>&1 || :
+    %{_sbindir}/setup-ds.pl -$DEBUGPOSTSETUPOPT -u -s General.UpdateMode=offline >> $output 2>&1 || :
 else
-    %{_sbindir}/setup-ds.pl -l $output2 -u -s General.UpdateMode=offline >> $output 2>&1 || :
+    %{_sbindir}/setup-ds.pl -u -s General.UpdateMode=offline >> $output 2>&1 || :
 fi
 
 # restart instances that require it
@@ -558,8 +579,22 @@ fi
 %{_sysconfdir}/%{pkgname}/dirsrvtests
 
 %changelog
-* Mon Aug 21 2017 Mark Reynolds <mreynolds@redhat.com> - 1.3.6.19-1
-- Bump version to 1.3.6.19-1
+* Thu Oct 5 2017 Mark Reynolds <mreynolds@redhat.com> - 1.3.6.1-21
+- Bump verions to 1.3.6.1-21
+- Resolves: Bug 1498958 - unable to retrieve specific cosAttribute when subtree password policy is configured
+
+* Mon Sep 18 2017 Mark Reynolds <mreynolds@redhat.com> - 1.3.6.1-20
+- Bump verions to 1.3.6.1-20
+- Resolves: Bug 1489693 - PasswordCheckSyntax attribute fails to validate cn, sn, uid
+- Resovles: Bug 1492829 - patch should of been applied to 7.4 but got missed
+- Resolves: Bug 1486128 - Performance issues with RHDS 10 - NDN cache investigation
+- Resolves: Bug 1489694 - crash in send_ldap_result
+- Resolves: Bug 1491778 - crash when adding invalid repl agmt
+- Resolves: Bug 1492830 - password expired control not sent
+- Resolves: Bug 1492833 - sasl-mechanisms removed during upgrade
+
+* Mon Aug 21 2017 Mark Reynolds <mreynolds@redhat.com> - 1.3.6.1-19
+- Bump version to 1.3.6.1-19
 - Remove old mozldap and db4 requirements
 - Resolves: Bug 1483865 - Crash while binding to a server during replication online init