Revert "more"
[metze/samba/wip.git] / source4 / scripting / python / samba / dbchecker.py
index 284c52908c317d61d0bc44a4812111fc78d0094d..06fd82752f756c03955f726f66414bc92db1f15f 100644 (file)
@@ -21,9 +21,10 @@ import ldb
 from samba import dsdb
 from samba import common
 from samba.dcerpc import misc
-from samba.ndr import ndr_unpack
+from samba.ndr import ndr_unpack, ndr_pack
 from samba.dcerpc import drsblobs
 from samba.common import dsdb_Dn
+from samba.dcerpc import security
 
 
 class dbcheck(object):
@@ -49,13 +50,31 @@ class dbcheck(object):
         self.fix_all_missing_backlinks = False
         self.fix_all_orphaned_backlinks = False
         self.fix_rmd_flags = False
+        self.fix_ntsecuritydescriptor = False
         self.seize_fsmo_role = False
         self.move_to_lost_and_found = False
+        self.fix_instancetype = False
         self.in_transaction = in_transaction
         self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
         self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
         self.schema_dn = samdb.get_schema_basedn()
         self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
+        self.ntds_dsa = samdb.get_dsServiceName()
+        self.class_schemaIDGUID = {}
+
+        res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
+        if "msDS-hasMasterNCs" in res[0]:
+            self.write_ncs = res[0]["msDS-hasMasterNCs"]
+        else:
+            # If the Forest Level is less than 2003 then there is no
+            # msDS-hasMasterNCs, so we fall back to hasMasterNCs
+            # no need to merge as all the NCs that are in hasMasterNCs must
+            # also be in msDS-hasMasterNCs (but not the opposite)
+            if "hasMasterNCs" in res[0]:
+                self.write_ncs = res[0]["hasMasterNCs"]
+            else:
+                self.write_ncs = None
+
 
     def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
         '''perform a database check, returning the number of errors found'''
@@ -358,13 +377,48 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
             self.report('Not moving object %s into LostAndFound' % (obj.dn))
             return
 
-        nc_root = self.samdb.get_nc_root(obj.dn);
-        lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
-        new_dn = ldb.Dn(self.samdb, str(obj.dn))
-        new_dn.remove_base_components(len(new_dn) - 1)
-        if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
-                          "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
-            self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
+        keep_transaction = True
+        self.samdb.transaction_start()
+        try:
+            nc_root = self.samdb.get_nc_root(obj.dn);
+            lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
+            new_dn = ldb.Dn(self.samdb, str(obj.dn))
+            new_dn.remove_base_components(len(new_dn) - 1)
+            if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
+                              "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
+                self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
+
+                m = ldb.Message()
+                m.dn = obj.dn
+                m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
+
+                if self.do_modify(m, [],
+                                  "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
+                    self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
+                    keep_transaction = True
+        except:
+            self.samdb.transaction_cancel()
+            raise
+
+        if keep_transaction:
+            self.samdb.transaction_commit()
+        else:
+            self.samdb.transaction_cancel()
+
+
+    def err_wrong_instancetype(self, obj, calculated_instancetype):
+        '''handle a wrong instanceType'''
+        self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
+        if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
+            self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
+            return
+
+        m = ldb.Message()
+        m.dn = obj.dn
+        m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
+        if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
+                          "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
+            self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
 
     def find_revealed_link(self, dn, attrname, guid):
         '''return a revealed link in an object'''
@@ -430,7 +484,7 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
             if is_deleted and not target_is_deleted and reverse_link_name is not None:
                 revealed_dn = self.find_revealed_link(obj.dn, attrname, guid)
                 rmd_flags = revealed_dn.dn.get_extended_component("RMD_FLAGS")
-                if rmd_flags != None and (int(rmd_flags) & 1) == 0:
+                if rmd_flags is not None and (int(rmd_flags) & 1) == 0:
                     # the RMD_FLAGS for this link should be 1, as the target is deleted
                     self.err_incorrect_rmd_flags(obj, attrname, revealed_dn)
                     continue
@@ -497,6 +551,164 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
                           "Failed to fix metadata for attribute %s" % attr):
             self.report("Fixed metadata for attribute %s" % attr)
 
