# # Copyright (C) 2016 FreeIPA Contributors see COPYING for license # import logging import time import ldap from ipalib.plugable import Registry from ipalib import errors from ipalib import Updater from ipapython.dn import DN logger = logging.getLogger(__name__) register = Registry() @register() class update_dna_shared_config(Updater): dna_plugin_names = ('posix IDs', 'Subordinate IDs') dna_plugin_dn = DN( ('cn', 'Distributed Numeric Assignment Plugin'), ('cn', 'plugins'), ('cn', 'config') ) def is_dna_enabled(self): """Check the plugin is enabled Else it is useless to update the shared entry """ try: entry = self.api.Backend.ldap2.get_entry(self.dna_plugin_dn) enabled = entry.single_value.get('nsslapd-pluginenabled') if enabled.lower() == 'off': return False else: return True except errors.NotFound: logger.error("Could not find DNA plugin entry: %s", self.dna_plugin_dn) return False def get_shared_cfg(self, plugin_name): dna_config_base = DN(('cn', plugin_name), self.dna_plugin_dn) try: entry = self.api.Backend.ldap2.get_entry(dna_config_base) except errors.NotFound: logger.error("Could not find DNA config entry: %s", dna_config_base) return False, () else: logger.debug('Found DNA config %s', dna_config_base) sharedcfgdn = entry.single_value.get("dnaSharedCfgDN") if sharedcfgdn is not None: sharedcfgdn = DN(sharedcfgdn) logger.debug("dnaSharedCfgDN: %s", sharedcfgdn) return sharedcfgdn else: logger.error( "Could not find DNA shared config DN in entry: %s", dna_config_base) return None def update_shared_cfg(self, sharedcfgdn, **options): """Update the shared config entry related to that host If the shared config entry already exists (like upgrade) the update occurs immediately without sleep. If the shared config entry does not exist (fresh install) DS server waits for 30s after its startup to create it. Startup likely occurred few sec before this function is called so this loop will wait for 30s max. In case the server is not able to create the entry The loop gives a grace period of slightly more than 60 seconds before it logs a failure and aborts the update. """ method = options.get('method', "SASL/GSSAPI") protocol = options.get('protocol', "LDAP") max_wait = 30 # times 2 second sleep conn = self.api.Backend.ldap2 fqdn = self.api.env.host for _i in range(0, max_wait + 1): try: entries = conn.get_entries( sharedcfgdn, scope=ldap.SCOPE_ONELEVEL, filter='dnaHostname=%s' % fqdn ) # There must be two entries: # - dnaHostname=fqdn+dnaPortNum=0 # - dnaHostname=fqdn+dnaPortNum=389 if len(entries) >= 2: break logger.debug("Got only one entry. Retry again in 2 sec.") time.sleep(2) except errors.NotFound: logger.debug( "Unable to find DNA shared config entry for " "dnaHostname=%s (under %s) so far. Retry in 2 sec.", fqdn, sharedcfgdn ) time.sleep(2) else: logger.error( "Could not get dnaHostname entries in %s seconds", max_wait * 2 ) return False # time to set the bind method and the protocol in the # shared config entries for entry in entries: entry["dnaRemoteBindMethod"] = method entry["dnaRemoteConnProtocol"] = protocol try: conn.update_entry(entry) except errors.EmptyModlist: logger.debug("Entry %s is already updated", entry.dn) except Exception as e: logger.error( "Failed to set SASL/GSSAPI bind method/protocol " "in entry %s: %s", entry, e ) else: logger.debug("Updated entry %s", entry.dn) return True def execute(self, **options): if not self.is_dna_enabled(): return False, () for plugin_name in self.dna_plugin_names: sharedcfgdn = self.get_shared_cfg(plugin_name) if sharedcfgdn is not None: self.update_shared_cfg(sharedcfgdn, **options) # no restart, no update return False, ()