revert ...
[metze/samba/wip.git] / source4 / scripting / python / samba / netcmd / ldapcmp.py
old mode 100755 (executable)
new mode 100644 (file)
index 76f7d95..8398205
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
 # Unix SMB/CIFS implementation.
 # A command to compare differences of objects and attributes between
 # two LDAP servers both running at the same time. It generally compares
@@ -30,14 +28,13 @@ import sys
 import samba
 import samba.getopt as options
 from samba import Ldb
-from samba.ndr import ndr_pack, ndr_unpack
+from samba.ndr import ndr_unpack
 from samba.dcerpc import security
 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, ERR_NO_SUCH_OBJECT, LdbError
 from samba.netcmd import (
     Command,
     CommandError,
     Option,
-    SuperCommand,
     )
 
 global summary
@@ -46,8 +43,9 @@ summary = {}
 class LDAPBase(object):
 
     def __init__(self, host, creds, lp,
-                 two=False, quiet=False, descriptor=False, verbose=False,
-                 view="section"):
+                 two=False, quiet=False, descriptor=False, sort_aces=False, verbose=False,
+                 view="section", base="", scope="SUB",
+                 outf=sys.stdout, errf=sys.stderr):
         ldb_options = []
         samdb_url = host
         if not "://" in host:
@@ -58,17 +56,25 @@ class LDAPBase(object):
         # use 'paged_search' module when connecting remotely
         if samdb_url.lower().startswith("ldap://"):
             ldb_options = ["modules:paged_searches"]
+        self.outf = outf
+        self.errf = errf
         self.ldb = Ldb(url=samdb_url,
                        credentials=creds,
                        lp=lp,
                        options=ldb_options)
+        self.search_base = base
+        self.search_scope = scope
         self.two_domains = two
         self.quiet = quiet
         self.descriptor = descriptor
+        self.sort_aces = sort_aces
         self.view = view
         self.verbose = verbose
         self.host = host
-        self.base_dn = self.find_basedn()
+        self.base_dn = str(self.ldb.get_default_basedn())
+        self.root_dn = str(self.ldb.get_root_basedn())
+        self.config_dn = str(self.ldb.get_config_basedn())
+        self.schema_dn = str(self.ldb.get_schema_basedn())
         self.domain_netbios = self.find_netbios()
         self.server_names = self.find_servers()
         self.domain_name = re.sub("[Dd][Cc]=", "", self.base_dn).replace(",", ".")
@@ -79,11 +85,15 @@ class LDAPBase(object):
         # Log some domain controller specific place-holers that are being used
         # when compare content of two DCs. Uncomment for DEBUG purposes.
         if self.two_domains and not self.quiet:
-            print "\n* Place-holders for %s:" % self.host
-            print 4*" " + "${DOMAIN_DN}      => %s" % self.base_dn
-            print 4*" " + "${DOMAIN_NETBIOS} => %s" % self.domain_netbios
-            print 4*" " + "${SERVER_NAME}     => %s" % self.server_names
-            print 4*" " + "${DOMAIN_NAME}    => %s" % self.domain_name
+            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)
 
     def find_domain_sid(self):
         res = self.ldb.search(base=self.base_dn, expression="(objectClass=*)", scope=SCOPE_BASE)
@@ -92,7 +102,7 @@ class LDAPBase(object):
     def find_servers(self):
         """
         """
