2010-01-13 11:41:56 +03:00
# Unix SMB/CIFS implementation.
2010-11-29 06:10:26 +03:00
# A command to compare differences of objects and attributes between
2010-01-13 11:41:56 +03:00
# two LDAP servers both running at the same time. It generally compares
# one of the three pratitions DOMAIN, CONFIGURATION or SCHEMA. Users
# that have to be provided sheould be able to read objects in any of the
# above partitions.
2010-07-05 18:55:11 +04:00
# Copyright (C) Zahari Zahariev <zahari.zahariev@postpath.com> 2009, 2010
2010-01-13 11:41:56 +03:00
#
# 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 os
import re
import sys
import samba
import samba . getopt as options
from samba import Ldb
2012-09-27 20:30:47 +04:00
from samba . ndr import ndr_unpack
2010-01-13 11:41:56 +03:00
from samba . dcerpc import security
from ldb import SCOPE_SUBTREE , SCOPE_ONELEVEL , SCOPE_BASE , ERR_NO_SUCH_OBJECT , LdbError
2010-11-29 06:10:26 +03:00
from samba . netcmd import (
Command ,
CommandError ,
Option ,
)
2010-01-13 11:41:56 +03:00
global summary
summary = { }
class LDAPBase ( object ) :
2010-11-29 06:10:26 +03:00
def __init__ ( self , host , creds , lp ,
2011-01-17 15:22:22 +03:00
two = False , quiet = False , descriptor = False , sort_aces = False , verbose = False ,
2012-03-26 11:31:55 +04:00
view = " section " , base = " " , scope = " SUB " ,
2013-02-14 07:59:28 +04:00
outf = sys . stdout , errf = sys . stderr , skip_missing_dn = True ) :
2010-09-22 04:24:01 +04:00
ldb_options = [ ]
samdb_url = host
2010-01-13 11:41:56 +03:00
if not " :// " in host :
2010-09-22 04:24:01 +04:00
if os . path . isfile ( host ) :
samdb_url = " tdb:// %s " % host
else :
2010-11-29 06:10:26 +03:00
samdb_url = " ldap:// %s " % host
2010-09-26 01:10:24 +04:00
# use 'paged_search' module when connecting remotely
if samdb_url . lower ( ) . startswith ( " ldap:// " ) :
ldb_options = [ " modules:paged_searches " ]
2012-03-26 11:31:55 +04:00
self . outf = outf
self . errf = errf
2010-09-22 04:24:01 +04:00
self . ldb = Ldb ( url = samdb_url ,
credentials = creds ,
lp = lp ,
options = ldb_options )
2011-01-05 19:56:23 +03:00
self . search_base = base
self . search_scope = scope
2010-11-29 06:10:26 +03:00
self . two_domains = two
self . quiet = quiet
self . descriptor = descriptor
2011-01-17 15:22:22 +03:00
self . sort_aces = sort_aces
2010-11-29 06:10:26 +03:00
self . view = view
self . verbose = verbose
2010-05-10 14:53:56 +04:00
self . host = host
2013-02-14 07:59:28 +04:00
self . skip_missing_dn = skip_missing_dn
2011-08-19 11:33:06 +04:00
self . base_dn = str ( self . ldb . get_default_basedn ( ) )
2011-09-20 11:31:24 +04:00
self . root_dn = str ( self . ldb . get_root_basedn ( ) )
2011-08-19 11:33:06 +04:00
self . config_dn = str ( self . ldb . get_config_basedn ( ) )
self . schema_dn = str ( self . ldb . get_schema_basedn ( ) )
2010-05-10 14:53:56 +04:00
self . domain_netbios = self . find_netbios ( )
self . server_names = self . find_servers ( )
2010-01-13 11:41:56 +03:00
self . domain_name = re . sub ( " [Dd][Cc]= " , " " , self . base_dn ) . replace ( " , " , " . " )
2010-09-30 05:13:02 +04:00
self . domain_sid = self . find_domain_sid ( )
self . get_sid_map ( )
2010-05-10 14:53:56 +04:00
#
2010-07-05 18:55:11 +04:00
# Log some domain controller specific place-holers that are being used
# when compare content of two DCs. Uncomment for DEBUG purposes.
2010-08-19 19:30:03 +04:00
if self . two_domains and not self . quiet :
2011-10-13 02:36:44 +04:00
self . outf . write ( " \n * Place-holders for %s : \n " % self . host )
self . outf . write ( 4 * " " + " $ {DOMAIN_DN} => %s \n " %
self . base_dn )
self . outf . write ( 4 * " " + " $ {DOMAIN_NETBIOS} => %s \n " %
self . domain_netbios )
self . outf . write ( 4 * " " + " $ {SERVER_NAME} => %s \n " %
self . server_names )
self . outf . write ( 4 * " " + " $ {DOMAIN_NAME} => %s \n " %
self . domain_name )
2010-05-10 14:53:56 +04:00
2010-09-30 05:13:02 +04:00
def find_domain_sid ( self ) :
res = self . ldb . search ( base = self . base_dn , expression = " (objectClass=*) " , scope = SCOPE_BASE )
return ndr_unpack ( security . dom_sid , res [ 0 ] [ " objectSid " ] [ 0 ] )
2010-05-10 14:53:56 +04:00
def find_servers ( self ) :
"""
"""
2012-09-27 20:30:47 +04:00
res = self . ldb . search ( base = " OU=Domain Controllers, %s " % self . base_dn ,
2010-05-10 14:53:56 +04:00
scope = SCOPE_SUBTREE , expression = " (objectClass=computer) " , attrs = [ " cn " ] )
assert len ( res ) > 0
srv = [ ]
for x in res :
srv . append ( x [ " cn " ] [ 0 ] )
return srv
2010-01-13 11:41:56 +03:00
def find_netbios ( self ) :
2012-09-27 20:30:47 +04:00
res = self . ldb . search ( base = " CN=Partitions, %s " % self . config_dn ,
2010-01-13 11:41:56 +03:00
scope = SCOPE_SUBTREE , attrs = [ " nETBIOSName " ] )
assert len ( res ) > 0
for x in res :
if " nETBIOSName " in x . keys ( ) :
return x [ " nETBIOSName " ] [ 0 ]
def object_exists ( self , object_dn ) :
res = None
try :
2010-11-29 05:30:46 +03:00
res = self . ldb . search ( base = object_dn , scope = SCOPE_BASE )
2018-02-23 17:31:38 +03:00
except LdbError as e2 :
( enum , estr ) = e2 . args
2010-11-29 05:30:46 +03:00
if enum == ERR_NO_SUCH_OBJECT :
return False
raise
2010-01-13 11:41:56 +03:00
return len ( res ) == 1
def delete_force ( self , object_dn ) :
try :
self . ldb . delete ( object_dn )
2018-02-14 00:07:23 +03:00
except Ldb . LdbError as e :
2010-01-13 11:41:56 +03:00
assert " No such object " in str ( e )
2011-02-14 16:18:14 +03:00
def get_attribute_name ( self , key ) :
""" Returns the real attribute name
It resolved ranged results e . g . member ; range = 0 - 1499
"""
r = re . compile ( " ^([^;]+);range=( \ d+)-( \ d+| \ *)$ " )
m = r . match ( key )
if m is None :
return key
return m . group ( 1 )
def get_attribute_values ( self , object_dn , key , vals ) :
""" Returns list with all attribute values
It resolved ranged results e . g . member ; range = 0 - 1499
"""
r = re . compile ( " ^([^;]+);range=( \ d+)-( \ d+| \ *)$ " )
m = r . match ( key )
if m is None :
# no range, just return the values
return vals
attr = m . group ( 1 )
hi = int ( m . group ( 3 ) )
# get additional values in a loop
# until we get a response with '*' at the end
while True :
n = " %s ;range= %d -* " % ( attr , hi + 1 )
res = self . ldb . search ( base = object_dn , scope = SCOPE_BASE , attrs = [ n ] )
assert len ( res ) == 1
res = dict ( res [ 0 ] )
del res [ " dn " ]
fm = None
fvals = None
for key in res . keys ( ) :
m = r . match ( key )
if m is None :
continue
if m . group ( 1 ) != attr :
continue
fm = m
fvals = list ( res [ key ] )
break
if fm is None :
break
vals . extend ( fvals )
if fm . group ( 3 ) == " * " :
# if we got "*" we're done
break
assert int ( fm . group ( 2 ) ) == hi + 1
hi = int ( fm . group ( 3 ) )
return vals
2010-01-13 11:41:56 +03:00
def get_attributes ( self , object_dn ) :
""" Returns dict with all default visible attributes
"""
res = self . ldb . search ( base = object_dn , scope = SCOPE_BASE , attrs = [ " * " ] )
assert len ( res ) == 1
res = dict ( res [ 0 ] )
# 'Dn' element is not iterable and we have it as 'distinguishedName'
del res [ " dn " ]
for key in res . keys ( ) :
2011-02-14 16:18:14 +03:00
vals = list ( res [ key ] )
del res [ key ]
name = self . get_attribute_name ( key )
res [ name ] = self . get_attribute_values ( object_dn , key , vals )
2010-01-13 11:41:56 +03:00
return res
2010-09-30 05:13:02 +04:00
def get_descriptor_sddl ( self , object_dn ) :
2010-01-13 11:41:56 +03:00
res = self . ldb . search ( base = object_dn , scope = SCOPE_BASE , attrs = [ " nTSecurityDescriptor " ] )
2010-09-30 05:13:02 +04:00
desc = res [ 0 ] [ " nTSecurityDescriptor " ] [ 0 ]
desc = ndr_unpack ( security . descriptor , desc )
return desc . as_sddl ( self . domain_sid )
def guid_as_string ( self , guid_blob ) :
""" Translate binary representation of schemaIDGUID to standard string representation.
@gid_blob : binary schemaIDGUID
"""
blob = " %s " % guid_blob
stops = [ 4 , 2 , 2 , 2 , 6 ]
index = 0
res = " "
x = 0
while x < len ( stops ) :
tmp = " "
y = 0
while y < stops [ x ] :
c = hex ( ord ( blob [ index ] ) ) . replace ( " 0x " , " " )
c = [ None , " 0 " + c , c ] [ len ( c ) ]
if 2 * index < len ( blob ) :
tmp = c + tmp
else :
tmp + = c
index + = 1
y + = 1
res + = tmp + " "
x + = 1
assert index == len ( blob )
return res . strip ( ) . replace ( " " , " - " )
def get_sid_map ( self ) :
""" Build dictionary that maps GUID to ' name ' attribute found in Schema or Extended-Rights.
"""
self . sid_map = { }
2011-08-19 11:33:06 +04:00
res = self . ldb . search ( base = self . base_dn ,
expression = " (objectSid=*) " , scope = SCOPE_SUBTREE , attrs = [ " objectSid " , " sAMAccountName " ] )
2010-09-30 05:13:02 +04:00
for item in res :
try :
self . sid_map [ " %s " % ndr_unpack ( security . dom_sid , item [ " objectSid " ] [ 0 ] ) ] = item [ " sAMAccountName " ] [ 0 ]
except KeyError :
pass
class Descriptor ( object ) :
2012-03-26 11:31:55 +04:00
def __init__ ( self , connection , dn , outf = sys . stdout , errf = sys . stderr ) :
self . outf = outf
self . errf = errf
2010-09-30 05:13:02 +04:00
self . con = connection
self . dn = dn
self . sddl = self . con . get_descriptor_sddl ( self . dn )
self . dacl_list = self . extract_dacl ( )
2011-01-17 15:22:22 +03:00
if self . con . sort_aces :
self . dacl_list . sort ( )
2010-09-30 05:13:02 +04:00
def extract_dacl ( self ) :
""" Extracts the DACL as a list of ACE string (with the brakets).
"""
try :
2011-01-17 18:44:10 +03:00
if " S: " in self . sddl :
res = re . search ( " D:(.*?)( \ (.*? \ ))S: " , self . sddl ) . group ( 2 )
else :
res = re . search ( " D:(.*?)( \ (.* \ )) " , self . sddl ) . group ( 2 )
2010-09-30 05:13:02 +04:00
except AttributeError :
return [ ]
return re . findall ( " ( \ (.*? \ )) " , res )
def fix_sid ( self , ace ) :
res = " %s " % ace
sids = re . findall ( " S-[-0-9]+ " , res )
# If there are not SIDs to replace return the same ACE
if len ( sids ) == 0 :
return res
for sid in sids :
try :
name = self . con . sid_map [ sid ]
res = res . replace ( sid , name )
except KeyError :
# Do not bother if the SID is not found in baseDN
pass
return res
def diff_1 ( self , other ) :
res = " "
if len ( self . dacl_list ) != len ( other . dacl_list ) :
res + = 4 * " " + " Difference in ACE count: \n "
res + = 8 * " " + " => %s \n " % len ( self . dacl_list )
res + = 8 * " " + " => %s \n " % len ( other . dacl_list )
#
i = 0
flag = True
while True :
self_ace = None
other_ace = None
try :
self_ace = " %s " % self . dacl_list [ i ]
except IndexError :
self_ace = " "
#
try :
other_ace = " %s " % other . dacl_list [ i ]
except IndexError :
other_ace = " "
if len ( self_ace ) + len ( other_ace ) == 0 :
break
2013-03-22 14:36:49 +04:00
self_ace_fixed = " %s " % self . fix_sid ( self_ace )
other_ace_fixed = " %s " % other . fix_sid ( other_ace )
2010-09-30 05:13:02 +04:00
if self_ace_fixed != other_ace_fixed :
res + = " %60s * %s \n " % ( self_ace_fixed , other_ace_fixed )
flag = False
else :
res + = " %60s | %s \n " % ( self_ace_fixed , other_ace_fixed )
i + = 1
return ( flag , res )
def diff_2 ( self , other ) :
res = " "
if len ( self . dacl_list ) != len ( other . dacl_list ) :
res + = 4 * " " + " Difference in ACE count: \n "
res + = 8 * " " + " => %s \n " % len ( self . dacl_list )
res + = 8 * " " + " => %s \n " % len ( other . dacl_list )
#
common_aces = [ ]
self_aces = [ ]
other_aces = [ ]
self_dacl_list_fixed = [ ]
other_dacl_list_fixed = [ ]
2013-03-22 14:36:49 +04:00
[ self_dacl_list_fixed . append ( self . fix_sid ( ace ) ) for ace in self . dacl_list ]
[ other_dacl_list_fixed . append ( other . fix_sid ( ace ) ) for ace in other . dacl_list ]
2010-09-30 05:13:02 +04:00
for ace in self_dacl_list_fixed :
try :
other_dacl_list_fixed . index ( ace )
except ValueError :
self_aces . append ( ace )
else :
common_aces . append ( ace )
self_aces = sorted ( self_aces )
if len ( self_aces ) > 0 :
res + = 4 * " " + " ACEs found only in %s : \n " % self . con . host
for ace in self_aces :
res + = 8 * " " + ace + " \n "
#
for ace in other_dacl_list_fixed :
try :
self_dacl_list_fixed . index ( ace )
except ValueError :
other_aces . append ( ace )
else :
common_aces . append ( ace )
other_aces = sorted ( other_aces )
if len ( other_aces ) > 0 :
res + = 4 * " " + " ACEs found only in %s : \n " % other . con . host
for ace in other_aces :
res + = 8 * " " + ace + " \n "
#
common_aces = sorted ( list ( set ( common_aces ) ) )
if self . con . verbose :
res + = 4 * " " + " ACEs found in both: \n "
for ace in common_aces :
res + = 8 * " " + ace + " \n "
return ( self_aces == [ ] and other_aces == [ ] , res )
2010-01-13 11:41:56 +03:00
2010-05-10 14:53:56 +04:00
class LDAPObject ( object ) :
2012-03-26 11:31:55 +04:00
def __init__ ( self , connection , dn , summary , filter_list ,
outf = sys . stdout , errf = sys . stderr ) :
self . outf = outf
self . errf = errf
2010-05-10 14:53:56 +04:00
self . con = connection
2010-09-30 05:13:02 +04:00
self . two_domains = self . con . two_domains
self . quiet = self . con . quiet
self . verbose = self . con . verbose
2010-01-13 11:41:56 +03:00
self . summary = summary
self . dn = dn . replace ( " $ {DOMAIN_DN} " , self . con . base_dn )
2010-05-10 14:53:56 +04:00
self . dn = self . dn . replace ( " CN=$ {DOMAIN_NETBIOS} " , " CN= %s " % self . con . domain_netbios )
for x in self . con . server_names :
2010-09-30 05:13:02 +04:00
self . dn = self . dn . replace ( " CN=$ {SERVER_NAME} " , " CN= %s " % x )
2010-01-13 11:41:56 +03:00
self . attributes = self . con . get_attributes ( self . dn )
2010-05-10 14:53:56 +04:00
# One domain - two domain controllers
2014-08-27 17:13:30 +04:00
#
# Some attributes are defined as FLAG_ATTR_NOT_REPLICATED
#
# The following list was generated by
# egrep '^systemFlags: |^ldapDisplayName: |^linkID: ' \
# source4/setup/ad-schema/MS-AD_Schema_2K8_R2_Attributes.txt | \
# grep -B1 FLAG_ATTR_NOT_REPLICATED | \
# grep ldapDisplayName | \
# cut -d ' ' -f2
self . non_replicated_attributes = [
" badPasswordTime " ,
" badPwdCount " ,
" dSCorePropagationData " ,
" lastLogoff " ,
" lastLogon " ,
" logonCount " ,
" modifiedCount " ,
" msDS-Cached-Membership " ,
" msDS-Cached-Membership-Time-Stamp " ,
" msDS-EnabledFeatureBL " ,
" msDS-ExecuteScriptPassword " ,
" msDS-NcType " ,
" msDS-ReplicationEpoch " ,
" msDS-RetiredReplNCSignatures " ,
" msDS-USNLastSyncSuccess " ,
# "distinguishedName", # This is implicitly replicated
# "objectGUID", # This is implicitly replicated
" partialAttributeDeletionList " ,
" partialAttributeSet " ,
" pekList " ,
" prefixMap " ,
" replPropertyMetaData " ,
" replUpToDateVector " ,
" repsFrom " ,
" repsTo " ,
" rIDNextRID " ,
" rIDPreviousAllocationPool " ,
" schemaUpdate " ,
" serverState " ,
" subRefs " ,
" uSNChanged " ,
" uSNCreated " ,
" uSNLastObjRem " ,
2016-08-08 13:53:26 +03:00
" whenChanged " , # This is implicitly replicated, but may diverge on updates of non-replicated attributes
2014-08-27 17:13:30 +04:00
]
self . ignore_attributes = self . non_replicated_attributes
self . ignore_attributes + = [ " msExchServer1HighestUSN " ]
2011-09-06 10:15:04 +04:00
if filter_list :
self . ignore_attributes + = filter_list
2010-05-10 14:53:56 +04:00
self . dn_attributes = [ ]
self . domain_attributes = [ ]
self . servername_attributes = [ ]
self . netbios_attributes = [ ]
self . other_attributes = [ ]
# Two domains - two domain controllers
if self . two_domains :
self . ignore_attributes + = [
2017-12-18 02:39:52 +03:00
" objectCategory " , " objectGUID " , " objectSid " , " whenCreated " ,
" whenChanged " , " pwdLastSet " , " uSNCreated " , " creationTime " ,
" modifiedCount " , " priorSetTime " , " rIDManagerReference " ,
" gPLink " , " ipsecNFAReference " , " fRSPrimaryMember " ,
" fSMORoleOwner " , " masteredBy " , " ipsecOwnersReference " ,
" wellKnownObjects " , " otherWellKnownObjects " , " badPwdCount " ,
" ipsecISAKMPReference " , " ipsecFilterReference " ,
" msDs-masteredBy " , " lastSetTime " ,
" ipsecNegotiationPolicyReference " , " subRefs " , " gPCFileSysPath " ,
" accountExpires " , " invocationId " , " operatingSystemVersion " ,
" oEMInformation " ,
2010-01-13 11:41:56 +03:00
# After Exchange preps
" targetAddress " , " msExchMailboxGuid " , " siteFolderGUID " ]
2010-05-10 14:53:56 +04:00
#
# Attributes that contain the unique DN tail part e.g. 'DC=samba,DC=org'
self . dn_attributes = [
" distinguishedName " , " defaultObjectCategory " , " member " , " memberOf " , " siteList " , " nCName " ,
" homeMDB " , " homeMTA " , " interSiteTopologyGenerator " , " serverReference " ,
" msDS-HasInstantiatedNCs " , " hasMasterNCs " , " msDS-hasMasterNCs " , " msDS-HasDomainNCs " , " dMDLocation " ,
" msDS-IsDomainFor " , " rIDSetReferences " , " serverReferenceBL " ,
2010-01-13 11:41:56 +03:00
# After Exchange preps
2010-05-10 14:53:56 +04:00
" msExchHomeRoutingGroup " , " msExchResponsibleMTAServer " , " siteFolderServer " , " msExchRoutingMasterDN " ,
" msExchRoutingGroupMembersBL " , " homeMDBBL " , " msExchHomePublicMDB " , " msExchOwningServer " , " templateRoots " ,
" addressBookRoots " , " msExchPolicyRoots " , " globalAddressList " , " msExchOwningPFTree " ,
2017-12-18 02:45:02 +03:00
" msExchResponsibleMTAServerBL " , " msExchOwningPFTreeBL " ,
# After 2012 R2 functional preparation
" msDS-MembersOfResourcePropertyListBL " ,
" msDS-ValueTypeReference " ,
" msDS-MembersOfResourcePropertyList " ,
" msDS-ValueTypeReferenceBL " ,
" msDS-ClaimTypeAppliesToClass " ,
]
2010-05-10 14:53:56 +04:00
self . dn_attributes = [ x . upper ( ) for x in self . dn_attributes ]
#
# Attributes that contain the Domain name e.g. 'samba.org'
self . domain_attributes = [
" proxyAddresses " , " mail " , " userPrincipalName " , " msExchSmtpFullyQualifiedDomainName " ,
" dnsHostName " , " networkAddress " , " dnsRoot " , " servicePrincipalName " , ]
self . domain_attributes = [ x . upper ( ) for x in self . domain_attributes ]
#
2010-09-30 05:13:02 +04:00
# May contain DOMAIN_NETBIOS and SERVER_NAME
2010-05-10 14:53:56 +04:00
self . servername_attributes = [ " distinguishedName " , " name " , " CN " , " sAMAccountName " , " dNSHostName " ,
" servicePrincipalName " , " rIDSetReferences " , " serverReference " , " serverReferenceBL " ,
" msDS-IsDomainFor " , " interSiteTopologyGenerator " , ]
self . servername_attributes = [ x . upper ( ) for x in self . servername_attributes ]
#
self . netbios_attributes = [ " servicePrincipalName " , " CN " , " distinguishedName " , " nETBIOSName " , " name " , ]
self . netbios_attributes = [ x . upper ( ) for x in self . netbios_attributes ]
#
self . other_attributes = [ " name " , " DC " , ]
self . other_attributes = [ x . upper ( ) for x in self . other_attributes ]
2010-01-13 11:41:56 +03:00
#
2010-05-10 14:53:56 +04:00
self . ignore_attributes = [ x . upper ( ) for x in self . ignore_attributes ]
def log ( self , msg ) :
"""
2013-11-25 18:29:09 +04:00
Log on the screen if there is no - - quiet option set
2010-05-10 14:53:56 +04:00
"""
if not self . quiet :
2011-10-13 02:36:44 +04:00
self . outf . write ( msg + " \n " )
2010-01-13 11:41:56 +03:00
def fix_dn ( self , s ) :
res = " %s " % s
2010-08-19 19:30:03 +04:00
if not self . two_domains :
return res
2010-01-13 11:41:56 +03:00
if res . upper ( ) . endswith ( self . con . base_dn . upper ( ) ) :
res = res [ : len ( res ) - len ( self . con . base_dn ) ] + " $ {DOMAIN_DN} "
return res
def fix_domain_name ( self , s ) :
res = " %s " % s
2010-08-19 19:30:03 +04:00
if not self . two_domains :
return res
2010-05-10 14:53:56 +04:00
res = res . replace ( self . con . domain_name . lower ( ) , self . con . domain_name . upper ( ) )
res = res . replace ( self . con . domain_name . upper ( ) , " $ {DOMAIN_NAME} " )
2010-01-13 11:41:56 +03:00
return res
2010-05-10 14:53:56 +04:00
def fix_domain_netbios ( self , s ) :
2010-01-13 11:41:56 +03:00
res = " %s " % s
2010-08-19 19:30:03 +04:00
if not self . two_domains :
return res
2010-05-10 14:53:56 +04:00
res = res . replace ( self . con . domain_netbios . lower ( ) , self . con . domain_netbios . upper ( ) )
res = res . replace ( self . con . domain_netbios . upper ( ) , " $ {DOMAIN_NETBIOS} " )
return res
def fix_server_name ( self , s ) :
res = " %s " % s
2010-08-19 19:30:03 +04:00
if not self . two_domains or len ( self . con . server_names ) > 1 :
return res
2010-05-10 14:53:56 +04:00
for x in self . con . server_names :
2010-09-30 05:13:02 +04:00
res = res . upper ( ) . replace ( x , " $ {SERVER_NAME} " )
2010-01-13 11:41:56 +03:00
return res
def __eq__ ( self , other ) :
2010-09-30 05:13:02 +04:00
if self . con . descriptor :
return self . cmp_desc ( other )
return self . cmp_attrs ( other )
def cmp_desc ( self , other ) :
2012-03-26 11:31:55 +04:00
d1 = Descriptor ( self . con , self . dn , outf = self . outf , errf = self . errf )
d2 = Descriptor ( other . con , other . dn , outf = self . outf , errf = self . errf )
2010-09-30 05:13:02 +04:00
if self . con . view == " section " :
res = d1 . diff_2 ( d2 )
elif self . con . view == " collision " :
res = d1 . diff_1 ( d2 )
else :
raise Exception ( " Unknown --view option value. " )
#
self . screen_output = res [ 1 ] [ : - 1 ]
other . screen_output = res [ 1 ] [ : - 1 ]
#
return res [ 0 ]
def cmp_attrs ( self , other ) :
2010-05-10 14:53:56 +04:00
res = " "
2010-01-13 11:41:56 +03:00
self . unique_attrs = [ ]
self . df_value_attrs = [ ]
other . unique_attrs = [ ]
if self . attributes . keys ( ) != other . attributes . keys ( ) :
#
2010-05-10 14:53:56 +04:00
title = 4 * " " + " Attributes found only in %s : " % self . con . host
2010-01-13 11:41:56 +03:00
for x in self . attributes . keys ( ) :
2010-05-10 14:53:56 +04:00
if not x in other . attributes . keys ( ) and \
not x . upper ( ) in [ q . upper ( ) for q in other . ignore_attributes ] :
2010-01-13 11:41:56 +03:00
if title :
2010-05-10 14:53:56 +04:00
res + = title + " \n "
2010-01-13 11:41:56 +03:00
title = None
2010-05-10 14:53:56 +04:00
res + = 8 * " " + x + " \n "
2010-01-13 11:41:56 +03:00
self . unique_attrs . append ( x )
#
2010-05-10 14:53:56 +04:00
title = 4 * " " + " Attributes found only in %s : " % other . con . host
2010-01-13 11:41:56 +03:00
for x in other . attributes . keys ( ) :
2010-05-10 14:53:56 +04:00
if not x in self . attributes . keys ( ) and \
not x . upper ( ) in [ q . upper ( ) for q in self . ignore_attributes ] :
2010-01-13 11:41:56 +03:00
if title :
2010-05-10 14:53:56 +04:00
res + = title + " \n "
2010-01-13 11:41:56 +03:00
title = None
2010-05-10 14:53:56 +04:00
res + = 8 * " " + x + " \n "
2010-01-13 11:41:56 +03:00
other . unique_attrs . append ( x )
#
missing_attrs = [ x . upper ( ) for x in self . unique_attrs ]
missing_attrs + = [ x . upper ( ) for x in other . unique_attrs ]
title = 4 * " " + " Difference in attribute values: "
for x in self . attributes . keys ( ) :
if x . upper ( ) in self . ignore_attributes or x . upper ( ) in missing_attrs :
continue
if isinstance ( self . attributes [ x ] , list ) and isinstance ( other . attributes [ x ] , list ) :
self . attributes [ x ] = sorted ( self . attributes [ x ] )
other . attributes [ x ] = sorted ( other . attributes [ x ] )
if self . attributes [ x ] != other . attributes [ x ] :
p = None
q = None
2010-05-10 14:53:56 +04:00
m = None
n = None
# First check if the difference can be fixed but shunting the first part
# of the DomainHostName e.g. 'mysamba4.test.local' => 'mysamba4'
if x . upper ( ) in self . other_attributes :
2010-01-13 11:41:56 +03:00
p = [ self . con . domain_name . split ( " . " ) [ 0 ] == j for j in self . attributes [ x ] ]
q = [ other . con . domain_name . split ( " . " ) [ 0 ] == j for j in other . attributes [ x ] ]
if p == q :
continue
2010-05-10 14:53:56 +04:00
# Attribute values that are list that contain DN based values that may differ
elif x . upper ( ) in self . dn_attributes :
m = p
n = q
if not p and not q :
m = self . attributes [ x ]
n = other . attributes [ x ]
p = [ self . fix_dn ( j ) for j in m ]
q = [ other . fix_dn ( j ) for j in n ]
if p == q :
continue
2010-01-13 11:41:56 +03:00
# Attributes that contain the Domain name in them
2010-05-10 14:53:56 +04:00
if x . upper ( ) in self . domain_attributes :
m = p
n = q
if not p and not q :
m = self . attributes [ x ]
n = other . attributes [ x ]
p = [ self . fix_domain_name ( j ) for j in m ]
q = [ other . fix_domain_name ( j ) for j in n ]
if p == q :
continue
#
if x . upper ( ) in self . servername_attributes :
2010-09-30 05:13:02 +04:00
# Attributes with SERVER_NAME
2010-05-10 14:53:56 +04:00
m = p
n = q
if not p and not q :
m = self . attributes [ x ]
n = other . attributes [ x ]
p = [ self . fix_server_name ( j ) for j in m ]
q = [ other . fix_server_name ( j ) for j in n ]
if p == q :
continue
#
if x . upper ( ) in self . netbios_attributes :
# Attributes with NETBIOS Domain name
m = p
n = q
if not p and not q :
m = self . attributes [ x ]
n = other . attributes [ x ]
p = [ self . fix_domain_netbios ( j ) for j in m ]
q = [ other . fix_domain_netbios ( j ) for j in n ]
2010-01-13 11:41:56 +03:00
if p == q :
continue
#
if title :
2010-05-10 14:53:56 +04:00
res + = title + " \n "
2010-01-13 11:41:56 +03:00
title = None
if p and q :
2010-05-10 14:53:56 +04:00
res + = 8 * " " + x + " => \n %s \n %s " % ( p , q ) + " \n "
2010-01-13 11:41:56 +03:00
else :
2010-05-10 14:53:56 +04:00
res + = 8 * " " + x + " => \n %s \n %s " % ( self . attributes [ x ] , other . attributes [ x ] ) + " \n "
2010-01-13 11:41:56 +03:00
self . df_value_attrs . append ( x )
#
if self . unique_attrs + other . unique_attrs != [ ] :
assert self . unique_attrs != other . unique_attrs
self . summary [ " unique_attrs " ] + = self . unique_attrs
self . summary [ " df_value_attrs " ] + = self . df_value_attrs
other . summary [ " unique_attrs " ] + = other . unique_attrs
other . summary [ " df_value_attrs " ] + = self . df_value_attrs # they are the same
#
2010-05-10 14:53:56 +04:00
self . screen_output = res [ : - 1 ]
other . screen_output = res [ : - 1 ]
#
return res == " "
2010-01-13 11:41:56 +03:00
2010-05-10 14:53:56 +04:00
class LDAPBundel ( object ) :
2011-10-13 02:36:44 +04:00
2012-03-26 11:31:55 +04:00
def __init__ ( self , connection , context , dn_list = None , filter_list = None ,
outf = sys . stdout , errf = sys . stderr ) :
self . outf = outf
self . errf = errf
2010-05-10 14:53:56 +04:00
self . con = connection
2010-09-30 05:13:02 +04:00
self . two_domains = self . con . two_domains
self . quiet = self . con . quiet
self . verbose = self . con . verbose
2011-01-05 19:56:23 +03:00
self . search_base = self . con . search_base
self . search_scope = self . con . search_scope
2013-02-14 07:59:28 +04:00
self . skip_missing_dn = self . con . skip_missing_dn
2010-01-13 11:41:56 +03:00
self . summary = { }
self . summary [ " unique_attrs " ] = [ ]
self . summary [ " df_value_attrs " ] = [ ]
self . summary [ " known_ignored_dn " ] = [ ]
self . summary [ " abnormal_ignored_dn " ] = [ ]
2011-09-06 10:15:04 +04:00
self . filter_list = filter_list
2010-01-13 11:41:56 +03:00
if dn_list :
self . dn_list = dn_list
2011-09-20 11:31:24 +04:00
elif context . upper ( ) in [ " DOMAIN " , " CONFIGURATION " , " SCHEMA " , " DNSDOMAIN " , " DNSFOREST " ] :
2010-01-13 11:41:56 +03:00
self . context = context . upper ( )
self . dn_list = self . get_dn_list ( context )
else :
2010-05-10 14:53:56 +04:00
raise Exception ( " Unknown initialization data for LDAPBundel(). " )
counter = 0
2010-08-19 19:30:03 +04:00
while counter < len ( self . dn_list ) and self . two_domains :
2010-05-10 14:53:56 +04:00
# Use alias reference
tmp = self . dn_list [ counter ]
tmp = tmp [ : len ( tmp ) - len ( self . con . base_dn ) ] + " $ {DOMAIN_DN} "
tmp = tmp . replace ( " CN= %s " % self . con . domain_netbios , " CN=$ {DOMAIN_NETBIOS} " )
2010-08-19 19:30:03 +04:00
if len ( self . con . server_names ) == 1 :
for x in self . con . server_names :
2010-09-30 05:13:02 +04:00
tmp = tmp . replace ( " CN= %s " % x , " CN=$ {SERVER_NAME} " )
2010-05-10 14:53:56 +04:00
self . dn_list [ counter ] = tmp
counter + = 1
2010-01-13 11:41:56 +03:00
self . dn_list = list ( set ( self . dn_list ) )
self . dn_list = sorted ( self . dn_list )
self . size = len ( self . dn_list )
2010-05-10 14:53:56 +04:00
def log ( self , msg ) :
"""
2013-11-25 18:29:09 +04:00
Log on the screen if there is no - - quiet option set
2010-05-10 14:53:56 +04:00
"""
if not self . quiet :
2011-10-13 02:36:44 +04:00
self . outf . write ( msg + " \n " )
2010-05-10 14:53:56 +04:00
2010-01-13 11:41:56 +03:00
def update_size ( self ) :
self . size = len ( self . dn_list )
self . dn_list = sorted ( self . dn_list )
def __eq__ ( self , other ) :
res = True
if self . size != other . size :
2010-07-05 18:55:11 +04:00
self . log ( " \n * DN lists have different size: %s != %s " % ( self . size , other . size ) )
2013-02-14 07:59:28 +04:00
if not self . skip_missing_dn :
res = False
2010-01-13 11:41:56 +03:00
#
2011-01-05 19:56:23 +03:00
# This is the case where we want to explicitly compare two objects with different DNs.
# It does not matter if they are in the same DC, in two DC in one domain or in two
# different domains.
if self . search_scope != SCOPE_BASE :
title = " \n * DNs found only in %s : " % self . con . host
for x in self . dn_list :
if not x . upper ( ) in [ q . upper ( ) for q in other . dn_list ] :
2013-02-14 07:59:28 +04:00
if title and not self . skip_missing_dn :
2011-01-05 19:56:23 +03:00
self . log ( title )
title = None
res = False
self . log ( 4 * " " + x )
self . dn_list [ self . dn_list . index ( x ) ] = " "
self . dn_list = [ x for x in self . dn_list if x ]
#
title = " \n * DNs found only in %s : " % other . con . host
for x in other . dn_list :
if not x . upper ( ) in [ q . upper ( ) for q in self . dn_list ] :
2013-02-14 07:59:28 +04:00
if title and not self . skip_missing_dn :
2011-01-05 19:56:23 +03:00
self . log ( title )
title = None
res = False
self . log ( 4 * " " + x )
other . dn_list [ other . dn_list . index ( x ) ] = " "
other . dn_list = [ x for x in other . dn_list if x ]
#
self . update_size ( )
other . update_size ( )
assert self . size == other . size
assert sorted ( [ x . upper ( ) for x in self . dn_list ] ) == sorted ( [ x . upper ( ) for x in other . dn_list ] )
2010-09-26 03:25:03 +04:00
self . log ( " \n * Objects to be compared: %s " % self . size )
2010-01-13 11:41:56 +03:00
index = 0
while index < self . size :
skip = False
try :
2010-05-10 14:53:56 +04:00
object1 = LDAPObject ( connection = self . con ,
2010-11-29 05:30:46 +03:00
dn = self . dn_list [ index ] ,
2011-09-06 10:15:04 +04:00
summary = self . summary ,
2012-03-26 11:31:55 +04:00
filter_list = self . filter_list ,
outf = self . outf , errf = self . errf )
2018-02-23 17:31:38 +03:00
except LdbError as e :
( enum , estr ) = e . args
2010-11-29 05:30:46 +03:00
if enum == ERR_NO_SUCH_OBJECT :
self . log ( " \n !!! Object not found: %s " % self . dn_list [ index ] )
skip = True
raise
2010-01-13 11:41:56 +03:00
try :
2010-05-10 14:53:56 +04:00
object2 = LDAPObject ( connection = other . con ,
dn = other . dn_list [ index ] ,
2011-09-06 10:15:04 +04:00
summary = other . summary ,
2012-03-26 11:31:55 +04:00
filter_list = self . filter_list ,
outf = self . outf , errf = self . errf )
2018-02-23 17:31:38 +03:00
except LdbError as e1 :
( enum , estr ) = e1 . args
2010-11-29 05:30:46 +03:00
if enum == ERR_NO_SUCH_OBJECT :
self . log ( " \n !!! Object not found: %s " % other . dn_list [ index ] )
skip = True
raise
2010-01-13 11:41:56 +03:00
if skip :
index + = 1
continue
if object1 == object2 :
2010-09-30 05:13:02 +04:00
if self . con . verbose :
2010-05-10 14:53:56 +04:00
self . log ( " \n Comparing: " )
self . log ( " ' %s ' [ %s ] " % ( object1 . dn , object1 . con . host ) )
self . log ( " ' %s ' [ %s ] " % ( object2 . dn , object2 . con . host ) )
self . log ( 4 * " " + " OK " )
2010-01-13 11:41:56 +03:00
else :
2010-05-10 14:53:56 +04:00
self . log ( " \n Comparing: " )
self . log ( " ' %s ' [ %s ] " % ( object1 . dn , object1 . con . host ) )
self . log ( " ' %s ' [ %s ] " % ( object2 . dn , object2 . con . host ) )
self . log ( object1 . screen_output )
self . log ( 4 * " " + " FAILED " )
2010-01-13 11:41:56 +03:00
res = False
self . summary = object1 . summary
other . summary = object2 . summary
index + = 1
#
return res
def get_dn_list ( self , context ) :
""" Query LDAP server about the DNs of certain naming self.con.ext Domain (or Default), Configuration, Schema.
Parse all DNs and filter those that are ' strange ' or abnormal .
"""
if context . upper ( ) == " DOMAIN " :
2011-08-19 11:33:06 +04:00
search_base = self . con . base_dn
2010-01-13 11:41:56 +03:00
elif context . upper ( ) == " CONFIGURATION " :
2011-08-19 11:33:06 +04:00
search_base = self . con . config_dn
2010-01-13 11:41:56 +03:00
elif context . upper ( ) == " SCHEMA " :
2011-08-19 11:33:06 +04:00
search_base = self . con . schema_dn
2011-09-20 11:31:24 +04:00
elif context . upper ( ) == " DNSDOMAIN " :
search_base = " DC=DomainDnsZones, %s " % self . con . base_dn
elif context . upper ( ) == " DNSFOREST " :
search_base = " DC=ForestDnsZones, %s " % self . con . root_dn
2010-01-13 11:41:56 +03:00
dn_list = [ ]
2011-01-05 19:56:23 +03:00
if not self . search_base :
self . search_base = search_base
self . search_scope = self . search_scope . upper ( )
if self . search_scope == " SUB " :
self . search_scope = SCOPE_SUBTREE
elif self . search_scope == " BASE " :
self . search_scope = SCOPE_BASE
elif self . search_scope == " ONE " :
self . search_scope = SCOPE_ONELEVEL
else :
raise StandardError ( " Wrong ' scope ' given. Choose from: SUB, ONE, BASE " )
2011-09-23 11:39:24 +04:00
try :
res = self . con . ldb . search ( base = self . search_base , scope = self . search_scope , attrs = [ " dn " ] )
2018-02-23 17:31:38 +03:00
except LdbError as e3 :
( enum , estr ) = e3 . args
2011-10-13 02:36:44 +04:00
self . outf . write ( " Failed search of base= %s \n " % self . search_base )
2011-09-23 11:39:24 +04:00
raise
2010-01-13 11:41:56 +03:00
for x in res :
dn_list . append ( x [ " dn " ] . get_linearized ( ) )
#
global summary
#
return dn_list
def print_summary ( self ) :
self . summary [ " unique_attrs " ] = list ( set ( self . summary [ " unique_attrs " ] ) )
self . summary [ " df_value_attrs " ] = list ( set ( self . summary [ " df_value_attrs " ] ) )
#
2010-05-10 14:53:56 +04:00
if self . summary [ " unique_attrs " ] :
self . log ( " \n Attributes found only in %s : " % self . con . host )
self . log ( " " . join ( [ str ( " \n " + 4 * " " + x ) for x in self . summary [ " unique_attrs " ] ] ) )
2010-01-13 11:41:56 +03:00
#
2010-05-10 14:53:56 +04:00
if self . summary [ " df_value_attrs " ] :
self . log ( " \n Attributes with different values: " )
self . log ( " " . join ( [ str ( " \n " + 4 * " " + x ) for x in self . summary [ " df_value_attrs " ] ] ) )
self . summary [ " df_value_attrs " ] = [ ]
2010-01-13 11:41:56 +03:00
2011-10-13 02:36:44 +04:00
2010-11-29 06:10:26 +03:00
class cmd_ldapcmp ( Command ) :
2012-10-09 13:53:21 +04:00
""" Compare two ldap databases. """
2012-07-06 10:11:09 +04:00
synopsis = " % prog <URL1> <URL2> (domain|configuration|schema|dnsdomain|dnsforest) [options] "
2010-11-29 06:10:26 +03:00
2011-07-26 16:23:34 +04:00
takes_optiongroups = {
" sambaopts " : options . SambaOptions ,
" versionopts " : options . VersionOptions ,
" credopts " : options . CredentialsOptionsDouble ,
}
2012-02-06 19:33:38 +04:00
2013-02-14 08:00:01 +04:00
takes_args = [ " URL1 " , " URL2 " , " context1? " , " context2? " , " context3? " , " context4? " , " context5? " ]
2010-11-29 06:10:26 +03:00
takes_options = [
Option ( " -w " , " --two " , dest = " two " , action = " store_true " , default = False ,
2011-01-05 19:56:23 +03:00
help = " Hosts are in two different domains " ) ,
2010-11-29 06:10:26 +03:00
Option ( " -q " , " --quiet " , dest = " quiet " , action = " store_true " , default = False ,
2011-01-05 19:56:23 +03:00
help = " Do not print anything but relay on just exit code " ) ,
2010-11-29 06:10:26 +03:00
Option ( " -v " , " --verbose " , dest = " verbose " , action = " store_true " , default = False ,
2011-01-05 19:56:23 +03:00
help = " Print all DN pairs that have been compared " ) ,
2010-11-29 06:10:26 +03:00
Option ( " --sd " , dest = " descriptor " , action = " store_true " , default = False ,
2011-01-05 19:56:23 +03:00
help = " Compare nTSecurityDescriptor attibutes only " ) ,
2011-01-17 15:22:22 +03:00
Option ( " --sort-aces " , dest = " sort_aces " , action = " store_true " , default = False ,
help = " Sort ACEs before comparison of nTSecurityDescriptor attribute " ) ,
2010-11-29 06:10:26 +03:00
Option ( " --view " , dest = " view " , default = " section " ,
2011-01-05 19:56:23 +03:00
help = " Display mode for nTSecurityDescriptor results. Possible values: section or collision. " ) ,
Option ( " --base " , dest = " base " , default = " " ,
help = " Pass search base that will build DN list for the first DC. " ) ,
Option ( " --base2 " , dest = " base2 " , default = " " ,
help = " Pass search base that will build DN list for the second DC. Used when --two or when compare two different DNs. " ) ,
Option ( " --scope " , dest = " scope " , default = " SUB " ,
help = " Pass search scope that builds DN list. Options: SUB, ONE, BASE " ) ,
2011-09-06 10:15:04 +04:00
Option ( " --filter " , dest = " filter " , default = " " ,
help = " List of comma separated attributes to ignore in the comparision " ) ,
2013-02-14 07:59:28 +04:00
Option ( " --skip-missing-dn " , dest = " skip_missing_dn " , action = " store_true " , default = False ,
help = " Skip report and failure due to missing DNs in one server or another " ) ,
2010-11-29 06:10:26 +03:00
]
def run ( self , URL1 , URL2 ,
2013-02-14 08:00:01 +04:00
context1 = None , context2 = None , context3 = None , context4 = None , context5 = None ,
2011-09-06 10:15:04 +04:00
two = False , quiet = False , verbose = False , descriptor = False , sort_aces = False ,
view = " section " , base = " " , base2 = " " , scope = " SUB " , filter = " " ,
2013-02-14 07:59:28 +04:00
credopts = None , sambaopts = None , versionopts = None , skip_missing_dn = False ) :
2011-07-12 04:41:52 +04:00
2010-11-29 06:10:26 +03:00
lp = sambaopts . get_loadparm ( )
2011-07-12 04:41:52 +04:00
using_ldap = URL1 . startswith ( " ldap " ) or URL2 . startswith ( " ldap " )
if using_ldap :
creds = credopts . get_credentials ( lp , fallback_machine = True )
else :
creds = None
2011-01-05 19:56:23 +03:00
creds2 = credopts . get_credentials2 ( lp , guess = False )
2011-01-05 16:32:06 +03:00
if creds2 . is_anonymous ( ) :
2010-11-29 06:10:26 +03:00
creds2 = creds
2011-01-05 17:16:59 +03:00
else :
creds2 . set_domain ( " " )
creds2 . set_workstation ( " " )
2011-07-12 04:41:52 +04:00
if using_ldap and not creds . authentication_requested ( ) :
2010-11-29 06:10:26 +03:00
raise CommandError ( " You must supply at least one username/password pair " )
# make a list of contexts to compare in
contexts = [ ]
if context1 is None :
2011-01-05 19:56:23 +03:00
if base and base2 :
# If search bases are specified context is defaulted to
# DOMAIN so the given search bases can be verified.
contexts = [ " DOMAIN " ]
else :
# if no argument given, we compare all contexts
2013-02-14 08:00:01 +04:00
contexts = [ " DOMAIN " , " CONFIGURATION " , " SCHEMA " , " DNSDOMAIN " , " DNSFOREST " ]
2010-09-26 03:25:03 +04:00
else :
2013-02-14 08:00:01 +04:00
for c in [ context1 , context2 , context3 , context4 , context5 ] :
2010-11-29 06:10:26 +03:00
if c is None :
continue
2011-09-20 11:31:24 +04:00
if not c . upper ( ) in [ " DOMAIN " , " CONFIGURATION " , " SCHEMA " , " DNSDOMAIN " , " DNSFOREST " ] :
2010-11-29 06:10:26 +03:00
raise CommandError ( " Incorrect argument: %s " % c )
contexts . append ( c . upper ( ) )
if verbose and quiet :
raise CommandError ( " You cannot set --verbose and --quiet together " )
2011-01-05 19:56:23 +03:00
if ( not base and base2 ) or ( base and not base2 ) :
raise CommandError ( " You need to specify both --base and --base2 at the same time " )
2010-11-29 06:10:26 +03:00
if descriptor and view . upper ( ) not in [ " SECTION " , " COLLISION " ] :
2011-01-05 19:56:23 +03:00
raise CommandError ( " Invalid --view value. Choose from: section or collision " )
if not scope . upper ( ) in [ " SUB " , " ONE " , " BASE " ] :
raise CommandError ( " Invalid --scope value. Choose from: SUB, ONE, BASE " )
2010-11-29 06:10:26 +03:00
con1 = LDAPBase ( URL1 , creds , lp ,
2011-01-17 15:22:22 +03:00
two = two , quiet = quiet , descriptor = descriptor , sort_aces = sort_aces ,
2012-03-26 11:31:55 +04:00
verbose = verbose , view = view , base = base , scope = scope ,
outf = self . outf , errf = self . errf )
2010-11-29 06:10:26 +03:00
assert len ( con1 . base_dn ) > 0
con2 = LDAPBase ( URL2 , creds2 , lp ,
2011-01-17 15:22:22 +03:00
two = two , quiet = quiet , descriptor = descriptor , sort_aces = sort_aces ,
2012-03-26 11:31:55 +04:00
verbose = verbose , view = view , base = base2 , scope = scope ,
outf = self . outf , errf = self . errf )
2010-11-29 06:10:26 +03:00
assert len ( con2 . base_dn ) > 0
2011-09-06 10:15:04 +04:00
filter_list = filter . split ( " , " )
2010-11-29 06:10:26 +03:00
status = 0
for context in contexts :
if not quiet :
2011-10-13 02:36:44 +04:00
self . outf . write ( " \n * Comparing [ %s ] context... \n " % context )
2010-11-29 06:10:26 +03:00
2012-03-26 11:31:55 +04:00
b1 = LDAPBundel ( con1 , context = context , filter_list = filter_list ,
outf = self . outf , errf = self . errf )
b2 = LDAPBundel ( con2 , context = context , filter_list = filter_list ,
outf = self . outf , errf = self . errf )
2010-11-29 06:10:26 +03:00
if b1 == b2 :
if not quiet :
2011-10-13 02:36:44 +04:00
self . outf . write ( " \n * Result for [ %s ]: SUCCESS \n " %
context )
2010-11-29 06:10:26 +03:00
else :
if not quiet :
2011-10-13 02:36:44 +04:00
self . outf . write ( " \n * Result for [ %s ]: FAILURE \n " % context )
2010-11-29 06:10:26 +03:00
if not descriptor :
assert len ( b1 . summary [ " df_value_attrs " ] ) == len ( b2 . summary [ " df_value_attrs " ] )
b2 . summary [ " df_value_attrs " ] = [ ]
2011-10-13 02:36:44 +04:00
self . outf . write ( " \n SUMMARY \n " )
self . outf . write ( " --------- \n " )
2010-11-29 06:10:26 +03:00
b1 . print_summary ( )
b2 . print_summary ( )
# mark exit status as FAILURE if a least one comparison failed
status = - 1
if status != 0 :
raise CommandError ( " Compare failed: %d " % status )