From 56373fb562fc86e53e5ba8ffd5e5d09c21f532f5 Mon Sep 17 00:00:00 2001 From: William Brown Date: Apr 16 2019 00:53:40 +0000 Subject: Ticket 49390, 50019 - support cn=config compare operations Bug Description: Ansible will attempt to check the state of a value before it makes an alteration on the ldap server. To do this in a correct and schema aware fashion, it will use the ldapcompare operation. It's a request that people want to manage their cn=config with ansible, however dse.c didn't support ldapcompare on these backends. Fix Description: Add support for ldapcompare operations on dse.c, including the ability to correctly generate the cn=config defaults into the entry for comparison. This also adds support for ldapcompare as the default comparitor in lib389. https://pagure.io/389-ds-base/issue/49390 https://pagure.io/389-ds-base/issue/50019 Author: William Brown Review by: ??? --- diff --git a/Makefile.am b/Makefile.am index 4d027e6..5649cbc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1301,6 +1301,7 @@ libslapd_la_SOURCES = ldap/servers/slapd/add.c \ ldap/servers/slapd/ch_malloc.c \ ldap/servers/slapd/computed.c \ ldap/servers/slapd/control.c \ + ldap/servers/slapd/configdse.c \ ldap/servers/slapd/counters.c \ ldap/servers/slapd/csn.c \ ldap/servers/slapd/csngen.c \ @@ -2056,7 +2057,6 @@ ns_slapd_SOURCES = ldap/servers/slapd/abandon.c \ ldap/servers/slapd/bind.c \ ldap/servers/slapd/compare.c \ ldap/servers/slapd/config.c \ - ldap/servers/slapd/configdse.c \ ldap/servers/slapd/connection.c \ ldap/servers/slapd/conntable.c \ ldap/servers/slapd/daemon.c \ diff --git a/dirsrvtests/tests/suites/lib389/config_compare_test.py b/dirsrvtests/tests/suites/lib389/config_compare_test.py index 6379555..2201609 100644 --- a/dirsrvtests/tests/suites/lib389/config_compare_test.py +++ b/dirsrvtests/tests/suites/lib389/config_compare_test.py @@ -16,7 +16,6 @@ def test_config_compare(topology_i2): :expectedresults: 1. It should be the same (excluding unique id attrs) """ - st1_config = topology_i2.ins.get('standalone1').config st2_config = topology_i2.ins.get('standalone2').config # 'nsslapd-port' attribute is expected to be same in cn=config comparison, @@ -30,7 +29,6 @@ def test_config_compare(topology_i2): assert Config.compare(st1_config, st2_config) - if __name__ == '__main__': # Run isolated # -s for DEBUG mode diff --git a/ldap/admin/src/scripts/ns-slapd-gdb.py b/ldap/admin/src/scripts/ns-slapd-gdb.py index 4e463b5..73e25aa 100644 --- a/ldap/admin/src/scripts/ns-slapd-gdb.py +++ b/ldap/admin/src/scripts/ns-slapd-gdb.py @@ -117,7 +117,55 @@ class DSIdleFilter(): frame_iter = map(DSIdleFilterDecorator, frame_iter) return frame_iter +class DSEntryPrint (gdb.Command): + """Display a Slapi_Entry""" + def __init__(self): + super (DSEntryPrint, self).__init__("ds-entry-print", gdb.COMMAND_DATA) + + def _display_values(self, a_name, va_ptr, num): + inum = int(num) + + for i in range(0, inum): + value = va_ptr[i].dereference() + bv = value['bv']['bv_val'] + if bv == 0: + print("%s: X 0" % a_name) + else: + print("%s: %s" % (a_name, bv.string())) + + + def _display_attrs(self, start): + if start.address == 0: + return + attr = start.dereference() + while True: + name = attr['a_type'].string() + # print(dir(name)) + va = attr['a_present_values']['va'] + num = attr['a_present_values']['num'] + self._display_values(name, va, num) + # Now loop + n = attr['a_next'] + if n == 0: + return + attr = n.dereference() + + + def invoke (self, arg, from_tty): + gdb.newest_frame() + cur_frame = gdb.selected_frame() + entry_val = cur_frame.read_var(arg) + entry_root = entry_val.dereference() + entry_sdn = entry_root['e_sdn']['ndn'] + # Display the SDN + print("Display Slapi_Entry: %s" % entry_sdn.string()) + # Display the attributes. + entry_attrs = entry_root['e_attrs'] + self._display_attrs(entry_attrs) + DSAccessLog() DSBacktrace() DSIdleFilter() +DSEntryPrint() + diff --git a/ldap/servers/slapd/backend_manager.c b/ldap/servers/slapd/backend_manager.c index d547505..299773f 100644 --- a/ldap/servers/slapd/backend_manager.c +++ b/ldap/servers/slapd/backend_manager.c @@ -113,7 +113,7 @@ be_new_internal(struct dse *pdse, const char *type, const char *name, struct sla be->be_database->plg_next_search_entry = &dse_next_search_entry; be->be_database->plg_search_results_release = &dse_search_set_release; be->be_database->plg_prev_search_results = &dse_prev_search_results; - be->be_database->plg_compare = &be_plgfn_unwillingtoperform; + be->be_database->plg_compare = &dse_compare; be->be_database->plg_modify = &dse_modify; be->be_database->plg_modrdn = &be_plgfn_unwillingtoperform; be->be_database->plg_add = &dse_add; diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c index 74dfd90..1f1f516 100644 --- a/ldap/servers/slapd/dse.c +++ b/ldap/servers/slapd/dse.c @@ -39,6 +39,8 @@ #include #include "slap.h" #include +/* Needed to access read_config_dse */ +#include "proto-slap.h" #include /* provides fsync/close */ @@ -186,16 +188,18 @@ dse_get_entry_copy(struct dse *pdse, const Slapi_DN *dn, int use_lock) Slapi_Entry *e = NULL; struct dse_node *n; - if (use_lock == DSE_USE_LOCK && pdse->dse_rwlock) + if (use_lock == DSE_USE_LOCK && pdse->dse_rwlock) { slapi_rwlock_rdlock(pdse->dse_rwlock); + } n = dse_find_node(pdse, dn); if (n != NULL) { e = slapi_entry_dup(n->entry); } - if (use_lock == DSE_USE_LOCK && pdse->dse_rwlock) + if (use_lock == DSE_USE_LOCK && pdse->dse_rwlock) { slapi_rwlock_unlock(pdse->dse_rwlock); + } return e; } @@ -1678,6 +1682,89 @@ dse_search(Slapi_PBlock *pb) /* JCM There should only be one exit point from thi return 0; } +int32_t +dse_compare(Slapi_PBlock *pb) +{ + /* + * Inspired largely by ldbm_compare.c. Allow schema aware comparison + * of entries in the DSE, including cn=config. + */ + backend *be = NULL; + char *type = NULL; + struct berval *bval = NULL; + Slapi_DN *sdn = NULL; + struct dse *pdse = NULL; + Slapi_Entry *ec = NULL; + Slapi_Value compare_value = {0}; + + slapi_pblock_get(pb, SLAPI_BACKEND, &be); + slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &pdse); + slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn); + slapi_pblock_get(pb, SLAPI_COMPARE_TYPE, &type); + slapi_pblock_get(pb, SLAPI_COMPARE_VALUE, &bval); + + /* get the entry */ + ec = dse_get_entry_copy(pdse, sdn, DSE_USE_LOCK); + if (ec == NULL) { + slapi_send_ldap_result(pb, LDAP_NO_SUCH_OBJECT, NULL, NULL, 0, NULL); + return -1; + } + + /* Access control check */ + int32_t err = slapi_access_allowed(pb, ec, type, bval, SLAPI_ACL_COMPARE); + if (err != LDAP_SUCCESS) { + slapi_entry_free(ec); + slapi_send_ldap_result(pb, err, NULL, NULL, 0, NULL); + return -1; + } + + /* If cn=config, setup the entry with ALL values we could check from defaults */ + Slapi_DN config_dn; + slapi_sdn_init_ndn_byref(&config_dn, "cn=config"); + if (slapi_sdn_compare(&config_dn, sdn) == 0) { + read_config_dse(pb, ec, NULL, &err, NULL, NULL); + /* + * read_config_dse, and in turn, config_set_entry always returns + * a 1 here, which is probably dse_callback related. + */ + if (err != 1) { + slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL); + return -1; + } + /* + * cn=config is now populated + */ + } + slapi_sdn_done(&config_dn); + + /* Do the schema aware check. */ + slapi_value_init_berval(&compare_value, bval); + + int32_t result = 0; + err = slapi_vattr_value_compare(ec, type, &compare_value, &result, 0); + + /* We have the results, now free and then send. */ + slapi_entry_free(ec); + value_done(&compare_value); + + /* Format the result as expected. */ + if (err != LDAP_SUCCESS) { + if (SLAPI_VIRTUALATTRS_NOT_FOUND == err) { + slapi_send_ldap_result(pb, LDAP_NO_SUCH_ATTRIBUTE, NULL, NULL, 0, NULL); + } else { + /* Some other problem, call it an operations error */ + slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL); + } + return -1; + } else { + if (result != 0) { + slapi_send_ldap_result(pb, LDAP_COMPARE_TRUE, NULL, NULL, 0, NULL); + } else { + slapi_send_ldap_result(pb, LDAP_COMPARE_FALSE, NULL, NULL, 0, NULL); + } + } + return 0; +} /* * -1 means something went wrong. diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h index dce4243..6f20097 100644 --- a/ldap/servers/slapd/proto-slap.h +++ b/ldap/servers/slapd/proto-slap.h @@ -701,6 +701,7 @@ int dse_read_file(struct dse *pdse, Slapi_PBlock *pb); int dse_bind(Slapi_PBlock *pb); int dse_unbind(Slapi_PBlock *pb); int dse_search(Slapi_PBlock *pb); +int32_t dse_compare(Slapi_PBlock *pb); int dse_modify(Slapi_PBlock *pb); int dse_add(Slapi_PBlock *pb); int dse_delete(Slapi_PBlock *pb); diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py index 6981d4a..e96e0fe 100644 --- a/src/lib389/lib389/_mapped_object.py +++ b/src/lib389/lib389/_mapped_object.py @@ -415,7 +415,7 @@ class DSLdapObject(DSLogging): raise ValueError('Too many arguments in the mod op') return self._instance.modify_ext_s(self._dn, mod_list, serverctrls=self._server_controls, clientctrls=self._client_controls, escapehatch='i am sure') - def _unsafe_compare_attribute(self, other): + def _unsafe_compare_attribute(self, attr, values): """Compare two attributes from two objects. This is currently marked unsafe as it's not complete yet. @@ -430,7 +430,12 @@ class DSLdapObject(DSLogging): To allow schema aware checking, we need to call ldap compare extop here. """ - pass + return all([ + self._instance.compare_ext_s(self._dn, attr, value, serverctrls=self._server_controls, + clientctrls=self._client_controls, escapehatch='i am sure') + for value in values + ]) + @classmethod def compare(cls, obj1, obj2): @@ -469,10 +474,14 @@ class DSLdapObject(DSLogging): if set(obj1_attrs.keys()) != set(obj2_attrs.keys()): obj1._log.debug("%s != %s" % (obj1_attrs.keys(), obj2_attrs.keys())) return False + obj1._log.debug(sorted(obj1_attrs.keys())) # Check the values of each key # using obj1_attrs.keys() because obj1_attrs.iterkleys() is not supported in python3 for key in obj1_attrs.keys(): - if set(obj1_attrs[key]) != set(obj2_attrs[key]): + # Check if they are offline/online? + # if set(obj1_attrs[key]) != set(obj2_attrs[key]): + obj1._log.debug("checking %s: %s ..." % (key, obj2_attrs[key])) + if not obj1._unsafe_compare_attribute(key, obj2_attrs[key]): obj1._log.debug(" v-- %s != %s" % (key, key)) obj1._log.debug("%s != %s" % (obj1_attrs[key], obj2_attrs[key])) return False