source: dassldapsync/dassldapsync.py@ 1218

Last change on this file since 1218 was 1218, checked in by joergs, on Nov 7, 2016 at 8:21:39 PM

initial (from trunk/people/slederer/dassldapsync)

  • Property svn:executable set to *
File size: 9.2 KB
Line 
1#!/usr/bin/python
2# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
3
4import ldap
5import ldif
6import ldap.modlist
7import ConfigParser
8import os
9import sys
10
11import dateutil.parser
12import dateutil.tz
13import datetime
14
15class Options:
16 def __init__(self):
17 self.delete=True
18 self.starttls=False
19 self.updateonly=False
20 self.attrfilter=None
21 self.exclude=None
22 self.renameattr=None
23 self.renamecommand=None
24 self.verbose=False
25
26def notify_created(dn):
27 print "notify_created",dn
28
29def notify_modified(dn):
30 print "notify_modified",dn
31
32def notify_deleted(dn):
33 print "notify_deleted",dn
34
35def notify_renamed(dn,newdn,uid,newuid,options):
36 print "notify_renamed",dn,newdn
37 subprocess.check_call("%s %s %s %s %s" % (options.renamecommand,dn,newdn,uid,newuid),shell=True)
38
39def readLDIFSource(path):
40 with open(path,'r') as f:
41 parser = ldif.LDIFRecordList(f)
42 parser.parse()
43 result = parser.all_records
44 return result
45
46def readLdapSource(server,binddn,bindpw,basedn,filter,starttls=False):
47 con = ldap.open(server,port=389)
48 if starttls:
49 con.start_tls_s()
50 con.simple_bind_s(binddn,bindpw)
51 results=con.search_s(basedn,ldap.SCOPE_SUBTREE,filter,None)
52 return results
53
54def syncLdapDestination(searchresult,destserver,destbinddn,destbindpw,srcbasedn,destbasedn,destrdn,options=Options()):
55
56 attrmap=ldap.cidict.cidict({
57 })
58 classmap={
59 }
60
61 junk_attrs = [ "memberof", "modifiersname", "modifytimestamp", "entryuuid", "entrycsn", "contextcsn", "creatorsname", "createtimestamp", "structuralobjectclass", "pwdchangedtime", "pwdfailuretime" ]
62 update_objects=[]
63
64 if len(searchresult)==0:
65 print "empty source, aborting"
66 return
67
68 for r in searchresult:
69 dn=r[0]
70
71 d=ldap.cidict.cidict(r[1])
72 objectclasses=d["objectclass"]
73
74 newObjectclasses=[]
75 for o in objectclasses:
76 if o.lower() in classmap:
77 new_oc = classmap[o.lower()]
78 if not new_oc in newObjectclasses:
79 newObjectclasses.append(new_oc)
80 else:
81 #pass
82 if not o in newObjectclasses:
83 newObjectclasses.append(o)
84
85 d["objectclass"]=newObjectclasses
86
87 rpath = dn[:-len(srcbasedn)]
88 # print "dn:",dn,"src:",srcbasedn,"rpath:",rpath,"dest:",destbasedn
89
90 for a in d.keys():
91 attr=a
92 if attrmap.has_key(a.lower()):
93 attr=attrmap[attr].lower()
94 if attr.lower()!=a.lower():
95 # print "# ",a," -> ",attr
96 values=d[a]
97 del d[a]
98 d[attr]=values
99 else:
100 # del d[a]
101 continue
102
103 dn=rpath+destbasedn
104
105 update_objects.append((dn,d))
106
107 con = ldap.open(destserver,port=389)
108 if options.starttls:
109 con.start_tls_s()
110 con.simple_bind_s(destbinddn,destbindpw)
111
112 exist=0
113 failed=0
114 good=0
115 deleted=0
116 existing=[]
117 tzutc = dateutil.tz.gettz('UTC')
118 now = datetime.datetime.now(tzutc)
119 max_age = datetime.timedelta(days=pwd_max_days)
120
121 for o in update_objects:
122 dn,entry=o
123 try:
124 result = None
125 if options.renameattr and entry.has_key(options.renameattr):
126 result = con.search_s(destbasedn,ldap.SCOPE_SUB,"%s=%s" % (options.renameattr,entry[options.renameattr][0]))
127 if result != None and len(result)>0:
128 existingDn, existingEntry = result[0]
129 if existingDn.lower() != dn.lower():
130 con.modrdn_s(existingDn,dn)
131 notify_renamed(existingDn,dn,existingEntry[options.renameattr][0],entry[options.renameattr][0],options)
132 continue
133
134 result=con.search_s(dn,ldap.SCOPE_BASE,"objectclass=*")
135 destDn,destEntry=result[0]
136
137 if options.exclude!=None and destDn.lower().endswith(options.exclude):
138 continue
139
140 # hack for syncing accounts locked by password policy
141 do_unlock = False
142 if pwd_max_days>0 and entry.has_key('pwdChangedTime'):
143 # print "pwdChangedTime set for",dn
144 pwdChange = entry['pwdChangedTime'][0]
145 d = dateutil.parser.parse(pwdChange)
146 if (now-d)>max_age:
147 if dn.startswith('cn=haydar aldetest'):
148 entry['pwdAccountLockedTime']=[ '000001010000Z' ]
149 print "locking",dn,pwdChange
150 else:
151 result = con.search_s(dn,ldap.SCOPE_BASE,"objectclass=*", \
152 attrlist = [ 'pwdAccountLockedTime' ])
153 tmp_dn, tmp_entry = result[0]
154 if tmp_entry.has_key('pwdAccountLockedTime'):
155 print "unlocking",dn,pwdChange
156 do_unlock = True
157
158 mod_attrs=ldap.modlist.modifyModlist(destEntry,entry)
159
160 # hack for unlocking, see above
161 if do_unlock:
162 mod_attrs.append( (ldap.MOD_DELETE,'pwdAccountLockedTime',None) )
163
164 if options.attrfilter!=None:
165 mod_attrs=[ a for a in mod_attrs if a[1] in options.attrfilter]
166
167 if junk_attrs!=None:
168 mod_attrs=[ a for a in mod_attrs if a[1].lower() not in junk_attrs]
169
170 if mod_attrs!=[]:
171 exist=exist+1
172 #if options.verbose:
173 # print dn, "already exists"
174 try:
175 # print dn,destEntry['objectClass'],entry['objectClass']
176 con.modify_s(dn,mod_attrs)
177 except:
178 print "error",dn,mod_attrs
179 notify_modified(dn)
180 else:
181 pass
182 # print "no changes, not modified"
183
184 except ldap.NO_SUCH_OBJECT:
185 if options.updateonly==True:
186 continue
187
188 try:
189 con.add_s(dn,ldap.modlist.addModlist(entry,junk_attrs))
190 notify_created(dn)
191 if options.verbose:
192 print dn,"created"
193 good=good+1
194 except (ldap.OBJECT_CLASS_VIOLATION,ldap.NO_SUCH_OBJECT):
195 print dn, "failed"
196 failed=failed+1
197
198 if options.delete==True and options.updateonly==False:
199 result=con.search_s(destbasedn,ldap.SCOPE_SUBTREE,filter)
200 existing=[ x[0].lower() for x in result ]
201
202 morituri=existing
203
204 if destbasedn.lower() in existing:
205 morituri.remove(destbasedn.lower())
206
207 for o in update_objects:
208 dn,entry=o
209 if dn.lower() in existing:
210 morituri.remove(dn.lower())
211 for dn in morituri:
212 if options.exclude != None and dn.lower().endswith(options.exclude):
213 # print "ignoring",dn
214 continue
215
216 try:
217 con.delete_s(dn)
218 except:
219 print "failed to delete",dn
220
221 notify_deleted(dn)
222 if options.verbose:
223 print dn,"deleted"
224 deleted=deleted+1
225
226 con.unbind()
227 print good,"entries created,",exist,"updated,",deleted,"deleted,",failed,"failed."
228
229
230if __name__ == "__main__":
231 conffile="ldapsync.conf"
232 filter = None
233 exclude = None
234 if len(sys.argv)>1:
235 conffile=sys.argv[1]
236
237 config=ConfigParser.ConfigParser()
238 config.read(conffile)
239
240 srcfile = None
241 try:
242 srcfile = config.get("source","file")
243 except:
244 pass
245
246 basedn = config.get("source","baseDn")
247
248 if srcfile==None:
249 srv = config.get("source","server")
250 admindn = config.get("source","bindDn")
251 adminpw = config.get("source","bindPassword")
252 filter = config.get("source","filter")
253 starttls = config.getboolean("source","starttls")
254
255 if filter==None:
256 filter = '(objectClass=*)'
257
258 options = Options()
259
260 try:
261 options.exclude = config.get("destination","excludesubtree").lower()
262 except:
263 pass
264
265 destsrv = config.get("destination","server")
266 destadmindn = config.get("destination","bindDn")
267 destadminpw = config.get("destination","bindPassword")
268 destbasedn = config.get("destination","baseDn")
269 destdelete = config.getboolean("destination","delete")
270 rdn = config.get("destination","rdn")
271
272 try:
273 options.updateonly = not config.getboolean("destination","create")
274 except:
275 options.updateonly = False
276 options.starttls = config.getboolean("destination","starttls")
277 try:
278 options.attrfilter = config.get("destination","attributes").split(",")
279 except:
280 options.attrfilter = None
281
282 try:
283 options.renameattr = config.get("destination","detectRename")
284 except:
285 options.renameattr = None
286
287 try:
288 options.renamecommand = config.get("destination","detectRename")
289 except:
290 options.renamecommand = None
291
292 if srcfile:
293 result = readLDIFSource(srcfile)
294 else:
295 result = readLdapSource(srv,admindn,adminpw,basedn,filter,starttls)
296
297 try:
298 pwd_max_days = int(config.get("source","pwd_max_days"))
299 except:
300 pwd_max_days = 0
301
302 syncLdapDestination(result,destsrv,destadmindn,destadminpw,basedn,destbasedn,rdn,options)
Note: See TracBrowser for help on using the repository browser.