diff options
Diffstat (limited to 'lib/openldap')
-rw-r--r-- | lib/openldap | 49 |
1 files changed, 47 insertions, 2 deletions
diff --git a/lib/openldap b/lib/openldap index 6f2bb68..2cc55db 100644 --- a/lib/openldap +++ b/lib/openldap @@ -6,40 +6,41 @@ # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. 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', ]) # 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 @@ -161,41 +162,41 @@ def processEntry(module, l, dn, entry): 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) 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(re.sub, sasl_ext_re, acl_sasl_ext) + 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]) ) if v != entry[a]: diff.append(( ldap.MOD_REPLACE, a, entry[a] )) elif v != entry[a]: # for non-indexed attribute, we update values in the # symmetric difference only s1 = set(v) s2 = set(entry[a]) if s1.isdisjoint(s2): # replace the former values with the new ones diff.append(( ldap.MOD_REPLACE, a, entry[a] )) else: x = list(s1.difference(s2)) if x: diff.append(( ldap.MOD_DELETE, a, x )) y = list(s2.difference(s1)) if y: @@ -275,74 +276,118 @@ def removeDB(module, dbdir, skipdn=None): changed = True if module.check_mode: module.exit_json(changed=changed, msg="remove dir %s" % dbdir) # slapd doesn't support database deletion, so we need to turn it # off and remove it from slapd.d manually. service( 'slapd', 'stop' ) path = [ os.sep, 'etc', 'ldap', 'slapd.d' ] ldif = explode_dn(dn)[::-1] ldif[-1] += ".ldif" path.extend( ldif ) os.unlink( os.path.join(*path) ) # delete all children in path, but not the path directory itself. for file in os.listdir(dbdir): os.unlink( os.path.join(dbdir, file) ) service( 'slapd', 'start' ) return changed +# Convert a *.schema file into *.ldif format. The algorithm can be found +# in /etc/ldap/schema/openldap.ldif . +def slapd_to_ldif(src, name): + s = open( src, 'r' ) + d = tempfile.NamedTemporaryFile(delete=False) + atexit.register(lambda: os.unlink( d.name )) + + d.write('dn: cn=%s,cn=schema,cn=config\n' % name) + d.write('objectClass: olcSchemaConfig\n') + + re1 = re.compile( r'^objectIdentifier\s(.*)', re.I ) + re2 = re.compile( r'^objectClass\s(.*)', re.I ) + re3 = re.compile( r'^attributeType\s(.*)', re.I ) + reSp = re.compile( r'^\s+' ) + for line in s.readlines(): + if line == '\n': + line = '#\n' + m1 = re1.match(line) + m2 = re2.match(line) + m3 = re3.match(line) + if m1 is not None: + line = 'olcObjectIdentifier: %s' % m1.group(1) + elif m2 is not None: + line = 'olcObjectClasses: %s' % m2.group(1) + elif m3 is not None: + line = 'olcAttributeTypes: %s' % m3.group(1) + + d.write( reSp.sub(line, ' ') ) + + + s.close() + d.close() + return d.name + + def main(): module = AnsibleModule( argument_spec = dict( dbdirectory = dict( default=None ), ignoredn = dict( default=None ), - state = dict(default="present", choices=["absent", "present"]), + state = dict( default="present", choices=["absent", "present"]), target = dict( default=None ), module = dict( default=None ), suffix = dict( default=None ), + format = dict( default="ldif", choices=["ldif","slapd.conf"] ), + name = dict( default=None ), ), supports_check_mode=True ) params = module.params state = params['state'] dbdirectory = params['dbdirectory'] ignoredn = params['ignoredn'] target = params['target'] 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") + 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) elif len(r) > 1: module.fail_json(msg="Multiple results found! This is a bug. Please report.") |