From ff8cdeecfc28be396dcbdc4af6b7e60ab9de45f1 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Wed, 22 Jun 2011 17:08:28 +1000 Subject: samba-tool: expanded dbcheck DN checking this now checks for bad GUID elements in DN links, and offers to fix them when possible Pair-Programmed-With: Andrew Bartlett --- source4/scripting/python/samba/netcmd/dbcheck.py | 125 +++++++++++++++++++---- 1 file changed, 104 insertions(+), 21 deletions(-) diff --git a/source4/scripting/python/samba/netcmd/dbcheck.py b/source4/scripting/python/samba/netcmd/dbcheck.py index 4c9e0a1af5..d7a492836e 100644 --- a/source4/scripting/python/samba/netcmd/dbcheck.py +++ b/source4/scripting/python/samba/netcmd/dbcheck.py @@ -21,7 +21,7 @@ import ldb, sys import samba.getopt as options from samba import dsdb -from samba.common import confirm +from samba import common from samba.auth import system_session from samba.samdb import SamDB from samba.dcerpc import misc @@ -74,7 +74,7 @@ class cmd_dbcheck(Command): Option("--fix", dest="fix", default=False, action='store_true', help='Fix any errors found'), Option("--yes", dest="yes", default=False, action='store_true', - help="don't confirm changes, just do them all"), + help="don't confirm changes, just do them all as a single transaction"), Option("--cross-ncs", dest="cross_ncs", default=False, action='store_true', help="cross naming context boundaries"), Option("-v", "--verbose", dest="verbose", action="store_true", default=False, @@ -102,6 +102,9 @@ class cmd_dbcheck(Command): if cross_ncs: controls.append("search_options:1:2") + if self.yes and self.fix: + self.samdb.transaction_start() + res = self.samdb.search(base=DN, scope=self.search_scope, attrs=['dn'], controls=controls) print('Checking %u objects' % len(res)) error_count = 0 @@ -110,18 +113,30 @@ class cmd_dbcheck(Command): if error_count != 0 and not self.fix: print("Please use --fix to fix these errors") print('Checked %u objects (%u errors)' % (len(res), error_count)) + + if self.yes and self.fix: + self.samdb.transaction_commit() + if error_count != 0: sys.exit(1) + + ################################################################ + # a local confirm function that obeys the --fix and --yes options + def confirm(self, msg): + '''confirm a change''' + if not self.fix: + return False + return common.confirm(msg, forced=self.yes) + + ################################################################ # handle empty attributes def err_empty_attribute(self, dn, attrname): '''fix empty attributes''' print("ERROR: Empty attribute %s in %s" % (attrname, dn)) - if not self.fix: - return - if not confirm('Remove empty attribute %s from %s?' % (attrname, dn), self.yes): + if not self.confirm('Remove empty attribute %s from %s?' % (attrname, dn)): print("Not fixing empty attribute %s" % attrname) return @@ -152,9 +167,7 @@ class cmd_dbcheck(Command): elif (normalised[0] != val): print("value '%s' should be '%s'" % (val, normalised[0])) mod_list.append((val, normalised[0])) - if not self.fix: - return - if not confirm('Fix normalisation for %s from %s?' % (attrname, dn), self.yes): + if not self.confirm('Fix normalisation for %s from %s?' % (attrname, dn)): print("Not fixing attribute %s" % attrname) return @@ -178,19 +191,18 @@ class cmd_dbcheck(Command): ################################################################ # handle a missing GUID extended DN component - def err_missing_dn_GUID(self, dn, attrname, val, dsdb_dn): - print("ERROR: missing GUID component for %s in object %s - %s" % (attrname, dn, val)) + def err_incorrect_dn_GUID(self, dn, attrname, val, dsdb_dn, errstr): + print("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val)) try: - res = self.samdb.search(base=dsdb_dn.dn, scope=ldb.SCOPE_BASE, attrs=['objectGUID']) - except LdbError, (enum, estr): + res = self.samdb.search(base=dsdb_dn.dn, scope=ldb.SCOPE_BASE, + attrs=[], controls=["extended_dn:1:1"]) + except ldb.LdbError, (enum, estr): print("unable to find object for DN %s - cannot fix (%s)" % (dsdb_dn.dn, estr)) return - guid = res[0]['objectGUID'][0] - guidstr = str(misc.GUID(guid)) - dsdb_dn.dn.set_extended_component("GUID", guid) + dsdb_dn.dn = res[0].dn - if not confirm('Add GUID %s giving DN %s?' % (guidstr, str(dsdb_dn))): - print("Not fixing missing GUID") + if not self.confirm('Change DN to %s?' % str(dsdb_dn)): + print("Not fixing %s" % errstr) return m = ldb.Message() m.dn = dn @@ -201,10 +213,53 @@ class cmd_dbcheck(Command): try: self.samdb.modify(m) except Exception, msg: - print("Failed to fix missing GUID on attribute %s : %s" % (attrname, msg)) + print("Failed to fix %s on attribute %s : %s" % (errstr, attrname, msg)) return - print("Fixed missing GUID on attribute %s" % attrname) + print("Fixed %s on attribute %s" % (errstr, attrname)) + + ################################################################ + # handle a DN pointing to a deleted object + def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn): + print("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val)) + print("Target GUID points at deleted DN %s" % correct_dn) + if not self.confirm('Remove DN?'): + print("Not removing") + return + m = ldb.Message() + m.dn = dn + m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) + if self.verbose: + print(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY)) + try: + self.samdb.modify(m) + except Exception, msg: + print("Failed to remove deleted DN attribute %s : %s" % (attrname, msg)) + return + print("Removed deleted DN on attribute %s" % attrname) + + + ################################################################ + # handle a DN string being incorrect + def err_dn_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn): + print("ERROR: incorrect DN string component for %s in object %s - %s" % (attrname, dn, val)) + dsdb_dn.dn = correct_dn + + if not self.confirm('Change DN to %s?' % str(dsdb_dn)): + print("Not fixing %s" % errstr) + return + m = ldb.Message() + m.dn = dn + m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) + m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname) + if self.verbose: + print(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY)) + try: + self.samdb.modify(m) + except Exception, msg: + print("Failed to fix incorrect DN string on attribute %s : %s" % (attrname, msg)) + return + print("Fixed incorrect DN string on attribute %s" % (attrname)) ################################################################ @@ -219,9 +274,37 @@ class cmd_dbcheck(Command): guid = dsdb_dn.dn.get_extended_component("GUID") if guid is None: error_count += 1 - self.err_missing_dn_GUID(obj.dn, attrname, val, dsdb_dn) + self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn, "missing GUID") + continue + + guidstr = str(misc.GUID(guid)) + + # check its the right GUID + try: + res = self.samdb.search(base="" % guidstr, scope=ldb.SCOPE_BASE, + attrs=['isDeleted'], controls=["extended_dn:1:1", "show_deleted:1"]) + except ldb.LdbError, (enum, estr): + error_count += 1 + self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn, "incorrect GUID") + continue + + # the target DN might be deleted + if (dsdb_dn.prefix != "B:32:18E2EA80684F11D2B9AA00C04F79F805:" and + 'isDeleted' in res[0] and + res[0]['isDeleted'][0].upper() == "TRUE"): + # note that we don't check this for the special wellKnownObjects prefix + # for Deleted Objects, as we expect that to be deleted + error_count += 1 + self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn) + continue + + # check the DN matches in string form + if res[0].dn.extended_str() != dsdb_dn.dn.extended_str(): + error_count += 1 + self.err_dn_target_mismatch(obj.dn, attrname, val, dsdb_dn, res[0].dn) + continue - return 0 + return error_count -- cgit