diff options
-rw-r--r-- | lib/openldap | 14 |
1 files changed, 10 insertions, 4 deletions
diff --git a/lib/openldap b/lib/openldap index 2cc55db..0a8df96 100644 --- a/lib/openldap +++ b/lib/openldap @@ -19,40 +19,41 @@ import ldap, ldap.sasl from ldap.filter import filter_format from ldap.dn import dn2str,explode_dn,str2dn from ldap.modlist import addModlist from ldif import LDIFParser from functools import partial import re, pwd import tempfile, atexit # Dirty hack to check equality between the targetted LDIF and that # currently in the directory. The value of some configuration (olc*) # attributes is automatically indexed when added; for those we'll add # explicit indices to what we find in the LDIF. indexedAttributes = frozenset([ 'olcAttributeTypes', 'olcObjectClasses', 'olcAccess', 'olcSyncrepl', 'olcOverlay', + 'olcLimits', ]) # Another hack. Configuration entries sometimes pollutes the DNs with # indices, thus it's not possible to directly use them as base. # Instead, we use their parent as a pase, and search for the *unique* # match with the same ObjectClass and the matching extra attributes. # ('%s' in the attribute value is replaced with the value of the source # entry.) indexedDN = { 'olcSchemaConfig': [('cn', '{*}%s')], 'olcHdbConfig': [('olcDbDirectory', '%s' )], 'olcOverlayConfig': [('olcOverlay', '%s' )], } # Allow for flexible ACLs for user using SASL's EXTERNAL mechanism. # "username=postfix,cn=peercred,cn=external,cn=auth" is replaced by # "gidNumber=106+uidNumber=102,cn=peercred,cn=external,cn=auth" where # 102 is postfix's UID and 106 its primary GID. # (Regular expressions are not allowed.) @@ -147,40 +148,45 @@ def flexibleSearch(module, l, dn, entry): f = '(&(' + ')('.join(f) + '))' r = l.search_s( base, scope, filterstr=f ) if len(r) > 1: module.fail_json(msg="Multiple results found! This is a bug. Please report.") elif r: return r.pop() # Add or modify (only the attributes that differ from those in the # directory) the entry for that DN. # l must be an LDAPObject, and should provide an open connection to the # directory with disclose/search/write access. def processEntry(module, l, dn, entry): changed = False r = flexibleSearch( module, l, dn, entry ) if r is None: changed = True if module.check_mode: module.exit_json(changed=changed, msg="add DN %s" % dn) + if 'olcAccess' in entry.keys(): + # replace "username=...,cn=peercred,cn=external,cn=auth" + # by a DN with proper gidNumber and uidNumber + entry['olcAccess'] = map ( partial(sasl_ext_re.sub, acl_sasl_ext) + , entry['olcAccess'] ) l.add_s( dn, addModlist(entry) ) else: d,e = r fst = str2dn(dn).pop(0)[0][0] diff = [] for a,v in e.iteritems(): if a not in entry.keys(): if a != fst: # delete all values except for the first attribute, # which is implicit diff.append(( ldap.MOD_DELETE, a, None )) elif a in indexedAttributes: if a == 'olcAccess': # replace "username=...,cn=peercred,cn=external,cn=auth" # by a DN with proper gidNumber and uidNumber entry[a] = map ( partial(sasl_ext_re.sub, acl_sasl_ext) , entry[a] ) # add explicit indices in the entry from the LDIF entry[a] = map( (lambda x: '{%d}%s' % x) , zip(range(len(entry[a])),entry[a]) ) @@ -216,61 +222,61 @@ def processEntry(module, l, dn, entry): # Load the given module. def loadModule(module, l, name): changed = False f = filter_format( '(&(objectClass=olcModuleList)(olcModuleLoad=%s))', [name] ) r = l.search_s( 'cn=config', ldap.SCOPE_ONELEVEL, filterstr = f, attrlist = [''] ) if not r: changed = True if module.check_mode: module.exit_json(changed=changed, msg="add module %s" % name) l.modify_s( 'cn=module{0},cn=config' , [(ldap.MOD_ADD, 'olcModuleLoad', name)] ) return changed # Find the database associated with a given attribute (eg, # olcDbDirectory or olcSuffix). -def getDN_DB(module, l, a, v): +def getDN_DB(module, l, a, v, attrlist=['']): f = filter_format( '(&(objectClass=olcDatabaseConfig)('+a+'=%s))', [v] ) return l.search_s( 'cn=config' , ldap.SCOPE_ONELEVEL , filterstr = f - , attrlist = [''] ) + , attrlist = attrlist ) # Clear the given DB directory and delete the associated database. Fail # if non empty, unless all existing DNS are in skipdns. def wontRemove(module, skipdns, d, _): if d not in skipdns: module.fail_json(msg="won't remove '%s'" % d) def removeDB(module, dbdir, skipdn=None): changed = False if not os.path.exists(dbdir): return False l = ldap.initialize( 'ldapi://' ) l.sasl_interactive_bind_s('', ldap.sasl.external()) - r = getDN_DB( module, l, 'olcDbDirectory', dbdir ) + r = getDN_DB( module, l, 'olcDbDirectory', dbdir, attrlist=['olcSuffix'] ) l.unbind_s() if len(r) > 1: module.fail_json(msg="Multiple results found! This is a bug. Please report.") elif r: dn,entry = r.pop() suffix = entry['olcSuffix'][0] skipdns = [suffix] if skipdn is not None: skipdns.extend([ "%s,%s" % (s,suffix) for s in skipdn ]) # here we need to use slapcat not search_s, because we may # not have read access on the database (even though we're # root!). p = slapcat( suffix=suffix ) parser = LDIFCallback( module, p.stdout , partial(wontRemove,module,skipdns) ) parser.parse() changed = True @@ -351,41 +357,41 @@ def main(): mod = params['module'] suffix = params['suffix'] form = params['format'] name = params['name'] if ignoredn is not None: ignoredn = ignoredn.split(':') changed = False try: if state == "absent": if dbdirectory is not None: changed = removeDB(module,dbdirectory,skipdn=ignoredn) # TODO: might be useful to be able remove DNs else: module.fail_json(msg="missing dbdirectory") elif state == "present": if form == 'slapd.conf': if name is None: - module.fail_json(msg="name") + module.fail_json(msg="missing name") target = slapd_to_ldif(target, name) if target is None and mod is None: module.fail_json(msg="missing target or module") # bind only once per LDIF file for performance l = ldap.initialize( 'ldapi://' ) l.sasl_interactive_bind_s('', ldap.sasl.external()) if mod is None: callback = partial(processEntry,module,l) else: changed |= loadModule (module, l, '%s.la' % mod) if target is None and suffix is None: l.unbind_s() module.exit_json(changed=changed) if target is None or suffix is None: module.fail_json(msg="missing target or suffix") r = getDN_DB(module, l, 'olcSuffix', suffix) if not r: module.fail_json(msg="No database found for suffix %s" % suffix) |