136 lines
5.1 KiB
Python
Executable File
136 lines
5.1 KiB
Python
Executable File
#!/usr/bin/env python
|
|
import os
|
|
import hashlib
|
|
import argparse
|
|
import ldif
|
|
import sys
|
|
import ldap
|
|
import ldap.sasl
|
|
import ldap.dn
|
|
import ldap.modlist
|
|
import base64
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='Load LDIF files into and LDAP server.')
|
|
parser.add_argument('ldifs', metavar='LDIF', nargs='+', help='files to parse')
|
|
parser.add_argument('-H', dest='url', help='URL of the LDAP server')
|
|
parser.add_argument('--quiet', dest='verbose', action='store_false', default=True, help='do not report all modifications')
|
|
parser.add_argument('--fake', action='store_true', default=False, help='do not apply modifications, only simulate them')
|
|
parser.add_argument('--merged-attribute', dest='merged_attribute', action='append',
|
|
default=[], help='attribute that should be merged, not replaced (old values are merged with the new ones)')
|
|
|
|
|
|
conn = ldap.initialize('ldapi://')
|
|
conn.sasl_interactive_bind_s("", ldap.sasl.external())
|
|
|
|
def lower_keys(d):
|
|
return dict((key.lower(), value) for key, value in d.iteritems())
|
|
|
|
def ssha_password(password):
|
|
'''Hash a password using salted SHA1'''
|
|
salt = os.urandom(4)
|
|
h = hashlib.sha1(password)
|
|
h.update(salt)
|
|
return "{SSHA}" + base64.urlsafe_b64encode(h.digest() + salt)
|
|
|
|
class MyLDIFParser(ldif.LDIFParser):
|
|
def __init__(self, *args, **kwargs):
|
|
self.entries = []
|
|
ldif.LDIFParser.__init__(self, *args, **kwargs)
|
|
|
|
def transform_entry(self, entry):
|
|
# crypt passwords with SSHA
|
|
for key in entry:
|
|
if key.lower() == 'userpassword':
|
|
hashed_passwords = []
|
|
for value in entry['userPassword']:
|
|
if not value.startswith('{'):
|
|
value = ssha_password(value)
|
|
hashed_passwords.append(value)
|
|
entry[key] = hashed_passwords
|
|
|
|
def handle(self, dn, entry):
|
|
self.transform_entry(entry)
|
|
self.entries.append((dn, lower_keys(entry)))
|
|
|
|
args = parser.parse_args()
|
|
args.merged_attribute = set(map(str.lower, args.merged_attribute))
|
|
|
|
# Array to store futures adds and modifies
|
|
adds = []
|
|
modifies = []
|
|
|
|
for ldif_path in args.ldifs:
|
|
parser = MyLDIFParser(file(ldif_path))
|
|
parser.parse()
|
|
parser.entries.sort(key=lambda x: ldap.dn.str2dn(x[0])[::-1])
|
|
|
|
for dn, attrs in parser.entries:
|
|
pass
|
|
try:
|
|
result = conn.search_s(dn, ldap.SCOPE_BASE)
|
|
old_attrs = lower_keys(result[0][1])
|
|
new_attrs = {}
|
|
# new keys are keys from source + keys from merged_attribute
|
|
for key in set(attrs.keys()) | args.merged_attribute:
|
|
values = set()
|
|
# merge attributes with their old value if key is in merged_attribute
|
|
if key in args.merged_attribute:
|
|
values |= set(old_attrs.get(key, []))
|
|
# add new values
|
|
values |= set(attrs.get(key, []))
|
|
# flatten to list for python-ldap
|
|
new_attrs[key] = list(values)
|
|
modlist = []
|
|
for key in old_attrs:
|
|
if key not in new_attrs:
|
|
modlist.append((ldap.MOD_DELETE, key, None))
|
|
for key in new_attrs:
|
|
new = set(new_attrs[key])
|
|
if key in old_attrs:
|
|
old = set(old_attrs[key])
|
|
if old & new:
|
|
if old-new:
|
|
modlist.append((ldap.MOD_DELETE, key, list(old-new)))
|
|
if new-old:
|
|
modlist.append((ldap.MOD_ADD, key, list(new-old)))
|
|
else:
|
|
modlist.append((ldap.MOD_REPLACE, key, list(new)))
|
|
elif new:
|
|
modlist.append((ldap.MOD_ADD, key, list(new)))
|
|
if modlist:
|
|
modifies.append((dn, modlist))
|
|
except ldap.NO_SUCH_OBJECT:
|
|
adds.append((dn, ldap.modlist.addModlist(attrs)))
|
|
|
|
for dn, add in adds:
|
|
try:
|
|
if not args.fake:
|
|
conn.add_s(dn, add)
|
|
if args.verbose:
|
|
attributes = [entry[0] for entry in add]
|
|
print '- added entry %s' % dn
|
|
for name, values in add:
|
|
print ' -', name, ':', ', '.join(values)
|
|
except ldap.LDAPError, e:
|
|
print >>sys.stderr, 'Unable to create entry %s: %s' % (dn, e)
|
|
for dn, modify in modifies:
|
|
try:
|
|
if not args.fake:
|
|
conn.modify_s(dn, modify)
|
|
if args.verbose:
|
|
print 'modified entry %s' % dn
|
|
for op, name, values in modify:
|
|
if op == ldap.MOD_ADD:
|
|
print ' - add values "', ', '.join(values), '" to attribute', name
|
|
elif op == ldap.MOD_DELETE:
|
|
if values == None:
|
|
print ' - removed all values of attribute', name
|
|
else:
|
|
print ' - removed values "', ', '.join(values), '" from attribute', name
|
|
elif op == ldap.MOD_REPLACE:
|
|
print ' - replaced values of attribute', name, 'with values "', ', '.join(values), '"'
|
|
except ldap.LDAPError, e:
|
|
print >>sys.stderr, 'Unable to modify entry %s: %s' % (dn, e)
|
|
|