1 # Samba4 AD database checker
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
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.
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.
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/>.
23 from base64 import b64decode, b64encode
24 from samba import dsdb
25 from samba import common
26 from samba.dcerpc import misc
27 from samba.dcerpc import drsuapi
28 from samba.ndr import ndr_unpack, ndr_pack
29 from samba.dcerpc import drsblobs
30 from samba.samdb import dsdb_Dn
31 from samba.dcerpc import security
32 from samba.descriptor import (
34 get_deletedobjects_descriptor,
37 from samba.auth import system_session, admin_session
38 from samba.netcmd import CommandError
39 from samba.netcmd.fsmo import get_fsmo_roleowner
40 from samba.colour import c_RED, c_DARK_YELLOW, c_DARK_CYAN, c_DARK_GREEN
42 def dump_attr_values(vals):
43 """Stringify a value list, using utf-8 if possible (which some tests
44 want), or the python bytes representation otherwise (with leading
45 'b' and escapes like b'\x00').
50 result.append(value.decode('utf-8'))
51 except UnicodeDecodeError:
52 result.append(repr(value))
53 return ','.join(result)
56 class dbcheck(object):
57 """check a SAM database for errors"""
59 def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
60 yes=False, quiet=False, in_transaction=False,
61 quick_membership_checks=False,
62 reset_well_known_acls=False,
63 check_expired_tombstones=False,
66 self.dict_oid_name = None
67 self.samdb_schema = (samdb_schema or samdb)
68 self.verbose = verbose
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
106 self.fix_base64_userparameters = False
107 self.fix_utf8_userparameters = False
108 self.fix_doubled_userparameters = False
109 self.fix_sid_rid_set_conflict = False
110 self.quick_membership_checks = quick_membership_checks
111 self.reset_well_known_acls = reset_well_known_acls
112 self.check_expired_tombstones = check_expired_tombstones
113 self.expired_tombstones = 0
114 self.reset_all_well_known_acls = False
115 self.in_transaction = in_transaction
116 self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
117 self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
118 self.schema_dn = samdb.get_schema_basedn()
119 self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
120 self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
121 self.class_schemaIDGUID = {}
122 self.wellknown_sds = get_wellknown_sds(self.samdb)
123 self.fix_all_missing_objectclass = False
124 self.fix_missing_deleted_objects = False
125 self.fix_replica_locations = False
126 self.fix_missing_rid_set_master = False
127 self.fix_changes_after_deletion_bug = False
130 self.link_id_cache = {}
133 base_dn = "CN=DnsAdmins,%s" % samdb.get_wellknown_dn(
134 samdb.get_default_basedn(),
135 dsdb.DS_GUID_USERS_CONTAINER)
136 res = samdb.search(base=base_dn, scope=ldb.SCOPE_BASE,
138 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
139 self.name_map['DnsAdmins'] = str(dnsadmins_sid)
140 except ldb.LdbError as e5:
141 (enum, estr) = e5.args
142 if enum != ldb.ERR_NO_SUCH_OBJECT:
145 self.system_session_info = system_session()
146 self.admin_session_info = admin_session(None, samdb.get_domain_sid())
148 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
149 if "msDS-hasMasterNCs" in res[0]:
150 self.write_ncs = res[0]["msDS-hasMasterNCs"]
152 # If the Forest Level is less than 2003 then there is no
153 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
154 # no need to merge as all the NCs that are in hasMasterNCs must
155 # also be in msDS-hasMasterNCs (but not the opposite)
156 if "hasMasterNCs" in res[0]:
157 self.write_ncs = res[0]["hasMasterNCs"]
159 self.write_ncs = None
161 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
162 self.deleted_objects_containers = []
163 self.ncs_lacking_deleted_containers = []
164 self.dns_partitions = []
166 self.ncs = res[0]["namingContexts"]
174 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc.decode('utf8')),
175 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
176 self.deleted_objects_containers.append(dn)
178 self.ncs_lacking_deleted_containers.append(ldb.Dn(self.samdb, nc.decode('utf8')))
180 domaindns_zone = 'DC=DomainDnsZones,%s' % self.samdb.get_default_basedn()
181 forestdns_zone = 'DC=ForestDnsZones,%s' % self.samdb.get_root_basedn()
182 domain = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
183 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
184 base=self.samdb.get_partitions_dn(),
185 expression="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone)
187 self.dns_partitions.append((ldb.Dn(self.samdb, forestdns_zone), domain[0]))
189 forest = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
190 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
191 base=self.samdb.get_partitions_dn(),
192 expression="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone)
194 self.dns_partitions.append((ldb.Dn(self.samdb, domaindns_zone), forest[0]))
196 fsmo_dn = ldb.Dn(self.samdb, "CN=RID Manager$,CN=System," + self.samdb.domain_dn())
197 rid_master = get_fsmo_roleowner(self.samdb, fsmo_dn, "rid")
198 if ldb.Dn(self.samdb, self.samdb.get_dsServiceName()) == rid_master:
199 self.is_rid_master = True
201 self.is_rid_master = False
203 # To get your rid set
205 res = self.samdb.search(base=ldb.Dn(self.samdb, self.samdb.get_serverName()),
206 scope=ldb.SCOPE_BASE, attrs=["serverReference"])
207 # 2. Get server reference
208 self.server_ref_dn = ldb.Dn(self.samdb, res[0]['serverReference'][0].decode('utf8'))
211 res = self.samdb.search(base=self.server_ref_dn,
212 scope=ldb.SCOPE_BASE, attrs=['rIDSetReferences'])
213 if "rIDSetReferences" in res[0]:
214 self.rid_set_dn = ldb.Dn(self.samdb, res[0]['rIDSetReferences'][0].decode('utf8'))
216 self.rid_set_dn = None
218 ntds_service_dn = "CN=Directory Service,CN=Windows NT,CN=Services,%s" % \
219 self.samdb.get_config_basedn().get_linearized()
220 res = samdb.search(base=ntds_service_dn,
221 scope=ldb.SCOPE_BASE,
222 expression="(objectClass=nTDSService)",
223 attrs=["tombstoneLifetime"])
224 if "tombstoneLifetime" in res[0]:
225 self.tombstoneLifetime = int(res[0]["tombstoneLifetime"][0])
227 self.tombstoneLifetime = 180
229 self.compatibleFeatures = []
230 self.requiredFeatures = []
233 res = self.samdb.search(scope=ldb.SCOPE_BASE,
235 attrs=["compatibleFeatures",
237 if "compatibleFeatures" in res[0]:
238 self.compatibleFeatures = res[0]["compatibleFeatures"]
239 if "requiredFeatures" in res[0]:
240 self.requiredFeatures = res[0]["requiredFeatures"]
241 except ldb.LdbError as e6:
242 (enum, estr) = e6.args
243 if enum != ldb.ERR_NO_SUCH_OBJECT:
246 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=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))
252 self.unfixable_errors = 0
254 error_count += self.check_deleted_objects_containers()
256 self.attribute_or_class_ids = set()
259 self.dn_set.add(str(object.dn))
260 error_count += self.check_object(object.dn, requested_attrs=attrs)
263 error_count += self.check_rootdse()
265 if self.expired_tombstones > 0:
266 self.report("NOTICE: found %d expired tombstones, "
267 "'samba' will remove them daily, "
268 "'samba-tool domain tombstones expunge' "
269 "would do that immediately." % (
270 self.expired_tombstones))
272 self.report('Checked %u objects (%u errors)' %
273 (len(res), error_count + self.unfixable_errors))
275 if self.unfixable_errors != 0:
276 self.report(f"WARNING: {self.unfixable_errors} "
277 "of these errors cannot be automatically fixed.")
279 if error_count != 0 and not self.fix:
280 self.report("Please use 'samba-tool dbcheck --fix' to fix "
281 f"{error_count} errors")
285 def check_deleted_objects_containers(self):
286 """This function only fixes conflicts on the Deleted Objects
287 containers, not the attributes"""
289 for nc in self.ncs_lacking_deleted_containers:
290 if nc == self.schema_dn:
293 self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc)
294 if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'):
297 dn = ldb.Dn(self.samdb, "CN=Deleted Objects")
302 # If something already exists here, add a conflict
303 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[],
304 controls=["show_deleted:1", "extended_dn:1:1",
305 "show_recycled:1", "reveal_internals:0"])
307 guid = res[0].dn.get_extended_component("GUID")
308 conflict_dn = ldb.Dn(self.samdb,
309 "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid)))
310 conflict_dn.add_base(nc)
312 except ldb.LdbError as e2:
313 (enum, estr) = e2.args
314 if enum == ldb.ERR_NO_SUCH_OBJECT:
317 self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr)
320 if conflict_dn is not None:
322 self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"])
323 except ldb.LdbError as e1:
324 (enum, estr) = e1.args
325 self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr))
328 # Refresh wellKnownObjects links
329 res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE,
330 attrs=['wellKnownObjects'],
331 controls=["show_deleted:1", "extended_dn:0",
332 "show_recycled:1", "reveal_internals:0"])
334 self.report("wellKnownObjects was not found for NC %s" % nc)
337 # Prevent duplicate deleted objects containers just in case
338 wko = res[0]["wellKnownObjects"]
340 proposed_objectguid = None
342 dsdb_dn = dsdb_Dn(self.samdb, o.decode('utf8'), dsdb.DSDB_SYNTAX_BINARY_DN)
343 if self.is_deleted_objects_dn(dsdb_dn):
344 self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o)
345 # We really want to put this back in the same spot
346 # as the original one, so that on replication we
347 # merge, rather than conflict.
348 proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID")
349 listwko.append(str(o))
351 if proposed_objectguid is not None:
352 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid))
354 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
355 listwko.append('%s:%s' % (wko_prefix, dn))
359 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
360 sec_desc = get_deletedobjects_descriptor(domain_sid,
361 name_map=self.name_map)
362 sec_desc_b64 = b64encode(sec_desc).decode('utf8')
364 # Insert a brand new Deleted Objects container
365 self.samdb.add_ldif("""dn: %s
367 objectClass: container
368 description: Container for deleted objects
370 isCriticalSystemObject: TRUE
371 showInAdvancedViewOnly: TRUE
372 nTSecurityDescriptor:: %s
373 systemFlags: -1946157056%s""" % (dn, sec_desc_b64, guid_suffix),
374 controls=["relax:0", "provision:0"])
376 delta = ldb.Message()
377 delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"]))
378 delta["wellKnownObjects"] = ldb.MessageElement(listwko,
379 ldb.FLAG_MOD_REPLACE,
382 # Insert the link to the brand new container
383 if self.do_modify(delta, ["relax:0"],
384 "NC %s lacks Deleted Objects WKGUID" % nc,
386 self.report("Added %s well known guid link" % dn)
388 self.deleted_objects_containers.append(dn)
392 def report(self, msg):
393 '''print a message unless quiet is set'''
397 if msg.startswith('ERROR'):
398 msg = c_RED('ERROR') + msg[5:]
399 elif msg.startswith('WARNING'):
400 msg = c_DARK_YELLOW('WARNING') + msg[7:]
401 elif msg.startswith('INFO'):
402 msg = c_DARK_CYAN('INFO') + msg[4:]
403 elif msg.startswith('NOTICE'):
404 msg = c_DARK_CYAN('NOTICE') + msg[6:]
405 elif msg.startswith('NOTE'):
406 msg = c_DARK_CYAN('NOTE') + msg[4:]
407 elif msg.startswith('SKIPPING'):
408 msg = c_DARK_GREEN('SKIPPING') + msg[8:]
412 def confirm(self, msg, allow_all=False, forced=False):
413 '''confirm a change'''
420 return common.confirm(msg, forced=forced, allow_all=allow_all)
422 ################################################################
423 # a local confirm function with support for 'all'
424 def confirm_all(self, msg, all_attr):
425 '''confirm a change with support for "all" '''
428 if getattr(self, all_attr) == 'NONE':
430 if getattr(self, all_attr) == 'ALL':
436 c = common.confirm(msg, forced=forced, allow_all=True)
438 setattr(self, all_attr, 'ALL')
441 setattr(self, all_attr, 'NONE')
445 def do_delete(self, dn, controls, msg):
446 '''delete dn with optional verbose output'''
448 self.report("delete DN %s" % dn)
450 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
451 self.samdb.delete(dn, controls=controls)
452 except Exception as err:
453 if self.in_transaction:
454 raise CommandError("%s : %s" % (msg, err))
455 self.report("%s : %s" % (msg, err))
459 def do_modify(self, m, controls, msg, validate=True):
460 '''perform a modify with optional verbose output'''
461 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
463 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
464 self.report("controls: %r" % controls)
466 self.samdb.modify(m, controls=controls, validate=validate)
467 except Exception as err:
468 if self.in_transaction:
469 raise CommandError("%s : %s" % (msg, err))
470 self.report("%s : %s" % (msg, err))
474 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
475 '''perform a rename with optional verbose output'''
477 self.report("""dn: %s
481 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
483 to_dn = to_rdn + to_base
484 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
485 self.samdb.rename(from_dn, to_dn, controls=controls)
486 except Exception as err:
487 if self.in_transaction:
488 raise CommandError("%s : %s" % (msg, err))
489 self.report("%s : %s" % (msg, err))
493 def get_attr_linkID_and_reverse_name(self, attrname):
494 if attrname in self.link_id_cache:
495 return self.link_id_cache[attrname]
496 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
498 revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
501 self.link_id_cache[attrname] = (linkID, revname)
502 return linkID, revname
504 def err_empty_attribute(self, dn, attrname):
505 '''fix empty attributes'''
506 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
507 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
508 self.report("Not fixing empty attribute %s" % attrname)
513 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
514 if self.do_modify(m, ["relax:0", "show_recycled:1"],
515 "Failed to remove empty attribute %s" % attrname, validate=False):
516 self.report("Removed empty attribute %s" % attrname)
518 def err_normalise_mismatch(self, dn, attrname, values):
519 '''fix attribute normalisation errors, without altering sort order'''
520 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
523 normalised = self.samdb.dsdb_normalise_attributes(
524 self.samdb_schema, attrname, [val])
525 if len(normalised) != 1:
526 self.report("Unable to normalise value '%s'" % val)
527 mod_list.append((val, ''))
528 elif (normalised[0] != val):
529 self.report("value '%s' should be '%s'" % (val, normalised[0]))
530 mod_list.append((val, normalised[0]))
531 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
532 self.report("Not fixing attribute %s" % attrname)
537 for i in range(0, len(mod_list)):
538 (val, nval) = mod_list[i]
539 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
541 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
544 if self.do_modify(m, ["relax:0", "show_recycled:1"],
545 "Failed to normalise attribute %s" % attrname,
547 self.report("Normalised attribute %s" % attrname)
549 def err_normalise_mismatch_replace(self, dn, attrname, values):
550 '''fix attribute normalisation and/or sort errors'''
551 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
552 if list(normalised) == values:
553 # how we got here is a mystery.
555 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
556 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
557 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
558 self.report("Not fixing attribute '%s'" % attrname)
563 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
565 if self.do_modify(m, ["relax:0", "show_recycled:1"],
566 "Failed to normalise attribute %s" % attrname,
568 self.report("Normalised attribute %s" % attrname)
570 def err_duplicate_values(self, dn, attrname, dup_values, values):
571 '''fix duplicate attribute values'''
572 self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
573 self.report("Values contain a duplicate: [%s]/[%s]!" %
574 (dump_attr_values(dup_values), dump_attr_values(values)))
575 if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
576 self.report("Not fixing attribute '%s'" % attrname)
581 m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
583 if self.do_modify(m, ["relax:0", "show_recycled:1"],
584 "Failed to remove duplicate value on attribute %s" % attrname,
586 self.report("Removed duplicate value on attribute %s" % attrname)
588 def is_deleted_objects_dn(self, dsdb_dn):
589 '''see if a dsdb_Dn is the special Deleted Objects DN'''
590 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
592 def err_missing_objectclass(self, dn):
593 """handle object without objectclass"""
594 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)))
595 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'):
596 self.report("Not deleting object with missing objectclass '%s'" % dn)
598 if self.do_delete(dn, ["relax:0"],
599 "Failed to remove DN %s" % dn):
600 self.report("Removed DN %s" % dn)
602 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False):
603 """handle a DN pointing to a deleted object"""
604 if not remove_plausible:
605 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
606 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
607 if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
608 self.report("Not removing")
611 self.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
612 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
613 if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
614 self.report("Not removing")
619 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
620 if self.do_modify(m, ["show_recycled:1",
621 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
622 "Failed to remove deleted DN attribute %s" % attrname):
623 self.report("Removed deleted DN on attribute %s" % attrname)
625 def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
626 """handle a missing target DN (if specified, GUID form can't be found,
627 and otherwise DN string form can't be found)"""
629 # Don't change anything if the object itself is deleted
630 if str(dn).find('\\0ADEL') != -1:
631 # We don't bump the error count as Samba produces these
632 # in normal operation
633 self.report("WARNING: no target object found for GUID "
634 "component link %s in deleted object "
635 "%s - %s" % (attrname, dn, val))
636 self.report("Not removing dangling one-way "
637 "link on deleted object "
638 "(tombstone garbage collection in progress?)")
641 # check if its a backlink
642 linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
643 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
645 linkID, reverse_link_name \
646 = self.get_attr_linkID_and_reverse_name(attrname)
647 if reverse_link_name is not None:
648 self.report("WARNING: no target object found for GUID "
649 "component for one-way forward link "
651 "%s - %s" % (attrname, dn, val))
652 self.report("Not removing dangling forward link")
655 nc_root = self.samdb.get_nc_root(dn)
657 target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn)
658 except ldb.LdbError as e:
659 (enum, estr) = e.args
660 if enum != ldb.ERR_NO_SUCH_OBJECT:
662 target_nc_root = None
664 if target_nc_root is None:
665 # We don't bump the error count as Samba produces
666 # these in normal operation creating a lab domain (due
667 # to the way the rename is handled, links to
668 # now-expunged objects will never be fixed to stay
670 self.report("WARNING: no target object found for GUID "
671 "component for link "
672 "%s in object to %s outside our NCs"
673 "%s - %s" % (attrname, dsdb_dn.dn, dn, val))
674 self.report("Not removing dangling one-way "
675 "left-over link outside our NCs "
676 "(we might be building a renamed/lab domain)")
679 if nc_root != target_nc_root:
680 # We don't bump the error count as Samba produces these
681 # in normal operation
682 self.report("WARNING: no target object found for GUID "
683 "component for cross-partition link "
685 "%s - %s" % (attrname, dn, val))
686 self.report("Not removing dangling one-way "
687 "cross-partition link "
688 "(we might be mid-replication)")
691 # Due to our link handling one-way links pointing to
692 # missing objects are plausible.
694 # We don't bump the error count as Samba produces these
695 # in normal operation
696 self.report("WARNING: no target object found for GUID "
697 "component for DN value %s in object "
698 "%s - %s" % (attrname, dn, val))
699 self.err_deleted_dn(dn, attrname, val,
700 dsdb_dn, dsdb_dn, True)
703 # We bump the error count here, as we should have deleted this
704 self.report("ERROR: no target object found for GUID "
705 "component for link %s in object "
706 "%s - %s" % (attrname, dn, val))
707 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
710 def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
711 """handle a missing GUID extended DN component"""
712 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
713 controls = ["extended_dn:1:1", "show_recycled:1"]
715 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
716 attrs=[], controls=controls)
717 except ldb.LdbError as e7:
718 (enum, estr) = e7.args
719 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
720 if enum != ldb.ERR_NO_SUCH_OBJECT:
722 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
725 self.report("unable to find object for DN %s" % dsdb_dn.dn)
726 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
728 dsdb_dn.dn = res[0].dn
730 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
731 self.report("Not fixing %s" % errstr)
735 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
736 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
738 if self.do_modify(m, ["show_recycled:1"],
739 "Failed to fix %s on attribute %s" % (errstr, attrname)):
740 self.report("Fixed %s on attribute %s" % (errstr, attrname))
742 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
743 """handle an incorrect binary DN component"""
744 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
746 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
747 self.report("Not fixing %s" % errstr)
751 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
752 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
754 if self.do_modify(m, ["show_recycled:1"],
755 "Failed to fix %s on attribute %s" % (errstr, attrname)):
756 self.report("Fixed %s on attribute %s" % (errstr, attrname))
758 def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn):
759 """handle a DN string being incorrect due to a rename or delete"""
760 self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val))
761 dsdb_dn.dn = correct_dn
763 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
764 'fix_all_old_dn_string_component_mismatch'):
765 self.report("Not fixing old string component")
769 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
770 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
771 if self.do_modify(m, ["show_recycled:1",
772 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME],
773 "Failed to fix old DN string on attribute %s" % (attrname)):
774 self.report("Fixed old DN string on attribute %s" % (attrname))
776 def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
777 """handle a DN string being incorrect"""
778 self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
779 dsdb_dn.dn = correct_dn
781 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
782 'fix_all_%s_dn_component_mismatch' % mismatch_type):
783 self.report("Not fixing %s component mismatch" % mismatch_type)
787 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
788 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
789 if self.do_modify(m, ["show_recycled:1"],
790 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
791 self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
793 def err_dn_component_missing_target_sid(self, dn, attrname, val, dsdb_dn, target_sid_blob):
794 """fix missing <SID=...> on linked attributes"""
795 self.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname, dn, val))
797 if len(dsdb_dn.prefix) != 0:
798 self.report("Not fixing missing DN SID on DN+BINARY or DN+STRING")
801 correct_dn = ldb.Dn(self.samdb, dsdb_dn.dn.extended_str())
802 correct_dn.set_extended_component("SID", target_sid_blob)
804 if not self.confirm_all('Change DN to %s?' % correct_dn.extended_str(),
805 'fix_all_SID_dn_component_missing'):
806 self.report("Not fixing missing DN SID component")
809 target_guid_blob = correct_dn.get_extended_component("GUID")
810 guid_sid_dn = ldb.Dn(self.samdb, "")
811 guid_sid_dn.set_extended_component("GUID", target_guid_blob)
812 guid_sid_dn.set_extended_component("SID", target_sid_blob)
816 m['new_value'] = ldb.MessageElement(guid_sid_dn.extended_str(), ldb.FLAG_MOD_ADD, attrname)
819 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID
821 if self.do_modify(m, controls,
822 "Failed to ADD missing DN SID on attribute %s" % (attrname)):
823 self.report("Fixed missing DN SID on attribute %s" % (attrname))
825 def err_unknown_attribute(self, obj, attrname):
826 '''handle an unknown attribute error'''
827 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
828 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
829 self.report("Not removing %s" % attrname)
833 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
834 if self.do_modify(m, ["relax:0", "show_recycled:1"],
835 "Failed to remove unknown attribute %s" % attrname):
836 self.report("Removed unknown attribute %s" % (attrname))
838 def err_undead_linked_attribute(self, obj, attrname, val):
839 '''handle a link that should not be there on a deleted object'''
840 self.report("ERROR: linked attribute '%s' to '%s' is present on "
841 "deleted object %s" % (attrname, val, obj.dn))
842 if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
843 self.report("Not removing linked attribute %s" % attrname)
847 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
849 if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
850 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
851 "Failed to delete forward link %s" % attrname):
852 self.report("Fixed undead forward link %s" % (attrname))
854 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
855 '''handle a missing backlink value'''
856 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
857 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
858 self.report("Not fixing missing backlink %s" % backlink_name)
862 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
863 if self.do_modify(m, ["show_recycled:1", "relax:0"],
864 "Failed to fix missing backlink %s" % backlink_name):
865 self.report("Fixed missing backlink %s" % (backlink_name))
867 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
868 '''handle a incorrect RMD_FLAGS value'''
869 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
870 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()))
871 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
872 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
876 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
877 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
878 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
879 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
881 def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val,
882 target_dn, forward_attr, forward_syntax,
883 check_duplicates=True):
884 '''handle a orphaned backlink value'''
885 if check_duplicates is True and self.has_duplicate_links(target_dn, forward_attr, forward_syntax):
886 self.report("WARNING: Keep orphaned backlink attribute " +
887 "'%s' in '%s' for link '%s' in '%s'" % (
888 backlink_attr, obj_dn, forward_attr, target_dn))
890 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn))
891 if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'):
892 self.report("Not removing orphaned backlink %s" % backlink_attr)
896 m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr)
897 if self.do_modify(m, ["show_recycled:1", "relax:0"],
898 "Failed to fix orphaned backlink %s" % backlink_attr):
899 self.report("Fixed orphaned backlink %s" % (backlink_attr))
901 def err_recover_forward_links(self, obj, forward_attr, forward_vals):
902 '''handle a duplicate links value'''
904 self.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn))
906 if not self.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'):
907 self.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
908 forward_attr, obj.dn))
912 m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr)
913 if self.do_modify(m, ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS],
914 "Failed to fix duplicate links in attribute '%s'" % forward_attr):
915 self.report("Fixed duplicate links in attribute '%s'" % (forward_attr))
916 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
917 assert duplicate_cache_key in self.duplicate_link_cache
918 self.duplicate_link_cache[duplicate_cache_key] = False
920 def err_no_fsmoRoleOwner(self, obj):
921 '''handle a missing fSMORoleOwner'''
922 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
923 res = self.samdb.search("",
924 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
926 serviceName = str(res[0]["dsServiceName"][0])
927 if not self.confirm_all('Seize role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
928 self.report("Not Seizing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
932 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
933 if self.do_modify(m, [],
934 "Failed to seize role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
935 self.report("Seized role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
937 def err_missing_parent(self, obj):
938 '''handle a missing parent'''
939 self.report("ERROR: parent object not found for %s" % (obj.dn))
940 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
941 self.report('Not moving object %s into LostAndFound' % (obj.dn))
944 keep_transaction = False
945 self.samdb.transaction_start()
947 nc_root = self.samdb.get_nc_root(obj.dn)
948 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
949 new_dn = ldb.Dn(self.samdb, str(obj.dn))
950 new_dn.remove_base_components(len(new_dn) - 1)
951 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
952 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
953 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
957 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
959 if self.do_modify(m, [],
960 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
961 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
962 keep_transaction = True
964 self.samdb.transaction_cancel()
968 self.samdb.transaction_commit()
970 self.samdb.transaction_cancel()
972 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val, controls):
973 '''handle a wrong dn'''
975 new_rdn = ldb.Dn(self.samdb, str(new_dn))
976 new_rdn.remove_base_components(len(new_rdn) - 1)
977 new_parent = new_dn.parent()
980 if rdn_val != name_val:
981 attributes += "%s=%r " % (rdn_attr, rdn_val)
982 attributes += "name=%r" % (name_val)
984 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
985 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
986 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
989 if self.do_rename(obj.dn, new_rdn, new_parent, controls,
990 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
991 self.report("Renamed %s into %s" % (obj.dn, new_dn))
993 def err_wrong_instancetype(self, obj, calculated_instancetype):
994 '''handle a wrong instanceType'''
995 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
996 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
997 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
1002 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
1003 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
1004 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
1005 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
1007 def err_short_userParameters(self, obj, attrname, value):
1008 # This is a truncated userParameters due to a pre 4.1 replication bug
1009 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)))
1011 def err_base64_userParameters(self, obj, attrname, value):
1012 '''handle a userParameters that is wrongly base64 encoded'''
1013 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
1014 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
1015 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
1020 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
1021 if self.do_modify(m, [],
1022 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
1023 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
1025 def err_utf8_userParameters(self, obj, attrname, value):
1026 '''handle a userParameters that is wrongly utf-8 encoded'''
1027 self.report("ERROR: wrongly formatted userParameters on %s, "
1028 "should not be pseudo-UTF8 encoded" % (obj.dn))
1029 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
1030 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
1035 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
1036 ldb.FLAG_MOD_REPLACE, 'userParameters')
1037 if self.do_modify(m, [],
1038 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
1039 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
1041 def err_doubled_userParameters(self, obj, attrname, value):
1042 '''handle a userParameters that has been utf-16 encoded twice'''
1043 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
1044 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
1045 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
1050 # m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
1051 # hmm the above old python2 code doesn't make sense to me and cannot
1052 # work in python3 because a string doesn't have a decode method.
1053 # However in python2 for some unknown reason this double decode
1054 # followed by encode seems to result in what looks like utf8.
1055 # In python2 just .decode('utf-16-le').encode('utf-16-le') does nothing
1056 # but trigger the 'double UTF16 encoded' condition again :/
1058 # In python2 and python3 value.decode('utf-16-le').encode('utf8') seems
1059 # to do the trick and work as expected.
1060 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').encode('utf8'),
1061 ldb.FLAG_MOD_REPLACE, 'userParameters')
1063 if self.do_modify(m, [],
1064 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
1065 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
1067 def err_odd_userParameters(self, obj, attrname):
1068 """Fix a truncated userParameters due to a pre 4.1 replication bug"""
1069 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)))
1071 def find_revealed_link(self, dn, attrname, guid):
1072 '''return a revealed link in an object'''
1073 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
1074 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
1075 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1076 for val in res[0][attrname]:
1077 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
1078 guid2 = dsdb_dn.dn.get_extended_component("GUID")
1083 def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr):
1084 '''check a linked values for duplicate forward links'''
1087 duplicate_dict = dict()
1088 unique_dict = dict()
1090 # Only forward links can have this problem
1091 if forward_linkID & 1:
1092 # If we got the reverse, skip it
1093 return (error_count, duplicate_dict, unique_dict)
1095 if backlink_attr is None:
1096 return (error_count, duplicate_dict, unique_dict)
1098 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
1099 if duplicate_cache_key not in self.duplicate_link_cache:
1100 self.duplicate_link_cache[duplicate_cache_key] = False
1102 for val in obj[forward_attr]:
1103 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), forward_syntax)
1105 # all DNs should have a GUID component
1106 guid = dsdb_dn.dn.get_extended_component("GUID")
1109 guidstr = str(misc.GUID(guid))
1110 keystr = guidstr + dsdb_dn.prefix
1111 if keystr not in unique_dict:
1112 unique_dict[keystr] = dsdb_dn
1115 if keystr not in duplicate_dict:
1116 duplicate_dict[keystr] = dict()
1117 duplicate_dict[keystr]["keep"] = None
1118 duplicate_dict[keystr]["delete"] = list()
1120 # Now check for the highest RMD_VERSION
1121 v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION"))
1122 v2 = int(dsdb_dn.dn.get_extended_component("RMD_VERSION"))
1124 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
1125 duplicate_dict[keystr]["delete"].append(dsdb_dn)
1128 duplicate_dict[keystr]["keep"] = dsdb_dn
1129 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
1130 unique_dict[keystr] = dsdb_dn
1132 # Fallback to the highest RMD_LOCAL_USN
1133 u1 = int(unique_dict[keystr].dn.get_extended_component("RMD_LOCAL_USN"))
1134 u2 = int(dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN"))
1136 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
1137 duplicate_dict[keystr]["delete"].append(dsdb_dn)
1139 duplicate_dict[keystr]["keep"] = dsdb_dn
1140 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
1141 unique_dict[keystr] = dsdb_dn
1143 if error_count != 0:
1144 self.duplicate_link_cache[duplicate_cache_key] = True
1146 return (error_count, duplicate_dict, unique_dict)
1148 def has_duplicate_links(self, dn, forward_attr, forward_syntax):
1149 '''check a linked values for duplicate forward links'''
1152 duplicate_cache_key = "%s:%s" % (str(dn), forward_attr)
1153 if duplicate_cache_key in self.duplicate_link_cache:
1154 return self.duplicate_link_cache[duplicate_cache_key]
1156 forward_linkID, backlink_attr = self.get_attr_linkID_and_reverse_name(forward_attr)
1158 attrs = [forward_attr]
1159 controls = ["extended_dn:1:1", "reveal_internals:0"]
1161 # check its the right GUID
1163 res = self.samdb.search(base=str(dn), scope=ldb.SCOPE_BASE,
1164 attrs=attrs, controls=controls)
1165 except ldb.LdbError as e8:
1166 (enum, estr) = e8.args
1167 if enum != ldb.ERR_NO_SUCH_OBJECT:
1173 error_count, duplicate_dict, unique_dict = \
1174 self.check_duplicate_links(obj, forward_attr, forward_syntax, forward_linkID, backlink_attr)
1176 if duplicate_cache_key in self.duplicate_link_cache:
1177 return self.duplicate_link_cache[duplicate_cache_key]
1181 def find_missing_forward_links_from_backlinks(self, obj,
1185 forward_unique_dict):
1186 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1187 missing_forward_links = []
1190 if backlink_attr is None:
1191 return (missing_forward_links, error_count)
1193 if forward_syntax != ldb.SYNTAX_DN:
1194 self.report("Not checking for missing forward links for syntax: %s" %
1196 return (missing_forward_links, error_count)
1198 if "sortedLinks" in self.compatibleFeatures:
1199 self.report("Not checking for missing forward links because the db " +
1200 "has the sortedLinks feature")
1201 return (missing_forward_links, error_count)
1204 obj_guid = obj['objectGUID'][0]
1205 obj_guid_str = str(ndr_unpack(misc.GUID, obj_guid))
1206 filter = "(%s=<GUID=%s>)" % (backlink_attr, obj_guid_str)
1208 res = self.samdb.search(expression=filter,
1209 scope=ldb.SCOPE_SUBTREE, attrs=["objectGUID"],
1210 controls=["extended_dn:1:1",
1211 "search_options:1:2",
1212 "paged_results:1:1000"])
1213 except ldb.LdbError as e9:
1214 (enum, estr) = e9.args
1218 target_dn = dsdb_Dn(self.samdb, r.dn.extended_str(), forward_syntax)
1220 guid = target_dn.dn.get_extended_component("GUID")
1221 guidstr = str(misc.GUID(guid))
1222 if guidstr in forward_unique_dict:
1225 # A valid forward link looks like this:
1227 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1228 # <RMD_ADDTIME=131607546230000000>;
1229 # <RMD_CHANGETIME=131607546230000000>;
1231 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1232 # <RMD_LOCAL_USN=3765>;
1233 # <RMD_ORIGINATING_USN=3765>;
1235 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1236 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1238 # Note that versions older than Samba 4.8 create
1239 # links with RMD_VERSION=0.
1241 # Try to get the local_usn and time from objectClass
1242 # if possible and fallback to any other one.
1243 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1244 obj['replPropertyMetadata'][0])
1245 for o in repl.ctr.array:
1246 local_usn = o.local_usn
1247 t = o.originating_change_time
1248 if o.attid == drsuapi.DRSUAPI_ATTID_objectClass:
1251 # We use a magic invocationID for restoring missing
1252 # forward links to recover from bug #13228.
1253 # This should allow some more future magic to fix the
1256 # It also means it looses the conflict resolution
1257 # against almost every real invocation, if the
1258 # version is also 0.
1259 originating_invocid = misc.GUID("ffffffff-4700-4700-4700-000000b13228")
1265 rmd_invocid = originating_invocid
1266 rmd_originating_usn = originating_usn
1267 rmd_local_usn = local_usn
1270 target_dn.dn.set_extended_component("RMD_ADDTIME", str(rmd_addtime))
1271 target_dn.dn.set_extended_component("RMD_CHANGETIME", str(rmd_changetime))
1272 target_dn.dn.set_extended_component("RMD_FLAGS", str(rmd_flags))
1273 target_dn.dn.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid))
1274 target_dn.dn.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn))
1275 target_dn.dn.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn))
1276 target_dn.dn.set_extended_component("RMD_VERSION", str(rmd_version))
1279 missing_forward_links.append(target_dn)
1281 return (missing_forward_links, error_count)
1283 def check_dn(self, obj, attrname, syntax_oid):
1284 '''check a DN attribute for correctness'''
1286 obj_guid = obj['objectGUID'][0]
1288 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1289 if reverse_link_name is not None:
1290 reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
1292 reverse_syntax_oid = None
1294 is_member_link = attrname in ("member", "memberOf")
1295 if is_member_link and self.quick_membership_checks:
1298 error_count, duplicate_dict, unique_dict = \
1299 self.check_duplicate_links(obj, attrname, syntax_oid,
1300 linkID, reverse_link_name)
1302 if len(duplicate_dict) != 0:
1304 missing_forward_links, missing_error_count = \
1305 self.find_missing_forward_links_from_backlinks(obj,
1306 attrname, syntax_oid,
1309 error_count += missing_error_count
1311 forward_links = [dn for dn in unique_dict.values()]
1313 if missing_error_count != 0:
1314 self.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1317 self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
1318 for m in missing_forward_links:
1319 self.report("Missing link '%s'" % (m))
1320 if not self.confirm_all("Schedule readding missing forward link for attribute %s" % attrname,
1321 'fix_all_missing_forward_links'):
1322 self.err_orphaned_backlink(m.dn, reverse_link_name,
1323 obj.dn.extended_str(), obj.dn,
1324 attrname, syntax_oid,
1325 check_duplicates=False)
1327 forward_links += [m]
1328 for keystr in duplicate_dict.keys():
1329 d = duplicate_dict[keystr]
1330 for dd in d["delete"]:
1331 self.report("Duplicate link '%s'" % dd)
1332 self.report("Correct link '%s'" % d["keep"])
1334 # We now construct the sorted dn values.
1335 # They're sorted by the objectGUID of the target
1336 # See dsdb_Dn.__cmp__()
1337 vals = [str(dn) for dn in sorted(forward_links)]
1338 self.err_recover_forward_links(obj, attrname, vals)
1339 # We should continue with the fixed values
1340 obj[attrname] = ldb.MessageElement(vals, 0, attrname)
1342 for val in obj[attrname]:
1343 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
1345 # all DNs should have a GUID component
1346 guid = dsdb_dn.dn.get_extended_component("GUID")
1349 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
1353 guidstr = str(misc.GUID(guid))
1354 attrs = ['isDeleted', 'replPropertyMetaData']
1356 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
1357 fixing_msDS_HasInstantiatedNCs = True
1358 attrs.append("instanceType")
1360 fixing_msDS_HasInstantiatedNCs = False
1362 if reverse_link_name is not None:
1363 attrs.append(reverse_link_name)
1365 # check its the right GUID
1367 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
1368 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
1369 "reveal_internals:0"
1371 except ldb.LdbError as e3:
1372 (enum, estr) = e3.args
1373 if enum != ldb.ERR_NO_SUCH_OBJECT:
1376 # We don't always want to
1377 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
1383 if fixing_msDS_HasInstantiatedNCs:
1384 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
1385 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
1387 if str(dsdb_dn) != str(val):
1389 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
1392 # now we have two cases - the source object might or might not be deleted
1393 is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE'
1394 target_is_deleted = 'isDeleted' in res[0] and str(res[0]['isDeleted'][0]).upper() == 'TRUE'
1396 if is_deleted and obj.dn not in self.deleted_objects_containers and linkID:
1397 # A fully deleted object should not have any linked
1398 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1399 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1401 self.err_undead_linked_attribute(obj, attrname, val)
1404 elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
1405 # the target DN is not allowed to be deleted, unless the target DN is the
1406 # special Deleted Objects container
1408 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
1410 if 'replPropertyMetaData' in res[0]:
1411 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1412 res[0]['replPropertyMetadata'][0])
1414 for o in repl.ctr.array:
1415 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1416 deleted_usn = o.local_usn
1417 if deleted_usn >= int(local_usn):
1418 # If the object was deleted after the link
1419 # was last modified then, clean it up here
1424 self.err_deleted_dn(obj.dn, attrname,
1425 val, dsdb_dn, res[0].dn, True)
1428 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
1431 # We should not check for incorrect
1432 # components on deleted links, as these are allowed to
1433 # go stale (we just need the GUID, not the name)
1434 rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
1436 if rmd_blob is not None:
1437 rmd_flags = int(rmd_blob)
1439 # assert the DN matches in string form, where a reverse
1440 # link exists, otherwise (below) offer to fix it as a non-error.
1441 # The string form is essentially only kept for forensics,
1442 # as we always re-resolve by GUID in normal operations.
1443 if not rmd_flags & 1 and reverse_link_name is not None:
1444 if str(res[0].dn) != str(dsdb_dn.dn):
1446 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1447 res[0].dn, "string")
1450 if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
1452 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1456 target_sid = res[0].dn.get_extended_component("SID")
1457 link_sid = dsdb_dn.dn.get_extended_component("SID")
1458 if link_sid is None and target_sid is not None:
1460 self.err_dn_component_missing_target_sid(obj.dn, attrname, val,
1461 dsdb_dn, target_sid)
1463 if link_sid != target_sid:
1465 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1469 # Only for non-links, not even forward-only links
1470 # (otherwise this breaks repl_meta_data):
1472 # Now we have checked the GUID and SID, offer to fix old
1473 # DN strings as a non-error (DNs, not links so no
1474 # backlink). Samba does not maintain this string
1475 # otherwise, so we don't increment error_count.
1476 if reverse_link_name is None:
1477 if linkID == 0 and str(res[0].dn) != str(dsdb_dn.dn):
1478 # Pass in the old/bad DN without the <GUID=...> part,
1479 # otherwise the LDB code will correct it on the way through
1480 # (Note: we still want to preserve the DSDB DN prefix in the
1481 # case of binary DNs)
1482 bad_dn = dsdb_dn.prefix + dsdb_dn.dn.get_linearized()
1483 self.err_dn_string_component_old(obj.dn, attrname, bad_dn,
1487 if is_member_link and self.quick_membership_checks:
1490 # check the reverse_link is correct if there should be one
1492 if reverse_link_name in res[0]:
1493 for v in res[0][reverse_link_name]:
1494 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1495 v_guid = v_dn.dn.get_extended_component("GUID")
1496 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1498 if v_blob is not None:
1499 v_rmd_flags = int(v_blob)
1502 if v_guid == obj_guid:
1505 if match_count != 1:
1506 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1508 # Forward binary multi-valued linked attribute
1510 for w in obj[attrname]:
1511 w_guid = dsdb_Dn(self.samdb, w.decode('utf8')).dn.get_extended_component("GUID")
1515 if match_count == forward_count:
1518 for v in obj[attrname]:
1519 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1520 v_guid = v_dn.dn.get_extended_component("GUID")
1521 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1523 if v_blob is not None:
1524 v_rmd_flags = int(v_blob)
1530 if match_count == expected_count:
1533 diff_count = expected_count - match_count
1536 # If there's a backward link on binary multi-valued linked attribute,
1537 # let the check on the forward link remedy the value.
1538 # UNLESS, there is no forward link detected.
1539 if match_count == 0:
1541 self.err_orphaned_backlink(obj.dn, attrname,
1546 # Only warn here and let the forward link logic fix it.
1547 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1548 attrname, expected_count, str(obj.dn),
1549 reverse_link_name, match_count, str(dsdb_dn.dn)))
1552 assert not target_is_deleted
1554 self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1555 attrname, expected_count, str(obj.dn),
1556 reverse_link_name, match_count, str(dsdb_dn.dn)))
1558 # Loop until the difference between the forward and
1559 # the backward links is resolved.
1560 while diff_count != 0:
1563 if match_count > 0 or diff_count > 1:
1564 # TODO no method to fix these right now
1565 self.report("ERROR: Can't fix missing "
1566 "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1568 self.err_missing_backlink(obj, attrname,
1569 obj.dn.extended_str(),
1574 self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1575 obj.dn.extended_str(), obj.dn,
1576 attrname, syntax_oid)
1581 def find_repl_attid(self, repl, attid):
1582 for o in repl.ctr.array:
1583 if o.attid == attid:
1588 def get_originating_time(self, val, attid):
1589 '''Read metadata properties and return the originating time for
1590 a given attributeId.
1592 :return: the originating time or 0 if not found
1595 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1596 o = self.find_repl_attid(repl, attid)
1598 return o.originating_change_time
1601 def process_metadata(self, dn, val):
1602 '''Read metadata properties and list attributes in it.
1603 raises KeyError if the attid is unknown.'''
1606 wrong_attids = set()
1608 in_schema_nc = dn.is_child_of(self.schema_dn)
1610 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1612 for o in repl.ctr.array:
1613 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1614 set_att.add(att.lower())
1615 list_attid.append(o.attid)
1616 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1617 is_schema_nc=in_schema_nc)
1618 if correct_attid != o.attid:
1619 wrong_attids.add(o.attid)
1621 return (set_att, list_attid, wrong_attids)
1623 def fix_metadata(self, obj, attr):
1624 '''re-write replPropertyMetaData elements for a single attribute for a
1625 object. This is used to fix missing replPropertyMetaData elements'''
1626 guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1627 dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1628 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attr],
1629 controls=["search_options:1:2",
1632 nmsg = ldb.Message()
1634 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1635 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1636 "Failed to fix metadata for attribute %s" % attr):
1637 self.report("Fixed metadata for attribute %s" % attr)
1639 def ace_get_effective_inherited_type(self, ace):
1640 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1644 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1646 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1648 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1650 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1656 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1659 return str(ace.object.inherited_type)
1661 def lookup_class_schemaIDGUID(self, cls):
1662 if cls in self.class_schemaIDGUID:
1663 return self.class_schemaIDGUID[cls]
1665 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1666 res = self.samdb.search(base=self.schema_dn,
1668 attrs=["schemaIDGUID"])
1669 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1671 self.class_schemaIDGUID[cls] = t
1674 def process_sd(self, dn, obj):
1675 sd_attr = "nTSecurityDescriptor"
1676 sd_val = obj[sd_attr]
1678 sd = ndr_unpack(security.descriptor, sd_val[0])
1680 is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE'
1682 # we don't fix deleted objects
1685 sd_clean = security.descriptor()
1686 sd_clean.owner_sid = sd.owner_sid
1687 sd_clean.group_sid = sd.group_sid
1688 sd_clean.type = sd.type
1689 sd_clean.revision = sd.revision
1692 last_inherited_type = None
1695 if sd.sacl is not None:
1697 for i in range(0, len(aces)):
1700 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1701 sd_clean.sacl_add(ace)
1704 t = self.ace_get_effective_inherited_type(ace)
1708 if last_inherited_type is not None:
1709 if t != last_inherited_type:
1710 # if it inherited from more than
1711 # one type it's very likely to be broken
1713 # If not the recalculation will calculate
1718 last_inherited_type = t
1721 if sd.dacl is not None:
1723 for i in range(0, len(aces)):
1726 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1727 sd_clean.dacl_add(ace)
1730 t = self.ace_get_effective_inherited_type(ace)
1734 if last_inherited_type is not None:
1735 if t != last_inherited_type:
1736 # if it inherited from more than
1737 # one type it's very likely to be broken
1739 # If not the recalculation will calculate
1744 last_inherited_type = t
1747 return (sd_clean, sd)
1749 if last_inherited_type is None:
1755 cls = obj["objectClass"][-1]
1756 except KeyError as e:
1760 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1761 attrs=["isDeleted", "objectClass"],
1762 controls=["show_recycled:1"])
1764 is_deleted = 'isDeleted' in o and str(o['isDeleted'][0]).upper() == 'TRUE'
1766 # we don't fix deleted objects
1768 cls = o["objectClass"][-1]
1770 t = self.lookup_class_schemaIDGUID(cls)
1772 if t != last_inherited_type:
1774 return (sd_clean, sd)
1779 def err_wrong_sd(self, dn, sd, sd_broken):
1780 '''re-write the SD due to incorrect inherited ACEs'''
1781 sd_attr = "nTSecurityDescriptor"
1782 sd_val = ndr_pack(sd)
1783 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1785 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1786 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1789 nmsg = ldb.Message()
1791 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1792 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1793 "Failed to fix attribute %s" % sd_attr):
1794 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1796 def err_wrong_default_sd(self, dn, sd, diff):
1797 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1798 sd_attr = "nTSecurityDescriptor"
1799 sd_val = ndr_pack(sd)
1800 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1801 if sd.owner_sid is not None:
1802 sd_flags |= security.SECINFO_OWNER
1803 if sd.group_sid is not None:
1804 sd_flags |= security.SECINFO_GROUP
1806 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1807 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1812 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1813 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1814 "Failed to reset attribute %s" % sd_attr):
1815 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1817 def err_missing_sd_owner(self, dn, sd):
1818 '''re-write the SD due to a missing owner or group'''
1819 sd_attr = "nTSecurityDescriptor"
1820 sd_val = ndr_pack(sd)
1821 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1823 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1824 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1827 nmsg = ldb.Message()
1829 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1831 # By setting the session_info to admin_session_info and
1832 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1833 # flags we cause the descriptor module to set the correct
1834 # owner and group on the SD, replacing the None/NULL values
1835 # for owner_sid and group_sid currently present.
1837 # The admin_session_info matches that used in provision, and
1838 # is the best guess we can make for an existing object that
1839 # hasn't had something specifically set.
1841 # This is important for the dns related naming contexts.
1842 self.samdb.set_session_info(self.admin_session_info)
1843 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1844 "Failed to fix metadata for attribute %s" % sd_attr):
1845 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1846 self.samdb.set_session_info(self.system_session_info)
1848 def is_expired_tombstone(self, dn, repl_val):
1849 if self.check_expired_tombstones:
1850 # This is not the default, it's just
1851 # used to keep dbcheck tests work with
1852 # old static provision dumps
1855 if dn in self.deleted_objects_containers:
1856 # The Deleted Objects container will look like an expired
1860 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl_val)
1862 isDeleted = self.find_repl_attid(repl, drsuapi.DRSUAPI_ATTID_isDeleted)
1864 delete_time = samba.nttime2unix(isDeleted.originating_change_time)
1865 current_time = time.time()
1867 tombstone_delta = self.tombstoneLifetime * (24 * 60 * 60)
1869 delta = current_time - delete_time
1870 if delta <= tombstone_delta:
1873 expunge_time = delete_time + tombstone_delta
1875 delta_days = delta / (24 * 60 * 60)
1878 self.report("SKIPPING additional checks on object "
1879 "%s which very recently "
1880 "became an expired tombstone (normal)" % dn)
1881 self.report("INFO: it is expected this will be expunged "
1882 "by the next daily task some time after %s, "
1884 % (time.ctime(expunge_time), delta // (60 * 60)))
1886 self.report("SKIPPING: object %s is an expired tombstone" % dn)
1887 self.report("INFO: it was expected this object would have "
1888 "been expunged soon after"
1890 % (time.ctime(expunge_time), delta_days))
1892 self.report("isDeleted: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1895 isDeleted.originating_invocation_id,
1896 isDeleted.originating_usn,
1897 isDeleted.local_usn,
1898 time.ctime(samba.nttime2unix(isDeleted.originating_change_time))))
1899 self.expired_tombstones += 1
1902 def find_changes_after_deletion(self, repl_val):
1903 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl_val)
1905 isDeleted = self.find_repl_attid(repl, drsuapi.DRSUAPI_ATTID_isDeleted)
1907 delete_time = samba.nttime2unix(isDeleted.originating_change_time)
1909 tombstone_delta = self.tombstoneLifetime * (24 * 60 * 60)
1912 for o in repl.ctr.array:
1913 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1916 if o.local_usn <= isDeleted.local_usn:
1919 if o.originating_change_time <= isDeleted.originating_change_time:
1922 change_time = samba.nttime2unix(o.originating_change_time)
1924 delta = change_time - delete_time
1925 if delta <= tombstone_delta:
1928 # If the modification happened after the tombstone lifetime
1929 # has passed, we have a bug as the object might be deleted
1930 # already on other DCs and won't be able to replicate
1934 return found, isDeleted
1936 def has_changes_after_deletion(self, dn, repl_val):
1937 found, isDeleted = self.find_changes_after_deletion(repl_val)
1941 def report_attid(o):
1943 attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1945 attname = "<unknown:0x%x08x>" % o.attid
1947 self.report("%s: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1948 attname, o.attid, o.version,
1949 o.originating_invocation_id,
1952 time.ctime(samba.nttime2unix(o.originating_change_time))))
1954 self.report("ERROR: object %s, has changes after deletion" % dn)
1955 report_attid(isDeleted)
1961 def err_changes_after_deletion(self, dn, repl_val):
1962 found, isDeleted = self.find_changes_after_deletion(repl_val)
1964 in_schema_nc = dn.is_child_of(self.schema_dn)
1965 rdn_attr = dn.get_rdn_name()
1966 rdn_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(rdn_attr,
1967 is_schema_nc=in_schema_nc)
1971 if o.attid == rdn_attid:
1973 if o.attid == drsuapi.DRSUAPI_ATTID_name:
1975 if o.attid == drsuapi.DRSUAPI_ATTID_lastKnownParent:
1978 attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1980 attname = "<unknown:0x%x08x>" % o.attid
1981 unexpected.append(attname)
1983 if len(unexpected) > 0:
1984 self.report('Unexpeted attributes: %s' % ",".join(unexpected))
1985 self.report('Not fixing changes after deletion bug')
1988 if not self.confirm_all('Delete broken tombstone object %s deleted %s days ago?' % (
1989 dn, self.tombstoneLifetime), 'fix_changes_after_deletion_bug'):
1990 self.report('Not fixing changes after deletion bug')
1993 if self.do_delete(dn, ["relax:0"],
1994 "Failed to remove DN %s" % dn):
1995 self.report("Removed DN %s" % dn)
1997 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1998 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
2003 # Search for a zero invocationID
2004 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
2008 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
2009 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
2010 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
2011 % (dn, o.attid, o.version,
2012 time.ctime(samba.nttime2unix(o.originating_change_time)),
2013 self.samdb.get_invocation_id()))
2017 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
2018 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
2021 now = samba.unix2nttime(int(time.time()))
2024 # Search for a zero invocationID
2025 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
2029 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
2030 o.version = o.version + 1
2031 o.originating_change_time = now
2032 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
2033 o.originating_usn = seq
2037 replBlob = ndr_pack(repl)
2041 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
2042 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
2043 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
2046 nmsg = ldb.Message()
2048 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
2049 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
2050 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
2051 "Failed to fix attribute %s" % attr):
2052 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
2054 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
2055 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
2059 # Search for an invalid attid
2061 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
2063 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
2066 def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
2067 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
2072 remove_attid = set()
2075 in_schema_nc = dn.is_child_of(self.schema_dn)
2078 # Sort the array, except for the last element. This strange
2079 # construction, creating a new list, due to bugs in samba's
2080 # array handling in IDL generated objects.
2081 ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
2082 # Now walk it in reverse, so we see the low (and so incorrect,
2083 # the correct values are above 0x80000000) values first and
2084 # remove the 'second' value we see.
2085 for o in reversed(ctr.array):
2086 print("%s: 0x%08x" % (dn, o.attid))
2087 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
2088 if att.lower() in set_att:
2089 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
2090 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
2091 % (attr, dn, o.attid, att, hash_att[att].attid),
2092 'fix_replmetadata_duplicate_attid'):
2093 self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
2094 % (o.attid, att, attr, dn))
2097 remove_attid.add(o.attid)
2098 # We want to set the metadata for the most recent
2099 # update to have been applied locally, that is the metadata
2100 # matching the (eg string) value in the attribute
2101 if o.local_usn > hash_att[att].local_usn:
2102 # This is always what we would have sent over DRS,
2103 # because the DRS server will have sent the
2104 # msDS-IntID, but with the values from both
2105 # attribute entries.
2106 hash_att[att].version = o.version
2107 hash_att[att].originating_change_time = o.originating_change_time
2108 hash_att[att].originating_invocation_id = o.originating_invocation_id
2109 hash_att[att].originating_usn = o.originating_usn
2110 hash_att[att].local_usn = o.local_usn
2112 # Do not re-add the value to the set or overwrite the hash value
2116 set_att.add(att.lower())
2118 # Generate a real list we can sort on properly
2119 new_list = [o for o in ctr.array if o.attid not in remove_attid]
2121 if (len(wrong_attids) > 0):
2123 if o.attid in wrong_attids:
2124 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
2125 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
2126 self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
2127 if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
2128 % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
2129 self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
2130 % (o.attid, correct_attid, att, attr, dn))
2133 o.attid = correct_attid
2135 # Sort the array, (we changed the value so must re-sort)
2136 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
2138 # If we did not already need to fix it, then ask about sorting
2140 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
2141 if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
2142 % (attr, dn), 'fix_replmetadata_unsorted_attid'):
2143 self.report('Not fixing %s on %s\n' % (attr, dn))
2146 # The actual sort done is done at the top of the function
2148 ctr.count = len(new_list)
2149 ctr.array = new_list
2150 replBlob = ndr_pack(repl)
2152 nmsg = ldb.Message()
2154 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
2155 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
2156 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
2157 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
2158 "Failed to fix attribute %s" % attr):
2159 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
2161 def is_deleted_deleted_objects(self, obj):
2163 if "description" not in obj:
2164 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
2166 if "showInAdvancedViewOnly" not in obj or str(obj['showInAdvancedViewOnly'][0]).upper() == 'FALSE':
2167 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
2169 if "objectCategory" not in obj:
2170 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
2172 if "isCriticalSystemObject" not in obj or str(obj['isCriticalSystemObject'][0]).upper() == 'FALSE':
2173 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
2175 if "isRecycled" in obj:
2176 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
2178 if "isDeleted" in obj and str(obj['isDeleted'][0]).upper() == 'FALSE':
2179 self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
2181 if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
2182 str(obj['objectClass'][0]) != 'top' or
2183 str(obj['objectClass'][1]) != 'container'):
2184 self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
2186 if "systemFlags" not in obj or str(obj['systemFlags'][0]) != '-1946157056':
2187 self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
2191 def err_deleted_deleted_objects(self, obj):
2192 nmsg = ldb.Message()
2193 nmsg.dn = dn = obj.dn
2195 if "description" not in obj:
2196 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
2197 if "showInAdvancedViewOnly" not in obj:
2198 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
2199 if "objectCategory" not in obj:
2200 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
2201 if "isCriticalSystemObject" not in obj:
2202 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
2203 if "isRecycled" in obj:
2204 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
2206 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2207 nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
2208 nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
2210 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
2211 % (dn), 'fix_deleted_deleted_objects'):
2212 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
2215 if self.do_modify(nmsg, ["relax:0"],
2216 "Failed to fix Deleted Objects container %s" % dn):
2217 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
2219 def err_replica_locations(self, obj, cross_ref, attr):
2220 nmsg = ldb.Message()
2222 target = self.samdb.get_dsServiceName()
2224 if self.samdb.am_rodc():
2225 self.report('Not fixing %s %s for the RODC' % (attr, obj.dn))
2228 if not self.confirm_all('Add yourself to the replica locations for %s?'
2229 % (obj.dn), 'fix_replica_locations'):
2230 self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
2233 nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
2234 if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
2235 self.report("Fixed %s for %s" % (attr, obj.dn))
2237 def is_fsmo_role(self, dn):
2238 if dn == self.samdb.domain_dn:
2240 if dn == self.infrastructure_dn:
2242 if dn == self.naming_dn:
2244 if dn == self.schema_dn:
2246 if dn == self.rid_dn:
2251 def calculate_instancetype(self, dn):
2253 nc_root = self.samdb.get_nc_root(dn)
2255 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
2257 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
2258 except ldb.LdbError as e4:
2259 (enum, estr) = e4.args
2260 if enum != ldb.ERR_NO_SUCH_OBJECT:
2263 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
2264 if self.write_ncs is not None and str(nc_root) in [str(x) for x in self.write_ncs]:
2265 instancetype |= dsdb.INSTANCE_TYPE_WRITE
2269 def get_wellknown_sd(self, dn):
2270 for [sd_dn, descriptor_fn] in self.wellknown_sds:
2272 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
2273 return ndr_unpack(security.descriptor,
2274 descriptor_fn(domain_sid,
2275 name_map=self.name_map))
2279 def find_checkable_attrs(self, dn, requested_attrs):
2280 """A helper function for check_object() that calculates the list of
2281 attributes that need to be checked, and returns that as a list
2282 in the original case, and a set normalised to lowercase (for
2283 easy existence checks).
2285 if requested_attrs is None:
2288 attrs = list(requested_attrs)
2290 lc_attrs = set(x.lower() for x in attrs)
2293 if a.lower() not in lc_attrs:
2295 lc_attrs.add(a.lower())
2297 if ("dn" in lc_attrs or
2298 "distinguishedname" in lc_attrs or
2299 dn.get_rdn_name().lower() in lc_attrs):
2300 attrs.append("name")
2301 lc_attrs.add('name')
2303 if 'name' in lc_attrs:
2304 for a in (dn.get_rdn_name(),
2309 need_replPropertyMetaData = False
2311 need_replPropertyMetaData = True
2314 linkID, _ = self.get_attr_linkID_and_reverse_name(a)
2319 need_replPropertyMetaData = True
2321 if need_replPropertyMetaData:
2322 add_attr("replPropertyMetaData")
2324 add_attr("objectGUID")
2326 return attrs, lc_attrs
2328 def check_object(self, dn, requested_attrs=None):
2329 '''check one object'''
2331 self.report("Checking object %s" % dn)
2333 # search attrs are used to find the attributes, lc_attrs are
2334 # used for existence checks
2335 search_attrs, lc_attrs = self.find_checkable_attrs(dn, requested_attrs)
2339 sd_flags |= security.SECINFO_OWNER
2340 sd_flags |= security.SECINFO_GROUP
2341 sd_flags |= security.SECINFO_DACL
2342 sd_flags |= security.SECINFO_SACL
2344 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
2349 "sd_flags:1:%d" % sd_flags,
2350 "reveal_internals:0",
2353 except ldb.LdbError as e10:
2354 (enum, estr) = e10.args
2355 if enum == ldb.ERR_NO_SUCH_OBJECT:
2356 if self.in_transaction:
2357 self.report("ERROR: Object %s disappeared during check" % dn)
2362 self.report("ERROR: Object %s failed to load during check" % dn)
2366 set_attrs_from_md = set()
2367 set_attrs_seen = set()
2368 got_objectclass = False
2370 nc_dn = self.samdb.get_nc_root(obj.dn)
2372 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
2373 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
2375 # We have no deleted objects DN for schema, and we check for this above for the other
2377 deleted_objects_dn = None
2379 object_rdn_attr = None
2380 object_rdn_val = None
2384 repl_meta_data_val = None
2386 for attrname in obj:
2387 if attrname.lower() == 'isdeleted':
2388 if str(obj[attrname][0]) != "FALSE":
2391 if attrname.lower() == 'systemflags':
2392 systemFlags = int(obj[attrname][0])
2394 if attrname.lower() == 'replpropertymetadata':
2395 repl_meta_data_val = obj[attrname][0]
2397 if isDeleted and repl_meta_data_val:
2398 if self.has_changes_after_deletion(dn, repl_meta_data_val):
2400 self.err_changes_after_deletion(dn, repl_meta_data_val)
2402 if self.is_expired_tombstone(dn, repl_meta_data_val):
2405 for attrname in obj:
2406 if attrname == 'dn' or attrname == "distinguishedName":
2409 if attrname.lower() == 'objectclass':
2410 got_objectclass = True
2412 if attrname.lower() == "name":
2413 if len(obj[attrname]) != 1:
2414 self.unfixable_errors += 1
2415 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2416 (len(obj[attrname]), attrname, str(obj.dn)))
2418 name_val = str(obj[attrname][0])
2420 if attrname.lower() == str(obj.dn.get_rdn_name()).lower():
2421 object_rdn_attr = attrname
2422 if len(obj[attrname]) != 1:
2423 self.unfixable_errors += 1
2424 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2425 (len(obj[attrname]), attrname, str(obj.dn)))
2427 object_rdn_val = str(obj[attrname][0])
2429 if attrname.lower() == 'replpropertymetadata':
2430 if self.has_replmetadata_zero_invocationid(dn, obj[attrname][0]):
2432 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname][0])
2433 # We don't continue, as we may also have other fixes for this attribute
2434 # based on what other attributes we see.
2437 (set_attrs_from_md, list_attid_from_md, wrong_attids) \
2438 = self.process_metadata(dn, obj[attrname][0])
2441 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
2444 if len(set_attrs_from_md) < len(list_attid_from_md) \
2445 or len(wrong_attids) > 0 \
2446 or sorted(list_attid_from_md) != list_attid_from_md:
2448 self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname][0], wrong_attids)
2451 # Here we check that the first attid is 0
2453 if list_attid_from_md[0] != 0:
2454 self.unfixable_errors += 1
2455 self.report("ERROR: Not fixing incorrect initial attributeID in '%s' on '%s', it should be objectClass" %
2456 (attrname, str(dn)))
2460 if attrname.lower() == 'ntsecuritydescriptor':
2461 (sd, sd_broken) = self.process_sd(dn, obj)
2462 if sd_broken is not None:
2463 self.err_wrong_sd(dn, sd, sd_broken)
2467 if sd.owner_sid is None or sd.group_sid is None:
2468 self.err_missing_sd_owner(dn, sd)
2472 if self.reset_well_known_acls:
2474 well_known_sd = self.get_wellknown_sd(dn)
2478 current_sd = ndr_unpack(security.descriptor,
2481 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
2483 self.err_wrong_default_sd(dn, well_known_sd, diff)
2488 if attrname.lower() == 'objectclass':
2489 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
2490 # Do not consider the attribute incorrect if:
2491 # - The sorted (alphabetically) list is the same, inclding case
2492 # - The first and last elements are the same
2494 # This avoids triggering an error due to
2495 # non-determinism in the sort routine in (at least)
2496 # 4.3 and earlier, and the fact that any AUX classes
2497 # in these attributes are also not sorted when
2498 # imported from Windows (they are just in the reverse
2499 # order of last set)
2500 if sorted(normalised) != sorted(obj[attrname]) \
2501 or normalised[0] != obj[attrname][0] \
2502 or normalised[-1] != obj[attrname][-1]:
2503 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
2507 if attrname.lower() == 'userparameters':
2508 userparams = obj[attrname][0]
2509 if userparams == b' ':
2511 self.err_short_userParameters(obj, attrname, obj[attrname])
2514 elif userparams[:16] == b'\x20\x00' * 8:
2515 # This is the correct, normal prefix
2518 elif userparams[:20] == b'IAAgACAAIAAgACAAIAAg':
2519 # this is the typical prefix from a windows migration
2521 self.err_base64_userParameters(obj, attrname, obj[attrname])
2524 #43:00:00:00:74:00:00:00:78
2525 elif (userparams[1] != 0 and
2526 userparams[3] != 0 and
2527 userparams[5] != 0 and
2528 userparams[7] != 0 and
2529 userparams[9] != 0):
2530 # This is a prefix that is not in UTF-16 format
2531 # for the space or munged dialback prefix
2533 self.err_utf8_userParameters(obj, attrname, obj[attrname])
2536 elif len(userparams) % 2 != 0:
2537 # This is a value that isn't even in length
2539 self.err_odd_userParameters(obj, attrname)
2542 elif (userparams[1] == 0 and
2543 userparams[2] == 0 and
2544 userparams[3] == 0 and
2545 userparams[4] != 0 and
2546 userparams[5] == 0):
2547 # This is a prefix that would happen if a
2548 # SAMR-written value was replicated from a Samba
2549 # 4.1 server to a working server
2551 self.err_doubled_userParameters(obj, attrname, obj[attrname])
2554 if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2555 if obj[attrname][0] in self.attribute_or_class_ids:
2556 self.unfixable_errors += 1
2557 self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2558 % (attrname, obj.dn, obj[attrname][0]))
2560 self.attribute_or_class_ids.add(obj[attrname][0])
2562 # check for empty attributes
2563 for val in obj[attrname]:
2565 self.err_empty_attribute(dn, attrname)
2569 # get the syntax oid for the attribute, so we can can have
2570 # special handling for some specific attribute types
2572 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2573 except Exception as msg:
2574 self.err_unknown_attribute(obj, attrname)
2578 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2580 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2581 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2582 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2584 set_attrs_seen.add(attrname.lower())
2586 if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2587 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN]:
2588 # it's some form of DN, do specialised checking on those
2589 error_count += self.check_dn(obj, attrname, syntax_oid)
2593 # check for incorrectly normalised attributes
2594 for val in obj[attrname]:
2597 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2598 if len(normalised) != 1 or normalised[0] != val:
2599 self.err_normalise_mismatch(dn, attrname, obj[attrname])
2603 if len(obj[attrname]) != len(values):
2604 self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2608 if attrname.lower() == "instancetype":
2609 calculated_instancetype = self.calculate_instancetype(dn)
2610 if len(obj["instanceType"]) != 1 or int(obj["instanceType"][0]) != calculated_instancetype:
2612 self.err_wrong_instancetype(obj, calculated_instancetype)
2614 if not got_objectclass and ("*" in lc_attrs or "objectclass" in lc_attrs):
2616 self.err_missing_objectclass(dn)
2618 if ("*" in lc_attrs or "name" in lc_attrs):
2619 if name_val is None:
2620 self.unfixable_errors += 1
2621 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2622 if object_rdn_attr is None:
2623 self.unfixable_errors += 1
2624 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2626 if name_val is not None:
2628 controls = ["show_recycled:1", "relax:0"]
2630 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2631 parent_dn = deleted_objects_dn
2632 controls += ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME]
2633 if parent_dn is None:
2634 parent_dn = obj.dn.parent()
2637 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2638 except ValueError as e:
2639 self.unfixable_errors += 1
2640 self.report(f"ERROR: could not handle parent DN '{parent_dn}': "
2641 "skipping RDN checks")
2643 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2645 if obj.dn == deleted_objects_dn:
2646 expected_dn = obj.dn
2648 if expected_dn != obj.dn:
2650 self.err_wrong_dn(obj, expected_dn, object_rdn_attr,
2651 object_rdn_val, name_val, controls)
2652 elif obj.dn.get_rdn_value() != object_rdn_val:
2653 self.unfixable_errors += 1
2654 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr,
2659 if repl_meta_data_val:
2660 if obj.dn == deleted_objects_dn:
2661 isDeletedAttId = 131120
2662 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2664 expectedTimeDo = 2650466015990000000
2665 originating = self.get_originating_time(repl_meta_data_val, isDeletedAttId)
2666 if originating != expectedTimeDo:
2667 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2668 nmsg = ldb.Message()
2670 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2672 self.samdb.modify(nmsg, controls=["provision:0"])
2675 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2677 for att in set_attrs_seen.difference(set_attrs_from_md):
2679 self.report("On object %s" % dn)
2682 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2683 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2684 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2686 self.fix_metadata(obj, att)
2688 if self.is_fsmo_role(dn):
2689 if "fSMORoleOwner" not in obj and ("*" in lc_attrs or "fsmoroleowner" in lc_attrs):
2690 self.err_no_fsmoRoleOwner(obj)
2694 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2695 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2696 controls=["show_recycled:1", "show_deleted:1"])
2697 except ldb.LdbError as e11:
2698 (enum, estr) = e11.args
2699 if enum == ldb.ERR_NO_SUCH_OBJECT:
2701 self.report("WARNING: parent object not found for %s" % (obj.dn))
2702 self.report("Not moving to LostAndFound "
2703 "(tombstone garbage collection in progress?)")
2705 self.err_missing_parent(obj)
2710 if dn in self.deleted_objects_containers and '*' in lc_attrs:
2711 if self.is_deleted_deleted_objects(obj):
2712 self.err_deleted_deleted_objects(obj)
2715 for (dns_part, msg) in self.dns_partitions:
2716 if dn == dns_part and 'repsFrom' in obj:
2717 location = "msDS-NC-Replica-Locations"
2718 if self.samdb.am_rodc():
2719 location = "msDS-NC-RO-Replica-Locations"
2721 if location not in msg:
2722 # There are no replica locations!
2723 self.err_replica_locations(obj, msg.dn, location)
2728 for loc in msg[location]:
2729 if str(loc) == self.samdb.get_dsServiceName():
2732 # This DC is not in the replica locations
2733 self.err_replica_locations(obj, msg.dn, location)
2736 if dn == self.server_ref_dn:
2737 # Check we have a valid RID Set
2738 if "*" in lc_attrs or "ridsetreferences" in lc_attrs:
2739 if "rIDSetReferences" not in obj:
2740 # NO RID SET reference
2741 # We are RID master, allocate it.
2744 if self.is_rid_master:
2745 # Allocate a RID Set
2746 if self.confirm_all('Allocate the missing RID set for '
2748 'fix_missing_rid_set_master'):
2750 # We don't have auto-transaction logic on
2751 # extended operations, so we have to do it
2754 self.samdb.transaction_start()
2757 self.samdb.create_own_rid_set()
2760 self.samdb.transaction_cancel()
2763 self.samdb.transaction_commit()
2765 elif not self.samdb.am_rodc():
2766 self.report("No RID Set found for this server: %s, "
2767 "and we are not the RID Master (so can "
2768 "not self-allocate)" % dn)
2770 # Check some details of our own RID Set
2772 # Note that the attributes have very bad names. From ridalloc.c:
2774 # Note: the RID allocation attributes in AD are very badly named.
2775 # Here is what we think they really do:
2777 # in RID Set object:
2778 # - rIDPreviousAllocationPool: the pool which a DC is currently
2779 # pulling RIDs from. Managed by client DC
2781 # - rIDAllocationPool: the pool that the DC will switch to next,
2782 # when rIDPreviousAllocationPool is exhausted. Managed by RID
2785 # - rIDNextRID: the last RID allocated by this DC. Managed by
2788 # in RID Manager object:
2789 # - rIDAvailablePool: the pool where the RID Manager gets new rID
2790 # pools from when it gets a EXOP_RID_ALLOC getncchanges call
2791 # (or locally when the DC is the RID Manager)
2793 if dn == self.rid_set_dn:
2794 pool_attrs = ["rIDAllocationPool", "rIDPreviousAllocationPool"]
2796 res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2799 for pool_attr in pool_attrs:
2800 if pool_attr not in res[0]:
2803 pool = int(res[0][pool_attr][0])
2806 low = 0xFFFFFFFF & pool
2808 if pool != 0 and low >= high:
2809 self.report("Invalid RID pool %d-%d, %d >= %d!" %
2810 (low, high, low, high))
2811 self.unfixable_errors += 1
2813 if "rIDAllocationPool" not in res[0]:
2814 self.report("No rIDAllocationPool found in %s" % dn)
2815 self.unfixable_errors += 1
2818 next_free_rid, high = self.samdb.free_rid_bounds()
2819 except ldb.LdbError as err:
2820 enum, estr = err.args
2821 self.report("Couldn't get available RIDs: %s" % estr)
2822 self.unfixable_errors += 1
2824 # Check the remainder of this pool for conflicts. If
2825 # ridalloc_allocate_rid() moves to a new pool, this
2826 # will be above high, so we will stop.
2827 domain_sid = self.samdb.get_domain_sid()
2828 while next_free_rid <= high:
2829 sid = "%s-%d" % (domain_sid, next_free_rid)
2831 res = self.samdb.search(base="<SID=%s>" % sid,
2832 scope=ldb.SCOPE_BASE,
2834 except ldb.LdbError as e:
2835 (enum, estr) = e.args
2836 if enum != ldb.ERR_NO_SUCH_OBJECT:
2840 self.report("SID %s for %s conflicts with our current "
2841 "RID set in %s" % (sid, res[0].dn, dn))
2844 if self.confirm_all('Fix conflict between SID %s and '
2845 'RID pool in %s by allocating a '
2848 'fix_sid_rid_set_conflict'):
2849 self.samdb.transaction_start()
2851 # This will burn RIDs, which will move
2852 # past the conflict. We then check again
2853 # to see if the new RID conflicts, until
2854 # the end of the current pool. We don't
2855 # look at the next pool to avoid burning
2856 # all RIDs in one go in some strange
2860 allocated_rid = self.samdb.allocate_rid()
2861 if allocated_rid >= next_free_rid:
2862 next_free_rid = allocated_rid + 1
2865 self.samdb.transaction_cancel()
2868 self.samdb.transaction_commit()
2876 ################################################################
2877 # check special @ROOTDSE attributes
2878 def check_rootdse(self):
2879 '''check the @ROOTDSE special object'''
2880 dn = ldb.Dn(self.samdb, '@ROOTDSE')
2882 self.report("Checking object %s" % dn)
2883 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2885 self.report("Object %s disappeared during check" % dn)
2890 # check that the dsServiceName is in GUID form
2891 if 'dsServiceName' not in obj:
2892 self.report('ERROR: dsServiceName missing in @ROOTDSE')
2893 return error_count + 1
2895 if not str(obj['dsServiceName'][0]).startswith('<GUID='):
2896 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2898 if not self.confirm('Change dsServiceName to GUID form?'):
2900 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0].decode('utf8')),
2901 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2902 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2905 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2906 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2907 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2908 self.report("Changed dsServiceName to GUID form")
2911 ###############################################
2912 # re-index the database
2914 def reindex_database(self):
2915 '''re-index the whole database'''
2917 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2918 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2919 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2920 return self.do_modify(m, [], 're-indexed database', validate=False)
2922 ###############################################
2924 def reset_modules(self):
2925 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2927 m.dn = ldb.Dn(self.samdb, "@MODULES")
2928 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2929 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)