Changeset 1221 for dassldapsync
- Timestamp:
- Nov 16, 2016, 4:18:48 PM (8 years ago)
- Location:
- dassldapsync
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
dassldapsync/dassldapsync.conf
r1219 r1221 12 12 #starttls=true 13 13 #pwd_max_days=0 14 #mode=syncrepl 14 15 15 16 [destination] … … 18 19 bindPassword=secret 19 20 baseDn=dc=example,dc=com 20 #attributes=21 21 delete=true 22 22 create=true -
dassldapsync/dassldapsync.py
r1220 r1221 2 2 # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 3 3 4 # core modules 4 5 import argparse 5 6 import ConfigParser 7 import logging 8 from pprint import pprint 9 import signal 10 import subprocess 11 import sys 12 import time 13 14 # external modules 6 15 import datetime 7 16 import dateutil.parser 8 17 import dateutil.tz 9 18 import ldap 19 from ldap.ldapobject import ReconnectLDAPObject 10 20 import ldap.modlist 21 from ldap.syncrepl import SyncreplConsumer 22 import ldapurl 11 23 import ldif 12 import logging 13 import os 14 import sys 24 25 15 26 16 27 def getArguments(): 17 28 configfile = '/etc/dassldapsync.conf' 18 parser = argparse.ArgumentParser(description='Synchronize the content of two LDAP servers.' 29 parser = argparse.ArgumentParser(description='Synchronize the content of two LDAP servers.') 19 30 parser.add_argument('-d', '--debug', action='store_true', help="enable debug output") 20 parser.add_argument('configfile', default=configfile, help="Configuration file [default: {}]".format(configfile))21 args = parser.parse_args()22 return args23 24 25 class Options :31 parser.add_argument('configfile', default=configfile, 32 help="Configuration file [default: {}]".format(configfile)) 33 return parser.parse_args() 34 35 36 class Options(object): 26 37 def __init__(self): 27 self.delete =True28 self.starttls =False29 self.updateonly =False30 self.filter =None31 self.attr filter=None32 self.exclude =None33 self.renameattr =None34 self.renamecommand =None35 self.pwd_max_days =036 38 self.delete = True 39 self.starttls = False 40 self.updateonly = False 41 self.filter = None 42 self.attrlist = None 43 self.exclude = None 44 self.renameattr = None 45 self.renamecommand = None 46 self.pwd_max_days = 0 47 37 48 class ConfigParserDefaults(ConfigParser.ConfigParser, object): 38 def get(self, section, option, default =None):49 def get(self, section, option, default=None): 39 50 try: 40 51 result = super(self.__class__, self).get(section, option) … … 46 57 return result 47 58 48 def get0(self, section, option, default = None): 59 def get_section(self, section): 60 if section in self._sections: 61 return self._sections[section] 62 63 def get0(self, section, option, default=None): 49 64 try: 50 65 result = super(self.__class__, self).get(section, option) … … 53 68 return result 54 69 55 def getboolean(self, section, option, default =None):70 def getboolean(self, section, option, default=None): 56 71 try: 57 72 result = super(self.__class__, self).getboolean(section, option) … … 63 78 return result 64 79 80 def get_ldap_url_obj(self, section): 81 baseurl = 'ldap://{server}:389/{basedn}'.format(**(self.get_section(section))) 82 attrs = None 83 if self.get0(section, 'attributes') is not None: 84 attrs = self.get(section, 'attributes').split(',') 85 return ldapurl.LDAPUrl( 86 baseurl, 87 dn=self.get(section, 'baseDn', ''), 88 who=self.get0(section, 'bindDn'), 89 cred=self.get0(section, 'basePassword'), 90 filterstr=self.get0(section, 'filter'), 91 attrs=attrs 92 ) 93 94 65 95 def readLDIFSource(path): 66 96 logger = logging.getLogger() 67 97 logger.info("reading LDAP objects from file {}".format(path)) 68 with open(path, 'r') as f:98 with open(path, 'r') as f: 69 99 parser = ldif.LDIFRecordList(f) 70 100 parser.parse() … … 72 102 return result 73 103 74 def readLdapSource(server, binddn,bindpw,basedn,filter,attrs=None,starttls=False):104 def readLdapSource(server, binddn, bindpw, basedn, filterstr, attrlist=None, starttls=False): 75 105 logger = logging.getLogger() 76 106 logger.info("reading LDAP objects from server {}".format(server)) 77 con = ldap.open(server, port=389)107 con = ldap.open(server, port=389) 78 108 if starttls: 79 80 con.simple_bind_s(binddn, bindpw)81 results =con.search_s(basedn,ldap.SCOPE_SUBTREE,filter,attrs)109 con.start_tls_s() 110 con.simple_bind_s(binddn, bindpw) 111 results = con.search_s(basedn, ldap.SCOPE_SUBTREE, filterstr, attrlist) 82 112 return results 83 113 84 114 class LdapSync(object): 85 def __init__(self, destserver,destbinddn,destbindpw,srcbasedn,destbasedn,options=Options()): 115 def __init__(self, destserver, 116 destbinddn, destbindpw, 117 srcbasedn, destbasedn, options=Options()): 86 118 self.logger = logging.getLogger() 87 119 88 120 self.destserver = destserver 89 121 self.destbasedn = destbasedn … … 91 123 self.destbindpw = destbindpw 92 124 self.options = options 93 125 94 126 self.srcbasedn = srcbasedn 95 127 96 128 self.con = None 97 98 self.attrmap=ldap.cidict.cidict({}) 99 self.classmap={} 100 101 self.junk_attrs = [ "memberof", "modifiersname", "modifytimestamp", "entryuuid", "entrycsn", "contextcsn", "creatorsname", "createtimestamp", "structuralobjectclass", "pwdchangedtime", "pwdfailuretime" ] 102 103 def __ldap_connect(self): 129 130 self.attrmap = ldap.cidict.cidict({}) 131 self.classmap = {} 132 133 self.junk_attrs = ["memberof", "modifiersname", "modifytimestamp", "entryuuid", 134 "entrycsn", "contextcsn", "creatorsname", "createtimestamp", 135 "structuralobjectclass", "pwdchangedtime", "pwdfailuretime"] 136 137 self.reset_result() 138 139 140 def reset_result(self): 141 self.result = { 142 'add': {'ok': [], 'failed': []}, 143 'update': {'ok': [], 'failed': []}, 144 'delete': {'ok': [], 'failed': []}, 145 } 146 147 148 def _dest_ldap_connect(self): 104 149 if self.con is None: 105 150 self.logger.info("connect to destination LDAP server {}".format(self.destserver)) 106 self.con = ldap.open(self.destserver, port=389)151 self.con = ldap.open(self.destserver, port=389) 107 152 if self.options.starttls: 108 153 self.con.start_tls_s() 109 self.con.simple_bind_s(self.destbinddn, self.destbindpw)154 self.con.simple_bind_s(self.destbinddn, self.destbindpw) 110 155 111 156 def __adapt_dn(self, dn): … … 113 158 if self.srcbasedn != self.destbasedn: 114 159 dn_old = dn 115 rpath = dn[:-len(s rcbasedn)]116 dn =rpath+self.destbasedn160 rpath = dn[:-len(self.srcbasedn)] 161 dn = rpath+self.destbasedn 117 162 self.logger.debug("moved {} to {}".format(dn_old, dn)) 118 163 # print "dn:",dn,"src:",srcbasedn,"rpath:",rpath,"dest:",destbasedn … … 132 177 self.logger.debug("modifying LDAP objects retrieved from source LDAP") 133 178 134 update_objects =[]135 179 update_objects = [] 180 136 181 for r in searchresult: 137 182 dn = self.__adapt_dn(r[0]) 138 d =ldap.cidict.cidict(r[1])183 d = ldap.cidict.cidict(r[1]) 139 184 140 185 if self.__is_dn_included(dn): 141 objectclasses =d["objectclass"]142 143 newObjectclasses =[]186 objectclasses = d["objectclass"] 187 188 newObjectclasses = [] 144 189 for o in objectclasses: 145 190 if o.lower() in self.classmap: 146 147 if not new_ocin newObjectclasses:148 191 new_oc = self.classmap[o.lower()] 192 if new_oc not in newObjectclasses: 193 newObjectclasses.append(new_oc) 149 194 else: 150 #pass 151 if not o in newObjectclasses: 195 if o not in newObjectclasses: 152 196 newObjectclasses.append(o) 153 197 154 d["objectclass"] =newObjectclasses198 d["objectclass"] = newObjectclasses 155 199 156 200 for a in d.keys(): 157 attr =a201 attr = a 158 202 if self.attrmap.has_key(a.lower()): 159 attr=self.attrmap[attr].lower() 160 if attr.lower()!=a.lower(): 161 # print "# ",a," -> ",attr 162 values=d[a] 203 attr = self.attrmap[attr].lower() 204 if attr.lower() != a.lower(): 205 values = d[a] 163 206 del d[a] 164 d[attr] =values165 166 update_objects.append((dn, d))207 d[attr] = values 208 209 update_objects.append((dn, d)) 167 210 return update_objects 168 211 169 212 170 def _ _get_dest_entry(self, dn, entry):213 def _get_dest_entry(self, dn, entry): 171 214 """ 172 215 In the destination LDAP, the objects should be named 173 216 according to options.renameattr. 174 217 """ 218 attrlist = self.options.attrlist 219 175 220 existingDestDn = None 176 221 existingDestEntry = None 177 222 if self.options.renameattr and entry.has_key(self.options.renameattr): 178 searchresult = self.con.search_s(self.destbasedn,ldap.SCOPE_SUB,"%s=%s" % (self.options.renameattr,entry[self.options.renameattr][0])) 179 if searchresult != None and len(searchresult)>0: 223 searchresult = self.con.search_s( 224 self.destbasedn, 225 ldap.SCOPE_SUBTREE, 226 '%s=%s' % (self.options.renameattr, entry[self.options.renameattr][0]), attrlist) 227 if searchresult is not None and len(searchresult) > 0: 180 228 existingDestDn, existingDestEntry = searchresult[0] 181 229 if existingDestDn.lower() != dn.lower(): 182 self.con.modrdn_s(existingDestDn,dn) 183 notify_renamed(existingDestDn, dn, existingDestEntry[self.options.renameattr][0],entry[self.options.renameattr][0],options) 230 self.con.modrdn_s(existingDestDn, dn) 231 self.notify_renamed(existingDestDn, dn, 232 existingDestEntry[self.options.renameattr][0], 233 entry[self.options.renameattr][0], 234 options) 184 235 if existingDestDn is None: 185 searchresult =self.con.search_s(dn,ldap.SCOPE_BASE,"objectclass=*")236 searchresult = self.con.search_s(dn, ldap.SCOPE_BASE, 'objectclass=*', attrlist) 186 237 existingDestDn, existingDestEntry = searchresult[0] 187 238 return (existingDestDn, existingDestEntry) … … 191 242 # hack for syncing accounts locked by password policy 192 243 do_unlock = False 193 if self.options.pwd_max_days >0 and entry.has_key('pwdChangedTime'):244 if self.options.pwd_max_days > 0 and entry.has_key('pwdChangedTime'): 194 245 # print "pwdChangedTime set for",dn 195 246 pwdChange = entry['pwdChangedTime'][0] 196 247 d = dateutil.parser.parse(pwdChange) 197 if (now-d) >max_age:198 entry['pwdAccountLockedTime'] =['000001010000Z']199 self.logger.info( 248 if (now-d) > max_age: 249 entry['pwdAccountLockedTime'] = ['000001010000Z'] 250 self.logger.info("locking {} {}".format(dn, pwdChange)) 200 251 else: 201 252 # pwdAccountLockedTime is a operational attribute, 202 253 # and therefore not part of entry. 203 254 # Do extra search to retrieve attribute. 204 searchresult = self.con.search_s(dn,ldap.SCOPE_BASE,"objectclass=*", \ 205 attrlist = ['pwdAccountLockedTime']) 255 searchresult = self.con.search_s( 256 dn, ldap.SCOPE_BASE, 257 "objectclass=*", attrlist=['pwdAccountLockedTime']) 206 258 tmp_dn, tmp_entry = searchresult[0] 207 259 if tmp_entry.has_key('pwdAccountLockedTime'): … … 210 262 211 263 212 213 def __syncLdapDestination(self, update_objects): 214 215 result = { 216 'add': { 'ok': [], 'failed': [] }, 217 'update': { 'ok': [], 'failed': [] }, 218 } 219 264 def _syncLdapObject(self, srcDn, srcAttributes): 220 265 tzutc = dateutil.tz.gettz('UTC') 221 266 now = datetime.datetime.now(tzutc) 222 267 max_age = datetime.timedelta(days=self.options.pwd_max_days) 223 268 269 try: 270 destDn, destAttributes = self._get_dest_entry(srcDn, srcAttributes) 271 272 # hack for syncing accounts locked by password policy 273 do_unlock = self.__handle_pwdAccountLockedTime(srcDn, srcAttributes, now, max_age) 274 275 mod_attrs = ldap.modlist.modifyModlist(destAttributes, srcAttributes) 276 277 # hack for unlocking, see above 278 if do_unlock: 279 self.logger.info("unlocking {} {}".format(destDn, 'pwdAccountLockedTime')) 280 mod_attrs.append((ldap.MOD_DELETE, 'pwdAccountLockedTime', None)) 281 282 if self.options.attrlist is not None: 283 mod_attrs = [a for a in mod_attrs if a[1].lower() in self.options.attrlist] 284 285 if self.junk_attrs is not None: 286 mod_attrs = [a for a in mod_attrs if a[1].lower() not in self.junk_attrs] 287 288 if mod_attrs: 289 try: 290 self.logger.debug('mod_attrs: ' + str(mod_attrs)) 291 self.con.modify_s(srcDn, mod_attrs) 292 self.notify_modified(srcDn) 293 except: 294 self.logger.exception('modify failed') 295 self.notify_modified(srcDn, False) 296 297 except ldap.NO_SUCH_OBJECT: 298 if not self.options.updateonly: 299 try: 300 self.con.add_s(srcDn, ldap.modlist.addModlist(srcAttributes, self.junk_attrs)) 301 self.notify_created(srcDn) 302 except (ldap.OBJECT_CLASS_VIOLATION, 303 ldap.NO_SUCH_OBJECT, 304 ldap.CONSTRAINT_VIOLATION): 305 self.notify_created(srcDn, False) 306 307 308 def __syncLdapDestination(self, update_objects): 309 224 310 logger.debug("writing data to destination LDAP") 225 for o in update_objects: 226 dn,entry=o 227 #logger.debug(dn) 228 try: 229 destDn,destEntry=self.__get_dest_entry(dn, entry) 230 231 # hack for syncing accounts locked by password policy 232 do_unlock = self.__handle_pwdAccountLockedTime(dn, entry, now, max_age) 233 234 mod_attrs=ldap.modlist.modifyModlist(destEntry,entry) 235 236 # hack for unlocking, see above 237 if do_unlock: 238 self.logger.info( "unlocking {} {}".format(dn,pwdChange)) 239 mod_attrs.append( (ldap.MOD_DELETE,'pwdAccountLockedTime',None) ) 240 241 if self.options.attrfilter is not None: 242 mod_attrs=[ a for a in mod_attrs if a[1].lower() in options.attrfilter] 243 244 if self.junk_attrs is not None: 245 mod_attrs=[ a for a in mod_attrs if a[1].lower() not in self.junk_attrs] 246 247 if mod_attrs: 248 try: 249 self.con.modify_s(dn,mod_attrs) 250 result['update']['ok'].append(dn) 251 self.notify_modified(dn) 252 except: 253 self.logger.error("failed to modify {} ({})".format(dn,mod_attrs)) 254 result['update']['failed'].append(dn) 255 256 except ldap.NO_SUCH_OBJECT: 257 if options.updateonly==True: 258 continue 259 260 try: 261 self.con.add_s(dn,ldap.modlist.addModlist(entry,self.junk_attrs)) 262 self.notify_created(dn) 263 result['add']['ok'].append(dn) 264 except (ldap.OBJECT_CLASS_VIOLATION, ldap.NO_SUCH_OBJECT, ldap.CONSTRAINT_VIOLATION): 265 self.logger.warning("failed to add {}".format(dn)) 266 result['add']['failed'].append(dn) 267 return result 268 269 311 for obj in update_objects: 312 dn, entry = obj 313 self._syncLdapObject(dn, entry) 314 315 270 316 def __deleteDestLdapObjects(self, update_objects): 271 317 """ … … 275 321 """ 276 322 277 result = { 278 'delete': { 'ok': [], 'failed': [] }, 279 } 280 281 searchresult=self.con.search_s(self.destbasedn,ldap.SCOPE_SUBTREE,self.options.filter) 282 existing=[ x[0].lower() for x in searchresult ] 283 284 morituri=existing 323 searchresult = self.con.search_s(self.destbasedn, ldap.SCOPE_SUBTREE, self.options.filter) 324 existing = [x[0].lower() for x in searchresult] 325 326 morituri = existing 285 327 286 328 if self.destbasedn.lower() in existing: 287 329 morituri.remove(self.destbasedn.lower()) 288 330 289 for o in update_objects:290 dn, entry=o331 for obj in update_objects: 332 dn, entry = obj 291 333 if dn.lower() in existing: 292 334 morituri.remove(dn.lower()) … … 295 337 try: 296 338 self.con.delete_s(dn) 297 self.notify_deleted(dn) 298 result['delete']['ok'].append(dn) 339 self.notify_deleted(dn) 299 340 except: 300 self.logger.error("failed to delete {}".format(dn)) 301 result['delete']['failed'].append(dn) 302 return result 303 341 self.notify_deleted(dn, False) 342 304 343 305 344 def sync(self, searchresult): … … 307 346 Synchronize entries from searchresult to destination LDAP server. 308 347 """ 309 result = { 310 'add': { 'ok': [], 'failed': [] }, 311 'update': { 'ok': [], 'failed': [] }, 312 'delete': { 'ok': [], 'failed': [] }, 313 } 314 315 if len(searchresult)==0: 348 if len(searchresult) == 0: 316 349 self.logger.error("empty source, aborting") 317 350 return 318 351 319 self._ _ldap_connect()352 self._dest_ldap_connect() 320 353 321 354 update_objects = self.__adapt_source_ldap_objects(searchresult) 322 result =self.__syncLdapDestination(update_objects)323 if self.options.delete ==True and self.options.updateonly==False:324 result.update(self.__deleteDestLdapObjects(update_objects))355 self.__syncLdapDestination(update_objects) 356 if self.options.delete and not self.options.updateonly: 357 self.__deleteDestLdapObjects(update_objects) 325 358 self.con.unbind() 326 327 self.__log_summary( result,True)328 329 330 331 def __log_summary(self, result, show_failed = True, show_ok = False):359 360 self.__log_summary(True) 361 362 363 def __log_summary(self, show_failed=True, show_ok=False): 364 result = self.result 332 365 for action in result.keys(): 333 366 ok = len(result[action]['ok']) … … 343 376 print "\n".join(result[action]['failed']) 344 377 345 346 def notify_created(self, dn): 347 print "created",dn 348 349 def notify_modified(self, dn): 350 print "modified",dn 351 352 def notify_deleted(self, dn): 353 print "deleted",dn 378 def get_short_dn(self, dn): 379 return dn.lower().replace(',' + self.srcbasedn.lower(), '') 380 381 def notify_created(self, dn, ok=True): 382 if ok: 383 logger.debug('{} created'.format(self.get_short_dn(dn))) 384 self.result['add']['ok'].append(dn) 385 else: 386 self.logger.warning("failed to add {}".format(dn)) 387 self.result['add']['failed'].append(dn) 388 389 def notify_modified(self, dn, ok=True): 390 if ok: 391 logger.debug('{} modified'.format(self.get_short_dn(dn))) 392 self.result['update']['ok'].append(dn) 393 else: 394 self.logger.error("failed to modify {}".format(dn)) 395 self.result['update']['failed'].append(dn) 396 397 def notify_deleted(self, dn, ok=True): 398 if ok: 399 logger.debug('{} deleted'.format(self.get_short_dn(dn))) 400 self.result['delete']['ok'].append(dn) 401 else: 402 self.logger.error("failed to delete {}".format(dn)) 403 self.result['delete']['failed'].append(dn) 354 404 355 405 def notify_renamed(self, dn, newdn, uid, newuid, options): 356 print "renamed",dn,newdn 357 subprocess.check_call("%s %s %s %s %s" % (options.renamecommand,dn,newdn,uid,newuid),shell=True) 406 print "renamed", dn, newdn 407 subprocess.check_call( 408 "%s %s %s %s %s" % (options.renamecommand, dn, newdn, uid, newuid), 409 shell=True) 410 411 412 413 class SyncReplConsumer(ReconnectLDAPObject, SyncreplConsumer): 414 """ 415 Syncrepl Consumer interface 416 """ 417 418 def __init__(self, dest, syncrepl_entry_callback, *args, **kwargs): 419 self.logger = logging.getLogger() 420 # Initialise the LDAP Connection first 421 ldap.ldapobject.ReconnectLDAPObject.__init__(self, *args, **kwargs) 422 # We need this for later internal use 423 self.__presentUUIDs = dict() 424 self.cookie = None 425 self.dest_ldap = dest 426 self.syncrepl_entry_callback = syncrepl_entry_callback 427 428 def syncrepl_get_cookie(self): 429 return self.cookie 430 431 def syncrepl_set_cookie(self, cookie): 432 self.cookie = cookie 433 434 def syncrepl_entry(self, dn, attributes, uuid): 435 # First we determine the type of change we have here 436 # (and store away the previous data for later if needed) 437 if uuid in self.__presentUUIDs: 438 change_type = 'modify' 439 else: 440 change_type = 'add' 441 # Now we store our knowledge of the existence of this entry 442 self.__presentUUIDs[uuid] = dn 443 # Debugging 444 logger.debug('{}: {} ({})'.format(dn, change_type, ",".join(attributes.keys()))) 445 # If we have a cookie then this is not our first time being run, 446 # so it must be a change 447 if self.cookie is not None: 448 self.syncrepl_entry_callback(dn, attributes) 449 450 451 def syncrepl_delete(self, uuids): 452 """ syncrepl_delete """ 453 # Make sure we know about the UUID being deleted, just in case... 454 uuids = [uuid for uuid in uuids if uuid in self.__presentUUIDs] 455 # Delete all the UUID values we know of 456 for uuid in uuids: 457 logger.debug('detected deletion of entry {} ({})', uuid, self.__presentUUIDs[uuid]) 458 del self.__presentUUIDs[uuid] 459 460 def syncrepl_present(self, uuids, refreshDeletes=False): 461 """ called on initial sync """ 462 if uuids is not None: 463 self.logger.debug('uuids: {}'.format(','.join(uuids))) 464 # If we have not been given any UUID values, 465 # then we have recieved all the present controls... 466 if uuids is None: 467 # We only do things if refreshDeletes is false as the syncrepl 468 # extension will call syncrepl_delete instead when it detects a 469 # delete notice 470 if not refreshDeletes: 471 deletedEntries = [ 472 uuid for uuid in self.__presentUUIDs 473 ] 474 self.syncrepl_delete(deletedEntries) 475 # Phase is now completed, reset the list 476 self.__presentUUIDs = {} 477 else: 478 # Note down all the UUIDs we have been sent 479 for uuid in uuids: 480 self.__presentUUIDs[uuid] = True 481 482 483 def syncrepl_refreshdone(self): 484 self.logger.info('Initial synchronization is now done, persist phase begins') 485 #self.logger.debug('UUIDs:\n' + '\n'.join(self.__presentUUIDs)) 486 487 488 489 class LdapSyncRepl(LdapSync): 490 def __init__(self, destsrv, 491 destadmindn, destadminpw, 492 basedn, destbasedn, 493 options=Options(), source_ldap_url_obj=None): 494 # Install our signal handlers 495 signal.signal(signal.SIGTERM, self.shutdown) 496 self.watcher_running = False 497 self.source_ldap_url_obj = source_ldap_url_obj 498 self.ldap_credentials = False 499 self.source_ldap_connection = None 500 super(LdapSyncRepl, self).__init__(destsrv, 501 destadmindn, destadminpw, 502 basedn, destbasedn, options) 503 504 505 def sync(self): 506 self._dest_ldap_connect() 507 self.watcher_running = True 508 while self.watcher_running: 509 self.logger.info('Connecting to source LDAP server') 510 # Prepare the LDAP server connection (triggers the connection as well) 511 self.source_ldap_connection = SyncReplConsumer(self.con, 512 self.perform_application_sync_callback, 513 self.source_ldap_url_obj.initializeUrl()) 514 515 if self.source_ldap_url_obj.who and self.source_ldap_url_obj.cred: 516 self.ldap_credentials = True 517 # Now we login to the LDAP server 518 try: 519 self.source_ldap_connection.simple_bind_s( 520 self.source_ldap_url_obj.who, self.source_ldap_url_obj.cred) 521 except ldap.INVALID_CREDENTIALS, e: 522 print 'Login to LDAP server failed: ', str(e) 523 sys.exit(1) 524 except ldap.SERVER_DOWN: 525 print 'LDAP server is down, going to retry.' 526 time.sleep(5) 527 continue 528 529 # Commence the syncing 530 self.logger.info('Staring sync process') 531 ldap_search = self.source_ldap_connection.syncrepl_search( 532 self.source_ldap_url_obj.dn or '', 533 self.source_ldap_url_obj.scope or ldap.SCOPE_SUBTREE, 534 mode='refreshAndPersist', 535 attrlist=self.source_ldap_url_obj.attrs, 536 filterstr=self.source_ldap_url_obj.filterstr or '(objectClass=*)' 537 ) 538 539 try: 540 while self.source_ldap_connection.syncrepl_poll(all=1, msgid=ldap_search): 541 print ".", 542 except KeyboardInterrupt: 543 # User asked to exit 544 print "aborted\n" 545 self.shutdown(None, None) 546 except Exception, e: 547 # Handle any exception 548 if self.watcher_running: 549 self.logger.exception('Encountered a problem, going to retry.') 550 time.sleep(5) 551 552 def perform_application_sync_callback(self, dn, attributes): 553 logger.debug('{}: src: {}'.format(dn, str(attributes))) 554 try: 555 self._syncLdapObject(dn, attributes) 556 except ldap.NO_SUCH_OBJECT: 557 self.logger.info("SKIPPED: {} object does not exist on target".format(dn)) 558 return False 559 return True 560 561 def shutdown(self, signum, stack): 562 # Declare the needed global variables 563 self.logger.info('Shutting down!') 564 565 # We are no longer running 566 self.watcher_running = False 358 567 359 568 … … 362 571 logger = logging.getLogger() 363 572 364 args =getArguments()573 args = getArguments() 365 574 if args.debug: 366 575 logger.setLevel(logging.DEBUG) 367 conffile =args.configfile576 conffile = args.configfile 368 577 369 578 exclude = None 370 579 371 config =ConfigParserDefaults()580 config = ConfigParserDefaults() 372 581 config.read(conffile) 373 582 374 583 srcfile = None 375 584 try: 376 srcfile = config.get("source", "file")585 srcfile = config.get("source", "file") 377 586 except: 378 587 pass 379 588 380 basedn = config.get("source","baseDn") 381 filter = config.get0("source","filter", None) 382 attrs = config.get0("source", "attributes", None) 383 384 if srcfile==None: 385 srv = config.get("source","server") 386 admindn = config.get("source","bindDn") 387 adminpw = config.get("source","bindPassword") 388 starttls = config.getboolean("source","starttls") 389 390 destsrv = config.get("destination","server") 391 destadmindn = config.get("destination","bindDn") 392 destadminpw = config.get("destination","bindPassword") 393 destbasedn = config.get("destination","baseDn") 394 destdelete = config.getboolean("destination","delete") 589 basedn = config.get("source", "baseDn") 590 filterstr = config.get0("source", "filter", None) 591 592 if srcfile is None: 593 srv = config.get("source", "server") 594 admindn = config.get("source", "bindDn") 595 adminpw = config.get("source", "bindPassword") 596 starttls = config.getboolean("source", "starttls") 597 598 destsrv = config.get("destination", "server") 599 destadmindn = config.get("destination", "bindDn") 600 destadminpw = config.get("destination", "bindPassword") 601 destbasedn = config.get("destination", "baseDn") 602 destdelete = config.getboolean("destination", "delete") 395 603 try: 396 rdn = config.get("destination", "rdn")604 rdn = config.get("destination", "rdn") 397 605 logger.warning("setting rdn is currently ignored") 398 606 except: … … 401 609 options = Options() 402 610 try: 403 options.exclude = config.get("destination", "excludesubtree").lower()611 options.exclude = config.get("destination", "excludesubtree").lower() 404 612 except: 405 613 pass 406 614 407 options.updateonly = not config.getboolean("destination","create", False) 408 options.starttls = config.getboolean("destination","starttls", False) 409 options.renameattr = config.get0("destination","detectRename", None) 410 options.renamecommand = config.get0("destination","detectRename", None) 411 options.pwd_max_days = int(config.get("source","pwd_max_days",0)) 412 options.filter = filter 413 414 615 options.updateonly = not config.getboolean("destination", "create", False) 616 options.starttls = config.getboolean("destination", "starttls", False) 617 options.renameattr = config.get0("destination", "detectRename", None) 618 options.renamecommand = config.get0("destination", "detectRename", None) 619 options.pwd_max_days = int(config.get("source", "pwd_max_days", 0)) 620 options.filter = filterstr 621 622 # Set source.attrlist as global option. 623 # If source would use less attributes than dest, 624 # all attributes not retrieved from source would be deleted from dest 415 625 try: 416 options.attr filter = config.get("destination","attributes").split(",")626 options.attrlist = config.get("source", "attributes").split(",") 417 627 except: 418 options.attrfilter = None 419 420 if srcfile: 421 objects = readLDIFSource(srcfile) 628 options.attrlist = None 629 630 if config.get0('source', 'mode') == 'syncrepl': 631 ldapsync = LdapSyncRepl( 632 destsrv, destadmindn, destadminpw, basedn, destbasedn, 633 options, 634 source_ldap_url_obj=config.get_ldap_url_obj('source')) 635 ldapsync.sync() 422 636 else: 423 objects = readLdapSource(srv,admindn,adminpw,basedn,filter,attrs,starttls) 424 425 ldapsync = LdapSync(destsrv,destadmindn,destadminpw,basedn,destbasedn,options) 426 ldapsync.sync(objects) 637 if srcfile: 638 objects = readLDIFSource(srcfile) 639 else: 640 objects = readLdapSource(srv, admindn, adminpw, 641 basedn, filterstr, options.attrlist, starttls) 642 643 ldapsync = LdapSync(destsrv, destadmindn, destadminpw, basedn, destbasedn, options) 644 ldapsync.sync(objects)
Note:
See TracChangeset
for help on using the changeset viewer.