+    def ace_get_effective_inherited_type(self, ace):
+        if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
+            return None
+
+        check = False
+        if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
+            check = True
+        elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
+            check = True
+        elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
+            check = True
+        elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
+            check = True
+
+        if not check:
+            return None
+
+        if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
+            return None
+
+        return str(ace.object.inherited_type)
+
+    def lookup_class_schemaIDGUID(self, cls):
+        if cls in self.class_schemaIDGUID:
+            return self.class_schemaIDGUID[cls]
+
+        flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
+        res = self.samdb.search(base=self.schema_dn,
+                                expression=flt,
+                                attrs=["schemaIDGUID"])
+        t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
+
+        self.class_schemaIDGUID[cls] = t
+        return t
+
+    def process_sd(self, dn, obj):
+        sd_attr = "nTSecurityDescriptor"
+        sd_val = obj[sd_attr]
+
+        sd = ndr_unpack(security.descriptor, str(sd_val))
+
+        is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
+        if is_deleted:
+            # we don't fix deleted objects
+            return (sd, None)
+
+        sd_clean = security.descriptor()
+        sd_clean.owner_sid = sd.owner_sid
+        sd_clean.group_sid = sd.group_sid
+        sd_clean.type = sd.type
+        sd_clean.revision = sd.revision
+
+        broken = False
+        last_inherited_type = None
+
+        aces = []
+        if sd.sacl is not None:
+            aces = sd.sacl.aces
+        for i in range(0, len(aces)):
+            ace = aces[i]
+
+            if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
+                sd_clean.sacl_add(ace)
+                continue
+
+            t = self.ace_get_effective_inherited_type(ace)
+            if t is None:
+                continue
+
+            if last_inherited_type is not None:
+                if t != last_inherited_type:
+                    # if it inherited from more than
+                    # one type it's very likely to be broken
+                    #
+                    # If not the recalculation will calculate
+                    # the same result.
+                    broken = True
+                continue
+
+            last_inherited_type = t
+
+        aces = []
+        if sd.dacl is not None:
+            aces = sd.dacl.aces
+        for i in range(0, len(aces)):
+            ace = aces[i]
+
+            if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
+                sd_clean.dacl_add(ace)
+                continue
+
+            t = self.ace_get_effective_inherited_type(ace)
+            if t is None:
+                continue
+
+            if last_inherited_type is not None:
+                if t != last_inherited_type:
+                    # if it inherited from more than
+                    # one type it's very likely to be broken
+                    #
+                    # If not the recalculation will calculate
+                    # the same result.
+                    broken = True
+                continue
+
+            last_inherited_type = t
+
+        if broken:
+            return (sd_clean, sd)
+
+        if last_inherited_type is None:
+            # ok
+            return (sd, None)
+
+        cls = None
+        try:
+            cls = obj["objectClass"][-1]
+        except KeyError, e:
+            pass
+
+        if cls is None:
+            res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
+                                    attrs=["isDeleted", "objectClass"],
+                                    controls=["show_recycled:1"])
+            o = res[0]
+            is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
+            if is_deleted:
+                # we don't fix deleted objects
+                return (sd, None)
+            cls = o["objectClass"][-1]
+
+        t = self.lookup_class_schemaIDGUID(cls)
+
+        if t != last_inherited_type:
+            # broken
+            return (sd_clean, sd)
+
+        # ok
+        return (sd, None)
+
+    def err_wrong_sd(self, dn, sd, sd_broken):
+        '''re-write replPropertyMetaData elements for a single attribute for a
+        object. This is used to fix missing replPropertyMetaData elements'''
+        sd_attr = "nTSecurityDescriptor"
+        sd_val = ndr_pack(sd)
+        sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
+
+        if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
+            self.report('Not fixing %s on %s\n' % (sd_attr, dn))
+            return
+
+        nmsg = ldb.Message()
+        nmsg.dn = dn
+        nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
+        if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
+                          "Failed to fix metadata for attribute %s" % sd_attr):
+            self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
+
     def is_fsmo_role(self, dn):
         if dn == self.samdb.domain_dn:
             return True
@@ -508,9 +720,27 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
             return True
         if dn == self.rid_dn:
             return True
-        
+
         return False
 
+    def calculate_instancetype(self, dn):
+        instancetype = 0
+        nc_root = self.samdb.get_nc_root(dn)
+        if dn == nc_root:
+            instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
+            try:
+                self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
+            except ldb.LdbError, (enum, estr):
+                if enum != ldb.ERR_NO_SUCH_OBJECT:
+                    raise
+            else:
+                instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
+
+        if self.write_ncs is not None and str(nc_root) in self.write_ncs:
+            instancetype |= dsdb.INSTANCE_TYPE_WRITE
+
+        return instancetype
+
     def check_object(self, dn, attrs=['*']):
         '''check one object'''
         if self.verbose:
@@ -519,8 +749,19 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
             attrs.append("replPropertyMetaData")
 
         try:
+            sd_flags = 0
+            sd_flags |= security.SECINFO_OWNER
+            sd_flags |= security.SECINFO_GROUP
+            sd_flags |= security.SECINFO_DACL
+            sd_flags |= security.SECINFO_SACL
+
             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
-                                    controls=["extended_dn:1:1", "show_recycled:1", "show_deleted:1"],
+                                    controls=[
+                                        "extended_dn:1:1",
+                                        "show_recycled:1",
+                                        "show_deleted:1",
+                                        "sd_flags:1:%d" % sd_flags,
+                                    ],
                                     attrs=attrs)
         except ldb.LdbError, (enum, estr):
             if enum == ldb.ERR_NO_SUCH_OBJECT:
@@ -547,6 +788,13 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
                 got_repl_property_meta_data = True
                 continue
 
+            if str(attrname).lower() == 'ntsecuritydescriptor':
+                (sd, sd_broken) = self.process_sd(dn, obj)
+                if sd_broken is not None:
+                    self.err_wrong_sd(dn, sd, sd_broken)
+                    error_count += 1
+                continue
+
             if str(attrname).lower() == 'objectclass':
                 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, list(obj[attrname]))
                 if list(normalised) != list(obj[attrname]):
@@ -589,6 +837,11 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
                     error_count += 1
                     break
 
+            if str(attrname).lower() == "instancetype":
+                calculated_instancetype = self.calculate_instancetype(dn)
+                if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
+                    self.err_wrong_instancetype(obj, calculated_instancetype)
+
         show_dn = True
         if got_repl_property_meta_data:
             rdn = (str(dn).split(","))[0]
@@ -683,3 +936,12 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
         m['add']    = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
         m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
         return self.do_modify(m, [], 're-indexed database', validate=False)
+
+    ###############################################
+    # reset @MODULES
+    def reset_modules(self):
+        '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
+        m = ldb.Message()
+        m.dn = ldb.Dn(self.samdb, "@MODULES")
+        m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
+        return self.do_modify(m, [], 'reset @MODULES on database', validate=False)