-        res = self.ldb.search(base="OU=Domain Controllers,%s" % self.base_dn, \
+        res = self.ldb.search(base="OU=Domain Controllers,%s" % self.base_dn,
                 scope=SCOPE_SUBTREE, expression="(objectClass=computer)", attrs=["cn"])
         assert len(res) > 0
         srv = []
@@ -101,19 +111,13 @@ class LDAPBase(object):
         return srv
 
     def find_netbios(self):
-        res = self.ldb.search(base="CN=Partitions,CN=Configuration,%s" % self.base_dn, \
+        res = self.ldb.search(base="CN=Partitions,%s" % self.config_dn,
                 scope=SCOPE_SUBTREE, attrs=["nETBIOSName"])
         assert len(res) > 0
         for x in res:
             if "nETBIOSName" in x.keys():
                 return x["nETBIOSName"][0]
 
-    def find_basedn(self):
-        res = self.ldb.search(base="", expression="(objectClass=*)", scope=SCOPE_BASE,
-                attrs=["defaultNamingContext"])
-        assert len(res) == 1
-        return res[0]["defaultNamingContext"][0]
-
     def object_exists(self, object_dn):
         res = None
         try:
@@ -130,6 +134,73 @@ class LDAPBase(object):
         except Ldb.LdbError, e:
             assert "No such object" in str(e)
 
+    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
+
     def get_attributes(self, object_dn):
         """ Returns dict with all default visible attributes
         """
@@ -139,7 +210,11 @@ class LDAPBase(object):
         # 'Dn' element is not iterable and we have it as 'distinguishedName'
         del res["dn"]
         for key in res.keys():
-            res[key] = list(res[key])
+            vals = list(res[key])
+            del res[key]
+            name = self.get_attribute_name(key)
+            res[name] = self.get_attribute_values(object_dn, key, vals)
+
         return res
 
     def get_descriptor_sddl(self, object_dn):
@@ -178,13 +253,13 @@ class LDAPBase(object):
         """ Build dictionary that maps GUID to 'name' attribute found in Schema or Extended-Rights.
         """
         self.guid_map = {}
-        res = self.ldb.search(base="cn=schema,cn=configuration,%s" % self.base_dn, \
-                expression="(schemaIdGuid=*)", scope=SCOPE_SUBTREE, attrs=["schemaIdGuid", "name"])
+        res = self.ldb.search(base=self.schema_dn,
+                              expression="(schemaIdGuid=*)", scope=SCOPE_SUBTREE, attrs=["schemaIdGuid", "name"])
         for item in res:
             self.guid_map[self.guid_as_string(item["schemaIdGuid"]).lower()] = item["name"][0]
         #
-        res = self.ldb.search(base="cn=extended-rights,cn=configuration,%s" % self.base_dn, \
-                expression="(rightsGuid=*)", scope=SCOPE_SUBTREE, attrs=["rightsGuid", "name"])
+        res = self.ldb.search(base="cn=extended-rights,%s" % self.config_dn,
+                              expression="(rightsGuid=*)", scope=SCOPE_SUBTREE, attrs=["rightsGuid", "name"])
         for item in res:
             self.guid_map[str(item["rightsGuid"]).lower()] = item["name"][0]
 
@@ -192,8 +267,8 @@ class LDAPBase(object):
         """ Build dictionary that maps GUID to 'name' attribute found in Schema or Extended-Rights.
         """
         self.sid_map = {}
-        res = self.ldb.search(base="%s" % self.base_dn, \
-                expression="(objectSid=*)", scope=SCOPE_SUBTREE, attrs=["objectSid", "sAMAccountName"])
+        res = self.ldb.search(base=self.base_dn,
+                              expression="(objectSid=*)", scope=SCOPE_SUBTREE, attrs=["objectSid", "sAMAccountName"])
         for item in res:
             try:
                 self.sid_map["%s" % ndr_unpack(security.dom_sid, item["objectSid"][0])] = item["sAMAccountName"][0]
@@ -201,17 +276,24 @@ class LDAPBase(object):
                 pass
 
 class Descriptor(object):
-    def __init__(self, connection, dn):
+    def __init__(self, connection, dn, outf=sys.stdout, errf=sys.stderr):
+        self.outf = outf
+        self.errf = errf
         self.con = connection
         self.dn = dn
         self.sddl = self.con.get_descriptor_sddl(self.dn)
         self.dacl_list = self.extract_dacl()
+        if self.con.sort_aces:
+            self.dacl_list.sort()
 
     def extract_dacl(self):
         """ Extracts the DACL as a list of ACE string (with the brakets).
         """
         try:
-            res = re.search("D:(.*?)(\(.*?\))S:", self.sddl).group(2)
+            if "S:" in self.sddl:
+                res = re.search("D:(.*?)(\(.*?\))S:", self.sddl).group(2)
+            else:
+                res = re.search("D:(.*?)(\(.*\))", self.sddl).group(2)
         except AttributeError:
             return []
         return re.findall("(\(.*?\))", res)
@@ -336,7 +418,10 @@ class Descriptor(object):
         return (self_aces == [] and other_aces == [], res)
 
 class LDAPObject(object):
-    def __init__(self, connection, dn, summary):
+    def __init__(self, connection, dn, summary, filter_list,
+                 outf=sys.stdout, errf=sys.stderr):
+        self.outf = outf
+        self.errf = errf
         self.con = connection
         self.two_domains = self.con.two_domains
         self.quiet = self.con.quiet
@@ -354,11 +439,15 @@ class LDAPObject(object):
                 # Default Naming Context
                 "lastLogon", "lastLogoff", "badPwdCount", "logonCount", "badPasswordTime", "modifiedCount",
                 "operatingSystemVersion","oEMInformation",
+                "ridNextRID", "rIDPreviousAllocationPool",
                 # Configuration Naming Context
                 "repsFrom", "dSCorePropagationData", "msExchServer1HighestUSN",
                 "replUpToDateVector", "repsTo", "whenChanged", "uSNChanged", "uSNCreated",
                 # Schema Naming Context
-                "prefixMap",]
+                "prefixMap"]
+        if filter_list:
+            self.ignore_attributes += filter_list
+
         self.dn_attributes = []
         self.domain_attributes = []
         self.servername_attributes = []
