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/>.
20 from __future__ import print_function
24 from base64 import b64decode
25 from samba import dsdb
26 from samba import common
27 from samba.dcerpc import misc
28 from samba.dcerpc import drsuapi
29 from samba.ndr import ndr_unpack, ndr_pack
30 from samba.dcerpc import drsblobs
31 from samba.samdb import dsdb_Dn
32 from samba.dcerpc import security
33 from samba.descriptor import get_wellknown_sds, get_diff_sds
34 from samba.auth import system_session, admin_session
35 from samba.netcmd import CommandError
36 from samba.netcmd.fsmo import get_fsmo_roleowner
38 # vals is a sequence of ldb.bytes objects which are a subclass
39 # of 'byte' type in python3 and just a str type in python2, to
40 # display as string these need to be converted to string via (str)
41 # function in python3 but that may generate a UnicodeDecode error,
42 # if so use repr instead. We need to at least try to get the 'str'
43 # value if possible to allow some tests which check the strings
44 # outputted to pass, these tests compare attr values logged to stdout
45 # against those in various results files.
47 def dump_attr_values(vals):
53 result = result + str(value)
54 except UnicodeDecodeError:
55 result = result + repr(value)
58 class dbcheck(object):
59 """check a SAM database for errors"""
61 def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
62 yes=False, quiet=False, in_transaction=False,
63 quick_membership_checks=False,
64 reset_well_known_acls=False,
65 check_expired_tombstones=False):
67 self.dict_oid_name = None
68 self.samdb_schema = (samdb_schema or samdb)
69 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
105 self.fix_incorrect_deleted_objects = False
107 self.fix_base64_userparameters = False
108 self.fix_utf8_userparameters = False
109 self.fix_doubled_userparameters = False
110 self.fix_sid_rid_set_conflict = False
111 self.quick_membership_checks = quick_membership_checks
112 self.reset_well_known_acls = reset_well_known_acls
113 self.check_expired_tombstones = check_expired_tombstones
114 self.expired_tombstones = 0
115 self.reset_all_well_known_acls = False
116 self.in_transaction = in_transaction
117 self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
118 self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
119 self.schema_dn = samdb.get_schema_basedn()
120 self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
121 self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
122 self.class_schemaIDGUID = {}
123 self.wellknown_sds = get_wellknown_sds(self.samdb)
124 self.fix_all_missing_objectclass = False
125 self.fix_missing_deleted_objects = False
126 self.fix_replica_locations = False
127 self.fix_missing_rid_set_master = False
128 self.fix_changes_after_deletion_bug = False
131 self.link_id_cache = {}
134 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE,
136 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
137 self.name_map['DnsAdmins'] = str(dnsadmins_sid)
138 except ldb.LdbError as e5:
139 (enum, estr) = e5.args
140 if enum != ldb.ERR_NO_SUCH_OBJECT:
144 self.system_session_info = system_session()
145 self.admin_session_info = admin_session(None, samdb.get_domain_sid())
147 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
148 if "msDS-hasMasterNCs" in res[0]:
149 self.write_ncs = res[0]["msDS-hasMasterNCs"]
151 # If the Forest Level is less than 2003 then there is no
152 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
153 # no need to merge as all the NCs that are in hasMasterNCs must
154 # also be in msDS-hasMasterNCs (but not the opposite)
155 if "hasMasterNCs" in res[0]:
156 self.write_ncs = res[0]["hasMasterNCs"]
158 self.write_ncs = None
160 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
161 self.deleted_objects_containers = []
162 self.ncs_lacking_deleted_containers = []
163 self.dns_partitions = []
165 self.ncs = res[0]["namingContexts"]
173 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc.decode('utf8')),
174 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
175 self.deleted_objects_containers.append(dn)
177 self.ncs_lacking_deleted_containers.append(ldb.Dn(self.samdb, nc.decode('utf8')))
179 domaindns_zone = 'DC=DomainDnsZones,%s' % self.samdb.get_default_basedn()
180 forestdns_zone = 'DC=ForestDnsZones,%s' % self.samdb.get_root_basedn()
181 domain = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
182 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
183 base=self.samdb.get_partitions_dn(),
184 expression="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone)
186 self.dns_partitions.append((ldb.Dn(self.samdb, forestdns_zone), domain[0]))
188 forest = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
189 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
190 base=self.samdb.get_partitions_dn(),
191 expression="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone)
193 self.dns_partitions.append((ldb.Dn(self.samdb, domaindns_zone), forest[0]))
195 fsmo_dn = ldb.Dn(self.samdb, "CN=RID Manager$,CN=System," + self.samdb.domain_dn())
196 rid_master = get_fsmo_roleowner(self.samdb, fsmo_dn, "rid")
197 if ldb.Dn(self.samdb, self.samdb.get_dsServiceName()) == rid_master:
198 self.is_rid_master = True
200 self.is_rid_master = False
202 # To get your rid set
204 res = self.samdb.search(base=ldb.Dn(self.samdb, self.samdb.get_serverName()),
205 scope=ldb.SCOPE_BASE, attrs=["serverReference"])
206 # 2. Get server reference
207 self.server_ref_dn = ldb.Dn(self.samdb, res[0]['serverReference'][0].decode('utf8'))
210 res = self.samdb.search(base=self.server_ref_dn,
211 scope=ldb.SCOPE_BASE, attrs=['rIDSetReferences'])
212 if "rIDSetReferences" in res[0]:
213 self.rid_set_dn = ldb.Dn(self.samdb, res[0]['rIDSetReferences'][0].decode('utf8'))
215 self.rid_set_dn = None
217 ntds_service_dn = "CN=Directory Service,CN=Windows NT,CN=Services,%s" % \
218 self.samdb.get_config_basedn().get_linearized()
219 res = samdb.search(base=ntds_service_dn,
220 scope=ldb.SCOPE_BASE,
221 expression="(objectClass=nTDSService)",
222 attrs=["tombstoneLifetime"])
223 if "tombstoneLifetime" in res[0]:
224 self.tombstoneLifetime = int(res[0]["tombstoneLifetime"][0])
226 self.tombstoneLifetime = 180
228 self.compatibleFeatures = []
229 self.requiredFeatures = []
232 res = self.samdb.search(scope=ldb.SCOPE_BASE,
234 attrs=["compatibleFeatures",
236 if "compatibleFeatures" in res[0]:
237 self.compatibleFeatures = res[0]["compatibleFeatures"]
238 if "requiredFeatures" in res[0]:
239 self.requiredFeatures = res[0]["requiredFeatures"]
240 except ldb.LdbError as e6:
241 (enum, estr) = e6.args
242 if enum != ldb.ERR_NO_SUCH_OBJECT:
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))
253 error_count += self.check_deleted_objects_containers()
255 self.attribute_or_class_ids = set()
258 self.dn_set.add(str(object.dn))
259 error_count += self.check_object(object.dn, attrs=attrs)
262 error_count += self.check_rootdse()
264 if self.expired_tombstones > 0:
265 self.report("NOTICE: found %d expired tombstones, "
266 "'samba' will remove them daily, "
267 "'samba-tool domain tombstones expunge' "
268 "would do that immediately." % (
269 self.expired_tombstones))
271 if error_count != 0 and not self.fix:
272 self.report("Please use --fix to fix these errors")
274 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
277 def check_deleted_objects_containers(self):
278 """This function only fixes conflicts on the Deleted Objects
279 containers, not the attributes"""
281 for nc in self.ncs_lacking_deleted_containers:
282 if nc == self.schema_dn:
285 self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc)
286 if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'):
289 dn = ldb.Dn(self.samdb, "CN=Deleted Objects")
294 # If something already exists here, add a conflict
295 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[],
296 controls=["show_deleted:1", "extended_dn:1:1",
297 "show_recycled:1", "reveal_internals:0"])
299 guid = res[0].dn.get_extended_component("GUID")
300 conflict_dn = ldb.Dn(self.samdb,
301 "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid)))
302 conflict_dn.add_base(nc)
304 except ldb.LdbError as e2:
305 (enum, estr) = e2.args
306 if enum == ldb.ERR_NO_SUCH_OBJECT:
309 self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr)
312 if conflict_dn is not None:
314 self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"])
315 except ldb.LdbError as e1:
316 (enum, estr) = e1.args
317 self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr))
320 # Refresh wellKnownObjects links
321 res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE,
322 attrs=['wellKnownObjects'],
323 controls=["show_deleted:1", "extended_dn:0",
324 "show_recycled:1", "reveal_internals:0"])
326 self.report("wellKnownObjects was not found for NC %s" % nc)
329 # Prevent duplicate deleted objects containers just in case
330 wko = res[0]["wellKnownObjects"]
332 proposed_objectguid = None
334 dsdb_dn = dsdb_Dn(self.samdb, o.decode('utf8'), dsdb.DSDB_SYNTAX_BINARY_DN)
335 if self.is_deleted_objects_dn(dsdb_dn):
336 self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o)
337 # We really want to put this back in the same spot
338 # as the original one, so that on replication we
339 # merge, rather than conflict.
340 proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID")
341 listwko.append(str(o))
343 if proposed_objectguid is not None:
344 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid))
346 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
347 listwko.append('%s:%s' % (wko_prefix, dn))
350 # Insert a brand new Deleted Objects container
351 self.samdb.add_ldif("""dn: %s
353 objectClass: container
354 description: Container for deleted objects
356 isCriticalSystemObject: TRUE
357 showInAdvancedViewOnly: TRUE
358 systemFlags: -1946157056%s""" % (dn, guid_suffix),
359 controls=["relax:0", "provision:0"])
361 delta = ldb.Message()
362 delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"]))
363 delta["wellKnownObjects"] = ldb.MessageElement(listwko,
364 ldb.FLAG_MOD_REPLACE,
367 # Insert the link to the brand new container
368 if self.do_modify(delta, ["relax:0"],
369 "NC %s lacks Deleted Objects WKGUID" % nc,
371 self.report("Added %s well known guid link" % dn)
373 self.deleted_objects_containers.append(dn)
377 def report(self, msg):
378 '''print a message unless quiet is set'''
382 def confirm(self, msg, allow_all=False, forced=False):
383 '''confirm a change'''
390 return common.confirm(msg, forced=forced, allow_all=allow_all)
392 ################################################################
393 # a local confirm function with support for 'all'
394 def confirm_all(self, msg, all_attr):
395 '''confirm a change with support for "all" '''
398 if getattr(self, all_attr) == 'NONE':
400 if getattr(self, all_attr) == 'ALL':
406 c = common.confirm(msg, forced=forced, allow_all=True)
408 setattr(self, all_attr, 'ALL')
411 setattr(self, all_attr, 'NONE')
415 def do_delete(self, dn, controls, msg):
416 '''delete dn with optional verbose output'''
418 self.report("delete DN %s" % dn)
420 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
421 self.samdb.delete(dn, controls=controls)
422 except Exception as err:
423 if self.in_transaction:
424 raise CommandError("%s : %s" % (msg, err))
425 self.report("%s : %s" % (msg, err))
429 def do_modify(self, m, controls, msg, validate=True):
430 '''perform a modify with optional verbose output'''
431 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
433 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
434 self.report("controls: %r" % controls)
436 self.samdb.modify(m, controls=controls, validate=validate)
437 except Exception as err:
438 if self.in_transaction:
439 raise CommandError("%s : %s" % (msg, err))
440 self.report("%s : %s" % (msg, err))
444 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
445 '''perform a modify with optional verbose output'''
447 self.report("""dn: %s
451 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
453 to_dn = to_rdn + to_base
454 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
455 self.samdb.rename(from_dn, to_dn, controls=controls)
456 except Exception as err:
457 if self.in_transaction:
458 raise CommandError("%s : %s" % (msg, err))
459 self.report("%s : %s" % (msg, err))
463 def get_attr_linkID_and_reverse_name(self, attrname):
464 if attrname in self.link_id_cache:
465 return self.link_id_cache[attrname]
466 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
468 revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
471 self.link_id_cache[attrname] = (linkID, revname)
472 return linkID, revname
474 def err_empty_attribute(self, dn, attrname):
475 '''fix empty attributes'''
476 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
477 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
478 self.report("Not fixing empty attribute %s" % attrname)
483 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
484 if self.do_modify(m, ["relax:0", "show_recycled:1"],
485 "Failed to remove empty attribute %s" % attrname, validate=False):
486 self.report("Removed empty attribute %s" % attrname)
488 def err_normalise_mismatch(self, dn, attrname, values):
489 '''fix attribute normalisation errors'''
490 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
493 normalised = self.samdb.dsdb_normalise_attributes(
494 self.samdb_schema, attrname, [val])
495 if len(normalised) != 1:
496 self.report("Unable to normalise value '%s'" % val)
497 mod_list.append((val, ''))
498 elif (normalised[0] != val):
499 self.report("value '%s' should be '%s'" % (val, normalised[0]))
500 mod_list.append((val, normalised[0]))
501 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
502 self.report("Not fixing attribute %s" % attrname)
507 for i in range(0, len(mod_list)):
508 (val, nval) = mod_list[i]
509 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
511 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
514 if self.do_modify(m, ["relax:0", "show_recycled:1"],
515 "Failed to normalise attribute %s" % attrname,
517 self.report("Normalised attribute %s" % attrname)
519 def err_normalise_mismatch_replace(self, dn, attrname, values):
520 '''fix attribute normalisation errors'''
521 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
522 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
523 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
524 if list(normalised) == values:
526 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
527 self.report("Not fixing attribute '%s'" % attrname)
532 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
534 if self.do_modify(m, ["relax:0", "show_recycled:1"],
535 "Failed to normalise attribute %s" % attrname,
537 self.report("Normalised attribute %s" % attrname)
539 def err_duplicate_values(self, dn, attrname, dup_values, values):
540 '''fix attribute normalisation errors'''
541 self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
542 self.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dump_attr_values(dup_values)), ','.join(dump_attr_values(values))))
543 if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
544 self.report("Not fixing attribute '%s'" % attrname)
549 m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
551 if self.do_modify(m, ["relax:0", "show_recycled:1"],
552 "Failed to remove duplicate value on attribute %s" % attrname,
554 self.report("Removed duplicate value on attribute %s" % attrname)
556 def is_deleted_objects_dn(self, dsdb_dn):
557 '''see if a dsdb_Dn is the special Deleted Objects DN'''
558 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
560 def err_missing_objectclass(self, dn):
561 """handle object without objectclass"""
562 self.report("ERROR: missing objectclass in object %s. If you have another working DC, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (dn, self.samdb.get_nc_root(dn)))
563 if not self.confirm_all("If you cannot re-sync from another DC, do you wish to delete object '%s'?" % dn, 'fix_all_missing_objectclass'):
564 self.report("Not deleting object with missing objectclass '%s'" % dn)
566 if self.do_delete(dn, ["relax:0"],
567 "Failed to remove DN %s" % dn):
568 self.report("Removed DN %s" % dn)
570 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False):
571 """handle a DN pointing to a deleted object"""
572 if not remove_plausible:
573 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
574 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
575 if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
576 self.report("Not removing")
579 self.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
580 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
581 if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
582 self.report("Not removing")
587 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
588 if self.do_modify(m, ["show_recycled:1",
589 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
590 "Failed to remove deleted DN attribute %s" % attrname):
591 self.report("Removed deleted DN on attribute %s" % attrname)
593 def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
594 """handle a missing target DN (if specified, GUID form can't be found,
595 and otherwise DN string form can't be found)"""
597 # Don't change anything if the object itself is deleted
598 if str(dn).find('\\0ADEL') != -1:
599 # We don't bump the error count as Samba produces these
600 # in normal operation
601 self.report("WARNING: no target object found for GUID "
602 "component link %s in deleted object "
603 "%s - %s" % (attrname, dn, val))
604 self.report("Not removing dangling one-way "
605 "link on deleted object "
606 "(tombstone garbage collection in progress?)")
609 # check if its a backlink
610 linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
611 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
613 linkID, reverse_link_name \
614 = self.get_attr_linkID_and_reverse_name(attrname)
615 if reverse_link_name is not None:
616 self.report("WARNING: no target object found for GUID "
617 "component for one-way forward link "
619 "%s - %s" % (attrname, dn, val))
620 self.report("Not removing dangling forward link")
623 nc_root = self.samdb.get_nc_root(dn)
625 target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn)
626 except ldb.LdbError as e:
627 (enum, estr) = e.args
628 if enum != ldb.ERR_NO_SUCH_OBJECT:
630 target_nc_root = None
632 if target_nc_root is None:
633 # We don't bump the error count as Samba produces
634 # these in normal operation creating a lab domain (due
635 # to the way the rename is handled, links to
636 # now-expunged objects will never be fixed to stay
638 self.report("WARNING: no target object found for GUID "
639 "component for link "
640 "%s in object to %s outside our NCs"
641 "%s - %s" % (attrname, dsdb_dn.dn, dn, val))
642 self.report("Not removing dangling one-way "
643 "left-over link outside our NCs "
644 "(we might be building a renamed/lab domain)")
647 if nc_root != target_nc_root:
648 # We don't bump the error count as Samba produces these
649 # in normal operation
650 self.report("WARNING: no target object found for GUID "
651 "component for cross-partition link "
653 "%s - %s" % (attrname, dn, val))
654 self.report("Not removing dangling one-way "
655 "cross-partition link "
656 "(we might be mid-replication)")
659 # Due to our link handling one-way links pointing to
660 # missing objects are plausible.
662 # We don't bump the error count as Samba produces these
663 # in normal operation
664 self.report("WARNING: no target object found for GUID "
665 "component for DN value %s in object "
666 "%s - %s" % (attrname, dn, val))
667 self.err_deleted_dn(dn, attrname, val,
668 dsdb_dn, dsdb_dn, True)
671 # We bump the error count here, as we should have deleted this
672 self.report("ERROR: no target object found for GUID "
673 "component for link %s in object "
674 "%s - %s" % (attrname, dn, val))
675 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
678 def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
679 """handle a missing GUID extended DN component"""
680 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
681 controls = ["extended_dn:1:1", "show_recycled:1"]
683 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
684 attrs=[], controls=controls)
685 except ldb.LdbError as e7:
686 (enum, estr) = e7.args
687 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
688 if enum != ldb.ERR_NO_SUCH_OBJECT:
690 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
693 self.report("unable to find object for DN %s" % dsdb_dn.dn)
694 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
696 dsdb_dn.dn = res[0].dn
698 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
699 self.report("Not fixing %s" % errstr)
703 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
704 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
706 if self.do_modify(m, ["show_recycled:1"],
707 "Failed to fix %s on attribute %s" % (errstr, attrname)):
708 self.report("Fixed %s on attribute %s" % (errstr, attrname))
710 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
711 """handle an incorrect binary DN component"""
712 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
714 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
715 self.report("Not fixing %s" % errstr)
719 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
720 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
722 if self.do_modify(m, ["show_recycled:1"],
723 "Failed to fix %s on attribute %s" % (errstr, attrname)):
724 self.report("Fixed %s on attribute %s" % (errstr, attrname))
726 def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn):
727 """handle a DN string being incorrect"""
728 self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val))
729 dsdb_dn.dn = correct_dn
731 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
732 'fix_all_old_dn_string_component_mismatch'):
733 self.report("Not fixing old string component")
737 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
738 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
739 if self.do_modify(m, ["show_recycled:1",
740 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME],
741 "Failed to fix old DN string on attribute %s" % (attrname)):
742 self.report("Fixed old DN string on attribute %s" % (attrname))
744 def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
745 """handle a DN string being incorrect"""
746 self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
747 dsdb_dn.dn = correct_dn
749 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
750 'fix_all_%s_dn_component_mismatch' % mismatch_type):
751 self.report("Not fixing %s component mismatch" % mismatch_type)
755 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
756 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
757 if self.do_modify(m, ["show_recycled:1"],
758 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
759 self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
761 def err_dn_component_missing_target_sid(self, dn, attrname, val, dsdb_dn, target_sid_blob):
762 """handle a DN string being incorrect"""
763 self.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname, dn, val))
765 if len(dsdb_dn.prefix) != 0:
766 self.report("Not fixing missing DN SID on DN+BINARY or DN+STRING")
769 correct_dn = ldb.Dn(self.samdb, dsdb_dn.dn.extended_str())
770 correct_dn.set_extended_component("SID", target_sid_blob)
772 if not self.confirm_all('Change DN to %s?' % correct_dn.extended_str(),
773 'fix_all_SID_dn_component_missing'):
774 self.report("Not fixing missing DN SID component")
777 target_guid_blob = correct_dn.get_extended_component("GUID")
778 guid_sid_dn = ldb.Dn(self.samdb, "")
779 guid_sid_dn.set_extended_component("GUID", target_guid_blob)
780 guid_sid_dn.set_extended_component("SID", target_sid_blob)
784 m['new_value'] = ldb.MessageElement(guid_sid_dn.extended_str(), ldb.FLAG_MOD_ADD, attrname)
787 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID
789 if self.do_modify(m, controls,
790 "Failed to ADD missing DN SID on attribute %s" % (attrname)):
791 self.report("Fixed missing DN SID on attribute %s" % (attrname))
793 def err_unknown_attribute(self, obj, attrname):
794 '''handle an unknown attribute error'''
795 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
796 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
797 self.report("Not removing %s" % attrname)
801 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
802 if self.do_modify(m, ["relax:0", "show_recycled:1"],
803 "Failed to remove unknown attribute %s" % attrname):
804 self.report("Removed unknown attribute %s" % (attrname))
806 def err_undead_linked_attribute(self, obj, attrname, val):
807 '''handle a link that should not be there on a deleted object'''
808 self.report("ERROR: linked attribute '%s' to '%s' is present on "
809 "deleted object %s" % (attrname, val, obj.dn))
810 if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
811 self.report("Not removing linked attribute %s" % attrname)
815 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
817 if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
818 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
819 "Failed to delete forward link %s" % attrname):
820 self.report("Fixed undead forward link %s" % (attrname))
822 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
823 '''handle a missing backlink value'''
824 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
825 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
826 self.report("Not fixing missing backlink %s" % backlink_name)
830 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
831 if self.do_modify(m, ["show_recycled:1", "relax:0"],
832 "Failed to fix missing backlink %s" % backlink_name):
833 self.report("Fixed missing backlink %s" % (backlink_name))
835 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
836 '''handle a incorrect RMD_FLAGS value'''
837 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
838 self.report("ERROR: incorrect RMD_FLAGS value %u for attribute '%s' in %s for link %s" % (rmd_flags, attrname, obj.dn, revealed_dn.dn.extended_str()))
839 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
840 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
844 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
845 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
846 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
847 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
849 def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val,
850 target_dn, forward_attr, forward_syntax,
851 check_duplicates=True):
852 '''handle a orphaned backlink value'''
853 if check_duplicates is True and self.has_duplicate_links(target_dn, forward_attr, forward_syntax):
854 self.report("WARNING: Keep orphaned backlink attribute " +
855 "'%s' in '%s' for link '%s' in '%s'" % (
856 backlink_attr, obj_dn, forward_attr, target_dn))
858 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn))
859 if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'):
860 self.report("Not removing orphaned backlink %s" % backlink_attr)
864 m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr)
865 if self.do_modify(m, ["show_recycled:1", "relax:0"],
866 "Failed to fix orphaned backlink %s" % backlink_attr):
867 self.report("Fixed orphaned backlink %s" % (backlink_attr))
869 def err_recover_forward_links(self, obj, forward_attr, forward_vals):
870 '''handle a duplicate links value'''
872 self.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn))
874 if not self.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'):
875 self.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
876 forward_attr, obj.dn))
880 m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr)
881 if self.do_modify(m, ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS],
882 "Failed to fix duplicate links in attribute '%s'" % forward_attr):
883 self.report("Fixed duplicate links in attribute '%s'" % (forward_attr))
884 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
885 assert duplicate_cache_key in self.duplicate_link_cache
886 self.duplicate_link_cache[duplicate_cache_key] = False
888 def err_no_fsmoRoleOwner(self, obj):
889 '''handle a missing fSMORoleOwner'''
890 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
891 res = self.samdb.search("",
892 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
894 serviceName = str(res[0]["dsServiceName"][0])
895 if not self.confirm_all('Seize role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
896 self.report("Not Seizing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
900 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
901 if self.do_modify(m, [],
902 "Failed to seize role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
903 self.report("Seized role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
905 def err_missing_parent(self, obj):
906 '''handle a missing parent'''
907 self.report("ERROR: parent object not found for %s" % (obj.dn))
908 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
909 self.report('Not moving object %s into LostAndFound' % (obj.dn))
912 keep_transaction = False
913 self.samdb.transaction_start()
915 nc_root = self.samdb.get_nc_root(obj.dn)
916 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
917 new_dn = ldb.Dn(self.samdb, str(obj.dn))
918 new_dn.remove_base_components(len(new_dn) - 1)
919 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
920 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
921 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
925 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
927 if self.do_modify(m, [],
928 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
929 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
930 keep_transaction = True
932 self.samdb.transaction_cancel()
936 self.samdb.transaction_commit()
938 self.samdb.transaction_cancel()
940 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val, controls):
941 '''handle a wrong dn'''
943 new_rdn = ldb.Dn(self.samdb, str(new_dn))
944 new_rdn.remove_base_components(len(new_rdn) - 1)
945 new_parent = new_dn.parent()
948 if rdn_val != name_val:
949 attributes += "%s=%r " % (rdn_attr, rdn_val)
950 attributes += "name=%r" % (name_val)
952 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
953 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
954 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
957 if self.do_rename(obj.dn, new_rdn, new_parent, controls,
958 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
959 self.report("Renamed %s into %s" % (obj.dn, new_dn))
961 def err_wrong_instancetype(self, obj, calculated_instancetype):
962 '''handle a wrong instanceType'''
963 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
964 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
965 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
970 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
971 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
972 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
973 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
975 def err_short_userParameters(self, obj, attrname, value):
976 # This is a truncated userParameters due to a pre 4.1 replication bug
977 self.report("ERROR: incorrect userParameters value on object %s. If you have another working DC that does not give this warning, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (obj.dn, self.samdb.get_nc_root(obj.dn)))
979 def err_base64_userParameters(self, obj, attrname, value):
980 '''handle a wrong userParameters'''
981 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
982 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
983 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
988 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
989 if self.do_modify(m, [],
990 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
991 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
993 def err_utf8_userParameters(self, obj, attrname, value):
994 '''handle a wrong userParameters'''
995 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
996 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
997 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
1002 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
1003 ldb.FLAG_MOD_REPLACE, 'userParameters')
1004 if self.do_modify(m, [],
1005 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
1006 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
1008 def err_doubled_userParameters(self, obj, attrname, value):
1009 '''handle a wrong userParameters'''
1010 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
1011 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
1012 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
1017 # m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
1018 # hmm the above old python2 code doesn't make sense to me and cannot
1019 # work in python3 because a string doesn't have a decode method.
1020 # However in python2 for some unknown reason this double decode
1021 # followed by encode seems to result in what looks like utf8.
1022 # In python2 just .decode('utf-16-le').encode('utf-16-le') does nothing
1023 # but trigger the 'double UTF16 encoded' condition again :/
1025 # In python2 and python3 value.decode('utf-16-le').encode('utf8') seems
1026 # to do the trick and work as expected.
1027 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').encode('utf8'),
1028 ldb.FLAG_MOD_REPLACE, 'userParameters')
1030 if self.do_modify(m, [],
1031 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
1032 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
1034 def err_odd_userParameters(self, obj, attrname):
1035 # This is a truncated userParameters due to a pre 4.1 replication bug
1036 self.report("ERROR: incorrect userParameters value on object %s (odd length). If you have another working DC that does not give this warning, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (obj.dn, self.samdb.get_nc_root(obj.dn)))
1038 def find_revealed_link(self, dn, attrname, guid):
1039 '''return a revealed link in an object'''
1040 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
1041 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
1042 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1043 for val in res[0][attrname]:
1044 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
1045 guid2 = dsdb_dn.dn.get_extended_component("GUID")
1050 def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr):
1051 '''check a linked values for duplicate forward links'''
1054 duplicate_dict = dict()
1055 unique_dict = dict()
1057 # Only forward links can have this problem
1058 if forward_linkID & 1:
1059 # If we got the reverse, skip it
1060 return (error_count, duplicate_dict, unique_dict)
1062 if backlink_attr is None:
1063 return (error_count, duplicate_dict, unique_dict)
1065 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
1066 if duplicate_cache_key not in self.duplicate_link_cache:
1067 self.duplicate_link_cache[duplicate_cache_key] = False
1069 for val in obj[forward_attr]:
1070 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), forward_syntax)
1072 # all DNs should have a GUID component
1073 guid = dsdb_dn.dn.get_extended_component("GUID")
1076 guidstr = str(misc.GUID(guid))
1077 keystr = guidstr + dsdb_dn.prefix
1078 if keystr not in unique_dict:
1079 unique_dict[keystr] = dsdb_dn
1082 if keystr not in duplicate_dict:
1083 duplicate_dict[keystr] = dict()
1084 duplicate_dict[keystr]["keep"] = None
1085 duplicate_dict[keystr]["delete"] = list()
1087 # Now check for the highest RMD_VERSION
1088 v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION"))
1089 v2 = int(dsdb_dn.dn.get_extended_component("RMD_VERSION"))
1091 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
1092 duplicate_dict[keystr]["delete"].append(dsdb_dn)
1095 duplicate_dict[keystr]["keep"] = dsdb_dn
1096 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
1097 unique_dict[keystr] = dsdb_dn
1099 # Fallback to the highest RMD_LOCAL_USN
1100 u1 = int(unique_dict[keystr].dn.get_extended_component("RMD_LOCAL_USN"))
1101 u2 = int(dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN"))
1103 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
1104 duplicate_dict[keystr]["delete"].append(dsdb_dn)
1106 duplicate_dict[keystr]["keep"] = dsdb_dn
1107 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
1108 unique_dict[keystr] = dsdb_dn
1110 if error_count != 0:
1111 self.duplicate_link_cache[duplicate_cache_key] = True
1113 return (error_count, duplicate_dict, unique_dict)
1115 def has_duplicate_links(self, dn, forward_attr, forward_syntax):
1116 '''check a linked values for duplicate forward links'''
1119 duplicate_cache_key = "%s:%s" % (str(dn), forward_attr)
1120 if duplicate_cache_key in self.duplicate_link_cache:
1121 return self.duplicate_link_cache[duplicate_cache_key]
1123 forward_linkID, backlink_attr = self.get_attr_linkID_and_reverse_name(forward_attr)
1125 attrs = [forward_attr]
1126 controls = ["extended_dn:1:1", "reveal_internals:0"]
1128 # check its the right GUID
1130 res = self.samdb.search(base=str(dn), scope=ldb.SCOPE_BASE,
1131 attrs=attrs, controls=controls)
1132 except ldb.LdbError as e8:
1133 (enum, estr) = e8.args
1134 if enum != ldb.ERR_NO_SUCH_OBJECT:
1140 error_count, duplicate_dict, unique_dict = \
1141 self.check_duplicate_links(obj, forward_attr, forward_syntax, forward_linkID, backlink_attr)
1143 if duplicate_cache_key in self.duplicate_link_cache:
1144 return self.duplicate_link_cache[duplicate_cache_key]
1148 def find_missing_forward_links_from_backlinks(self, obj,
1152 forward_unique_dict):
1153 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1154 missing_forward_links = []
1157 if backlink_attr is None:
1158 return (missing_forward_links, error_count)
1160 if forward_syntax != ldb.SYNTAX_DN:
1161 self.report("Not checking for missing forward links for syntax: %s" %
1163 return (missing_forward_links, error_count)
1165 if "sortedLinks" in self.compatibleFeatures:
1166 self.report("Not checking for missing forward links because the db " +
1167 "has the sortedLinks feature")
1168 return (missing_forward_links, error_count)
1171 obj_guid = obj['objectGUID'][0]
1172 obj_guid_str = str(ndr_unpack(misc.GUID, obj_guid))
1173 filter = "(%s=<GUID=%s>)" % (backlink_attr, obj_guid_str)
1175 res = self.samdb.search(expression=filter,
1176 scope=ldb.SCOPE_SUBTREE, attrs=["objectGUID"],
1177 controls=["extended_dn:1:1",
1178 "search_options:1:2",
1179 "paged_results:1:1000"])
1180 except ldb.LdbError as e9:
1181 (enum, estr) = e9.args
1185 target_dn = dsdb_Dn(self.samdb, r.dn.extended_str(), forward_syntax)
1187 guid = target_dn.dn.get_extended_component("GUID")
1188 guidstr = str(misc.GUID(guid))
1189 if guidstr in forward_unique_dict:
1192 # A valid forward link looks like this:
1194 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1195 # <RMD_ADDTIME=131607546230000000>;
1196 # <RMD_CHANGETIME=131607546230000000>;
1198 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1199 # <RMD_LOCAL_USN=3765>;
1200 # <RMD_ORIGINATING_USN=3765>;
1202 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1203 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1205 # Note that versions older than Samba 4.8 create
1206 # links with RMD_VERSION=0.
1208 # Try to get the local_usn and time from objectClass
1209 # if possible and fallback to any other one.
1210 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1211 obj['replPropertyMetadata'][0])
1212 for o in repl.ctr.array:
1213 local_usn = o.local_usn
1214 t = o.originating_change_time
1215 if o.attid == drsuapi.DRSUAPI_ATTID_objectClass:
1218 # We use a magic invocationID for restoring missing
1219 # forward links to recover from bug #13228.
1220 # This should allow some more future magic to fix the
1223 # It also means it looses the conflict resolution
1224 # against almost every real invocation, if the
1225 # version is also 0.
1226 originating_invocid = misc.GUID("ffffffff-4700-4700-4700-000000b13228")
1232 rmd_invocid = originating_invocid
1233 rmd_originating_usn = originating_usn
1234 rmd_local_usn = local_usn
1237 target_dn.dn.set_extended_component("RMD_ADDTIME", str(rmd_addtime))
1238 target_dn.dn.set_extended_component("RMD_CHANGETIME", str(rmd_changetime))
1239 target_dn.dn.set_extended_component("RMD_FLAGS", str(rmd_flags))
1240 target_dn.dn.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid))
1241 target_dn.dn.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn))
1242 target_dn.dn.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn))
1243 target_dn.dn.set_extended_component("RMD_VERSION", str(rmd_version))
1246 missing_forward_links.append(target_dn)
1248 return (missing_forward_links, error_count)
1250 def check_dn(self, obj, attrname, syntax_oid):
1251 '''check a DN attribute for correctness'''
1253 obj_guid = obj['objectGUID'][0]
1255 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1256 if reverse_link_name is not None:
1257 reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
1259 reverse_syntax_oid = None
1261 is_member_link = attrname in ("member", "memberOf")
1262 if is_member_link and self.quick_membership_checks:
1265 error_count, duplicate_dict, unique_dict = \
1266 self.check_duplicate_links(obj, attrname, syntax_oid,
1267 linkID, reverse_link_name)
1269 if len(duplicate_dict) != 0:
1271 missing_forward_links, missing_error_count = \
1272 self.find_missing_forward_links_from_backlinks(obj,
1273 attrname, syntax_oid,
1276 error_count += missing_error_count
1278 forward_links = [dn for dn in unique_dict.values()]
1280 if missing_error_count != 0:
1281 self.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1284 self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
1285 for m in missing_forward_links:
1286 self.report("Missing link '%s'" % (m))
1287 if not self.confirm_all("Schedule readding missing forward link for attribute %s" % attrname,
1288 'fix_all_missing_forward_links'):
1289 self.err_orphaned_backlink(m.dn, reverse_link_name,
1290 obj.dn.extended_str(), obj.dn,
1291 attrname, syntax_oid,
1292 check_duplicates=False)
1294 forward_links += [m]
1295 for keystr in duplicate_dict.keys():
1296 d = duplicate_dict[keystr]
1297 for dd in d["delete"]:
1298 self.report("Duplicate link '%s'" % dd)
1299 self.report("Correct link '%s'" % d["keep"])
1301 # We now construct the sorted dn values.
1302 # They're sorted by the objectGUID of the target
1303 # See dsdb_Dn.__cmp__()
1304 vals = [str(dn) for dn in sorted(forward_links)]
1305 self.err_recover_forward_links(obj, attrname, vals)
1306 # We should continue with the fixed values
1307 obj[attrname] = ldb.MessageElement(vals, 0, attrname)
1309 for val in obj[attrname]:
1310 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
1312 # all DNs should have a GUID component
1313 guid = dsdb_dn.dn.get_extended_component("GUID")
1316 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
1320 guidstr = str(misc.GUID(guid))
1321 attrs = ['isDeleted', 'replPropertyMetaData']
1323 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
1324 fixing_msDS_HasInstantiatedNCs = True
1325 attrs.append("instanceType")
1327 fixing_msDS_HasInstantiatedNCs = False
1329 if reverse_link_name is not None:
1330 attrs.append(reverse_link_name)
1332 # check its the right GUID
1334 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
1335 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
1336 "reveal_internals:0"
1338 except ldb.LdbError as e3:
1339 (enum, estr) = e3.args
1340 if enum != ldb.ERR_NO_SUCH_OBJECT:
1343 # We don't always want to
1344 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
1350 if fixing_msDS_HasInstantiatedNCs:
1351 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
1352 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
1354 if str(dsdb_dn) != str(val):
1356 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
1359 # now we have two cases - the source object might or might not be deleted
1360 is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE'
1361 target_is_deleted = 'isDeleted' in res[0] and str(res[0]['isDeleted'][0]).upper() == 'TRUE'
1363 if is_deleted and obj.dn not in self.deleted_objects_containers and linkID:
1364 # A fully deleted object should not have any linked
1365 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1366 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1368 self.err_undead_linked_attribute(obj, attrname, val)
1371 elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
1372 # the target DN is not allowed to be deleted, unless the target DN is the
1373 # special Deleted Objects container
1375 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
1377 if 'replPropertyMetaData' in res[0]:
1378 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1379 res[0]['replPropertyMetadata'][0])
1381 for o in repl.ctr.array:
1382 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1383 deleted_usn = o.local_usn
1384 if deleted_usn >= int(local_usn):
1385 # If the object was deleted after the link
1386 # was last modified then, clean it up here
1391 self.err_deleted_dn(obj.dn, attrname,
1392 val, dsdb_dn, res[0].dn, True)
1395 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
1398 # We should not check for incorrect
1399 # components on deleted links, as these are allowed to
1400 # go stale (we just need the GUID, not the name)
1401 rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
1403 if rmd_blob is not None:
1404 rmd_flags = int(rmd_blob)
1406 # assert the DN matches in string form, where a reverse
1407 # link exists, otherwise (below) offer to fix it as a non-error.
1408 # The string form is essentially only kept for forensics,
1409 # as we always re-resolve by GUID in normal operations.
1410 if not rmd_flags & 1 and reverse_link_name is not None:
1411 if str(res[0].dn) != str(dsdb_dn.dn):
1413 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1414 res[0].dn, "string")
1417 if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
1419 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1423 target_sid = res[0].dn.get_extended_component("SID")
1424 link_sid = dsdb_dn.dn.get_extended_component("SID")
1425 if link_sid is None and target_sid is not None:
1427 self.err_dn_component_missing_target_sid(obj.dn, attrname, val,
1428 dsdb_dn, target_sid)
1430 if link_sid != target_sid:
1432 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1436 # Only for non-links, not even forward-only links
1437 # (otherwise this breaks repl_meta_data):
1439 # Now we have checked the GUID and SID, offer to fix old
1440 # DN strings as a non-error (DNs, not links so no
1441 # backlink). Samba does not maintain this string
1442 # otherwise, so we don't increment error_count.
1443 if reverse_link_name is None:
1444 if linkID == 0 and str(res[0].dn) != str(dsdb_dn.dn):
1445 # Pass in the old/bad DN without the <GUID=...> part,
1446 # otherwise the LDB code will correct it on the way through
1447 # (Note: we still want to preserve the DSDB DN prefix in the
1448 # case of binary DNs)
1449 bad_dn = dsdb_dn.prefix + dsdb_dn.dn.get_linearized()
1450 self.err_dn_string_component_old(obj.dn, attrname, bad_dn,
1454 if is_member_link and self.quick_membership_checks:
1457 # check the reverse_link is correct if there should be one
1459 if reverse_link_name in res[0]:
1460 for v in res[0][reverse_link_name]:
1461 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1462 v_guid = v_dn.dn.get_extended_component("GUID")
1463 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1465 if v_blob is not None:
1466 v_rmd_flags = int(v_blob)
1469 if v_guid == obj_guid:
1472 if match_count != 1:
1473 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1475 # Forward binary multi-valued linked attribute
1477 for w in obj[attrname]:
1478 w_guid = dsdb_Dn(self.samdb, w.decode('utf8')).dn.get_extended_component("GUID")
1482 if match_count == forward_count:
1485 for v in obj[attrname]:
1486 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1487 v_guid = v_dn.dn.get_extended_component("GUID")
1488 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1490 if v_blob is not None:
1491 v_rmd_flags = int(v_blob)
1497 if match_count == expected_count:
1500 diff_count = expected_count - match_count
1503 # If there's a backward link on binary multi-valued linked attribute,
1504 # let the check on the forward link remedy the value.
1505 # UNLESS, there is no forward link detected.
1506 if match_count == 0:
1508 self.err_orphaned_backlink(obj.dn, attrname,
1513 # Only warn here and let the forward link logic fix it.
1514 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1515 attrname, expected_count, str(obj.dn),
1516 reverse_link_name, match_count, str(dsdb_dn.dn)))
1519 assert not target_is_deleted
1521 self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1522 attrname, expected_count, str(obj.dn),
1523 reverse_link_name, match_count, str(dsdb_dn.dn)))
1525 # Loop until the difference between the forward and
1526 # the backward links is resolved.
1527 while diff_count != 0:
1530 if match_count > 0 or diff_count > 1:
1531 # TODO no method to fix these right now
1532 self.report("ERROR: Can't fix missing "
1533 "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1535 self.err_missing_backlink(obj, attrname,
1536 obj.dn.extended_str(),
1541 self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1542 obj.dn.extended_str(), obj.dn,
1543 attrname, syntax_oid)
1548 def find_repl_attid(self, repl, attid):
1549 for o in repl.ctr.array:
1550 if o.attid == attid:
1555 def get_originating_time(self, val, attid):
1556 '''Read metadata properties and return the originating time for
1557 a given attributeId.
1559 :return: the originating time or 0 if not found
1562 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1563 o = self.find_repl_attid(repl, attid)
1565 return o.originating_change_time
1568 def process_metadata(self, dn, val):
1569 '''Read metadata properties and list attributes in it.
1570 raises KeyError if the attid is unknown.'''
1573 wrong_attids = set()
1575 in_schema_nc = dn.is_child_of(self.schema_dn)
1577 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1579 for o in repl.ctr.array:
1580 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1581 set_att.add(att.lower())
1582 list_attid.append(o.attid)
1583 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1584 is_schema_nc=in_schema_nc)
1585 if correct_attid != o.attid:
1586 wrong_attids.add(o.attid)
1588 return (set_att, list_attid, wrong_attids)
1590 def fix_metadata(self, obj, attr):
1591 '''re-write replPropertyMetaData elements for a single attribute for a
1592 object. This is used to fix missing replPropertyMetaData elements'''
1593 guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1594 dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1595 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attr],
1596 controls=["search_options:1:2",
1599 nmsg = ldb.Message()
1601 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1602 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1603 "Failed to fix metadata for attribute %s" % attr):
1604 self.report("Fixed metadata for attribute %s" % attr)
1606 def ace_get_effective_inherited_type(self, ace):
1607 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1611 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1613 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1615 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1617 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1623 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1626 return str(ace.object.inherited_type)
1628 def lookup_class_schemaIDGUID(self, cls):
1629 if cls in self.class_schemaIDGUID:
1630 return self.class_schemaIDGUID[cls]
1632 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1633 res = self.samdb.search(base=self.schema_dn,
1635 attrs=["schemaIDGUID"])
1636 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1638 self.class_schemaIDGUID[cls] = t
1641 def process_sd(self, dn, obj):
1642 sd_attr = "nTSecurityDescriptor"
1643 sd_val = obj[sd_attr]
1645 sd = ndr_unpack(security.descriptor, sd_val[0])
1647 is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE'
1649 # we don't fix deleted objects
1652 sd_clean = security.descriptor()
1653 sd_clean.owner_sid = sd.owner_sid
1654 sd_clean.group_sid = sd.group_sid
1655 sd_clean.type = sd.type
1656 sd_clean.revision = sd.revision
1659 last_inherited_type = None
1662 if sd.sacl is not None:
1664 for i in range(0, len(aces)):
1667 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1668 sd_clean.sacl_add(ace)
1671 t = self.ace_get_effective_inherited_type(ace)
1675 if last_inherited_type is not None:
1676 if t != last_inherited_type:
1677 # if it inherited from more than
1678 # one type it's very likely to be broken
1680 # If not the recalculation will calculate
1685 last_inherited_type = t
1688 if sd.dacl is not None:
1690 for i in range(0, len(aces)):
1693 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1694 sd_clean.dacl_add(ace)
1697 t = self.ace_get_effective_inherited_type(ace)
1701 if last_inherited_type is not None:
1702 if t != last_inherited_type:
1703 # if it inherited from more than
1704 # one type it's very likely to be broken
1706 # If not the recalculation will calculate
1711 last_inherited_type = t
1714 return (sd_clean, sd)
1716 if last_inherited_type is None:
1722 cls = obj["objectClass"][-1]
1723 except KeyError as e:
1727 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1728 attrs=["isDeleted", "objectClass"],
1729 controls=["show_recycled:1"])
1731 is_deleted = 'isDeleted' in o and str(o['isDeleted'][0]).upper() == 'TRUE'
1733 # we don't fix deleted objects
1735 cls = o["objectClass"][-1]
1737 t = self.lookup_class_schemaIDGUID(cls)
1739 if t != last_inherited_type:
1741 return (sd_clean, sd)
1746 def err_wrong_sd(self, dn, sd, sd_broken):
1747 '''re-write the SD due to incorrect inherited ACEs'''
1748 sd_attr = "nTSecurityDescriptor"
1749 sd_val = ndr_pack(sd)
1750 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1752 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1753 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1756 nmsg = ldb.Message()
1758 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1759 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1760 "Failed to fix attribute %s" % sd_attr):
1761 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1763 def err_wrong_default_sd(self, dn, sd, diff):
1764 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1765 sd_attr = "nTSecurityDescriptor"
1766 sd_val = ndr_pack(sd)
1767 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1768 if sd.owner_sid is not None:
1769 sd_flags |= security.SECINFO_OWNER
1770 if sd.group_sid is not None:
1771 sd_flags |= security.SECINFO_GROUP
1773 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1774 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1779 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1780 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1781 "Failed to reset attribute %s" % sd_attr):
1782 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1784 def err_missing_sd_owner(self, dn, sd):
1785 '''re-write the SD due to a missing owner or group'''
1786 sd_attr = "nTSecurityDescriptor"
1787 sd_val = ndr_pack(sd)
1788 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1790 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1791 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1794 nmsg = ldb.Message()
1796 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1798 # By setting the session_info to admin_session_info and
1799 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1800 # flags we cause the descriptor module to set the correct
1801 # owner and group on the SD, replacing the None/NULL values
1802 # for owner_sid and group_sid currently present.
1804 # The admin_session_info matches that used in provision, and
1805 # is the best guess we can make for an existing object that
1806 # hasn't had something specifically set.
1808 # This is important for the dns related naming contexts.
1809 self.samdb.set_session_info(self.admin_session_info)
1810 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1811 "Failed to fix metadata for attribute %s" % sd_attr):
1812 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1813 self.samdb.set_session_info(self.system_session_info)
1815 def is_expired_tombstone(self, dn, repl_val):
1816 if self.check_expired_tombstones:
1817 # This is not the default, it's just
1818 # used to keep dbcheck tests work with
1819 # old static provision dumps
1822 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl_val)
1824 isDeleted = self.find_repl_attid(repl, drsuapi.DRSUAPI_ATTID_isDeleted)
1826 delete_time = samba.nttime2unix(isDeleted.originating_change_time)
1827 current_time = time.time()
1829 tombstone_delta = self.tombstoneLifetime * (24 * 60 * 60)
1831 delta = current_time - delete_time
1832 if delta <= tombstone_delta:
1835 self.report("SKIPING: object %s is an expired tombstone" % dn)
1836 self.report("isDeleted: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1839 isDeleted.originating_invocation_id,
1840 isDeleted.originating_usn,
1841 isDeleted.local_usn,
1842 time.ctime(samba.nttime2unix(isDeleted.originating_change_time))))
1843 self.expired_tombstones += 1
1846 def find_changes_after_deletion(self, repl_val):
1847 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl_val)
1849 isDeleted = self.find_repl_attid(repl, drsuapi.DRSUAPI_ATTID_isDeleted)
1851 delete_time = samba.nttime2unix(isDeleted.originating_change_time)
1853 tombstone_delta = self.tombstoneLifetime * (24 * 60 * 60)
1856 for o in repl.ctr.array:
1857 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1860 if o.local_usn <= isDeleted.local_usn:
1863 if o.originating_change_time <= isDeleted.originating_change_time:
1866 change_time = samba.nttime2unix(o.originating_change_time)
1868 delta = change_time - delete_time
1869 if delta <= tombstone_delta:
1872 # If the modification happened after the tombstone lifetime
1873 # has passed, we have a bug as the object might be deleted
1874 # already on other DCs and won't be able to replicate
1878 return found, isDeleted
1880 def has_changes_after_deletion(self, dn, repl_val):
1881 found, isDeleted = self.find_changes_after_deletion(repl_val)
1885 def report_attid(o):
1887 attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1889 attname = "<unknown:0x%x08x>" % o.attid
1891 self.report("%s: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1892 attname, o.attid, o.version,
1893 o.originating_invocation_id,
1896 time.ctime(samba.nttime2unix(o.originating_change_time))))
1898 self.report("ERROR: object %s, has changes after deletion" % dn)
1899 report_attid(isDeleted)
1905 def err_changes_after_deletion(self, dn, repl_val):
1906 found, isDeleted = self.find_changes_after_deletion(repl_val)
1908 in_schema_nc = dn.is_child_of(self.schema_dn)
1909 rdn_attr = dn.get_rdn_name()
1910 rdn_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(rdn_attr,
1911 is_schema_nc=in_schema_nc)
1915 if o.attid == rdn_attid:
1917 if o.attid == drsuapi.DRSUAPI_ATTID_name:
1919 if o.attid == drsuapi.DRSUAPI_ATTID_lastKnownParent:
1922 attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1924 attname = "<unknown:0x%x08x>" % o.attid
1925 unexpected.append(attname)
1927 if len(unexpected) > 0:
1928 self.report('Unexpeted attributes: %s' % ",".join(unexpected))
1929 self.report('Not fixing changes after deletion bug')
1932 if not self.confirm_all('Delete broken tombstone object %s deleted %s days ago?' % (
1933 dn, self.tombstoneLifetime), 'fix_changes_after_deletion_bug'):
1934 self.report('Not fixing changes after deletion bug')
1937 if self.do_delete(dn, ["relax:0"],
1938 "Failed to remove DN %s" % dn):
1939 self.report("Removed DN %s" % dn)
1941 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1942 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1947 # Search for a zero invocationID
1948 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1952 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1953 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1954 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1955 % (dn, o.attid, o.version,
1956 time.ctime(samba.nttime2unix(o.originating_change_time)),
1957 self.samdb.get_invocation_id()))
1961 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1962 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1965 now = samba.unix2nttime(int(time.time()))
1968 # Search for a zero invocationID
1969 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1973 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1974 o.version = o.version + 1
1975 o.originating_change_time = now
1976 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1977 o.originating_usn = seq
1981 replBlob = ndr_pack(repl)
1985 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1986 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1987 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1990 nmsg = ldb.Message()
1992 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1993 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1994 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1995 "Failed to fix attribute %s" % attr):
1996 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1998 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1999 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
2003 # Search for an invalid attid
2005 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
2007 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
2010 def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
2011 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
2016 remove_attid = set()
2019 in_schema_nc = dn.is_child_of(self.schema_dn)
2022 # Sort the array, except for the last element. This strange
2023 # construction, creating a new list, due to bugs in samba's
2024 # array handling in IDL generated objects.
2025 ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
2026 # Now walk it in reverse, so we see the low (and so incorrect,
2027 # the correct values are above 0x80000000) values first and
2028 # remove the 'second' value we see.
2029 for o in reversed(ctr.array):
2030 print("%s: 0x%08x" % (dn, o.attid))
2031 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
2032 if att.lower() in set_att:
2033 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
2034 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
2035 % (attr, dn, o.attid, att, hash_att[att].attid),
2036 'fix_replmetadata_duplicate_attid'):
2037 self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
2038 % (o.attid, att, attr, dn))
2041 remove_attid.add(o.attid)
2042 # We want to set the metadata for the most recent
2043 # update to have been applied locally, that is the metadata
2044 # matching the (eg string) value in the attribute
2045 if o.local_usn > hash_att[att].local_usn:
2046 # This is always what we would have sent over DRS,
2047 # because the DRS server will have sent the
2048 # msDS-IntID, but with the values from both
2049 # attribute entries.
2050 hash_att[att].version = o.version
2051 hash_att[att].originating_change_time = o.originating_change_time
2052 hash_att[att].originating_invocation_id = o.originating_invocation_id
2053 hash_att[att].originating_usn = o.originating_usn
2054 hash_att[att].local_usn = o.local_usn
2056 # Do not re-add the value to the set or overwrite the hash value
2060 set_att.add(att.lower())
2062 # Generate a real list we can sort on properly
2063 new_list = [o for o in ctr.array if o.attid not in remove_attid]
2065 if (len(wrong_attids) > 0):
2067 if o.attid in wrong_attids:
2068 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
2069 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
2070 self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
2071 if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
2072 % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
2073 self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
2074 % (o.attid, correct_attid, att, attr, dn))
2077 o.attid = correct_attid
2079 # Sort the array, (we changed the value so must re-sort)
2080 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
2082 # If we did not already need to fix it, then ask about sorting
2084 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
2085 if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
2086 % (attr, dn), 'fix_replmetadata_unsorted_attid'):
2087 self.report('Not fixing %s on %s\n' % (attr, dn))
2090 # The actual sort done is done at the top of the function
2092 ctr.count = len(new_list)
2093 ctr.array = new_list
2094 replBlob = ndr_pack(repl)
2096 nmsg = ldb.Message()
2098 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
2099 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
2100 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
2101 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
2102 "Failed to fix attribute %s" % attr):
2103 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
2105 def is_deleted_deleted_objects(self, obj):
2107 if "description" not in obj:
2108 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
2110 if "showInAdvancedViewOnly" not in obj or str(obj['showInAdvancedViewOnly'][0]).upper() == 'FALSE':
2111 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
2113 if "objectCategory" not in obj:
2114 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
2116 if "isCriticalSystemObject" not in obj or str(obj['isCriticalSystemObject'][0]).upper() == 'FALSE':
2117 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
2119 if "isRecycled" in obj:
2120 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
2122 if "isDeleted" in obj and str(obj['isDeleted'][0]).upper() == 'FALSE':
2123 self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
2125 if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
2126 str(obj['objectClass'][0]) != 'top' or
2127 str(obj['objectClass'][1]) != 'container'):
2128 self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
2130 if "systemFlags" not in obj or str(obj['systemFlags'][0]) != '-1946157056':
2131 self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
2135 def err_deleted_deleted_objects(self, obj):
2136 nmsg = ldb.Message()
2137 nmsg.dn = dn = obj.dn
2139 if "description" not in obj:
2140 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
2141 if "showInAdvancedViewOnly" not in obj:
2142 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
2143 if "objectCategory" not in obj:
2144 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
2145 if "isCriticalSystemObject" not in obj:
2146 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
2147 if "isRecycled" in obj:
2148 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
2150 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2151 nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
2152 nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
2154 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
2155 % (dn), 'fix_deleted_deleted_objects'):
2156 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
2159 if self.do_modify(nmsg, ["relax:0"],
2160 "Failed to fix Deleted Objects container %s" % dn):
2161 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
2163 def err_replica_locations(self, obj, cross_ref, attr):
2164 nmsg = ldb.Message()
2166 target = self.samdb.get_dsServiceName()
2168 if self.samdb.am_rodc():
2169 self.report('Not fixing %s %s for the RODC' % (attr, obj.dn))
2172 if not self.confirm_all('Add yourself to the replica locations for %s?'
2173 % (obj.dn), 'fix_replica_locations'):
2174 self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
2177 nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
2178 if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
2179 self.report("Fixed %s for %s" % (attr, obj.dn))
2181 def is_fsmo_role(self, dn):
2182 if dn == self.samdb.domain_dn:
2184 if dn == self.infrastructure_dn:
2186 if dn == self.naming_dn:
2188 if dn == self.schema_dn:
2190 if dn == self.rid_dn:
2195 def calculate_instancetype(self, dn):
2197 nc_root = self.samdb.get_nc_root(dn)
2199 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
2201 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
2202 except ldb.LdbError as e4:
2203 (enum, estr) = e4.args
2204 if enum != ldb.ERR_NO_SUCH_OBJECT:
2207 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
2208 if self.write_ncs is not None and str(nc_root) in [str(x) for x in self.write_ncs]:
2209 instancetype |= dsdb.INSTANCE_TYPE_WRITE
2213 def get_wellknown_sd(self, dn):
2214 for [sd_dn, descriptor_fn] in self.wellknown_sds:
2216 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
2217 return ndr_unpack(security.descriptor,
2218 descriptor_fn(domain_sid,
2219 name_map=self.name_map))
2223 def check_object(self, dn, attrs=None):
2224 '''check one object'''
2226 self.report("Checking object %s" % dn)
2230 # make a local copy to modify
2232 if "dn" in map(str.lower, attrs):
2233 attrs.append("name")
2234 if "distinguishedname" in map(str.lower, attrs):
2235 attrs.append("name")
2236 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
2237 attrs.append("name")
2238 if 'name' in map(str.lower, attrs):
2239 attrs.append(dn.get_rdn_name())
2240 attrs.append("isDeleted")
2241 attrs.append("systemFlags")
2242 need_replPropertyMetaData = False
2244 need_replPropertyMetaData = True
2247 linkID, _ = self.get_attr_linkID_and_reverse_name(a)
2252 need_replPropertyMetaData = True
2254 if need_replPropertyMetaData:
2255 attrs.append("replPropertyMetaData")
2256 attrs.append("objectGUID")
2260 sd_flags |= security.SECINFO_OWNER
2261 sd_flags |= security.SECINFO_GROUP
2262 sd_flags |= security.SECINFO_DACL
2263 sd_flags |= security.SECINFO_SACL
2265 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
2270 "sd_flags:1:%d" % sd_flags,
2271 "reveal_internals:0",
2274 except ldb.LdbError as e10:
2275 (enum, estr) = e10.args
2276 if enum == ldb.ERR_NO_SUCH_OBJECT:
2277 if self.in_transaction:
2278 self.report("ERROR: Object %s disappeared during check" % dn)
2283 self.report("ERROR: Object %s failed to load during check" % dn)
2287 set_attrs_from_md = set()
2288 set_attrs_seen = set()
2289 got_objectclass = False
2291 nc_dn = self.samdb.get_nc_root(obj.dn)
2293 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
2294 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
2296 # We have no deleted objects DN for schema, and we check for this above for the other
2298 deleted_objects_dn = None
2300 object_rdn_attr = None
2301 object_rdn_val = None
2305 repl_meta_data_val = None
2307 for attrname in obj:
2308 if str(attrname).lower() == 'isdeleted':
2309 if str(obj[attrname][0]) != "FALSE":
2312 if str(attrname).lower() == 'systemflags':
2313 systemFlags = int(obj[attrname][0])
2315 if str(attrname).lower() == 'replpropertymetadata':
2316 repl_meta_data_val = obj[attrname][0]
2318 if isDeleted and repl_meta_data_val:
2319 if self.has_changes_after_deletion(dn, repl_meta_data_val):
2321 self.err_changes_after_deletion(dn, repl_meta_data_val)
2323 if self.is_expired_tombstone(dn, repl_meta_data_val):
2326 for attrname in obj:
2327 if attrname == 'dn' or attrname == "distinguishedName":
2330 if str(attrname).lower() == 'objectclass':
2331 got_objectclass = True
2333 if str(attrname).lower() == "name":
2334 if len(obj[attrname]) != 1:
2336 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2337 (len(obj[attrname]), attrname, str(obj.dn)))
2339 name_val = str(obj[attrname][0])
2341 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
2342 object_rdn_attr = attrname
2343 if len(obj[attrname]) != 1:
2345 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2346 (len(obj[attrname]), attrname, str(obj.dn)))
2348 object_rdn_val = str(obj[attrname][0])
2350 if str(attrname).lower() == 'replpropertymetadata':
2351 if self.has_replmetadata_zero_invocationid(dn, obj[attrname][0]):
2353 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname][0])
2354 # We don't continue, as we may also have other fixes for this attribute
2355 # based on what other attributes we see.
2358 (set_attrs_from_md, list_attid_from_md, wrong_attids) \
2359 = self.process_metadata(dn, obj[attrname][0])
2362 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
2365 if len(set_attrs_from_md) < len(list_attid_from_md) \
2366 or len(wrong_attids) > 0 \
2367 or sorted(list_attid_from_md) != list_attid_from_md:
2369 self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname][0], wrong_attids)
2372 # Here we check that the first attid is 0
2374 if list_attid_from_md[0] != 0:
2376 self.report("ERROR: Not fixing incorrect initial attributeID in '%s' on '%s', it should be objectClass" %
2377 (attrname, str(dn)))
2381 if str(attrname).lower() == 'ntsecuritydescriptor':
2382 (sd, sd_broken) = self.process_sd(dn, obj)
2383 if sd_broken is not None:
2384 self.err_wrong_sd(dn, sd, sd_broken)
2388 if sd.owner_sid is None or sd.group_sid is None:
2389 self.err_missing_sd_owner(dn, sd)
2393 if self.reset_well_known_acls:
2395 well_known_sd = self.get_wellknown_sd(dn)
2399 current_sd = ndr_unpack(security.descriptor,
2402 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
2404 self.err_wrong_default_sd(dn, well_known_sd, diff)
2409 if str(attrname).lower() == 'objectclass':
2410 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
2411 # Do not consider the attribute incorrect if:
2412 # - The sorted (alphabetically) list is the same, inclding case
2413 # - The first and last elements are the same
2415 # This avoids triggering an error due to
2416 # non-determinism in the sort routine in (at least)
2417 # 4.3 and earlier, and the fact that any AUX classes
2418 # in these attributes are also not sorted when
2419 # imported from Windows (they are just in the reverse
2420 # order of last set)
2421 if sorted(normalised) != sorted(obj[attrname]) \
2422 or normalised[0] != obj[attrname][0] \
2423 or normalised[-1] != obj[attrname][-1]:
2424 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
2428 if str(attrname).lower() == 'userparameters':
2429 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == b'\x20'[0]:
2431 self.err_short_userParameters(obj, attrname, obj[attrname])
2434 elif obj[attrname][0][:16] == b'\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2435 # This is the correct, normal prefix
2438 elif obj[attrname][0][:20] == b'IAAgACAAIAAgACAAIAAg':
2439 # this is the typical prefix from a windows migration
2441 self.err_base64_userParameters(obj, attrname, obj[attrname])
2444 #43:00:00:00:74:00:00:00:78
2445 elif obj[attrname][0][1] != b'\x00'[0] and obj[attrname][0][3] != b'\x00'[0] and obj[attrname][0][5] != b'\x00'[0] and obj[attrname][0][7] != b'\x00'[0] and obj[attrname][0][9] != b'\x00'[0]:
2446 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2448 self.err_utf8_userParameters(obj, attrname, obj[attrname])
2451 elif len(obj[attrname][0]) % 2 != 0:
2452 # This is a value that isn't even in length
2454 self.err_odd_userParameters(obj, attrname)
2457 elif obj[attrname][0][1] == b'\x00'[0] and obj[attrname][0][2] == b'\x00'[0] and obj[attrname][0][3] == b'\x00'[0] and obj[attrname][0][4] != b'\x00'[0] and obj[attrname][0][5] == b'\x00'[0]:
2458 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2460 self.err_doubled_userParameters(obj, attrname, obj[attrname])
2463 if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2464 if obj[attrname][0] in self.attribute_or_class_ids:
2466 self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2467 % (attrname, obj.dn, obj[attrname][0]))
2469 self.attribute_or_class_ids.add(obj[attrname][0])
2471 # check for empty attributes
2472 for val in obj[attrname]:
2474 self.err_empty_attribute(dn, attrname)
2478 # get the syntax oid for the attribute, so we can can have
2479 # special handling for some specific attribute types
2481 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2482 except Exception as msg:
2483 self.err_unknown_attribute(obj, attrname)
2487 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2489 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2490 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2491 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2493 set_attrs_seen.add(str(attrname).lower())
2495 if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2496 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN]:
2497 # it's some form of DN, do specialised checking on those
2498 error_count += self.check_dn(obj, attrname, syntax_oid)
2502 # check for incorrectly normalised attributes
2503 for val in obj[attrname]:
2506 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2507 if len(normalised) != 1 or normalised[0] != val:
2508 self.err_normalise_mismatch(dn, attrname, obj[attrname])
2512 if len(obj[attrname]) != len(values):
2513 self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2517 if str(attrname).lower() == "instancetype":
2518 calculated_instancetype = self.calculate_instancetype(dn)
2519 if len(obj["instanceType"]) != 1 or int(obj["instanceType"][0]) != calculated_instancetype:
2521 self.err_wrong_instancetype(obj, calculated_instancetype)
2523 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
2525 self.err_missing_objectclass(dn)
2527 if ("*" in attrs or "name" in map(str.lower, attrs)):
2528 if name_val is None:
2530 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2531 if object_rdn_attr is None:
2533 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2535 if name_val is not None:
2537 controls = ["show_recycled:1", "relax:0"]
2539 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2540 parent_dn = deleted_objects_dn
2541 controls += ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME]
2542 if parent_dn is None:
2543 parent_dn = obj.dn.parent()
2544 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2545 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2547 if obj.dn == deleted_objects_dn:
2548 expected_dn = obj.dn
2550 if expected_dn != obj.dn:
2552 self.err_wrong_dn(obj, expected_dn, object_rdn_attr,
2553 object_rdn_val, name_val, controls)
2554 elif obj.dn.get_rdn_value() != object_rdn_val:
2556 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
2559 if repl_meta_data_val:
2560 if obj.dn == deleted_objects_dn:
2561 isDeletedAttId = 131120
2562 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2564 expectedTimeDo = 2650466015990000000
2565 originating = self.get_originating_time(repl_meta_data_val, isDeletedAttId)
2566 if originating != expectedTimeDo:
2567 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2568 nmsg = ldb.Message()
2570 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2572 self.samdb.modify(nmsg, controls=["provision:0"])
2575 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2577 for att in set_attrs_seen.difference(set_attrs_from_md):
2579 self.report("On object %s" % dn)
2582 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2583 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2584 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2586 self.fix_metadata(obj, att)
2588 if self.is_fsmo_role(dn):
2589 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
2590 self.err_no_fsmoRoleOwner(obj)
2594 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2595 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2596 controls=["show_recycled:1", "show_deleted:1"])
2597 except ldb.LdbError as e11:
2598 (enum, estr) = e11.args
2599 if enum == ldb.ERR_NO_SUCH_OBJECT:
2601 self.report("WARNING: parent object not found for %s" % (obj.dn))
2602 self.report("Not moving to LostAndFound "
2603 "(tombstone garbage collection in progress?)")
2605 self.err_missing_parent(obj)
2610 if dn in self.deleted_objects_containers and '*' in attrs:
2611 if self.is_deleted_deleted_objects(obj):
2612 self.err_deleted_deleted_objects(obj)
2615 for (dns_part, msg) in self.dns_partitions:
2616 if dn == dns_part and 'repsFrom' in obj:
2617 location = "msDS-NC-Replica-Locations"
2618 if self.samdb.am_rodc():
2619 location = "msDS-NC-RO-Replica-Locations"
2621 if location not in msg:
2622 # There are no replica locations!
2623 self.err_replica_locations(obj, msg.dn, location)
2628 for loc in msg[location]:
2629 if str(loc) == self.samdb.get_dsServiceName():
2632 # This DC is not in the replica locations
2633 self.err_replica_locations(obj, msg.dn, location)
2636 if dn == self.server_ref_dn:
2637 # Check we have a valid RID Set
2638 if "*" in attrs or "rIDSetReferences" in attrs:
2639 if "rIDSetReferences" not in obj:
2640 # NO RID SET reference
2641 # We are RID master, allocate it.
2644 if self.is_rid_master:
2645 # Allocate a RID Set
2646 if self.confirm_all('Allocate the missing RID set for RID master?',
2647 'fix_missing_rid_set_master'):
2649 # We don't have auto-transaction logic on
2650 # extended operations, so we have to do it
2653 self.samdb.transaction_start()
2656 self.samdb.create_own_rid_set()
2659 self.samdb.transaction_cancel()
2662 self.samdb.transaction_commit()
2664 elif not self.samdb.am_rodc():
2665 self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2667 # Check some details of our own RID Set
2668 if dn == self.rid_set_dn:
2669 res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2670 attrs=["rIDAllocationPool",
2671 "rIDPreviousAllocationPool",
2674 if "rIDAllocationPool" not in res[0]:
2675 self.report("No rIDAllocationPool found in %s" % dn)
2678 next_pool = int(res[0]["rIDAllocationPool"][0])
2680 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2681 low = 0x00000000FFFFFFFF & next_pool
2684 self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2687 if "rIDNextRID" in res[0]:
2688 next_free_rid = int(res[0]["rIDNextRID"][0])
2692 if next_free_rid == 0:
2697 # Check the remainder of this pool for conflicts. If
2698 # ridalloc_allocate_rid() moves to a new pool, this
2699 # will be above high, so we will stop.
2700 while next_free_rid <= high:
2701 sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2703 res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2705 except ldb.LdbError as e:
2706 (enum, estr) = e.args
2707 if enum != ldb.ERR_NO_SUCH_OBJECT:
2711 self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2714 if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2716 'fix_sid_rid_set_conflict'):
2717 self.samdb.transaction_start()
2719 # This will burn RIDs, which will move
2720 # past the conflict. We then check again
2721 # to see if the new RID conflicts, until
2722 # the end of the current pool. We don't
2723 # look at the next pool to avoid burning
2724 # all RIDs in one go in some strange
2728 allocated_rid = self.samdb.allocate_rid()
2729 if allocated_rid >= next_free_rid:
2730 next_free_rid = allocated_rid + 1
2733 self.samdb.transaction_cancel()
2736 self.samdb.transaction_commit()
2744 ################################################################
2745 # check special @ROOTDSE attributes
2746 def check_rootdse(self):
2747 '''check the @ROOTDSE special object'''
2748 dn = ldb.Dn(self.samdb, '@ROOTDSE')
2750 self.report("Checking object %s" % dn)
2751 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2753 self.report("Object %s disappeared during check" % dn)
2758 # check that the dsServiceName is in GUID form
2759 if 'dsServiceName' not in obj:
2760 self.report('ERROR: dsServiceName missing in @ROOTDSE')
2761 return error_count + 1
2763 if not str(obj['dsServiceName'][0]).startswith('<GUID='):
2764 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2766 if not self.confirm('Change dsServiceName to GUID form?'):
2768 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0].decode('utf8')),
2769 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2770 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2773 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2774 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2775 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2776 self.report("Changed dsServiceName to GUID form")
2779 ###############################################
2780 # re-index the database
2782 def reindex_database(self):
2783 '''re-index the whole database'''
2785 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2786 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2787 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2788 return self.do_modify(m, [], 're-indexed database', validate=False)
2790 ###############################################
2792 def reset_modules(self):
2793 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2795 m.dn = ldb.Dn(self.samdb, "@MODULES")
2796 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2797 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)