1# Samba4 AD database checker 2# 3# Copyright (C) Andrew Tridgell 2011 4# Copyright (C) Matthieu Patou <mat@matws.net> 2011 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 3 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <http://www.gnu.org/licenses/>. 18# 19 20from __future__ import print_function 21import ldb 22import samba 23import time 24from base64 import b64decode 25from samba import dsdb 26from samba import common 27from samba.dcerpc import misc 28from samba.dcerpc import drsuapi 29from samba.ndr import ndr_unpack, ndr_pack 30from samba.dcerpc import drsblobs 31from samba.common import dsdb_Dn 32from samba.dcerpc import security 33from samba.descriptor import get_wellknown_sds, get_diff_sds 34from samba.auth import system_session, admin_session 35from samba.netcmd import CommandError 36from samba.netcmd.fsmo import get_fsmo_roleowner 37 38# vals is a sequence of ldb.bytes objects which are a subclass 39# of 'byte' type in python3 and just a str type in python2, to 40# display as string these need to be converted to string via (str) 41# function in python3 but that may generate a UnicodeDecode error, 42# if so use repr instead. We need to at least try to get the 'str' 43# value if possible to allow some tests which check the strings 44# outputted to pass, these tests compare attr values logged to stdout 45# against those in various results files. 46 47def dump_attr_values(vals): 48 result = "" 49 for value in vals: 50 if len(result): 51 result = "," + result 52 try: 53 result = result + str(value) 54 except UnicodeDecodeError: 55 result = result + repr(value) 56 return result 57 58class dbcheck(object): 59 """check a SAM database for errors""" 60 61 def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False, 62 yes=False, quiet=False, in_transaction=False, 63 quick_membership_checks=False, 64 reset_well_known_acls=False, 65 check_expired_tombstones=False): 66 self.samdb = samdb 67 self.dict_oid_name = None 68 self.samdb_schema = (samdb_schema or samdb) 69 self.verbose = verbose 70 self.fix = fix 71 self.yes = yes 72 self.quiet = quiet 73 self.remove_all_unknown_attributes = False 74 self.remove_all_empty_attributes = False 75 self.fix_all_normalisation = False 76 self.fix_all_duplicates = False 77 self.fix_all_DN_GUIDs = False 78 self.fix_all_binary_dn = False 79 self.remove_implausible_deleted_DN_links = False 80 self.remove_plausible_deleted_DN_links = False 81 self.fix_all_string_dn_component_mismatch = False 82 self.fix_all_GUID_dn_component_mismatch = False 83 self.fix_all_SID_dn_component_mismatch = False 84 self.fix_all_SID_dn_component_missing = False 85 self.fix_all_old_dn_string_component_mismatch = False 86 self.fix_all_metadata = False 87 self.fix_time_metadata = False 88 self.fix_undead_linked_attributes = False 89 self.fix_all_missing_backlinks = False 90 self.fix_all_orphaned_backlinks = False 91 self.fix_all_missing_forward_links = False 92 self.duplicate_link_cache = dict() 93 self.recover_all_forward_links = False 94 self.fix_rmd_flags = False 95 self.fix_ntsecuritydescriptor = False 96 self.fix_ntsecuritydescriptor_owner_group = False 97 self.seize_fsmo_role = False 98 self.move_to_lost_and_found = False 99 self.fix_instancetype = False 100 self.fix_replmetadata_zero_invocationid = False 101 self.fix_replmetadata_duplicate_attid = False 102 self.fix_replmetadata_wrong_attid = False 103 self.fix_replmetadata_unsorted_attid = False 104 self.fix_deleted_deleted_objects = False 105 self.fix_incorrect_deleted_objects = False 106 self.fix_dn = False 107 self.fix_base64_userparameters = False 108 self.fix_utf8_userparameters = False 109 self.fix_doubled_userparameters = False 110 self.fix_sid_rid_set_conflict = False 111 self.quick_membership_checks = quick_membership_checks 112 self.reset_well_known_acls = reset_well_known_acls 113 self.check_expired_tombstones = check_expired_tombstones 114 self.expired_tombstones = 0 115 self.reset_all_well_known_acls = False 116 self.in_transaction = in_transaction 117 self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn()) 118 self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn()) 119 self.schema_dn = samdb.get_schema_basedn() 120 self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn()) 121 self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName()) 122 self.class_schemaIDGUID = {} 123 self.wellknown_sds = get_wellknown_sds(self.samdb) 124 self.fix_all_missing_objectclass = False 125 self.fix_missing_deleted_objects = False 126 self.fix_replica_locations = False 127 self.fix_missing_rid_set_master = False 128 self.fix_changes_after_deletion_bug = False 129 130 self.dn_set = set() 131 self.link_id_cache = {} 132 self.name_map = {} 133 try: 134 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE, 135 attrs=["objectSid"]) 136 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]) 137 self.name_map['DnsAdmins'] = str(dnsadmins_sid) 138 except ldb.LdbError as e5: 139 (enum, estr) = e5.args 140 if enum != ldb.ERR_NO_SUCH_OBJECT: 141 raise 142 pass 143 144 self.system_session_info = system_session() 145 self.admin_session_info = admin_session(None, samdb.get_domain_sid()) 146 147 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs']) 148 if "msDS-hasMasterNCs" in res[0]: 149 self.write_ncs = res[0]["msDS-hasMasterNCs"] 150 else: 151 # If the Forest Level is less than 2003 then there is no 152 # msDS-hasMasterNCs, so we fall back to hasMasterNCs 153 # no need to merge as all the NCs that are in hasMasterNCs must 154 # also be in msDS-hasMasterNCs (but not the opposite) 155 if "hasMasterNCs" in res[0]: 156 self.write_ncs = res[0]["hasMasterNCs"] 157 else: 158 self.write_ncs = None 159 160 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts']) 161 self.deleted_objects_containers = [] 162 self.ncs_lacking_deleted_containers = [] 163 self.dns_partitions = [] 164 try: 165 self.ncs = res[0]["namingContexts"] 166 except KeyError: 167 pass 168 except IndexError: 169 pass 170 171 for nc in self.ncs: 172 try: 173 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc.decode('utf8')), 174 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER) 175 self.deleted_objects_containers.append(dn) 176 except KeyError: 177 self.ncs_lacking_deleted_containers.append(ldb.Dn(self.samdb, nc.decode('utf8'))) 178 179 domaindns_zone = 'DC=DomainDnsZones,%s' % self.samdb.get_default_basedn() 180 forestdns_zone = 'DC=ForestDnsZones,%s' % self.samdb.get_root_basedn() 181 domain = self.samdb.search(scope=ldb.SCOPE_ONELEVEL, 182 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"], 183 base=self.samdb.get_partitions_dn(), 184 expression="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone) 185 if len(domain) == 1: 186 self.dns_partitions.append((ldb.Dn(self.samdb, forestdns_zone), domain[0])) 187 188 forest = self.samdb.search(scope=ldb.SCOPE_ONELEVEL, 189 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"], 190 base=self.samdb.get_partitions_dn(), 191 expression="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone) 192 if len(forest) == 1: 193 self.dns_partitions.append((ldb.Dn(self.samdb, domaindns_zone), forest[0])) 194 195 fsmo_dn = ldb.Dn(self.samdb, "CN=RID Manager$,CN=System," + self.samdb.domain_dn()) 196 rid_master = get_fsmo_roleowner(self.samdb, fsmo_dn, "rid") 197 if ldb.Dn(self.samdb, self.samdb.get_dsServiceName()) == rid_master: 198 self.is_rid_master = True 199 else: 200 self.is_rid_master = False 201 202 # To get your rid set 203 # 1. Get server name 204 res = self.samdb.search(base=ldb.Dn(self.samdb, self.samdb.get_serverName()), 205 scope=ldb.SCOPE_BASE, attrs=["serverReference"]) 206 # 2. Get server reference 207 self.server_ref_dn = ldb.Dn(self.samdb, res[0]['serverReference'][0].decode('utf8')) 208 209 # 3. Get RID Set 210 res = self.samdb.search(base=self.server_ref_dn, 211 scope=ldb.SCOPE_BASE, attrs=['rIDSetReferences']) 212 if "rIDSetReferences" in res[0]: 213 self.rid_set_dn = ldb.Dn(self.samdb, res[0]['rIDSetReferences'][0].decode('utf8')) 214 else: 215 self.rid_set_dn = None 216 217 ntds_service_dn = "CN=Directory Service,CN=Windows NT,CN=Services,%s" % \ 218 self.samdb.get_config_basedn().get_linearized() 219 res = samdb.search(base=ntds_service_dn, 220 scope=ldb.SCOPE_BASE, 221 expression="(objectClass=nTDSService)", 222 attrs=["tombstoneLifetime"]) 223 if "tombstoneLifetime" in res[0]: 224 self.tombstoneLifetime = int(res[0]["tombstoneLifetime"][0]) 225 else: 226 self.tombstoneLifetime = 180 227 228 self.compatibleFeatures = [] 229 self.requiredFeatures = [] 230 231 try: 232 res = self.samdb.search(scope=ldb.SCOPE_BASE, 233 base="@SAMBA_DSDB", 234 attrs=["compatibleFeatures", 235 "requiredFeatures"]) 236 if "compatibleFeatures" in res[0]: 237 self.compatibleFeatures = res[0]["compatibleFeatures"] 238 if "requiredFeatures" in res[0]: 239 self.requiredFeatures = res[0]["requiredFeatures"] 240 except ldb.LdbError as e6: 241 (enum, estr) = e6.args 242 if enum != ldb.ERR_NO_SUCH_OBJECT: 243 raise 244 pass 245 246 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=None, 247 attrs=None): 248 '''perform a database check, returning the number of errors found''' 249 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls) 250 self.report('Checking %u objects' % len(res)) 251 error_count = 0 252 253 error_count += self.check_deleted_objects_containers() 254 255 self.attribute_or_class_ids = set() 256 257 for object in res: 258 self.dn_set.add(str(object.dn)) 259 error_count += self.check_object(object.dn, attrs=attrs) 260 261 if DN is None: 262 error_count += self.check_rootdse() 263 264 if self.expired_tombstones > 0: 265 self.report("NOTICE: found %d expired tombstones, " 266 "'samba' will remove them daily, " 267 "'samba-tool domain tombstones expunge' " 268 "would do that immediately." % ( 269 self.expired_tombstones)) 270 271 if error_count != 0 and not self.fix: 272 self.report("Please use --fix to fix these errors") 273 274 self.report('Checked %u objects (%u errors)' % (len(res), error_count)) 275 return error_count 276 277 def check_deleted_objects_containers(self): 278 """This function only fixes conflicts on the Deleted Objects 279 containers, not the attributes""" 280 error_count = 0 281 for nc in self.ncs_lacking_deleted_containers: 282 if nc == self.schema_dn: 283 continue 284 error_count += 1 285 self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc) 286 if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'): 287 continue 288 289 dn = ldb.Dn(self.samdb, "CN=Deleted Objects") 290 dn.add_base(nc) 291 292 conflict_dn = None 293 try: 294 # If something already exists here, add a conflict 295 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[], 296 controls=["show_deleted:1", "extended_dn:1:1", 297 "show_recycled:1", "reveal_internals:0"]) 298 if len(res) != 0: 299 guid = res[0].dn.get_extended_component("GUID") 300 conflict_dn = ldb.Dn(self.samdb, 301 "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid))) 302 conflict_dn.add_base(nc) 303 304 except ldb.LdbError as e2: 305 (enum, estr) = e2.args 306 if enum == ldb.ERR_NO_SUCH_OBJECT: 307 pass 308 else: 309 self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr) 310 return 1 311 312 if conflict_dn is not None: 313 try: 314 self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"]) 315 except ldb.LdbError as e1: 316 (enum, estr) = e1.args 317 self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr)) 318 return 1 319 320 # Refresh wellKnownObjects links 321 res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE, 322 attrs=['wellKnownObjects'], 323 controls=["show_deleted:1", "extended_dn:0", 324 "show_recycled:1", "reveal_internals:0"]) 325 if len(res) != 1: 326 self.report("wellKnownObjects was not found for NC %s" % nc) 327 return 1 328 329 # Prevent duplicate deleted objects containers just in case 330 wko = res[0]["wellKnownObjects"] 331 listwko = [] 332 proposed_objectguid = None 333 for o in wko: 334 dsdb_dn = dsdb_Dn(self.samdb, o.decode('utf8'), dsdb.DSDB_SYNTAX_BINARY_DN) 335 if self.is_deleted_objects_dn(dsdb_dn): 336 self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o) 337 # We really want to put this back in the same spot 338 # as the original one, so that on replication we 339 # merge, rather than conflict. 340 proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID") 341 listwko.append(str(o)) 342 343 if proposed_objectguid is not None: 344 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid)) 345 else: 346 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER 347 listwko.append('%s:%s' % (wko_prefix, dn)) 348 guid_suffix = "" 349 350 # Insert a brand new Deleted Objects container 351 self.samdb.add_ldif("""dn: %s 352objectClass: top 353objectClass: container 354description: Container for deleted objects 355isDeleted: TRUE 356isCriticalSystemObject: TRUE 357showInAdvancedViewOnly: TRUE 358systemFlags: -1946157056%s""" % (dn, guid_suffix), 359 controls=["relax:0", "provision:0"]) 360 361 delta = ldb.Message() 362 delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"])) 363 delta["wellKnownObjects"] = ldb.MessageElement(listwko, 364 ldb.FLAG_MOD_REPLACE, 365 "wellKnownObjects") 366 367 # Insert the link to the brand new container 368 if self.do_modify(delta, ["relax:0"], 369 "NC %s lacks Deleted Objects WKGUID" % nc, 370 validate=False): 371 self.report("Added %s well known guid link" % dn) 372 373 self.deleted_objects_containers.append(dn) 374 375 return error_count 376 377 def report(self, msg): 378 '''print a message unless quiet is set''' 379 if not self.quiet: 380 print(msg) 381 382 def confirm(self, msg, allow_all=False, forced=False): 383 '''confirm a change''' 384 if not self.fix: 385 return False 386 if self.quiet: 387 return self.yes 388 if self.yes: 389 forced = True 390 return common.confirm(msg, forced=forced, allow_all=allow_all) 391 392 ################################################################ 393 # a local confirm function with support for 'all' 394 def confirm_all(self, msg, all_attr): 395 '''confirm a change with support for "all" ''' 396 if not self.fix: 397 return False 398 if getattr(self, all_attr) == 'NONE': 399 return False 400 if getattr(self, all_attr) == 'ALL': 401 forced = True 402 else: 403 forced = self.yes 404 if self.quiet: 405 return forced 406 c = common.confirm(msg, forced=forced, allow_all=True) 407 if c == 'ALL': 408 setattr(self, all_attr, 'ALL') 409 return True 410 if c == 'NONE': 411 setattr(self, all_attr, 'NONE') 412 return False 413 return c 414 415 def do_delete(self, dn, controls, msg): 416 '''delete dn with optional verbose output''' 417 if self.verbose: 418 self.report("delete DN %s" % dn) 419 try: 420 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK] 421 self.samdb.delete(dn, controls=controls) 422 except Exception as err: 423 if self.in_transaction: 424 raise CommandError("%s : %s" % (msg, err)) 425 self.report("%s : %s" % (msg, err)) 426 return False 427 return True 428 429 def do_modify(self, m, controls, msg, validate=True): 430 '''perform a modify with optional verbose output''' 431 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK] 432 if self.verbose: 433 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY)) 434 self.report("controls: %r" % controls) 435 try: 436 self.samdb.modify(m, controls=controls, validate=validate) 437 except Exception as err: 438 if self.in_transaction: 439 raise CommandError("%s : %s" % (msg, err)) 440 self.report("%s : %s" % (msg, err)) 441 return False 442 return True 443 444 def do_rename(self, from_dn, to_rdn, to_base, controls, msg): 445 '''perform a modify with optional verbose output''' 446 if self.verbose: 447 self.report("""dn: %s 448changeType: modrdn 449newrdn: %s 450deleteOldRdn: 1 451newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) 452 try: 453 to_dn = to_rdn + to_base 454 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK] 455 self.samdb.rename(from_dn, to_dn, controls=controls) 456 except Exception as err: 457 if self.in_transaction: 458 raise CommandError("%s : %s" % (msg, err)) 459 self.report("%s : %s" % (msg, err)) 460 return False 461 return True 462 463 def get_attr_linkID_and_reverse_name(self, attrname): 464 if attrname in self.link_id_cache: 465 return self.link_id_cache[attrname] 466 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname) 467 if linkID: 468 revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname) 469 else: 470 revname = None 471 self.link_id_cache[attrname] = (linkID, revname) 472 return linkID, revname 473 474 def err_empty_attribute(self, dn, attrname): 475 '''fix empty attributes''' 476 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn)) 477 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'): 478 self.report("Not fixing empty attribute %s" % attrname) 479 return 480 481 m = ldb.Message() 482 m.dn = dn 483 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname) 484 if self.do_modify(m, ["relax:0", "show_recycled:1"], 485 "Failed to remove empty attribute %s" % attrname, validate=False): 486 self.report("Removed empty attribute %s" % attrname) 487 488 def err_normalise_mismatch(self, dn, attrname, values): 489 '''fix attribute normalisation errors''' 490 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn)) 491 mod_list = [] 492 for val in values: 493 normalised = self.samdb.dsdb_normalise_attributes( 494 self.samdb_schema, attrname, [val]) 495 if len(normalised) != 1: 496 self.report("Unable to normalise value '%s'" % val) 497 mod_list.append((val, '')) 498 elif (normalised[0] != val): 499 self.report("value '%s' should be '%s'" % (val, normalised[0])) 500 mod_list.append((val, normalised[0])) 501 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'): 502 self.report("Not fixing attribute %s" % attrname) 503 return 504 505 m = ldb.Message() 506 m.dn = dn 507 for i in range(0, len(mod_list)): 508 (val, nval) = mod_list[i] 509 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) 510 if nval != '': 511 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD, 512 attrname) 513 514 if self.do_modify(m, ["relax:0", "show_recycled:1"], 515 "Failed to normalise attribute %s" % attrname, 516 validate=False): 517 self.report("Normalised attribute %s" % attrname) 518 519 def err_normalise_mismatch_replace(self, dn, attrname, values): 520 '''fix attribute normalisation errors''' 521 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values) 522 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn)) 523 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised))) 524 if list(normalised) == values: 525 return 526 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'): 527 self.report("Not fixing attribute '%s'" % attrname) 528 return 529 530 m = ldb.Message() 531 m.dn = dn 532 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname) 533 534 if self.do_modify(m, ["relax:0", "show_recycled:1"], 535 "Failed to normalise attribute %s" % attrname, 536 validate=False): 537 self.report("Normalised attribute %s" % attrname) 538 539 def err_duplicate_values(self, dn, attrname, dup_values, values): 540 '''fix attribute normalisation errors''' 541 self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn)) 542 self.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dump_attr_values(dup_values)), ','.join(dump_attr_values(values)))) 543 if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'): 544 self.report("Not fixing attribute '%s'" % attrname) 545 return 546 547 m = ldb.Message() 548 m.dn = dn 549 m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname) 550 551 if self.do_modify(m, ["relax:0", "show_recycled:1"], 552 "Failed to remove duplicate value on attribute %s" % attrname, 553 validate=False): 554 self.report("Removed duplicate value on attribute %s" % attrname) 555 556 def is_deleted_objects_dn(self, dsdb_dn): 557 '''see if a dsdb_Dn is the special Deleted Objects DN''' 558 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER 559 560 def err_missing_objectclass(self, dn): 561 """handle object without objectclass""" 562 self.report("ERROR: missing objectclass in object %s. If you have another working DC, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (dn, self.samdb.get_nc_root(dn))) 563 if not self.confirm_all("If you cannot re-sync from another DC, do you wish to delete object '%s'?" % dn, 'fix_all_missing_objectclass'): 564 self.report("Not deleting object with missing objectclass '%s'" % dn) 565 return 566 if self.do_delete(dn, ["relax:0"], 567 "Failed to remove DN %s" % dn): 568 self.report("Removed DN %s" % dn) 569 570 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False): 571 """handle a DN pointing to a deleted object""" 572 if not remove_plausible: 573 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val)) 574 self.report("Target GUID points at deleted DN %r" % str(correct_dn)) 575 if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'): 576 self.report("Not removing") 577 return 578 else: 579 self.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname, dn, val)) 580 self.report("Target GUID points at deleted DN %r" % str(correct_dn)) 581 if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'): 582 self.report("Not removing") 583 return 584 585 m = ldb.Message() 586 m.dn = dn 587 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) 588 if self.do_modify(m, ["show_recycled:1", 589 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS], 590 "Failed to remove deleted DN attribute %s" % attrname): 591 self.report("Removed deleted DN on attribute %s" % attrname) 592 593 def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn): 594 """handle a missing target DN (if specified, GUID form can't be found, 595 and otherwise DN string form can't be found)""" 596 597 # Don't change anything if the object itself is deleted 598 if str(dn).find('\\0ADEL') != -1: 599 # We don't bump the error count as Samba produces these 600 # in normal operation 601 self.report("WARNING: no target object found for GUID " 602 "component link %s in deleted object " 603 "%s - %s" % (attrname, dn, val)) 604 self.report("Not removing dangling one-way " 605 "link on deleted object " 606 "(tombstone garbage collection in progress?)") 607 return 0 608 609 # check if its a backlink 610 linkID, _ = self.get_attr_linkID_and_reverse_name(attrname) 611 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1: 612 613 linkID, reverse_link_name \ 614 = self.get_attr_linkID_and_reverse_name(attrname) 615 if reverse_link_name is not None: 616 self.report("WARNING: no target object found for GUID " 617 "component for one-way forward link " 618 "%s in object " 619 "%s - %s" % (attrname, dn, val)) 620 self.report("Not removing dangling forward link") 621 return 0 622 623 nc_root = self.samdb.get_nc_root(dn) 624 try: 625 target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn) 626 except ldb.LdbError as e: 627 (enum, estr) = e.args 628 if enum != ldb.ERR_NO_SUCH_OBJECT: 629 raise 630 target_nc_root = None 631 632 if target_nc_root is None: 633 # We don't bump the error count as Samba produces 634 # these in normal operation creating a lab domain (due 635 # to the way the rename is handled, links to 636 # now-expunged objects will never be fixed to stay 637 # inside the NC 638 self.report("WARNING: no target object found for GUID " 639 "component for link " 640 "%s in object to %s outside our NCs" 641 "%s - %s" % (attrname, dsdb_dn.dn, dn, val)) 642 self.report("Not removing dangling one-way " 643 "left-over link outside our NCs " 644 "(we might be building a renamed/lab domain)") 645 return 0 646 647 if nc_root != target_nc_root: 648 # We don't bump the error count as Samba produces these 649 # in normal operation 650 self.report("WARNING: no target object found for GUID " 651 "component for cross-partition link " 652 "%s in object " 653 "%s - %s" % (attrname, dn, val)) 654 self.report("Not removing dangling one-way " 655 "cross-partition link " 656 "(we might be mid-replication)") 657 return 0 658 659 # Due to our link handling one-way links pointing to 660 # missing objects are plausible. 661 # 662 # We don't bump the error count as Samba produces these 663 # in normal operation 664 self.report("WARNING: no target object found for GUID " 665 "component for DN value %s in object " 666 "%s - %s" % (attrname, dn, val)) 667 self.err_deleted_dn(dn, attrname, val, 668 dsdb_dn, dsdb_dn, True) 669 return 0 670 671 # We bump the error count here, as we should have deleted this 672 self.report("ERROR: no target object found for GUID " 673 "component for link %s in object " 674 "%s - %s" % (attrname, dn, val)) 675 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False) 676 return 1 677 678 def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr): 679 """handle a missing GUID extended DN component""" 680 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val)) 681 controls = ["extended_dn:1:1", "show_recycled:1"] 682 try: 683 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE, 684 attrs=[], controls=controls) 685 except ldb.LdbError as e7: 686 (enum, estr) = e7.args 687 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr)) 688 if enum != ldb.ERR_NO_SUCH_OBJECT: 689 raise 690 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn) 691 return 692 if len(res) == 0: 693 self.report("unable to find object for DN %s" % dsdb_dn.dn) 694 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn) 695 return 696 dsdb_dn.dn = res[0].dn 697 698 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'): 699 self.report("Not fixing %s" % errstr) 700 return 701 m = ldb.Message() 702 m.dn = dn 703 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) 704 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname) 705 706 if self.do_modify(m, ["show_recycled:1"], 707 "Failed to fix %s on attribute %s" % (errstr, attrname)): 708 self.report("Fixed %s on attribute %s" % (errstr, attrname)) 709 710 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr): 711 """handle an incorrect binary DN component""" 712 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val)) 713 714 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'): 715 self.report("Not fixing %s" % errstr) 716 return 717 m = ldb.Message() 718 m.dn = dn 719 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) 720 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname) 721 722 if self.do_modify(m, ["show_recycled:1"], 723 "Failed to fix %s on attribute %s" % (errstr, attrname)): 724 self.report("Fixed %s on attribute %s" % (errstr, attrname)) 725 726 def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn): 727 """handle a DN string being incorrect""" 728 self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val)) 729 dsdb_dn.dn = correct_dn 730 731 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 732 'fix_all_old_dn_string_component_mismatch'): 733 self.report("Not fixing old string component") 734 return 735 m = ldb.Message() 736 m.dn = dn 737 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) 738 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname) 739 if self.do_modify(m, ["show_recycled:1", 740 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME], 741 "Failed to fix old DN string on attribute %s" % (attrname)): 742 self.report("Fixed old DN string on attribute %s" % (attrname)) 743 744 def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type): 745 """handle a DN string being incorrect""" 746 self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val)) 747 dsdb_dn.dn = correct_dn 748 749 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 750 'fix_all_%s_dn_component_mismatch' % mismatch_type): 751 self.report("Not fixing %s component mismatch" % mismatch_type) 752 return 753 m = ldb.Message() 754 m.dn = dn 755 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) 756 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname) 757 if self.do_modify(m, ["show_recycled:1"], 758 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)): 759 self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname)) 760 761 def err_dn_component_missing_target_sid(self, dn, attrname, val, dsdb_dn, target_sid_blob): 762 """handle a DN string being incorrect""" 763 self.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname, dn, val)) 764 765 if len(dsdb_dn.prefix) != 0: 766 self.report("Not fixing missing DN SID on DN+BINARY or DN+STRING") 767 return 768 769 correct_dn = ldb.Dn(self.samdb, dsdb_dn.dn.extended_str()) 770 correct_dn.set_extended_component("SID", target_sid_blob) 771 772 if not self.confirm_all('Change DN to %s?' % correct_dn.extended_str(), 773 'fix_all_SID_dn_component_missing'): 774 self.report("Not fixing missing DN SID component") 775 return 776 777 target_guid_blob = correct_dn.get_extended_component("GUID") 778 guid_sid_dn = ldb.Dn(self.samdb, "") 779 guid_sid_dn.set_extended_component("GUID", target_guid_blob) 780 guid_sid_dn.set_extended_component("SID", target_sid_blob) 781 782 m = ldb.Message() 783 m.dn = dn 784 m['new_value'] = ldb.MessageElement(guid_sid_dn.extended_str(), ldb.FLAG_MOD_ADD, attrname) 785 controls = [ 786 "show_recycled:1", 787 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID 788 ] 789 if self.do_modify(m, controls, 790 "Failed to ADD missing DN SID on attribute %s" % (attrname)): 791 self.report("Fixed missing DN SID on attribute %s" % (attrname)) 792 793 def err_unknown_attribute(self, obj, attrname): 794 '''handle an unknown attribute error''' 795 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn)) 796 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'): 797 self.report("Not removing %s" % attrname) 798 return 799 m = ldb.Message() 800 m.dn = obj.dn 801 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname) 802 if self.do_modify(m, ["relax:0", "show_recycled:1"], 803 "Failed to remove unknown attribute %s" % attrname): 804 self.report("Removed unknown attribute %s" % (attrname)) 805 806 def err_undead_linked_attribute(self, obj, attrname, val): 807 '''handle a link that should not be there on a deleted object''' 808 self.report("ERROR: linked attribute '%s' to '%s' is present on " 809 "deleted object %s" % (attrname, val, obj.dn)) 810 if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'): 811 self.report("Not removing linked attribute %s" % attrname) 812 return 813 m = ldb.Message() 814 m.dn = obj.dn 815 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) 816 817 if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0", 818 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS], 819 "Failed to delete forward link %s" % attrname): 820 self.report("Fixed undead forward link %s" % (attrname)) 821 822 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn): 823 '''handle a missing backlink value''' 824 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn)) 825 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'): 826 self.report("Not fixing missing backlink %s" % backlink_name) 827 return 828 m = ldb.Message() 829 m.dn = target_dn 830 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name) 831 if self.do_modify(m, ["show_recycled:1", "relax:0"], 832 "Failed to fix missing backlink %s" % backlink_name): 833 self.report("Fixed missing backlink %s" % (backlink_name)) 834 835 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn): 836 '''handle a incorrect RMD_FLAGS value''' 837 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS")) 838 self.report("ERROR: incorrect RMD_FLAGS value %u for attribute '%s' in %s for link %s" % (rmd_flags, attrname, obj.dn, revealed_dn.dn.extended_str())) 839 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'): 840 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags) 841 return 842 m = ldb.Message() 843 m.dn = obj.dn 844 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname) 845 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"], 846 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags): 847 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags)) 848 849 def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val, 850 target_dn, forward_attr, forward_syntax, 851 check_duplicates=True): 852 '''handle a orphaned backlink value''' 853 if check_duplicates is True and self.has_duplicate_links(target_dn, forward_attr, forward_syntax): 854 self.report("WARNING: Keep orphaned backlink attribute " + 855 "'%s' in '%s' for link '%s' in '%s'" % ( 856 backlink_attr, obj_dn, forward_attr, target_dn)) 857 return 858 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn)) 859 if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'): 860 self.report("Not removing orphaned backlink %s" % backlink_attr) 861 return 862 m = ldb.Message() 863 m.dn = obj_dn 864 m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr) 865 if self.do_modify(m, ["show_recycled:1", "relax:0"], 866 "Failed to fix orphaned backlink %s" % backlink_attr): 867 self.report("Fixed orphaned backlink %s" % (backlink_attr)) 868 869 def err_recover_forward_links(self, obj, forward_attr, forward_vals): 870 '''handle a duplicate links value''' 871 872 self.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn)) 873 874 if not self.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'): 875 self.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % ( 876 forward_attr, obj.dn)) 877 return 878 m = ldb.Message() 879 m.dn = obj.dn 880 m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr) 881 if self.do_modify(m, ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS], 882 "Failed to fix duplicate links in attribute '%s'" % forward_attr): 883 self.report("Fixed duplicate links in attribute '%s'" % (forward_attr)) 884 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr) 885 assert duplicate_cache_key in self.duplicate_link_cache 886 self.duplicate_link_cache[duplicate_cache_key] = False 887 888 def err_no_fsmoRoleOwner(self, obj): 889 '''handle a missing fSMORoleOwner''' 890 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn)) 891 res = self.samdb.search("", 892 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"]) 893 assert len(res) == 1 894 serviceName = str(res[0]["dsServiceName"][0]) 895 if not self.confirm_all('Seize role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'): 896 self.report("Not Seizing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)) 897 return 898 m = ldb.Message() 899 m.dn = obj.dn 900 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner') 901 if self.do_modify(m, [], 902 "Failed to seize role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)): 903 self.report("Seized role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)) 904 905 def err_missing_parent(self, obj): 906 '''handle a missing parent''' 907 self.report("ERROR: parent object not found for %s" % (obj.dn)) 908 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'): 909 self.report('Not moving object %s into LostAndFound' % (obj.dn)) 910 return 911 912 keep_transaction = False 913 self.samdb.transaction_start() 914 try: 915 nc_root = self.samdb.get_nc_root(obj.dn) 916 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER) 917 new_dn = ldb.Dn(self.samdb, str(obj.dn)) 918 new_dn.remove_base_components(len(new_dn) - 1) 919 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"], 920 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)): 921 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)) 922 923 m = ldb.Message() 924 m.dn = obj.dn 925 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent') 926 927 if self.do_modify(m, [], 928 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)): 929 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)) 930 keep_transaction = True 931 except: 932 self.samdb.transaction_cancel() 933 raise 934 935 if keep_transaction: 936 self.samdb.transaction_commit() 937 else: 938 self.samdb.transaction_cancel() 939 940 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val, controls): 941 '''handle a wrong dn''' 942 943 new_rdn = ldb.Dn(self.samdb, str(new_dn)) 944 new_rdn.remove_base_components(len(new_rdn) - 1) 945 new_parent = new_dn.parent() 946 947 attributes = "" 948 if rdn_val != name_val: 949 attributes += "%s=%r " % (rdn_attr, rdn_val) 950 attributes += "name=%r" % (name_val) 951 952 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn)) 953 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'): 954 self.report("Not renaming %s to %s" % (obj.dn, new_dn)) 955 return 956 957 if self.do_rename(obj.dn, new_rdn, new_parent, controls, 958 "Failed to rename object %s into %s" % (obj.dn, new_dn)): 959 self.report("Renamed %s into %s" % (obj.dn, new_dn)) 960 961 def err_wrong_instancetype(self, obj, calculated_instancetype): 962 '''handle a wrong instanceType''' 963 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype)) 964 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'): 965 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn)) 966 return 967 968 m = ldb.Message() 969 m.dn = obj.dn 970 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType') 971 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA], 972 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)): 973 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)) 974 975 def err_short_userParameters(self, obj, attrname, value): 976 # This is a truncated userParameters due to a pre 4.1 replication bug 977 self.report("ERROR: incorrect userParameters value on object %s. If you have another working DC that does not give this warning, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (obj.dn, self.samdb.get_nc_root(obj.dn))) 978 979 def err_base64_userParameters(self, obj, attrname, value): 980 '''handle a wrong userParameters''' 981 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn)) 982 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'): 983 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn)) 984 return 985 986 m = ldb.Message() 987 m.dn = obj.dn 988 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters') 989 if self.do_modify(m, [], 990 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)): 991 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn)) 992 993 def err_utf8_userParameters(self, obj, attrname, value): 994 '''handle a wrong userParameters''' 995 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn)) 996 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'): 997 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn)) 998 return 999 1000 m = ldb.Message() 1001 m.dn = obj.dn 1002 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'), 1003 ldb.FLAG_MOD_REPLACE, 'userParameters') 1004 if self.do_modify(m, [], 1005 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)): 1006 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)) 1007 1008 def err_doubled_userParameters(self, obj, attrname, value): 1009 '''handle a wrong userParameters''' 1010 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn)) 1011 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'): 1012 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn)) 1013 return 1014 1015 m = ldb.Message() 1016 m.dn = obj.dn 1017 # m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'), 1018 # hmm the above old python2 code doesn't make sense to me and cannot 1019 # work in python3 because a string doesn't have a decode method. 1020 # However in python2 for some unknown reason this double decode 1021 # followed by encode seems to result in what looks like utf8. 1022 # In python2 just .decode('utf-16-le').encode('utf-16-le') does nothing 1023 # but trigger the 'double UTF16 encoded' condition again :/ 1024 # 1025 # In python2 and python3 value.decode('utf-16-le').encode('utf8') seems 1026 # to do the trick and work as expected. 1027 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').encode('utf8'), 1028 ldb.FLAG_MOD_REPLACE, 'userParameters') 1029 1030 if self.do_modify(m, [], 1031 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)): 1032 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)) 1033 1034 def err_odd_userParameters(self, obj, attrname): 1035 # This is a truncated userParameters due to a pre 4.1 replication bug 1036 self.report("ERROR: incorrect userParameters value on object %s (odd length). If you have another working DC that does not give this warning, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (obj.dn, self.samdb.get_nc_root(obj.dn))) 1037 1038 def find_revealed_link(self, dn, attrname, guid): 1039 '''return a revealed link in an object''' 1040 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname], 1041 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"]) 1042 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname) 1043 for val in res[0][attrname]: 1044 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid) 1045 guid2 = dsdb_dn.dn.get_extended_component("GUID") 1046 if guid == guid2: 1047 return dsdb_dn 1048 return None 1049 1050 def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr): 1051 '''check a linked values for duplicate forward links''' 1052 error_count = 0 1053 1054 duplicate_dict = dict() 1055 unique_dict = dict() 1056 1057 # Only forward links can have this problem 1058 if forward_linkID & 1: 1059 # If we got the reverse, skip it 1060 return (error_count, duplicate_dict, unique_dict) 1061 1062 if backlink_attr is None: 1063 return (error_count, duplicate_dict, unique_dict) 1064 1065 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr) 1066 if duplicate_cache_key not in self.duplicate_link_cache: 1067 self.duplicate_link_cache[duplicate_cache_key] = False 1068 1069 for val in obj[forward_attr]: 1070 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), forward_syntax) 1071 1072 # all DNs should have a GUID component 1073 guid = dsdb_dn.dn.get_extended_component("GUID") 1074 if guid is None: 1075 continue 1076 guidstr = str(misc.GUID(guid)) 1077 keystr = guidstr + dsdb_dn.prefix 1078 if keystr not in unique_dict: 1079 unique_dict[keystr] = dsdb_dn 1080 continue 1081 error_count += 1 1082 if keystr not in duplicate_dict: 1083 duplicate_dict[keystr] = dict() 1084 duplicate_dict[keystr]["keep"] = None 1085 duplicate_dict[keystr]["delete"] = list() 1086 1087 # Now check for the highest RMD_VERSION 1088 v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION")) 1089 v2 = int(dsdb_dn.dn.get_extended_component("RMD_VERSION")) 1090 if v1 > v2: 1091 duplicate_dict[keystr]["keep"] = unique_dict[keystr] 1092 duplicate_dict[keystr]["delete"].append(dsdb_dn) 1093 continue 1094 if v1 < v2: 1095 duplicate_dict[keystr]["keep"] = dsdb_dn 1096 duplicate_dict[keystr]["delete"].append(unique_dict[keystr]) 1097 unique_dict[keystr] = dsdb_dn 1098 continue 1099 # Fallback to the highest RMD_LOCAL_USN 1100 u1 = int(unique_dict[keystr].dn.get_extended_component("RMD_LOCAL_USN")) 1101 u2 = int(dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")) 1102 if u1 >= u2: 1103 duplicate_dict[keystr]["keep"] = unique_dict[keystr] 1104 duplicate_dict[keystr]["delete"].append(dsdb_dn) 1105 continue 1106 duplicate_dict[keystr]["keep"] = dsdb_dn 1107 duplicate_dict[keystr]["delete"].append(unique_dict[keystr]) 1108 unique_dict[keystr] = dsdb_dn 1109 1110 if error_count != 0: 1111 self.duplicate_link_cache[duplicate_cache_key] = True 1112 1113 return (error_count, duplicate_dict, unique_dict) 1114 1115 def has_duplicate_links(self, dn, forward_attr, forward_syntax): 1116 '''check a linked values for duplicate forward links''' 1117 error_count = 0 1118 1119 duplicate_cache_key = "%s:%s" % (str(dn), forward_attr) 1120 if duplicate_cache_key in self.duplicate_link_cache: 1121 return self.duplicate_link_cache[duplicate_cache_key] 1122 1123 forward_linkID, backlink_attr = self.get_attr_linkID_and_reverse_name(forward_attr) 1124 1125 attrs = [forward_attr] 1126 controls = ["extended_dn:1:1", "reveal_internals:0"] 1127 1128 # check its the right GUID 1129 try: 1130 res = self.samdb.search(base=str(dn), scope=ldb.SCOPE_BASE, 1131 attrs=attrs, controls=controls) 1132 except ldb.LdbError as e8: 1133 (enum, estr) = e8.args 1134 if enum != ldb.ERR_NO_SUCH_OBJECT: 1135 raise 1136 1137 return False 1138 1139 obj = res[0] 1140 error_count, duplicate_dict, unique_dict = \ 1141 self.check_duplicate_links(obj, forward_attr, forward_syntax, forward_linkID, backlink_attr) 1142 1143 if duplicate_cache_key in self.duplicate_link_cache: 1144 return self.duplicate_link_cache[duplicate_cache_key] 1145 1146 return False 1147 1148 def find_missing_forward_links_from_backlinks(self, obj, 1149 forward_attr, 1150 forward_syntax, 1151 backlink_attr, 1152 forward_unique_dict): 1153 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict''' 1154 missing_forward_links = [] 1155 error_count = 0 1156 1157 if backlink_attr is None: 1158 return (missing_forward_links, error_count) 1159 1160 if forward_syntax != ldb.SYNTAX_DN: 1161 self.report("Not checking for missing forward links for syntax: %s" % 1162 forward_syntax) 1163 return (missing_forward_links, error_count) 1164 1165 if "sortedLinks" in self.compatibleFeatures: 1166 self.report("Not checking for missing forward links because the db " + 1167 "has the sortedLinks feature") 1168 return (missing_forward_links, error_count) 1169 1170 try: 1171 obj_guid = obj['objectGUID'][0] 1172 obj_guid_str = str(ndr_unpack(misc.GUID, obj_guid)) 1173 filter = "(%s=<GUID=%s>)" % (backlink_attr, obj_guid_str) 1174 1175 res = self.samdb.search(expression=filter, 1176 scope=ldb.SCOPE_SUBTREE, attrs=["objectGUID"], 1177 controls=["extended_dn:1:1", 1178 "search_options:1:2", 1179 "paged_results:1:1000"]) 1180 except ldb.LdbError as e9: 1181 (enum, estr) = e9.args 1182 raise 1183 1184 for r in res: 1185 target_dn = dsdb_Dn(self.samdb, r.dn.extended_str(), forward_syntax) 1186 1187 guid = target_dn.dn.get_extended_component("GUID") 1188 guidstr = str(misc.GUID(guid)) 1189 if guidstr in forward_unique_dict: 1190 continue 1191 1192 # A valid forward link looks like this: 1193 # 1194 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>; 1195 # <RMD_ADDTIME=131607546230000000>; 1196 # <RMD_CHANGETIME=131607546230000000>; 1197 # <RMD_FLAGS=0>; 1198 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>; 1199 # <RMD_LOCAL_USN=3765>; 1200 # <RMD_ORIGINATING_USN=3765>; 1201 # <RMD_VERSION=1>; 1202 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>; 1203 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp 1204 # 1205 # Note that versions older than Samba 4.8 create 1206 # links with RMD_VERSION=0. 1207 # 1208 # Try to get the local_usn and time from objectClass 1209 # if possible and fallback to any other one. 1210 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, 1211 obj['replPropertyMetadata'][0]) 1212 for o in repl.ctr.array: 1213 local_usn = o.local_usn 1214 t = o.originating_change_time 1215 if o.attid == drsuapi.DRSUAPI_ATTID_objectClass: 1216 break 1217 1218 # We use a magic invocationID for restoring missing 1219 # forward links to recover from bug #13228. 1220 # This should allow some more future magic to fix the 1221 # problem. 1222 # 1223 # It also means it looses the conflict resolution 1224 # against almost every real invocation, if the 1225 # version is also 0. 1226 originating_invocid = misc.GUID("ffffffff-4700-4700-4700-000000b13228") 1227 originating_usn = 1 1228 1229 rmd_addtime = t 1230 rmd_changetime = t 1231 rmd_flags = 0 1232 rmd_invocid = originating_invocid 1233 rmd_originating_usn = originating_usn 1234 rmd_local_usn = local_usn 1235 rmd_version = 0 1236 1237 target_dn.dn.set_extended_component("RMD_ADDTIME", str(rmd_addtime)) 1238 target_dn.dn.set_extended_component("RMD_CHANGETIME", str(rmd_changetime)) 1239 target_dn.dn.set_extended_component("RMD_FLAGS", str(rmd_flags)) 1240 target_dn.dn.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid)) 1241 target_dn.dn.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn)) 1242 target_dn.dn.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn)) 1243 target_dn.dn.set_extended_component("RMD_VERSION", str(rmd_version)) 1244 1245 error_count += 1 1246 missing_forward_links.append(target_dn) 1247 1248 return (missing_forward_links, error_count) 1249 1250 def check_dn(self, obj, attrname, syntax_oid): 1251 '''check a DN attribute for correctness''' 1252 error_count = 0 1253 obj_guid = obj['objectGUID'][0] 1254 1255 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname) 1256 if reverse_link_name is not None: 1257 reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name) 1258 else: 1259 reverse_syntax_oid = None 1260 1261 is_member_link = attrname in ("member", "memberOf") 1262 if is_member_link and self.quick_membership_checks: 1263 duplicate_dict = {} 1264 else: 1265 error_count, duplicate_dict, unique_dict = \ 1266 self.check_duplicate_links(obj, attrname, syntax_oid, 1267 linkID, reverse_link_name) 1268 1269 if len(duplicate_dict) != 0: 1270 1271 missing_forward_links, missing_error_count = \ 1272 self.find_missing_forward_links_from_backlinks(obj, 1273 attrname, syntax_oid, 1274 reverse_link_name, 1275 unique_dict) 1276 error_count += missing_error_count 1277 1278 forward_links = [dn for dn in unique_dict.values()] 1279 1280 if missing_error_count != 0: 1281 self.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % ( 1282 attrname, obj.dn)) 1283 else: 1284 self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn)) 1285 for m in missing_forward_links: 1286 self.report("Missing link '%s'" % (m)) 1287 if not self.confirm_all("Schedule readding missing forward link for attribute %s" % attrname, 1288 'fix_all_missing_forward_links'): 1289 self.err_orphaned_backlink(m.dn, reverse_link_name, 1290 obj.dn.extended_str(), obj.dn, 1291 attrname, syntax_oid, 1292 check_duplicates=False) 1293 continue 1294 forward_links += [m] 1295 for keystr in duplicate_dict.keys(): 1296 d = duplicate_dict[keystr] 1297 for dd in d["delete"]: 1298 self.report("Duplicate link '%s'" % dd) 1299 self.report("Correct link '%s'" % d["keep"]) 1300 1301 # We now construct the sorted dn values. 1302 # They're sorted by the objectGUID of the target 1303 # See dsdb_Dn.__cmp__() 1304 vals = [str(dn) for dn in sorted(forward_links)] 1305 self.err_recover_forward_links(obj, attrname, vals) 1306 # We should continue with the fixed values 1307 obj[attrname] = ldb.MessageElement(vals, 0, attrname) 1308 1309 for val in obj[attrname]: 1310 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid) 1311 1312 # all DNs should have a GUID component 1313 guid = dsdb_dn.dn.get_extended_component("GUID") 1314 if guid is None: 1315 error_count += 1 1316 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn, 1317 "missing GUID") 1318 continue 1319 1320 guidstr = str(misc.GUID(guid)) 1321 attrs = ['isDeleted', 'replPropertyMetaData'] 1322 1323 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa): 1324 fixing_msDS_HasInstantiatedNCs = True 1325 attrs.append("instanceType") 1326 else: 1327 fixing_msDS_HasInstantiatedNCs = False 1328 1329 if reverse_link_name is not None: 1330 attrs.append(reverse_link_name) 1331 1332 # check its the right GUID 1333 try: 1334 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE, 1335 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1", 1336 "reveal_internals:0" 1337 ]) 1338 except ldb.LdbError as e3: 1339 (enum, estr) = e3.args 1340 if enum != ldb.ERR_NO_SUCH_OBJECT: 1341 raise 1342 1343 # We don't always want to 1344 error_count += self.err_missing_target_dn_or_GUID(obj.dn, 1345 attrname, 1346 val, 1347 dsdb_dn) 1348 continue 1349 1350 if fixing_msDS_HasInstantiatedNCs: 1351 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0]) 1352 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0]) 1353 1354 if str(dsdb_dn) != str(val): 1355 error_count += 1 1356 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN") 1357 continue 1358 1359 # now we have two cases - the source object might or might not be deleted 1360 is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE' 1361 target_is_deleted = 'isDeleted' in res[0] and str(res[0]['isDeleted'][0]).upper() == 'TRUE' 1362 1363 if is_deleted and obj.dn not in self.deleted_objects_containers and linkID: 1364 # A fully deleted object should not have any linked 1365 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone 1366 # Requirements and 3.1.1.5.5.1.3 Recycled-Object 1367 # Requirements) 1368 self.err_undead_linked_attribute(obj, attrname, val) 1369 error_count += 1 1370 continue 1371 elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID: 1372 # the target DN is not allowed to be deleted, unless the target DN is the 1373 # special Deleted Objects container 1374 error_count += 1 1375 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN") 1376 if local_usn: 1377 if 'replPropertyMetaData' in res[0]: 1378 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, 1379 res[0]['replPropertyMetadata'][0]) 1380 found_data = False 1381 for o in repl.ctr.array: 1382 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted: 1383 deleted_usn = o.local_usn 1384 if deleted_usn >= int(local_usn): 1385 # If the object was deleted after the link 1386 # was last modified then, clean it up here 1387 found_data = True 1388 break 1389 1390 if found_data: 1391 self.err_deleted_dn(obj.dn, attrname, 1392 val, dsdb_dn, res[0].dn, True) 1393 continue 1394 1395 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False) 1396 continue 1397 1398 # We should not check for incorrect 1399 # components on deleted links, as these are allowed to 1400 # go stale (we just need the GUID, not the name) 1401 rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS") 1402 rmd_flags = 0 1403 if rmd_blob is not None: 1404 rmd_flags = int(rmd_blob) 1405 1406 # assert the DN matches in string form, where a reverse 1407 # link exists, otherwise (below) offer to fix it as a non-error. 1408 # The string form is essentially only kept for forensics, 1409 # as we always re-resolve by GUID in normal operations. 1410 if not rmd_flags & 1 and reverse_link_name is not None: 1411 if str(res[0].dn) != str(dsdb_dn.dn): 1412 error_count += 1 1413 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn, 1414 res[0].dn, "string") 1415 continue 1416 1417 if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"): 1418 error_count += 1 1419 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn, 1420 res[0].dn, "GUID") 1421 continue 1422 1423 target_sid = res[0].dn.get_extended_component("SID") 1424 link_sid = dsdb_dn.dn.get_extended_component("SID") 1425 if link_sid is None and target_sid is not None: 1426 error_count += 1 1427 self.err_dn_component_missing_target_sid(obj.dn, attrname, val, 1428 dsdb_dn, target_sid) 1429 continue 1430 if link_sid != target_sid: 1431 error_count += 1 1432 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn, 1433 res[0].dn, "SID") 1434 continue 1435 1436 # Only for non-links, not even forward-only links 1437 # (otherwise this breaks repl_meta_data): 1438 # 1439 # Now we have checked the GUID and SID, offer to fix old 1440 # DN strings as a non-error (DNs, not links so no 1441 # backlink). Samba does not maintain this string 1442 # otherwise, so we don't increment error_count. 1443 if reverse_link_name is None: 1444 if linkID == 0 and str(res[0].dn) != str(dsdb_dn.dn): 1445 # Pass in the old/bad DN without the <GUID=...> part, 1446 # otherwise the LDB code will correct it on the way through 1447 # (Note: we still want to preserve the DSDB DN prefix in the 1448 # case of binary DNs) 1449 bad_dn = dsdb_dn.prefix + dsdb_dn.dn.get_linearized() 1450 self.err_dn_string_component_old(obj.dn, attrname, bad_dn, 1451 dsdb_dn, res[0].dn) 1452 continue 1453 1454 if is_member_link and self.quick_membership_checks: 1455 continue 1456 1457 # check the reverse_link is correct if there should be one 1458 match_count = 0 1459 if reverse_link_name in res[0]: 1460 for v in res[0][reverse_link_name]: 1461 v_dn = dsdb_Dn(self.samdb, v.decode('utf8')) 1462 v_guid = v_dn.dn.get_extended_component("GUID") 1463 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS") 1464 v_rmd_flags = 0 1465 if v_blob is not None: 1466 v_rmd_flags = int(v_blob) 1467 if v_rmd_flags & 1: 1468 continue 1469 if v_guid == obj_guid: 1470 match_count += 1 1471 1472 if match_count != 1: 1473 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN: 1474 if not linkID & 1: 1475 # Forward binary multi-valued linked attribute 1476 forward_count = 0 1477 for w in obj[attrname]: 1478 w_guid = dsdb_Dn(self.samdb, w.decode('utf8')).dn.get_extended_component("GUID") 1479 if w_guid == guid: 1480 forward_count += 1 1481 1482 if match_count == forward_count: 1483 continue 1484 expected_count = 0 1485 for v in obj[attrname]: 1486 v_dn = dsdb_Dn(self.samdb, v.decode('utf8')) 1487 v_guid = v_dn.dn.get_extended_component("GUID") 1488 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS") 1489 v_rmd_flags = 0 1490 if v_blob is not None: 1491 v_rmd_flags = int(v_blob) 1492 if v_rmd_flags & 1: 1493 continue 1494 if v_guid == guid: 1495 expected_count += 1 1496 1497 if match_count == expected_count: 1498 continue 1499 1500 diff_count = expected_count - match_count 1501 1502 if linkID & 1: 1503 # If there's a backward link on binary multi-valued linked attribute, 1504 # let the check on the forward link remedy the value. 1505 # UNLESS, there is no forward link detected. 1506 if match_count == 0: 1507 error_count += 1 1508 self.err_orphaned_backlink(obj.dn, attrname, 1509 val, dsdb_dn.dn, 1510 reverse_link_name, 1511 reverse_syntax_oid) 1512 continue 1513 # Only warn here and let the forward link logic fix it. 1514 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % ( 1515 attrname, expected_count, str(obj.dn), 1516 reverse_link_name, match_count, str(dsdb_dn.dn))) 1517 continue 1518 1519 assert not target_is_deleted 1520 1521 self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % ( 1522 attrname, expected_count, str(obj.dn), 1523 reverse_link_name, match_count, str(dsdb_dn.dn))) 1524 1525 # Loop until the difference between the forward and 1526 # the backward links is resolved. 1527 while diff_count != 0: 1528 error_count += 1 1529 if diff_count > 0: 1530 if match_count > 0 or diff_count > 1: 1531 # TODO no method to fix these right now 1532 self.report("ERROR: Can't fix missing " 1533 "multi-valued backlinks on %s" % str(dsdb_dn.dn)) 1534 break 1535 self.err_missing_backlink(obj, attrname, 1536 obj.dn.extended_str(), 1537 reverse_link_name, 1538 dsdb_dn.dn) 1539 diff_count -= 1 1540 else: 1541 self.err_orphaned_backlink(res[0].dn, reverse_link_name, 1542 obj.dn.extended_str(), obj.dn, 1543 attrname, syntax_oid) 1544 diff_count += 1 1545 1546 return error_count 1547 1548 def find_repl_attid(self, repl, attid): 1549 for o in repl.ctr.array: 1550 if o.attid == attid: 1551 return o 1552 1553 return None 1554 1555 def get_originating_time(self, val, attid): 1556 '''Read metadata properties and return the originating time for 1557 a given attributeId. 1558 1559 :return: the originating time or 0 if not found 1560 ''' 1561 1562 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val) 1563 o = self.find_repl_attid(repl, attid) 1564 if o is not None: 1565 return o.originating_change_time 1566 return 0 1567 1568 def process_metadata(self, dn, val): 1569 '''Read metadata properties and list attributes in it. 1570 raises KeyError if the attid is unknown.''' 1571 1572 set_att = set() 1573 wrong_attids = set() 1574 list_attid = [] 1575 in_schema_nc = dn.is_child_of(self.schema_dn) 1576 1577 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val) 1578 1579 for o in repl.ctr.array: 1580 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid) 1581 set_att.add(att.lower()) 1582 list_attid.append(o.attid) 1583 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, 1584 is_schema_nc=in_schema_nc) 1585 if correct_attid != o.attid: 1586 wrong_attids.add(o.attid) 1587 1588 return (set_att, list_attid, wrong_attids) 1589 1590 def fix_metadata(self, obj, attr): 1591 '''re-write replPropertyMetaData elements for a single attribute for a 1592 object. This is used to fix missing replPropertyMetaData elements''' 1593 guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0])) 1594 dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str) 1595 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attr], 1596 controls=["search_options:1:2", 1597 "show_recycled:1"]) 1598 msg = res[0] 1599 nmsg = ldb.Message() 1600 nmsg.dn = dn 1601 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr) 1602 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"], 1603 "Failed to fix metadata for attribute %s" % attr): 1604 self.report("Fixed metadata for attribute %s" % attr) 1605 1606 def ace_get_effective_inherited_type(self, ace): 1607 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY: 1608 return None 1609 1610 check = False 1611 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT: 1612 check = True 1613 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT: 1614 check = True 1615 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT: 1616 check = True 1617 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT: 1618 check = True 1619 1620 if not check: 1621 return None 1622 1623 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT: 1624 return None 1625 1626 return str(ace.object.inherited_type) 1627 1628 def lookup_class_schemaIDGUID(self, cls): 1629 if cls in self.class_schemaIDGUID: 1630 return self.class_schemaIDGUID[cls] 1631 1632 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls 1633 res = self.samdb.search(base=self.schema_dn, 1634 expression=flt, 1635 attrs=["schemaIDGUID"]) 1636 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0])) 1637 1638 self.class_schemaIDGUID[cls] = t 1639 return t 1640 1641 def process_sd(self, dn, obj): 1642 sd_attr = "nTSecurityDescriptor" 1643 sd_val = obj[sd_attr] 1644 1645 sd = ndr_unpack(security.descriptor, sd_val[0]) 1646 1647 is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE' 1648 if is_deleted: 1649 # we don't fix deleted objects 1650 return (sd, None) 1651 1652 sd_clean = security.descriptor() 1653 sd_clean.owner_sid = sd.owner_sid 1654 sd_clean.group_sid = sd.group_sid 1655 sd_clean.type = sd.type 1656 sd_clean.revision = sd.revision 1657 1658 broken = False 1659 last_inherited_type = None 1660 1661 aces = [] 1662 if sd.sacl is not None: 1663 aces = sd.sacl.aces 1664 for i in range(0, len(aces)): 1665 ace = aces[i] 1666 1667 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE: 1668 sd_clean.sacl_add(ace) 1669 continue 1670 1671 t = self.ace_get_effective_inherited_type(ace) 1672 if t is None: 1673 continue 1674 1675 if last_inherited_type is not None: 1676 if t != last_inherited_type: 1677 # if it inherited from more than 1678 # one type it's very likely to be broken 1679 # 1680 # If not the recalculation will calculate 1681 # the same result. 1682 broken = True 1683 continue 1684 1685 last_inherited_type = t 1686 1687 aces = [] 1688 if sd.dacl is not None: 1689 aces = sd.dacl.aces 1690 for i in range(0, len(aces)): 1691 ace = aces[i] 1692 1693 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE: 1694 sd_clean.dacl_add(ace) 1695 continue 1696 1697 t = self.ace_get_effective_inherited_type(ace) 1698 if t is None: 1699 continue 1700 1701 if last_inherited_type is not None: 1702 if t != last_inherited_type: 1703 # if it inherited from more than 1704 # one type it's very likely to be broken 1705 # 1706 # If not the recalculation will calculate 1707 # the same result. 1708 broken = True 1709 continue 1710 1711 last_inherited_type = t 1712 1713 if broken: 1714 return (sd_clean, sd) 1715 1716 if last_inherited_type is None: 1717 # ok 1718 return (sd, None) 1719 1720 cls = None 1721 try: 1722 cls = obj["objectClass"][-1] 1723 except KeyError as e: 1724 pass 1725 1726 if cls is None: 1727 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, 1728 attrs=["isDeleted", "objectClass"], 1729 controls=["show_recycled:1"]) 1730 o = res[0] 1731 is_deleted = 'isDeleted' in o and str(o['isDeleted'][0]).upper() == 'TRUE' 1732 if is_deleted: 1733 # we don't fix deleted objects 1734 return (sd, None) 1735 cls = o["objectClass"][-1] 1736 1737 t = self.lookup_class_schemaIDGUID(cls) 1738 1739 if t != last_inherited_type: 1740 # broken 1741 return (sd_clean, sd) 1742 1743 # ok 1744 return (sd, None) 1745 1746 def err_wrong_sd(self, dn, sd, sd_broken): 1747 '''re-write the SD due to incorrect inherited ACEs''' 1748 sd_attr = "nTSecurityDescriptor" 1749 sd_val = ndr_pack(sd) 1750 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL 1751 1752 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'): 1753 self.report('Not fixing %s on %s\n' % (sd_attr, dn)) 1754 return 1755 1756 nmsg = ldb.Message() 1757 nmsg.dn = dn 1758 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr) 1759 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags], 1760 "Failed to fix attribute %s" % sd_attr): 1761 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn)) 1762 1763 def err_wrong_default_sd(self, dn, sd, sd_old, diff): 1764 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)''' 1765 sd_attr = "nTSecurityDescriptor" 1766 sd_val = ndr_pack(sd) 1767 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL 1768 if sd.owner_sid is not None: 1769 sd_flags |= security.SECINFO_OWNER 1770 if sd.group_sid is not None: 1771 sd_flags |= security.SECINFO_GROUP 1772 1773 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'): 1774 self.report('Not resetting %s on %s\n' % (sd_attr, dn)) 1775 return 1776 1777 m = ldb.Message() 1778 m.dn = dn 1779 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr) 1780 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags], 1781 "Failed to reset attribute %s" % sd_attr): 1782 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn)) 1783 1784 def err_missing_sd_owner(self, dn, sd): 1785 '''re-write the SD due to a missing owner or group''' 1786 sd_attr = "nTSecurityDescriptor" 1787 sd_val = ndr_pack(sd) 1788 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP 1789 1790 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'): 1791 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn)) 1792 return 1793 1794 nmsg = ldb.Message() 1795 nmsg.dn = dn 1796 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr) 1797 1798 # By setting the session_info to admin_session_info and 1799 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP 1800 # flags we cause the descriptor module to set the correct 1801 # owner and group on the SD, replacing the None/NULL values 1802 # for owner_sid and group_sid currently present. 1803 # 1804 # The admin_session_info matches that used in provision, and 1805 # is the best guess we can make for an existing object that 1806 # hasn't had something specifically set. 1807 # 1808 # This is important for the dns related naming contexts. 1809 self.samdb.set_session_info(self.admin_session_info) 1810 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags], 1811 "Failed to fix metadata for attribute %s" % sd_attr): 1812 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn)) 1813 self.samdb.set_session_info(self.system_session_info) 1814 1815 def is_expired_tombstone(self, dn, repl_val): 1816 if self.check_expired_tombstones: 1817 # This is not the default, it's just 1818 # used to keep dbcheck tests work with 1819 # old static provision dumps 1820 return False 1821 1822 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl_val) 1823 1824 isDeleted = self.find_repl_attid(repl, drsuapi.DRSUAPI_ATTID_isDeleted) 1825 1826 delete_time = samba.nttime2unix(isDeleted.originating_change_time) 1827 current_time = time.time() 1828 1829 tombstone_delta = self.tombstoneLifetime * (24 * 60 * 60) 1830 1831 delta = current_time - delete_time 1832 if delta <= tombstone_delta: 1833 return False 1834 1835 self.report("SKIPING: object %s is an expired tombstone" % dn) 1836 self.report("isDeleted: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % ( 1837 isDeleted.attid, 1838 isDeleted.version, 1839 isDeleted.originating_invocation_id, 1840 isDeleted.originating_usn, 1841 isDeleted.local_usn, 1842 time.ctime(samba.nttime2unix(isDeleted.originating_change_time)))) 1843 self.expired_tombstones += 1 1844 return True 1845 1846 def find_changes_after_deletion(self, repl_val): 1847 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl_val) 1848 1849 isDeleted = self.find_repl_attid(repl, drsuapi.DRSUAPI_ATTID_isDeleted) 1850 1851 delete_time = samba.nttime2unix(isDeleted.originating_change_time) 1852 1853 tombstone_delta = self.tombstoneLifetime * (24 * 60 * 60) 1854 1855 found = [] 1856 for o in repl.ctr.array: 1857 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted: 1858 continue 1859 1860 if o.local_usn <= isDeleted.local_usn: 1861 continue 1862 1863 if o.originating_change_time <= isDeleted.originating_change_time: 1864 continue 1865 1866 change_time = samba.nttime2unix(o.originating_change_time) 1867 1868 delta = change_time - delete_time 1869 if delta <= tombstone_delta: 1870 continue 1871 1872 # If the modification happened after the tombstone lifetime 1873 # has passed, we have a bug as the object might be deleted 1874 # already on other DCs and won't be able to replicate 1875 # back 1876 found.append(o) 1877 1878 return found, isDeleted 1879 1880 def has_changes_after_deletion(self, dn, repl_val): 1881 found, isDeleted = self.find_changes_after_deletion(repl_val) 1882 if len(found) == 0: 1883 return False 1884 1885 def report_attid(o): 1886 try: 1887 attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid) 1888 except KeyError: 1889 attname = "<unknown:0x%x08x>" % o.attid 1890 1891 self.report("%s: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % ( 1892 attname, o.attid, o.version, 1893 o.originating_invocation_id, 1894 o.originating_usn, 1895 o.local_usn, 1896 time.ctime(samba.nttime2unix(o.originating_change_time)))) 1897 1898 self.report("ERROR: object %s, has changes after deletion" % dn) 1899 report_attid(isDeleted) 1900 for o in found: 1901 report_attid(o) 1902 1903 return True 1904 1905 def err_changes_after_deletion(self, dn, repl_val): 1906 found, isDeleted = self.find_changes_after_deletion(repl_val) 1907 1908 in_schema_nc = dn.is_child_of(self.schema_dn) 1909 rdn_attr = dn.get_rdn_name() 1910 rdn_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(rdn_attr, 1911 is_schema_nc=in_schema_nc) 1912 1913 unexpected = [] 1914 for o in found: 1915 if o.attid == rdn_attid: 1916 continue 1917 if o.attid == drsuapi.DRSUAPI_ATTID_name: 1918 continue 1919 if o.attid == drsuapi.DRSUAPI_ATTID_lastKnownParent: 1920 continue 1921 try: 1922 attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid) 1923 except KeyError: 1924 attname = "<unknown:0x%x08x>" % o.attid 1925 unexpected.append(attname) 1926 1927 if len(unexpected) > 0: 1928 self.report('Unexpeted attributes: %s' % ",".join(unexpected)) 1929 self.report('Not fixing changes after deletion bug') 1930 return 1931 1932 if not self.confirm_all('Delete broken tombstone object %s deleted %s days ago?' % ( 1933 dn, self.tombstoneLifetime), 'fix_changes_after_deletion_bug'): 1934 self.report('Not fixing changes after deletion bug') 1935 return 1936 1937 if self.do_delete(dn, ["relax:0"], 1938 "Failed to remove DN %s" % dn): 1939 self.report("Removed DN %s" % dn) 1940 1941 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data): 1942 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, 1943 repl_meta_data) 1944 ctr = repl.ctr 1945 found = False 1946 for o in ctr.array: 1947 # Search for a zero invocationID 1948 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"): 1949 continue 1950 1951 found = True 1952 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x, 1953 version %d changed at %s is 00000000-0000-0000-0000-000000000000, 1954 but should be non-zero. Proposed fix is to set to our invocationID (%s).''' 1955 % (dn, o.attid, o.version, 1956 time.ctime(samba.nttime2unix(o.originating_change_time)), 1957 self.samdb.get_invocation_id())) 1958 1959 return found 1960 1961 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data): 1962 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, 1963 repl_meta_data) 1964 ctr = repl.ctr 1965 now = samba.unix2nttime(int(time.time())) 1966 found = False 1967 for o in ctr.array: 1968 # Search for a zero invocationID 1969 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"): 1970 continue 1971 1972 found = True 1973 seq = self.samdb.sequence_number(ldb.SEQ_NEXT) 1974 o.version = o.version + 1 1975 o.originating_change_time = now 1976 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id()) 1977 o.originating_usn = seq 1978 o.local_usn = seq 1979 1980 if found: 1981 replBlob = ndr_pack(repl) 1982 msg = ldb.Message() 1983 msg.dn = dn 1984 1985 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?' 1986 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'): 1987 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn)) 1988 return 1989 1990 nmsg = ldb.Message() 1991 nmsg.dn = dn 1992 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr) 1993 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA, 1994 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"], 1995 "Failed to fix attribute %s" % attr): 1996 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn)) 1997 1998 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data): 1999 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, 2000 repl_meta_data) 2001 ctr = repl.ctr 2002 for o in ctr.array: 2003 # Search for an invalid attid 2004 try: 2005 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid) 2006 except KeyError: 2007 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn)) 2008 return 2009 2010 def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids): 2011 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, 2012 repl_meta_data) 2013 fix = False 2014 2015 set_att = set() 2016 remove_attid = set() 2017 hash_att = {} 2018 2019 in_schema_nc = dn.is_child_of(self.schema_dn) 2020 2021 ctr = repl.ctr 2022 # Sort the array, except for the last element. This strange 2023 # construction, creating a new list, due to bugs in samba's 2024 # array handling in IDL generated objects. 2025 ctr.array = sorted(ctr.array[:], key=lambda o: o.attid) 2026 # Now walk it in reverse, so we see the low (and so incorrect, 2027 # the correct values are above 0x80000000) values first and 2028 # remove the 'second' value we see. 2029 for o in reversed(ctr.array): 2030 print("%s: 0x%08x" % (dn, o.attid)) 2031 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid) 2032 if att.lower() in set_att: 2033 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn)) 2034 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?' 2035 % (attr, dn, o.attid, att, hash_att[att].attid), 2036 'fix_replmetadata_duplicate_attid'): 2037 self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n' 2038 % (o.attid, att, attr, dn)) 2039 return 2040 fix = True 2041 remove_attid.add(o.attid) 2042 # We want to set the metadata for the most recent 2043 # update to have been applied locally, that is the metadata 2044 # matching the (eg string) value in the attribute 2045 if o.local_usn > hash_att[att].local_usn: 2046 # This is always what we would have sent over DRS, 2047 # because the DRS server will have sent the 2048 # msDS-IntID, but with the values from both 2049 # attribute entries. 2050 hash_att[att].version = o.version 2051 hash_att[att].originating_change_time = o.originating_change_time 2052 hash_att[att].originating_invocation_id = o.originating_invocation_id 2053 hash_att[att].originating_usn = o.originating_usn 2054 hash_att[att].local_usn = o.local_usn 2055 2056 # Do not re-add the value to the set or overwrite the hash value 2057 continue 2058 2059 hash_att[att] = o 2060 set_att.add(att.lower()) 2061 2062 # Generate a real list we can sort on properly 2063 new_list = [o for o in ctr.array if o.attid not in remove_attid] 2064 2065 if (len(wrong_attids) > 0): 2066 for o in new_list: 2067 if o.attid in wrong_attids: 2068 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid) 2069 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc) 2070 self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn)) 2071 if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?' 2072 % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'): 2073 self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n' 2074 % (o.attid, correct_attid, att, attr, dn)) 2075 return 2076 fix = True 2077 o.attid = correct_attid 2078 if fix: 2079 # Sort the array, (we changed the value so must re-sort) 2080 new_list[:] = sorted(new_list[:], key=lambda o: o.attid) 2081 2082 # If we did not already need to fix it, then ask about sorting 2083 if not fix: 2084 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn)) 2085 if not self.confirm_all('Fix %s on %s by sorting the attribute list?' 2086 % (attr, dn), 'fix_replmetadata_unsorted_attid'): 2087 self.report('Not fixing %s on %s\n' % (attr, dn)) 2088 return 2089 2090 # The actual sort done is done at the top of the function 2091 2092 ctr.count = len(new_list) 2093 ctr.array = new_list 2094 replBlob = ndr_pack(repl) 2095 2096 nmsg = ldb.Message() 2097 nmsg.dn = dn 2098 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr) 2099 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA, 2100 "local_oid:1.3.6.1.4.1.7165.4.3.14:0", 2101 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"], 2102 "Failed to fix attribute %s" % attr): 2103 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn)) 2104 2105 def is_deleted_deleted_objects(self, obj): 2106 faulty = False 2107 if "description" not in obj: 2108 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn) 2109 faulty = True 2110 if "showInAdvancedViewOnly" not in obj or str(obj['showInAdvancedViewOnly'][0]).upper() == 'FALSE': 2111 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn) 2112 faulty = True 2113 if "objectCategory" not in obj: 2114 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn) 2115 faulty = True 2116 if "isCriticalSystemObject" not in obj or str(obj['isCriticalSystemObject'][0]).upper() == 'FALSE': 2117 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn) 2118 faulty = True 2119 if "isRecycled" in obj: 2120 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn) 2121 faulty = True 2122 if "isDeleted" in obj and str(obj['isDeleted'][0]).upper() == 'FALSE': 2123 self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn) 2124 faulty = True 2125 if "objectClass" not in obj or (len(obj['objectClass']) != 2 or 2126 str(obj['objectClass'][0]) != 'top' or 2127 str(obj['objectClass'][1]) != 'container'): 2128 self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn) 2129 faulty = True 2130 if "systemFlags" not in obj or str(obj['systemFlags'][0]) != '-1946157056': 2131 self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn) 2132 faulty = True 2133 return faulty 2134 2135 def err_deleted_deleted_objects(self, obj): 2136 nmsg = ldb.Message() 2137 nmsg.dn = dn = obj.dn 2138 2139 if "description" not in obj: 2140 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description") 2141 if "showInAdvancedViewOnly" not in obj: 2142 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly") 2143 if "objectCategory" not in obj: 2144 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory") 2145 if "isCriticalSystemObject" not in obj: 2146 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject") 2147 if "isRecycled" in obj: 2148 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled") 2149 2150 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted") 2151 nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags") 2152 nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass") 2153 2154 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?' 2155 % (dn), 'fix_deleted_deleted_objects'): 2156 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn)) 2157 return 2158 2159 if self.do_modify(nmsg, ["relax:0"], 2160 "Failed to fix Deleted Objects container %s" % dn): 2161 self.report("Fixed Deleted Objects container '%s'\n" % (dn)) 2162 2163 def err_replica_locations(self, obj, cross_ref, attr): 2164 nmsg = ldb.Message() 2165 nmsg.dn = cross_ref 2166 target = self.samdb.get_dsServiceName() 2167 2168 if self.samdb.am_rodc(): 2169 self.report('Not fixing %s %s for the RODC' % (attr, obj.dn)) 2170 return 2171 2172 if not self.confirm_all('Add yourself to the replica locations for %s?' 2173 % (obj.dn), 'fix_replica_locations'): 2174 self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn)) 2175 return 2176 2177 nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr) 2178 if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)): 2179 self.report("Fixed %s for %s" % (attr, obj.dn)) 2180 2181 def is_fsmo_role(self, dn): 2182 if dn == self.samdb.domain_dn: 2183 return True 2184 if dn == self.infrastructure_dn: 2185 return True 2186 if dn == self.naming_dn: 2187 return True 2188 if dn == self.schema_dn: 2189 return True 2190 if dn == self.rid_dn: 2191 return True 2192 2193 return False 2194 2195 def calculate_instancetype(self, dn): 2196 instancetype = 0 2197 nc_root = self.samdb.get_nc_root(dn) 2198 if dn == nc_root: 2199 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD 2200 try: 2201 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"]) 2202 except ldb.LdbError as e4: 2203 (enum, estr) = e4.args 2204 if enum != ldb.ERR_NO_SUCH_OBJECT: 2205 raise 2206 else: 2207 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE 2208 if self.write_ncs is not None and str(nc_root) in [str(x) for x in self.write_ncs]: 2209 instancetype |= dsdb.INSTANCE_TYPE_WRITE 2210 2211 return instancetype 2212 2213 def get_wellknown_sd(self, dn): 2214 for [sd_dn, descriptor_fn] in self.wellknown_sds: 2215 if dn == sd_dn: 2216 domain_sid = security.dom_sid(self.samdb.get_domain_sid()) 2217 return ndr_unpack(security.descriptor, 2218 descriptor_fn(domain_sid, 2219 name_map=self.name_map)) 2220 2221 raise KeyError 2222 2223 def check_object(self, dn, attrs=None): 2224 '''check one object''' 2225 if self.verbose: 2226 self.report("Checking object %s" % dn) 2227 if attrs is None: 2228 attrs = ['*'] 2229 else: 2230 # make a local copy to modify 2231 attrs = list(attrs) 2232 if "dn" in map(str.lower, attrs): 2233 attrs.append("name") 2234 if "distinguishedname" in map(str.lower, attrs): 2235 attrs.append("name") 2236 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs): 2237 attrs.append("name") 2238 if 'name' in map(str.lower, attrs): 2239 attrs.append(dn.get_rdn_name()) 2240 attrs.append("isDeleted") 2241 attrs.append("systemFlags") 2242 need_replPropertyMetaData = False 2243 if '*' in attrs: 2244 need_replPropertyMetaData = True 2245 else: 2246 for a in attrs: 2247 linkID, _ = self.get_attr_linkID_and_reverse_name(a) 2248 if linkID == 0: 2249 continue 2250 if linkID & 1: 2251 continue 2252 need_replPropertyMetaData = True 2253 break 2254 if need_replPropertyMetaData: 2255 attrs.append("replPropertyMetaData") 2256 attrs.append("objectGUID") 2257 2258 try: 2259 sd_flags = 0 2260 sd_flags |= security.SECINFO_OWNER 2261 sd_flags |= security.SECINFO_GROUP 2262 sd_flags |= security.SECINFO_DACL 2263 sd_flags |= security.SECINFO_SACL 2264 2265 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, 2266 controls=[ 2267 "extended_dn:1:1", 2268 "show_recycled:1", 2269 "show_deleted:1", 2270 "sd_flags:1:%d" % sd_flags, 2271 "reveal_internals:0", 2272 ], 2273 attrs=attrs) 2274 except ldb.LdbError as e10: 2275 (enum, estr) = e10.args 2276 if enum == ldb.ERR_NO_SUCH_OBJECT: 2277 if self.in_transaction: 2278 self.report("ERROR: Object %s disappeared during check" % dn) 2279 return 1 2280 return 0 2281 raise 2282 if len(res) != 1: 2283 self.report("ERROR: Object %s failed to load during check" % dn) 2284 return 1 2285 obj = res[0] 2286 error_count = 0 2287 set_attrs_from_md = set() 2288 set_attrs_seen = set() 2289 got_objectclass = False 2290 2291 nc_dn = self.samdb.get_nc_root(obj.dn) 2292 try: 2293 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn, 2294 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER) 2295 except KeyError: 2296 # We have no deleted objects DN for schema, and we check for this above for the other 2297 # NCs 2298 deleted_objects_dn = None 2299 2300 object_rdn_attr = None 2301 object_rdn_val = None 2302 name_val = None 2303 isDeleted = False 2304 systemFlags = 0 2305 repl_meta_data_val = None 2306 2307 for attrname in obj: 2308 if str(attrname).lower() == 'isdeleted': 2309 if str(obj[attrname][0]) != "FALSE": 2310 isDeleted = True 2311 2312 if str(attrname).lower() == 'systemflags': 2313 systemFlags = int(obj[attrname][0]) 2314 2315 if str(attrname).lower() == 'replpropertymetadata': 2316 repl_meta_data_val = obj[attrname][0] 2317 2318 if isDeleted and repl_meta_data_val: 2319 if self.has_changes_after_deletion(dn, repl_meta_data_val): 2320 error_count += 1 2321 self.err_changes_after_deletion(dn, repl_meta_data_val) 2322 return error_count 2323 if self.is_expired_tombstone(dn, repl_meta_data_val): 2324 return error_count 2325 2326 for attrname in obj: 2327 if attrname == 'dn' or attrname == "distinguishedName": 2328 continue 2329 2330 if str(attrname).lower() == 'objectclass': 2331 got_objectclass = True 2332 2333 if str(attrname).lower() == "name": 2334 if len(obj[attrname]) != 1: 2335 error_count += 1 2336 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" % 2337 (len(obj[attrname]), attrname, str(obj.dn))) 2338 else: 2339 name_val = str(obj[attrname][0]) 2340 2341 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower(): 2342 object_rdn_attr = attrname 2343 if len(obj[attrname]) != 1: 2344 error_count += 1 2345 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" % 2346 (len(obj[attrname]), attrname, str(obj.dn))) 2347 else: 2348 object_rdn_val = str(obj[attrname][0]) 2349 2350 if str(attrname).lower() == 'replpropertymetadata': 2351 if self.has_replmetadata_zero_invocationid(dn, obj[attrname][0]): 2352 error_count += 1 2353 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname][0]) 2354 # We don't continue, as we may also have other fixes for this attribute 2355 # based on what other attributes we see. 2356 2357 try: 2358 (set_attrs_from_md, list_attid_from_md, wrong_attids) \ 2359 = self.process_metadata(dn, obj[attrname][0]) 2360 except KeyError: 2361 error_count += 1 2362 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname]) 2363 continue 2364 2365 if len(set_attrs_from_md) < len(list_attid_from_md) \ 2366 or len(wrong_attids) > 0 \ 2367 or sorted(list_attid_from_md) != list_attid_from_md: 2368 error_count += 1 2369 self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname][0], wrong_attids) 2370 2371 else: 2372 # Here we check that the first attid is 0 2373 # (objectClass). 2374 if list_attid_from_md[0] != 0: 2375 error_count += 1 2376 self.report("ERROR: Not fixing incorrect initial attributeID in '%s' on '%s', it should be objectClass" % 2377 (attrname, str(dn))) 2378 2379 continue 2380 2381 if str(attrname).lower() == 'ntsecuritydescriptor': 2382 (sd, sd_broken) = self.process_sd(dn, obj) 2383 if sd_broken is not None: 2384 self.err_wrong_sd(dn, sd, sd_broken) 2385 error_count += 1 2386 continue 2387 2388 if sd.owner_sid is None or sd.group_sid is None: 2389 self.err_missing_sd_owner(dn, sd) 2390 error_count += 1 2391 continue 2392 2393 if self.reset_well_known_acls: 2394 try: 2395 well_known_sd = self.get_wellknown_sd(dn) 2396 except KeyError: 2397 continue 2398 2399 current_sd = ndr_unpack(security.descriptor, 2400 obj[attrname][0]) 2401 2402 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid())) 2403 if diff != "": 2404 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff) 2405 error_count += 1 2406 continue 2407 continue 2408 2409 if str(attrname).lower() == 'objectclass': 2410 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname]) 2411 # Do not consider the attribute incorrect if: 2412 # - The sorted (alphabetically) list is the same, inclding case 2413 # - The first and last elements are the same 2414 # 2415 # This avoids triggering an error due to 2416 # non-determinism in the sort routine in (at least) 2417 # 4.3 and earlier, and the fact that any AUX classes 2418 # in these attributes are also not sorted when 2419 # imported from Windows (they are just in the reverse 2420 # order of last set) 2421 if sorted(normalised) != sorted(obj[attrname]) \ 2422 or normalised[0] != obj[attrname][0] \ 2423 or normalised[-1] != obj[attrname][-1]: 2424 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname])) 2425 error_count += 1 2426 continue 2427 2428 if str(attrname).lower() == 'userparameters': 2429 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == b'\x20'[0]: 2430 error_count += 1 2431 self.err_short_userParameters(obj, attrname, obj[attrname]) 2432 continue 2433 2434 elif obj[attrname][0][:16] == b'\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00': 2435 # This is the correct, normal prefix 2436 continue 2437 2438 elif obj[attrname][0][:20] == b'IAAgACAAIAAgACAAIAAg': 2439 # this is the typical prefix from a windows migration 2440 error_count += 1 2441 self.err_base64_userParameters(obj, attrname, obj[attrname]) 2442 continue 2443 2444 #43:00:00:00:74:00:00:00:78 2445 elif obj[attrname][0][1] != b'\x00'[0] and obj[attrname][0][3] != b'\x00'[0] and obj[attrname][0][5] != b'\x00'[0] and obj[attrname][0][7] != b'\x00'[0] and obj[attrname][0][9] != b'\x00'[0]: 2446 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix 2447 error_count += 1 2448 self.err_utf8_userParameters(obj, attrname, obj[attrname]) 2449 continue 2450 2451 elif len(obj[attrname][0]) % 2 != 0: 2452 # This is a value that isn't even in length 2453 error_count += 1 2454 self.err_odd_userParameters(obj, attrname) 2455 continue 2456 2457 elif obj[attrname][0][1] == b'\x00'[0] and obj[attrname][0][2] == b'\x00'[0] and obj[attrname][0][3] == b'\x00'[0] and obj[attrname][0][4] != b'\x00'[0] and obj[attrname][0][5] == b'\x00'[0]: 2458 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server 2459 error_count += 1 2460 self.err_doubled_userParameters(obj, attrname, obj[attrname]) 2461 continue 2462 2463 if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid': 2464 if obj[attrname][0] in self.attribute_or_class_ids: 2465 error_count += 1 2466 self.report('Error: %s %s on %s already exists as an attributeId or governsId' 2467 % (attrname, obj.dn, obj[attrname][0])) 2468 else: 2469 self.attribute_or_class_ids.add(obj[attrname][0]) 2470 2471 # check for empty attributes 2472 for val in obj[attrname]: 2473 if val == b'': 2474 self.err_empty_attribute(dn, attrname) 2475 error_count += 1 2476 continue 2477 2478 # get the syntax oid for the attribute, so we can can have 2479 # special handling for some specific attribute types 2480 try: 2481 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname) 2482 except Exception as msg: 2483 self.err_unknown_attribute(obj, attrname) 2484 error_count += 1 2485 continue 2486 2487 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname) 2488 2489 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname) 2490 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED 2491 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED 2492 and not linkID): 2493 set_attrs_seen.add(str(attrname).lower()) 2494 2495 if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME, 2496 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN]: 2497 # it's some form of DN, do specialised checking on those 2498 error_count += self.check_dn(obj, attrname, syntax_oid) 2499 else: 2500 2501 values = set() 2502 # check for incorrectly normalised attributes 2503 for val in obj[attrname]: 2504 values.add(val) 2505 2506 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val]) 2507 if len(normalised) != 1 or normalised[0] != val: 2508 self.err_normalise_mismatch(dn, attrname, obj[attrname]) 2509 error_count += 1 2510 break 2511 2512 if len(obj[attrname]) != len(values): 2513 self.err_duplicate_values(dn, attrname, obj[attrname], list(values)) 2514 error_count += 1 2515 break 2516 2517 if str(attrname).lower() == "instancetype": 2518 calculated_instancetype = self.calculate_instancetype(dn) 2519 if len(obj["instanceType"]) != 1 or int(obj["instanceType"][0]) != calculated_instancetype: 2520 error_count += 1 2521 self.err_wrong_instancetype(obj, calculated_instancetype) 2522 2523 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)): 2524 error_count += 1 2525 self.err_missing_objectclass(dn) 2526 2527 if ("*" in attrs or "name" in map(str.lower, attrs)): 2528 if name_val is None: 2529 error_count += 1 2530 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn))) 2531 if object_rdn_attr is None: 2532 error_count += 1 2533 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn))) 2534 2535 if name_val is not None: 2536 parent_dn = None 2537 controls = ["show_recycled:1", "relax:0"] 2538 if isDeleted: 2539 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE): 2540 parent_dn = deleted_objects_dn 2541 controls += ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME] 2542 if parent_dn is None: 2543 parent_dn = obj.dn.parent() 2544 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn)) 2545 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val) 2546 2547 if obj.dn == deleted_objects_dn: 2548 expected_dn = obj.dn 2549 2550 if expected_dn != obj.dn: 2551 error_count += 1 2552 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, 2553 object_rdn_val, name_val, controls) 2554 elif obj.dn.get_rdn_value() != object_rdn_val: 2555 error_count += 1 2556 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn))) 2557 2558 show_dn = True 2559 if repl_meta_data_val: 2560 if obj.dn == deleted_objects_dn: 2561 isDeletedAttId = 131120 2562 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container 2563 2564 expectedTimeDo = 2650466015990000000 2565 originating = self.get_originating_time(repl_meta_data_val, isDeletedAttId) 2566 if originating != expectedTimeDo: 2567 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'): 2568 nmsg = ldb.Message() 2569 nmsg.dn = dn 2570 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted") 2571 error_count += 1 2572 self.samdb.modify(nmsg, controls=["provision:0"]) 2573 2574 else: 2575 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn)) 2576 2577 for att in set_attrs_seen.difference(set_attrs_from_md): 2578 if show_dn: 2579 self.report("On object %s" % dn) 2580 show_dn = False 2581 error_count += 1 2582 self.report("ERROR: Attribute %s not present in replication metadata" % att) 2583 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'): 2584 self.report("Not fixing missing replPropertyMetaData element '%s'" % att) 2585 continue 2586 self.fix_metadata(obj, att) 2587 2588 if self.is_fsmo_role(dn): 2589 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)): 2590 self.err_no_fsmoRoleOwner(obj) 2591 error_count += 1 2592 2593 try: 2594 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set: 2595 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, 2596 controls=["show_recycled:1", "show_deleted:1"]) 2597 except ldb.LdbError as e11: 2598 (enum, estr) = e11.args 2599 if enum == ldb.ERR_NO_SUCH_OBJECT: 2600 if isDeleted: 2601 self.report("WARNING: parent object not found for %s" % (obj.dn)) 2602 self.report("Not moving to LostAndFound " 2603 "(tombstone garbage collection in progress?)") 2604 else: 2605 self.err_missing_parent(obj) 2606 error_count += 1 2607 else: 2608 raise 2609 2610 if dn in self.deleted_objects_containers and '*' in attrs: 2611 if self.is_deleted_deleted_objects(obj): 2612 self.err_deleted_deleted_objects(obj) 2613 error_count += 1 2614 2615 for (dns_part, msg) in self.dns_partitions: 2616 if dn == dns_part and 'repsFrom' in obj: 2617 location = "msDS-NC-Replica-Locations" 2618 if self.samdb.am_rodc(): 2619 location = "msDS-NC-RO-Replica-Locations" 2620 2621 if location not in msg: 2622 # There are no replica locations! 2623 self.err_replica_locations(obj, msg.dn, location) 2624 error_count += 1 2625 continue 2626 2627 found = False 2628 for loc in msg[location]: 2629 if str(loc) == self.samdb.get_dsServiceName(): 2630 found = True 2631 if not found: 2632 # This DC is not in the replica locations 2633 self.err_replica_locations(obj, msg.dn, location) 2634 error_count += 1 2635 2636 if dn == self.server_ref_dn: 2637 # Check we have a valid RID Set 2638 if "*" in attrs or "rIDSetReferences" in attrs: 2639 if "rIDSetReferences" not in obj: 2640 # NO RID SET reference 2641 # We are RID master, allocate it. 2642 error_count += 1 2643 2644 if self.is_rid_master: 2645 # Allocate a RID Set 2646 if self.confirm_all('Allocate the missing RID set for RID master?', 2647 'fix_missing_rid_set_master'): 2648 2649 # We don't have auto-transaction logic on 2650 # extended operations, so we have to do it 2651 # here. 2652 2653 self.samdb.transaction_start() 2654 2655 try: 2656 self.samdb.create_own_rid_set() 2657 2658 except: 2659 self.samdb.transaction_cancel() 2660 raise 2661 2662 self.samdb.transaction_commit() 2663 2664 elif not self.samdb.am_rodc(): 2665 self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn) 2666 2667 # Check some details of our own RID Set 2668 if dn == self.rid_set_dn: 2669 res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE, 2670 attrs=["rIDAllocationPool", 2671 "rIDPreviousAllocationPool", 2672 "rIDUsedPool", 2673 "rIDNextRID"]) 2674 if "rIDAllocationPool" not in res[0]: 2675 self.report("No rIDAllocationPool found in %s" % dn) 2676 error_count += 1 2677 else: 2678 next_pool = int(res[0]["rIDAllocationPool"][0]) 2679 2680 high = (0xFFFFFFFF00000000 & next_pool) >> 32 2681 low = 0x00000000FFFFFFFF & next_pool 2682 2683 if high <= low: 2684 self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high)) 2685 error_count += 1 2686 2687 if "rIDNextRID" in res[0]: 2688 next_free_rid = int(res[0]["rIDNextRID"][0]) 2689 else: 2690 next_free_rid = 0 2691 2692 if next_free_rid == 0: 2693 next_free_rid = low 2694 else: 2695 next_free_rid += 1 2696 2697 # Check the remainder of this pool for conflicts. If 2698 # ridalloc_allocate_rid() moves to a new pool, this 2699 # will be above high, so we will stop. 2700 while next_free_rid <= high: 2701 sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid) 2702 try: 2703 res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE, 2704 attrs=[]) 2705 except ldb.LdbError as e: 2706 (enum, estr) = e.args 2707 if enum != ldb.ERR_NO_SUCH_OBJECT: 2708 raise 2709 res = None 2710 if res is not None: 2711 self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn)) 2712 error_count += 1 2713 2714 if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?' 2715 % (sid, dn), 2716 'fix_sid_rid_set_conflict'): 2717 self.samdb.transaction_start() 2718 2719 # This will burn RIDs, which will move 2720 # past the conflict. We then check again 2721 # to see if the new RID conflicts, until 2722 # the end of the current pool. We don't 2723 # look at the next pool to avoid burning 2724 # all RIDs in one go in some strange 2725 # failure case. 2726 try: 2727 while True: 2728 allocated_rid = self.samdb.allocate_rid() 2729 if allocated_rid >= next_free_rid: 2730 next_free_rid = allocated_rid + 1 2731 break 2732 except: 2733 self.samdb.transaction_cancel() 2734 raise 2735 2736 self.samdb.transaction_commit() 2737 else: 2738 break 2739 else: 2740 next_free_rid += 1 2741 2742 return error_count 2743 2744 ################################################################ 2745 # check special @ROOTDSE attributes 2746 def check_rootdse(self): 2747 '''check the @ROOTDSE special object''' 2748 dn = ldb.Dn(self.samdb, '@ROOTDSE') 2749 if self.verbose: 2750 self.report("Checking object %s" % dn) 2751 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE) 2752 if len(res) != 1: 2753 self.report("Object %s disappeared during check" % dn) 2754 return 1 2755 obj = res[0] 2756 error_count = 0 2757 2758 # check that the dsServiceName is in GUID form 2759 if 'dsServiceName' not in obj: 2760 self.report('ERROR: dsServiceName missing in @ROOTDSE') 2761 return error_count + 1 2762 2763 if not str(obj['dsServiceName'][0]).startswith('<GUID='): 2764 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE') 2765 error_count += 1 2766 if not self.confirm('Change dsServiceName to GUID form?'): 2767 return error_count 2768 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0].decode('utf8')), 2769 scope=ldb.SCOPE_BASE, attrs=['objectGUID']) 2770 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0])) 2771 m = ldb.Message() 2772 m.dn = dn 2773 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str, 2774 ldb.FLAG_MOD_REPLACE, 'dsServiceName') 2775 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False): 2776 self.report("Changed dsServiceName to GUID form") 2777 return error_count 2778 2779 ############################################### 2780 # re-index the database 2781 2782 def reindex_database(self): 2783 '''re-index the whole database''' 2784 m = ldb.Message() 2785 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES") 2786 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex') 2787 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex') 2788 return self.do_modify(m, [], 're-indexed database', validate=False) 2789 2790 ############################################### 2791 # reset @MODULES 2792 def reset_modules(self): 2793 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)''' 2794 m = ldb.Message() 2795 m.dn = ldb.Dn(self.samdb, "@MODULES") 2796 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST') 2797 return self.do_modify(m, [], 'reset @MODULES on database', validate=False) 2798