@@ -414,7 +503,7 @@ class LDAPObject(object):
         Log on the screen if there is no --quiet oprion set
         """
         if not self.quiet:
-            print msg
+            self.outf.write(msg+"\n")
 
     def fix_dn(self, s):
         res = "%s" % s
@@ -454,8 +543,8 @@ class LDAPObject(object):
         return self.cmp_attrs(other)
 
     def cmp_desc(self, other):
-        d1 = Descriptor(self.con, self.dn)
-        d2 = Descriptor(other.con, other.dn)
+        d1 = Descriptor(self.con, self.dn, outf=self.outf, errf=self.errf)
+        d2 = Descriptor(other.con, other.dn, outf=self.outf, errf=self.errf)
         if self.con.view == "section":
             res = d1.diff_2(d2)
         elif self.con.view == "collision":
@@ -586,19 +675,26 @@ class LDAPObject(object):
 
 
 class LDAPBundel(object):
-    def __init__(self, connection, context, dn_list=None):
+
+    def __init__(self, connection, context, dn_list=None, filter_list=None,
+                 outf=sys.stdout, errf=sys.stderr):
+        self.outf = outf
+        self.errf = errf
         self.con = connection
         self.two_domains = self.con.two_domains
         self.quiet = self.con.quiet
         self.verbose = self.con.verbose
+        self.search_base = self.con.search_base
+        self.search_scope = self.con.search_scope
         self.summary = {}
         self.summary["unique_attrs"] = []
         self.summary["df_value_attrs"] = []
         self.summary["known_ignored_dn"] = []
         self.summary["abnormal_ignored_dn"] = []
+        self.filter_list = filter_list
         if dn_list:
             self.dn_list = dn_list
-        elif context.upper() in ["DOMAIN", "CONFIGURATION", "SCHEMA"]:
+        elif context.upper() in ["DOMAIN", "CONFIGURATION", "SCHEMA", "DNSDOMAIN", "DNSFOREST"]:
             self.context = context.upper()
             self.dn_list = self.get_dn_list(context)
         else:
@@ -623,7 +719,7 @@ class LDAPBundel(object):
         Log on the screen if there is no --quiet oprion set
         """
         if not self.quiet:
-            print msg
+            self.outf.write(msg+"\n")
 
     def update_size(self):
         self.size = len(self.dn_list)
@@ -635,32 +731,36 @@ class LDAPBundel(object):
             self.log( "\n* DN lists have different size: %s != %s" % (self.size, other.size) )
             res = False
         #
-        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]:
-                if title:
-                    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]:
-                if title:
-                    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])
+        # 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]:
+                    if title:
+                        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]:
+                    if title:
+                        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])
         self.log( "\n* Objects to be compared: %s" % self.size )
 
         index = 0
