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 +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. <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 + ++#include ++#include /* for size_t */ ++ ++#if defined(HAVE_SYS_ENDIAN_H) ++#include ++#elif defined(HAVE_ENDIAN_H) ++#include ++#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) + * + */ + ++/* ++ Copyright (c) 2013 Marek Majkowski ++ ++ 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. ++ ++ ++ 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 +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 +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 +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 +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 +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 +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 +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(×tring); +- 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(×tring); +- } ++ 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(×tring); ++ 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(×tring); ++ } + + 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(×tring); +- +- 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(×tring); ++ ++ 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 +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 +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 +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 - 1.3.6.19-1 -- Bump version to 1.3.6.19-1 +* Thu Oct 5 2017 Mark Reynolds - 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 - 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 - 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