@@ -669,7 +769,9 @@ class LDAPBundel(object):
             try:
                 object1 = LDAPObject(connection=self.con,
                                      dn=self.dn_list[index],
-                                     summary=self.summary)
+                                     summary=self.summary,
+                                     filter_list=self.filter_list,
+                                     outf=self.outf, errf=self.errf)
             except LdbError, (enum, estr):
                 if enum == ERR_NO_SUCH_OBJECT:
                     self.log( "\n!!! Object not found: %s" % self.dn_list[index] )
@@ -678,7 +780,9 @@ class LDAPBundel(object):
             try:
                 object2 = LDAPObject(connection=other.con,
                         dn=other.dn_list[index],
-                        summary=other.summary)
+                        summary=other.summary,
+                        filter_list=self.filter_list,
+                        outf=self.outf, errf=self.errf)
             except LdbError, (enum, estr):
                 if enum == ERR_NO_SUCH_OBJECT:
                     self.log( "\n!!! Object not found: %s" % other.dn_list[index] )
@@ -711,17 +815,35 @@ class LDAPBundel(object):
             Parse all DNs and filter those that are 'strange' or abnormal.
         """
         if context.upper() == "DOMAIN":
-            search_base = "%s" % self.con.base_dn
+            search_base = self.con.base_dn
         elif context.upper() == "CONFIGURATION":
-            search_base = "CN=Configuration,%s" % self.con.base_dn
+            search_base = self.con.config_dn
         elif context.upper() == "SCHEMA":
-            search_base = "CN=Schema,CN=Configuration,%s" % self.con.base_dn
+            search_base = self.con.schema_dn
+        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
 
         dn_list = []
-        res = self.con.ldb.search(base=search_base, scope=SCOPE_SUBTREE, attrs=["dn"])
+        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")
+        try:
+            res = self.con.ldb.search(base=self.search_base, scope=self.search_scope, attrs=["dn"])
+        except LdbError, (enum, estr):
+            self.outf.write("Failed search of base=%s\n" % self.search_base)
+            raise
         for x in res:
            dn_list.append(x["dn"].get_linearized())
-
         #
         global summary
         #
@@ -740,9 +862,16 @@ class LDAPBundel(object):
             self.log( "".join([str("\n" + 4*" " + x) for x in self.summary["df_value_attrs"]]) )
             self.summary["df_value_attrs"] = []
 
+
 class cmd_ldapcmp(Command):
-    """compare two ldap databases"""
-    synopsis = "ldapcmp URL1 URL2 <domain|configuration|schema> [options]"
+    """Compare two ldap databases."""
+    synopsis = "%prog <URL1> <URL2> (domain|configuration|schema|dnsdomain|dnsforest) [options]"
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "versionopts": options.VersionOptions,
+        "credopts": options.CredentialsOptionsDouble,
+    }
 
     takes_optiongroups = {
         "sambaopts": options.SambaOptions,
@@ -754,74 +883,113 @@ class cmd_ldapcmp(Command):
 
     takes_options = [
         Option("-w", "--two", dest="two", action="store_true", default=False,
-               help="Hosts are in two different domains"),
+            help="Hosts are in two different domains"),
         Option("-q", "--quiet", dest="quiet", action="store_true", default=False,
-               help="Do not print anything but relay on just exit code"),
+            help="Do not print anything but relay on just exit code"),
         Option("-v", "--verbose", dest="verbose", action="store_true", default=False,
-               help="Print all DN pairs that have been compared"),
+            help="Print all DN pairs that have been compared"),
         Option("--sd", dest="descriptor", action="store_true", default=False,
-                help="Compare nTSecurityDescriptor attibutes only"),
+            help="Compare nTSecurityDescriptor attibutes only"),
+        Option("--sort-aces", dest="sort_aces", action="store_true", default=False,
+            help="Sort ACEs before comparison of nTSecurityDescriptor attribute"),
         Option("--view", dest="view", default="section",
-               help="Display mode for nTSecurityDescriptor results. Possible values: section or collision.")
+            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"),
+        Option("--filter", dest="filter", default="",
+            help="List of comma separated attributes to ignore in the comparision"),
         ]
 
     def run(self, URL1, URL2,
             context1=None, context2=None, context3=None,
-            two=False, quiet=False, verbose=False, descriptor=False, view="section",
+            two=False, quiet=False, verbose=False, descriptor=False, sort_aces=False,
+            view="section", base="", base2="", scope="SUB", filter="",
             credopts=None, sambaopts=None, versionopts=None):
+
         lp = sambaopts.get_loadparm()
-        creds = credopts.get_credentials(lp, fallback_machine=True)
-        creds2 = credopts.get_credentials2(lp)
-        if credopts.no_pass2:
+
+        using_ldap = URL1.startswith("ldap") or URL2.startswith("ldap")
+
+        if using_ldap:
+            creds = credopts.get_credentials(lp, fallback_machine=True)
+        else:
+            creds = None
+        creds2 = credopts.get_credentials2(lp, guess=False)
+        if creds2.is_anonymous():
             creds2 = creds
-        if not creds.authentication_requested():
+        else:
+            creds2.set_domain("")
+            creds2.set_workstation("")
+        if using_ldap and not creds.authentication_requested():
             raise CommandError("You must supply at least one username/password pair")
 
         # make a list of contexts to compare in
         contexts = []
         if context1 is None:
-            # if no argument given, we compare all contexts
-            contexts = ["DOMAIN", "CONFIGURATION", "SCHEMA"]
+            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
+                contexts = ["DOMAIN", "CONFIGURATION", "SCHEMA"]
         else:
             for c in [context1, context2, context3]:
                 if c is None:
                     continue
-                if not c.upper() in ["DOMAIN", "CONFIGURATION", "SCHEMA"]:
+                if not c.upper() in ["DOMAIN", "CONFIGURATION", "SCHEMA", "DNSDOMAIN", "DNSFOREST"]:
                     raise CommandError("Incorrect argument: %s" % c)
                 contexts.append(c.upper())
 
         if verbose and quiet:
             raise CommandError("You cannot set --verbose and --quiet together")
+        if (not base and base2) or (base and not base2):
+            raise CommandError("You need to specify both --base and --base2 at the same time")
         if descriptor and view.upper() not in ["SECTION", "COLLISION"]:
-            raise CommandError("Unknown --view option value. Choose from: section or collision.")
+            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")
 
         con1 = LDAPBase(URL1, creds, lp,
-                        two=two, quiet=quiet, descriptor=descriptor, verbose=verbose, view=view)
+                        two=two, quiet=quiet, descriptor=descriptor, sort_aces=sort_aces,
+                        verbose=verbose,view=view, base=base, scope=scope,
+                        outf=self.outf, errf=self.errf)
         assert len(con1.base_dn) > 0
 
         con2 = LDAPBase(URL2, creds2, lp,
-                        two=two, quiet=quiet, descriptor=descriptor, verbose=verbose, view=view)
+                        two=two, quiet=quiet, descriptor=descriptor, sort_aces=sort_aces,
+                        verbose=verbose, view=view, base=base2, scope=scope,
+                        outf=self.outf, errf=self.errf)
         assert len(con2.base_dn) > 0
 
+        filter_list = filter.split(",")
+
         status = 0
         for context in contexts:
             if not quiet:
-                print "\n* Comparing [%s] context..." % context
+                self.outf.write("\n* Comparing [%s] context...\n" % context)
 
-            b1 = LDAPBundel(con1, context=context)
-            b2 = LDAPBundel(con2, context=context)
+            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)
 
             if b1 == b2:
                 if not quiet:
-                    print "\n* Result for [%s]: SUCCESS" % context
+                    self.outf.write("\n* Result for [%s]: SUCCESS\n" %
+                        context)
             else:
                 if not quiet:
-                    print "\n* Result for [%s]: FAILURE" % context
+                    self.outf.write("\n* Result for [%s]: FAILURE\n" % context)
                     if not descriptor:
                         assert len(b1.summary["df_value_attrs"]) == len(b2.summary["df_value_attrs"])
                         b2.summary["df_value_attrs"] = []
-                        print "\nSUMMARY"
-                        print "---------"
+                        self.outf.write("\nSUMMARY\n")
+                        self.outf.write("---------\n")
                         b1.print_summary()
                         b2.print_summary()
                 # mark exit status as FAILURE if a least one comparison failed