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.common 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 reset_well_known_acls=False):
65 self.dict_oid_name = None
66 self.samdb_schema = (samdb_schema or samdb)
67 self.verbose = verbose
71 self.remove_all_unknown_attributes = False
72 self.remove_all_empty_attributes = False
73 self.fix_all_normalisation = False
74 self.fix_all_duplicates = False
75 self.fix_all_DN_GUIDs = False
76 self.fix_all_binary_dn = False
77 self.remove_implausible_deleted_DN_links = False
78 self.remove_plausible_deleted_DN_links = False
79 self.fix_all_string_dn_component_mismatch = False
80 self.fix_all_GUID_dn_component_mismatch = False
81 self.fix_all_SID_dn_component_mismatch = False
82 self.fix_all_SID_dn_component_missing = False
83 self.fix_all_old_dn_string_component_mismatch = False
84 self.fix_all_metadata = False
85 self.fix_time_metadata = False
86 self.fix_undead_linked_attributes = False
87 self.fix_all_missing_backlinks = False
88 self.fix_all_orphaned_backlinks = False
89 self.fix_all_missing_forward_links = False
90 self.duplicate_link_cache = dict()
91 self.recover_all_forward_links = False
92 self.fix_rmd_flags = False
93 self.fix_ntsecuritydescriptor = False
94 self.fix_ntsecuritydescriptor_owner_group = False
95 self.seize_fsmo_role = False
96 self.move_to_lost_and_found = False
97 self.fix_instancetype = False
98 self.fix_replmetadata_zero_invocationid = False
99 self.fix_replmetadata_duplicate_attid = False
100 self.fix_replmetadata_wrong_attid = False
101 self.fix_replmetadata_unsorted_attid = False
102 self.fix_deleted_deleted_objects = False
103 self.fix_incorrect_deleted_objects = False
105 self.fix_base64_userparameters = False
106 self.fix_utf8_userparameters = False
107 self.fix_doubled_userparameters = False
108 self.fix_sid_rid_set_conflict = False
109 self.reset_well_known_acls = reset_well_known_acls
110 self.reset_all_well_known_acls = False
111 self.in_transaction = in_transaction
112 self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
113 self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
114 self.schema_dn = samdb.get_schema_basedn()
115 self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
116 self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
117 self.class_schemaIDGUID = {}
118 self.wellknown_sds = get_wellknown_sds(self.samdb)
119 self.fix_all_missing_objectclass = False
120 self.fix_missing_deleted_objects = False
121 self.fix_replica_locations = False
122 self.fix_missing_rid_set_master = False
125 self.link_id_cache = {}
128 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE,
130 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
131 self.name_map['DnsAdmins'] = str(dnsadmins_sid)
132 except ldb.LdbError as e5:
133 (enum, estr) = e5.args
134 if enum != ldb.ERR_NO_SUCH_OBJECT:
138 self.system_session_info = system_session()
139 self.admin_session_info = admin_session(None, samdb.get_domain_sid())
141 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
142 if "msDS-hasMasterNCs" in res[0]:
143 self.write_ncs = res[0]["msDS-hasMasterNCs"]
145 # If the Forest Level is less than 2003 then there is no
146 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
147 # no need to merge as all the NCs that are in hasMasterNCs must
148 # also be in msDS-hasMasterNCs (but not the opposite)
149 if "hasMasterNCs" in res[0]:
150 self.write_ncs = res[0]["hasMasterNCs"]
152 self.write_ncs = None
154 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
155 self.deleted_objects_containers = []
156 self.ncs_lacking_deleted_containers = []
157 self.dns_partitions = []
159 self.ncs = res[0]["namingContexts"]
167 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc.decode('utf8')),
168 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
169 self.deleted_objects_containers.append(dn)
171 self.ncs_lacking_deleted_containers.append(ldb.Dn(self.samdb, nc.decode('utf8')))
173 domaindns_zone = 'DC=DomainDnsZones,%s' % self.samdb.get_default_basedn()
174 forestdns_zone = 'DC=ForestDnsZones,%s' % self.samdb.get_root_basedn()
175 domain = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
176 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
177 base=self.samdb.get_partitions_dn(),
178 expression="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone)
180 self.dns_partitions.append((ldb.Dn(self.samdb, forestdns_zone), domain[0]))
182 forest = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
183 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
184 base=self.samdb.get_partitions_dn(),
185 expression="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone)
187 self.dns_partitions.append((ldb.Dn(self.samdb, domaindns_zone), forest[0]))
189 fsmo_dn = ldb.Dn(self.samdb, "CN=RID Manager$,CN=System," + self.samdb.domain_dn())
190 rid_master = get_fsmo_roleowner(self.samdb, fsmo_dn, "rid")
191 if ldb.Dn(self.samdb, self.samdb.get_dsServiceName()) == rid_master:
192 self.is_rid_master = True
194 self.is_rid_master = False
196 # To get your rid set
198 res = self.samdb.search(base=ldb.Dn(self.samdb, self.samdb.get_serverName()),
199 scope=ldb.SCOPE_BASE, attrs=["serverReference"])
200 # 2. Get server reference
201 self.server_ref_dn = ldb.Dn(self.samdb, res[0]['serverReference'][0].decode('utf8'))
204 res = self.samdb.search(base=self.server_ref_dn,
205 scope=ldb.SCOPE_BASE, attrs=['rIDSetReferences'])
206 if "rIDSetReferences" in res[0]:
207 self.rid_set_dn = ldb.Dn(self.samdb, res[0]['rIDSetReferences'][0].decode('utf8'))
209 self.rid_set_dn = None
211 self.compatibleFeatures = []
212 self.requiredFeatures = []
215 res = self.samdb.search(scope=ldb.SCOPE_BASE,
217 attrs=["compatibleFeatures",
219 if "compatibleFeatures" in res[0]:
220 self.compatibleFeatures = res[0]["compatibleFeatures"]
221 if "requiredFeatures" in res[0]:
222 self.requiredFeatures = res[0]["requiredFeatures"]
223 except ldb.LdbError as e6:
224 (enum, estr) = e6.args
225 if enum != ldb.ERR_NO_SUCH_OBJECT:
229 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
230 '''perform a database check, returning the number of errors found'''
231 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
232 self.report('Checking %u objects' % len(res))
235 error_count += self.check_deleted_objects_containers()
237 self.attribute_or_class_ids = set()
240 self.dn_set.add(str(object.dn))
241 error_count += self.check_object(object.dn, attrs=attrs)
244 error_count += self.check_rootdse()
246 if error_count != 0 and not self.fix:
247 self.report("Please use --fix to fix these errors")
249 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
252 def check_deleted_objects_containers(self):
253 """This function only fixes conflicts on the Deleted Objects
254 containers, not the attributes"""
256 for nc in self.ncs_lacking_deleted_containers:
257 if nc == self.schema_dn:
260 self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc)
261 if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'):
264 dn = ldb.Dn(self.samdb, "CN=Deleted Objects")
269 # If something already exists here, add a conflict
270 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[],
271 controls=["show_deleted:1", "extended_dn:1:1",
272 "show_recycled:1", "reveal_internals:0"])
274 guid = res[0].dn.get_extended_component("GUID")
275 conflict_dn = ldb.Dn(self.samdb,
276 "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid)))
277 conflict_dn.add_base(nc)
279 except ldb.LdbError as e2:
280 (enum, estr) = e2.args
281 if enum == ldb.ERR_NO_SUCH_OBJECT:
284 self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr)
287 if conflict_dn is not None:
289 self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"])
290 except ldb.LdbError as e1:
291 (enum, estr) = e1.args
292 self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr))
295 # Refresh wellKnownObjects links
296 res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE,
297 attrs=['wellKnownObjects'],
298 controls=["show_deleted:1", "extended_dn:0",
299 "show_recycled:1", "reveal_internals:0"])
301 self.report("wellKnownObjects was not found for NC %s" % nc)
304 # Prevent duplicate deleted objects containers just in case
305 wko = res[0]["wellKnownObjects"]
307 proposed_objectguid = None
309 dsdb_dn = dsdb_Dn(self.samdb, o.decode('utf8'), dsdb.DSDB_SYNTAX_BINARY_DN)
310 if self.is_deleted_objects_dn(dsdb_dn):
311 self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o)
312 # We really want to put this back in the same spot
313 # as the original one, so that on replication we
314 # merge, rather than conflict.
315 proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID")
316 listwko.append(str(o))
318 if proposed_objectguid is not None:
319 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid))
321 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
322 listwko.append('%s:%s' % (wko_prefix, dn))
325 # Insert a brand new Deleted Objects container
326 self.samdb.add_ldif("""dn: %s
328 objectClass: container
329 description: Container for deleted objects
331 isCriticalSystemObject: TRUE
332 showInAdvancedViewOnly: TRUE
333 systemFlags: -1946157056%s""" % (dn, guid_suffix),
334 controls=["relax:0", "provision:0"])
336 delta = ldb.Message()
337 delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"]))
338 delta["wellKnownObjects"] = ldb.MessageElement(listwko,
339 ldb.FLAG_MOD_REPLACE,
342 # Insert the link to the brand new container
343 if self.do_modify(delta, ["relax:0"],
344 "NC %s lacks Deleted Objects WKGUID" % nc,
346 self.report("Added %s well known guid link" % dn)
348 self.deleted_objects_containers.append(dn)
352 def report(self, msg):
353 '''print a message unless quiet is set'''
357 def confirm(self, msg, allow_all=False, forced=False):
358 '''confirm a change'''
365 return common.confirm(msg, forced=forced, allow_all=allow_all)
367 ################################################################
368 # a local confirm function with support for 'all'
369 def confirm_all(self, msg, all_attr):
370 '''confirm a change with support for "all" '''
373 if getattr(self, all_attr) == 'NONE':
375 if getattr(self, all_attr) == 'ALL':
381 c = common.confirm(msg, forced=forced, allow_all=True)
383 setattr(self, all_attr, 'ALL')
386 setattr(self, all_attr, 'NONE')
390 def do_delete(self, dn, controls, msg):
391 '''delete dn with optional verbose output'''
393 self.report("delete DN %s" % dn)
395 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
396 self.samdb.delete(dn, controls=controls)
397 except Exception as err:
398 if self.in_transaction:
399 raise CommandError("%s : %s" % (msg, err))
400 self.report("%s : %s" % (msg, err))
404 def do_modify(self, m, controls, msg, validate=True):
405 '''perform a modify with optional verbose output'''
406 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
408 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
409 self.report("controls: %r" % controls)
411 self.samdb.modify(m, controls=controls, validate=validate)
412 except Exception as err:
413 if self.in_transaction:
414 raise CommandError("%s : %s" % (msg, err))
415 self.report("%s : %s" % (msg, err))
419 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
420 '''perform a modify with optional verbose output'''
422 self.report("""dn: %s
426 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
428 to_dn = to_rdn + to_base
429 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
430 self.samdb.rename(from_dn, to_dn, controls=controls)
431 except Exception as err:
432 if self.in_transaction:
433 raise CommandError("%s : %s" % (msg, err))
434 self.report("%s : %s" % (msg, err))
438 def get_attr_linkID_and_reverse_name(self, attrname):
439 if attrname in self.link_id_cache:
440 return self.link_id_cache[attrname]
441 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
443 revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
446 self.link_id_cache[attrname] = (linkID, revname)
447 return linkID, revname
449 def err_empty_attribute(self, dn, attrname):
450 '''fix empty attributes'''
451 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
452 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
453 self.report("Not fixing empty attribute %s" % attrname)
458 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
459 if self.do_modify(m, ["relax:0", "show_recycled:1"],
460 "Failed to remove empty attribute %s" % attrname, validate=False):
461 self.report("Removed empty attribute %s" % attrname)
463 def err_normalise_mismatch(self, dn, attrname, values):
464 '''fix attribute normalisation errors'''
465 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
468 normalised = self.samdb.dsdb_normalise_attributes(
469 self.samdb_schema, attrname, [val])
470 if len(normalised) != 1:
471 self.report("Unable to normalise value '%s'" % val)
472 mod_list.append((val, ''))
473 elif (normalised[0] != val):
474 self.report("value '%s' should be '%s'" % (val, normalised[0]))
475 mod_list.append((val, normalised[0]))
476 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
477 self.report("Not fixing attribute %s" % attrname)
482 for i in range(0, len(mod_list)):
483 (val, nval) = mod_list[i]
484 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
486 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
489 if self.do_modify(m, ["relax:0", "show_recycled:1"],
490 "Failed to normalise attribute %s" % attrname,
492 self.report("Normalised attribute %s" % attrname)
494 def err_normalise_mismatch_replace(self, dn, attrname, values):
495 '''fix attribute normalisation errors'''
496 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
497 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
498 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
499 if list(normalised) == values:
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 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
509 if self.do_modify(m, ["relax:0", "show_recycled:1"],
510 "Failed to normalise attribute %s" % attrname,
512 self.report("Normalised attribute %s" % attrname)
514 def err_duplicate_values(self, dn, attrname, dup_values, values):
515 '''fix attribute normalisation errors'''
516 self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
517 self.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dump_attr_values(dup_values)), ','.join(dump_attr_values(values))))
518 if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
519 self.report("Not fixing attribute '%s'" % attrname)
524 m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
526 if self.do_modify(m, ["relax:0", "show_recycled:1"],
527 "Failed to remove duplicate value on attribute %s" % attrname,
529 self.report("Removed duplicate value on attribute %s" % attrname)
531 def is_deleted_objects_dn(self, dsdb_dn):
532 '''see if a dsdb_Dn is the special Deleted Objects DN'''
533 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
535 def err_missing_objectclass(self, dn):
536 """handle object without objectclass"""
537 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)))
538 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'):
539 self.report("Not deleting object with missing objectclass '%s'" % dn)
541 if self.do_delete(dn, ["relax:0"],
542 "Failed to remove DN %s" % dn):
543 self.report("Removed DN %s" % dn)
545 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False):
546 """handle a DN pointing to a deleted object"""
547 if not remove_plausible:
548 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
549 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
550 if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
551 self.report("Not removing")
554 self.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
555 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
556 if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
557 self.report("Not removing")
562 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
563 if self.do_modify(m, ["show_recycled:1",
564 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
565 "Failed to remove deleted DN attribute %s" % attrname):
566 self.report("Removed deleted DN on attribute %s" % attrname)
568 def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
569 """handle a missing target DN (if specified, GUID form can't be found,
570 and otherwise DN string form can't be found)"""
571 # check if its a backlink
572 linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
573 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
575 linkID, reverse_link_name \
576 = self.get_attr_linkID_and_reverse_name(attrname)
577 if reverse_link_name is not None:
578 self.report("WARNING: no target object found for GUID "
579 "component for one-way forward link "
581 "%s - %s" % (attrname, dn, val))
582 self.report("Not removing dangling forward link")
585 nc_root = self.samdb.get_nc_root(dn)
586 target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn)
587 if nc_root != target_nc_root:
588 # We don't bump the error count as Samba produces these
589 # in normal operation
590 self.report("WARNING: no target object found for GUID "
591 "component for cross-partition link "
593 "%s - %s" % (attrname, dn, val))
594 self.report("Not removing dangling one-way "
595 "cross-partition link "
596 "(we might be mid-replication)")
599 # Due to our link handling one-way links pointing to
600 # missing objects are plausible.
602 # We don't bump the error count as Samba produces these
603 # in normal operation
604 self.report("WARNING: no target object found for GUID "
605 "component for DN value %s in object "
606 "%s - %s" % (attrname, dn, val))
607 self.err_deleted_dn(dn, attrname, val,
608 dsdb_dn, dsdb_dn, True)
611 # We bump the error count here, as we should have deleted this
612 self.report("ERROR: no target object found for GUID "
613 "component for link %s in object "
614 "%s - %s" % (attrname, dn, val))
615 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
618 def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
619 """handle a missing GUID extended DN component"""
620 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
621 controls = ["extended_dn:1:1", "show_recycled:1"]
623 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
624 attrs=[], controls=controls)
625 except ldb.LdbError as e7:
626 (enum, estr) = e7.args
627 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
628 if enum != ldb.ERR_NO_SUCH_OBJECT:
630 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
633 self.report("unable to find object for DN %s" % dsdb_dn.dn)
634 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
636 dsdb_dn.dn = res[0].dn
638 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
639 self.report("Not fixing %s" % errstr)
643 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
644 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
646 if self.do_modify(m, ["show_recycled:1"],
647 "Failed to fix %s on attribute %s" % (errstr, attrname)):
648 self.report("Fixed %s on attribute %s" % (errstr, attrname))
650 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
651 """handle an incorrect binary DN component"""
652 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
653 controls = ["extended_dn:1:1", "show_recycled:1"]
655 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
656 self.report("Not fixing %s" % errstr)
660 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
661 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
663 if self.do_modify(m, ["show_recycled:1"],
664 "Failed to fix %s on attribute %s" % (errstr, attrname)):
665 self.report("Fixed %s on attribute %s" % (errstr, attrname))
667 def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn):
668 """handle a DN string being incorrect"""
669 self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val))
670 dsdb_dn.dn = correct_dn
672 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
673 'fix_all_old_dn_string_component_mismatch'):
674 self.report("Not fixing old string component")
678 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
679 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
680 if self.do_modify(m, ["show_recycled:1",
681 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME],
682 "Failed to fix old DN string on attribute %s" % (attrname)):
683 self.report("Fixed old DN string on attribute %s" % (attrname))
685 def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
686 """handle a DN string being incorrect"""
687 self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
688 dsdb_dn.dn = correct_dn
690 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
691 'fix_all_%s_dn_component_mismatch' % mismatch_type):
692 self.report("Not fixing %s component mismatch" % mismatch_type)
696 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
697 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
698 if self.do_modify(m, ["show_recycled:1"],
699 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
700 self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
702 def err_dn_component_missing_target_sid(self, dn, attrname, val, dsdb_dn, target_sid_blob):
703 """handle a DN string being incorrect"""
704 self.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname, dn, val))
706 if len(dsdb_dn.prefix) != 0:
707 self.report("Not fixing missing DN SID on DN+BINARY or DN+STRING")
710 correct_dn = ldb.Dn(self.samdb, dsdb_dn.dn.extended_str())
711 correct_dn.set_extended_component("SID", target_sid_blob)
713 if not self.confirm_all('Change DN to %s?' % correct_dn.extended_str(),
714 'fix_all_SID_dn_component_missing'):
715 self.report("Not fixing missing DN SID component")
718 target_guid_blob = correct_dn.get_extended_component("GUID")
719 guid_sid_dn = ldb.Dn(self.samdb, "")
720 guid_sid_dn.set_extended_component("GUID", target_guid_blob)
721 guid_sid_dn.set_extended_component("SID", target_sid_blob)
725 m['new_value'] = ldb.MessageElement(guid_sid_dn.extended_str(), ldb.FLAG_MOD_ADD, attrname)
728 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID
730 if self.do_modify(m, controls,
731 "Failed to ADD missing DN SID on attribute %s" % (attrname)):
732 self.report("Fixed missing DN SID on attribute %s" % (attrname))
734 def err_unknown_attribute(self, obj, attrname):
735 '''handle an unknown attribute error'''
736 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
737 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
738 self.report("Not removing %s" % attrname)
742 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
743 if self.do_modify(m, ["relax:0", "show_recycled:1"],
744 "Failed to remove unknown attribute %s" % attrname):
745 self.report("Removed unknown attribute %s" % (attrname))
747 def err_undead_linked_attribute(self, obj, attrname, val):
748 '''handle a link that should not be there on a deleted object'''
749 self.report("ERROR: linked attribute '%s' to '%s' is present on "
750 "deleted object %s" % (attrname, val, obj.dn))
751 if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
752 self.report("Not removing linked attribute %s" % attrname)
756 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
758 if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
759 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
760 "Failed to delete forward link %s" % attrname):
761 self.report("Fixed undead forward link %s" % (attrname))
763 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
764 '''handle a missing backlink value'''
765 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
766 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
767 self.report("Not fixing missing backlink %s" % backlink_name)
771 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
772 if self.do_modify(m, ["show_recycled:1", "relax:0"],
773 "Failed to fix missing backlink %s" % backlink_name):
774 self.report("Fixed missing backlink %s" % (backlink_name))
776 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
777 '''handle a incorrect RMD_FLAGS value'''
778 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
779 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()))
780 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
781 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
785 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
786 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
787 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
788 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
790 def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val,
791 target_dn, forward_attr, forward_syntax,
792 check_duplicates=True):
793 '''handle a orphaned backlink value'''
794 if check_duplicates is True and self.has_duplicate_links(target_dn, forward_attr, forward_syntax):
795 self.report("WARNING: Keep orphaned backlink attribute " +
796 "'%s' in '%s' for link '%s' in '%s'" % (
797 backlink_attr, obj_dn, forward_attr, target_dn))
799 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn))
800 if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'):
801 self.report("Not removing orphaned backlink %s" % backlink_attr)
805 m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr)
806 if self.do_modify(m, ["show_recycled:1", "relax:0"],
807 "Failed to fix orphaned backlink %s" % backlink_attr):
808 self.report("Fixed orphaned backlink %s" % (backlink_attr))
810 def err_recover_forward_links(self, obj, forward_attr, forward_vals):
811 '''handle a duplicate links value'''
813 self.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn))
815 if not self.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'):
816 self.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
817 forward_attr, obj.dn))
821 m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr)
822 if self.do_modify(m, ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS],
823 "Failed to fix duplicate links in attribute '%s'" % forward_attr):
824 self.report("Fixed duplicate links in attribute '%s'" % (forward_attr))
825 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
826 assert duplicate_cache_key in self.duplicate_link_cache
827 self.duplicate_link_cache[duplicate_cache_key] = False
829 def err_no_fsmoRoleOwner(self, obj):
830 '''handle a missing fSMORoleOwner'''
831 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
832 res = self.samdb.search("",
833 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
835 serviceName = str(res[0]["dsServiceName"][0])
836 if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
837 self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
841 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
842 if self.do_modify(m, [],
843 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
844 self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
846 def err_missing_parent(self, obj):
847 '''handle a missing parent'''
848 self.report("ERROR: parent object not found for %s" % (obj.dn))
849 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
850 self.report('Not moving object %s into LostAndFound' % (obj.dn))
853 keep_transaction = False
854 self.samdb.transaction_start()
856 nc_root = self.samdb.get_nc_root(obj.dn)
857 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
858 new_dn = ldb.Dn(self.samdb, str(obj.dn))
859 new_dn.remove_base_components(len(new_dn) - 1)
860 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
861 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
862 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
866 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
868 if self.do_modify(m, [],
869 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
870 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
871 keep_transaction = True
873 self.samdb.transaction_cancel()
877 self.samdb.transaction_commit()
879 self.samdb.transaction_cancel()
881 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
882 '''handle a wrong dn'''
884 new_rdn = ldb.Dn(self.samdb, str(new_dn))
885 new_rdn.remove_base_components(len(new_rdn) - 1)
886 new_parent = new_dn.parent()
889 if rdn_val != name_val:
890 attributes += "%s=%r " % (rdn_attr, rdn_val)
891 attributes += "name=%r" % (name_val)
893 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
894 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
895 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
898 if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
899 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
900 self.report("Renamed %s into %s" % (obj.dn, new_dn))
902 def err_wrong_instancetype(self, obj, calculated_instancetype):
903 '''handle a wrong instanceType'''
904 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
905 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
906 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
911 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
912 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
913 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
914 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
916 def err_short_userParameters(self, obj, attrname, value):
917 # This is a truncated userParameters due to a pre 4.1 replication bug
918 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)))
920 def err_base64_userParameters(self, obj, attrname, value):
921 '''handle a wrong userParameters'''
922 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
923 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
924 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
929 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
930 if self.do_modify(m, [],
931 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
932 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
934 def err_utf8_userParameters(self, obj, attrname, value):
935 '''handle a wrong userParameters'''
936 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
937 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
938 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
943 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
944 ldb.FLAG_MOD_REPLACE, 'userParameters')
945 if self.do_modify(m, [],
946 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
947 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
949 def err_doubled_userParameters(self, obj, attrname, value):
950 '''handle a wrong userParameters'''
951 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
952 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
953 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
958 # m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
959 # hmm the above old python2 code doesn't make sense to me and cannot
960 # work in python3 because a string doesn't have a decode method.
961 # However in python2 for some unknown reason this double decode
962 # followed by encode seems to result in what looks like utf8.
963 # In python2 just .decode('utf-16-le').encode('utf-16-le') does nothing
964 # but trigger the 'double UTF16 encoded' condition again :/
966 # In python2 and python3 value.decode('utf-16-le').encode('utf8') seems
967 # to do the trick and work as expected.
968 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').encode('utf8'),
969 ldb.FLAG_MOD_REPLACE, 'userParameters')
971 if self.do_modify(m, [],
972 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
973 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
975 def err_odd_userParameters(self, obj, attrname):
976 # This is a truncated userParameters due to a pre 4.1 replication bug
977 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)))
979 def find_revealed_link(self, dn, attrname, guid):
980 '''return a revealed link in an object'''
981 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
982 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
983 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
984 for val in res[0][attrname]:
985 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
986 guid2 = dsdb_dn.dn.get_extended_component("GUID")
991 def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr):
992 '''check a linked values for duplicate forward links'''
995 duplicate_dict = dict()
998 # Only forward links can have this problem
999 if forward_linkID & 1:
1000 # If we got the reverse, skip it
1001 return (error_count, duplicate_dict, unique_dict)
1003 if backlink_attr is None:
1004 return (error_count, duplicate_dict, unique_dict)
1006 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
1007 if duplicate_cache_key not in self.duplicate_link_cache:
1008 self.duplicate_link_cache[duplicate_cache_key] = False
1010 for val in obj[forward_attr]:
1011 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), forward_syntax)
1013 # all DNs should have a GUID component
1014 guid = dsdb_dn.dn.get_extended_component("GUID")
1017 guidstr = str(misc.GUID(guid))
1018 keystr = guidstr + dsdb_dn.prefix
1019 if keystr not in unique_dict:
1020 unique_dict[keystr] = dsdb_dn
1023 if keystr not in duplicate_dict:
1024 duplicate_dict[keystr] = dict()
1025 duplicate_dict[keystr]["keep"] = None
1026 duplicate_dict[keystr]["delete"] = list()
1028 # Now check for the highest RMD_VERSION
1029 v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION"))
1030 v2 = int(dsdb_dn.dn.get_extended_component("RMD_VERSION"))
1032 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
1033 duplicate_dict[keystr]["delete"].append(dsdb_dn)
1036 duplicate_dict[keystr]["keep"] = dsdb_dn
1037 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
1038 unique_dict[keystr] = dsdb_dn
1040 # Fallback to the highest RMD_LOCAL_USN
1041 u1 = int(unique_dict[keystr].dn.get_extended_component("RMD_LOCAL_USN"))
1042 u2 = int(dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN"))
1044 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
1045 duplicate_dict[keystr]["delete"].append(dsdb_dn)
1047 duplicate_dict[keystr]["keep"] = dsdb_dn
1048 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
1049 unique_dict[keystr] = dsdb_dn
1051 if error_count != 0:
1052 self.duplicate_link_cache[duplicate_cache_key] = True
1054 return (error_count, duplicate_dict, unique_dict)
1056 def has_duplicate_links(self, dn, forward_attr, forward_syntax):
1057 '''check a linked values for duplicate forward links'''
1060 duplicate_cache_key = "%s:%s" % (str(dn), forward_attr)
1061 if duplicate_cache_key in self.duplicate_link_cache:
1062 return self.duplicate_link_cache[duplicate_cache_key]
1064 forward_linkID, backlink_attr = self.get_attr_linkID_and_reverse_name(forward_attr)
1066 attrs = [forward_attr]
1067 controls = ["extended_dn:1:1", "reveal_internals:0"]
1069 # check its the right GUID
1071 res = self.samdb.search(base=str(dn), scope=ldb.SCOPE_BASE,
1072 attrs=attrs, controls=controls)
1073 except ldb.LdbError as e8:
1074 (enum, estr) = e8.args
1075 if enum != ldb.ERR_NO_SUCH_OBJECT:
1081 error_count, duplicate_dict, unique_dict = \
1082 self.check_duplicate_links(obj, forward_attr, forward_syntax, forward_linkID, backlink_attr)
1084 if duplicate_cache_key in self.duplicate_link_cache:
1085 return self.duplicate_link_cache[duplicate_cache_key]
1089 def find_missing_forward_links_from_backlinks(self, obj,
1093 forward_unique_dict):
1094 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1095 missing_forward_links = []
1098 if backlink_attr is None:
1099 return (missing_forward_links, error_count)
1101 if forward_syntax != ldb.SYNTAX_DN:
1102 self.report("Not checking for missing forward links for syntax: %s" %
1104 return (missing_forward_links, error_count)
1106 if "sortedLinks" in self.compatibleFeatures:
1107 self.report("Not checking for missing forward links because the db " +
1108 "has the sortedLinks feature")
1109 return (missing_forward_links, error_count)
1112 obj_guid = obj['objectGUID'][0]
1113 obj_guid_str = str(ndr_unpack(misc.GUID, obj_guid))
1114 filter = "(%s=<GUID=%s>)" % (backlink_attr, obj_guid_str)
1116 res = self.samdb.search(expression=filter,
1117 scope=ldb.SCOPE_SUBTREE, attrs=["objectGUID"],
1118 controls=["extended_dn:1:1",
1119 "search_options:1:2",
1120 "paged_results:1:1000"])
1121 except ldb.LdbError as e9:
1122 (enum, estr) = e9.args
1126 target_dn = dsdb_Dn(self.samdb, r.dn.extended_str(), forward_syntax)
1128 guid = target_dn.dn.get_extended_component("GUID")
1129 guidstr = str(misc.GUID(guid))
1130 if guidstr in forward_unique_dict:
1133 # A valid forward link looks like this:
1135 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1136 # <RMD_ADDTIME=131607546230000000>;
1137 # <RMD_CHANGETIME=131607546230000000>;
1139 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1140 # <RMD_LOCAL_USN=3765>;
1141 # <RMD_ORIGINATING_USN=3765>;
1143 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1144 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1146 # Note that versions older than Samba 4.8 create
1147 # links with RMD_VERSION=0.
1149 # Try to get the local_usn and time from objectClass
1150 # if possible and fallback to any other one.
1151 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1152 obj['replPropertyMetadata'][0])
1153 for o in repl.ctr.array:
1154 local_usn = o.local_usn
1155 t = o.originating_change_time
1156 if o.attid == drsuapi.DRSUAPI_ATTID_objectClass:
1159 # We use a magic invocationID for restoring missing
1160 # forward links to recover from bug #13228.
1161 # This should allow some more future magic to fix the
1164 # It also means it looses the conflict resolution
1165 # against almost every real invocation, if the
1166 # version is also 0.
1167 originating_invocid = misc.GUID("ffffffff-4700-4700-4700-000000b13228")
1173 rmd_invocid = originating_invocid
1174 rmd_originating_usn = originating_usn
1175 rmd_local_usn = local_usn
1178 target_dn.dn.set_extended_component("RMD_ADDTIME", str(rmd_addtime))
1179 target_dn.dn.set_extended_component("RMD_CHANGETIME", str(rmd_changetime))
1180 target_dn.dn.set_extended_component("RMD_FLAGS", str(rmd_flags))
1181 target_dn.dn.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid))
1182 target_dn.dn.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn))
1183 target_dn.dn.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn))
1184 target_dn.dn.set_extended_component("RMD_VERSION", str(rmd_version))
1187 missing_forward_links.append(target_dn)
1189 return (missing_forward_links, error_count)
1191 def check_dn(self, obj, attrname, syntax_oid):
1192 '''check a DN attribute for correctness'''
1194 obj_guid = obj['objectGUID'][0]
1196 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1197 if reverse_link_name is not None:
1198 reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
1200 reverse_syntax_oid = None
1202 error_count, duplicate_dict, unique_dict = \
1203 self.check_duplicate_links(obj, attrname, syntax_oid, linkID, reverse_link_name)
1205 if len(duplicate_dict) != 0:
1207 missing_forward_links, missing_error_count = \
1208 self.find_missing_forward_links_from_backlinks(obj,
1209 attrname, syntax_oid,
1212 error_count += missing_error_count
1214 forward_links = [dn for dn in unique_dict.values()]
1216 if missing_error_count != 0:
1217 self.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1220 self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
1221 for m in missing_forward_links:
1222 self.report("Missing link '%s'" % (m))
1223 if not self.confirm_all("Schedule readding missing forward link for attribute %s" % attrname,
1224 'fix_all_missing_forward_links'):
1225 self.err_orphaned_backlink(m.dn, reverse_link_name,
1226 obj.dn.extended_str(), obj.dn,
1227 attrname, syntax_oid,
1228 check_duplicates=False)
1230 forward_links += [m]
1231 for keystr in duplicate_dict.keys():
1232 d = duplicate_dict[keystr]
1233 for dd in d["delete"]:
1234 self.report("Duplicate link '%s'" % dd)
1235 self.report("Correct link '%s'" % d["keep"])
1237 # We now construct the sorted dn values.
1238 # They're sorted by the objectGUID of the target
1239 # See dsdb_Dn.__cmp__()
1240 vals = [str(dn) for dn in sorted(forward_links)]
1241 self.err_recover_forward_links(obj, attrname, vals)
1242 # We should continue with the fixed values
1243 obj[attrname] = ldb.MessageElement(vals, 0, attrname)
1245 for val in obj[attrname]:
1246 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
1248 # all DNs should have a GUID component
1249 guid = dsdb_dn.dn.get_extended_component("GUID")
1252 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
1256 guidstr = str(misc.GUID(guid))
1257 attrs = ['isDeleted', 'replPropertyMetaData']
1259 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
1260 fixing_msDS_HasInstantiatedNCs = True
1261 attrs.append("instanceType")
1263 fixing_msDS_HasInstantiatedNCs = False
1265 if reverse_link_name is not None:
1266 attrs.append(reverse_link_name)
1268 # check its the right GUID
1270 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
1271 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
1272 "reveal_internals:0"
1274 except ldb.LdbError as e3:
1275 (enum, estr) = e3.args
1276 if enum != ldb.ERR_NO_SUCH_OBJECT:
1279 # We don't always want to
1280 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
1286 if fixing_msDS_HasInstantiatedNCs:
1287 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
1288 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
1290 if str(dsdb_dn) != str(val):
1292 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
1295 # now we have two cases - the source object might or might not be deleted
1296 is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE'
1297 target_is_deleted = 'isDeleted' in res[0] and str(res[0]['isDeleted'][0]).upper() == 'TRUE'
1299 if is_deleted and obj.dn not in self.deleted_objects_containers and linkID:
1300 # A fully deleted object should not have any linked
1301 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1302 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1304 self.err_undead_linked_attribute(obj, attrname, val)
1307 elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
1308 # the target DN is not allowed to be deleted, unless the target DN is the
1309 # special Deleted Objects container
1311 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
1313 if 'replPropertyMetaData' in res[0]:
1314 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1315 res[0]['replPropertyMetadata'][0])
1317 for o in repl.ctr.array:
1318 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1319 deleted_usn = o.local_usn
1320 if deleted_usn >= int(local_usn):
1321 # If the object was deleted after the link
1322 # was last modified then, clean it up here
1327 self.err_deleted_dn(obj.dn, attrname,
1328 val, dsdb_dn, res[0].dn, True)
1331 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
1334 # We should not check for incorrect
1335 # components on deleted links, as these are allowed to
1336 # go stale (we just need the GUID, not the name)
1337 rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
1339 if rmd_blob is not None:
1340 rmd_flags = int(rmd_blob)
1342 # assert the DN matches in string form, where a reverse
1343 # link exists, otherwise (below) offer to fix it as a non-error.
1344 # The string form is essentially only kept for forensics,
1345 # as we always re-resolve by GUID in normal operations.
1346 if not rmd_flags & 1 and reverse_link_name is not None:
1347 if str(res[0].dn) != str(dsdb_dn.dn):
1349 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1350 res[0].dn, "string")
1353 if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
1355 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1359 target_sid = res[0].dn.get_extended_component("SID")
1360 link_sid = dsdb_dn.dn.get_extended_component("SID")
1361 if link_sid is None and target_sid is not None:
1363 self.err_dn_component_missing_target_sid(obj.dn, attrname, val,
1364 dsdb_dn, target_sid)
1366 if link_sid != target_sid:
1368 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1372 # Only for non-links, not even forward-only links
1373 # (otherwise this breaks repl_meta_data):
1375 # Now we have checked the GUID and SID, offer to fix old
1376 # DN strings as a non-error (DNs, not links so no
1377 # backlink). Samba does not maintain this string
1378 # otherwise, so we don't increment error_count.
1379 if reverse_link_name is None:
1380 if linkID == 0 and str(res[0].dn) != str(dsdb_dn.dn):
1381 # Pass in the old/bad DN without the <GUID=...> part,
1382 # otherwise the LDB code will correct it on the way through
1383 # (Note: we still want to preserve the DSDB DN prefix in the
1384 # case of binary DNs)
1385 bad_dn = dsdb_dn.prefix + dsdb_dn.dn.get_linearized()
1386 self.err_dn_string_component_old(obj.dn, attrname, bad_dn,
1390 # check the reverse_link is correct if there should be one
1392 if reverse_link_name in res[0]:
1393 for v in res[0][reverse_link_name]:
1394 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1395 v_guid = v_dn.dn.get_extended_component("GUID")
1396 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1398 if v_blob is not None:
1399 v_rmd_flags = int(v_blob)
1402 if v_guid == obj_guid:
1405 if match_count != 1:
1406 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1408 # Forward binary multi-valued linked attribute
1410 for w in obj[attrname]:
1411 w_guid = dsdb_Dn(self.samdb, w.decode('utf8')).dn.get_extended_component("GUID")
1415 if match_count == forward_count:
1418 for v in obj[attrname]:
1419 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1420 v_guid = v_dn.dn.get_extended_component("GUID")
1421 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1423 if v_blob is not None:
1424 v_rmd_flags = int(v_blob)
1430 if match_count == expected_count:
1433 diff_count = expected_count - match_count
1436 # If there's a backward link on binary multi-valued linked attribute,
1437 # let the check on the forward link remedy the value.
1438 # UNLESS, there is no forward link detected.
1439 if match_count == 0:
1441 self.err_orphaned_backlink(obj.dn, attrname,
1446 # Only warn here and let the forward link logic fix it.
1447 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1448 attrname, expected_count, str(obj.dn),
1449 reverse_link_name, match_count, str(dsdb_dn.dn)))
1452 assert not target_is_deleted
1454 self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1455 attrname, expected_count, str(obj.dn),
1456 reverse_link_name, match_count, str(dsdb_dn.dn)))
1458 # Loop until the difference between the forward and
1459 # the backward links is resolved.
1460 while diff_count != 0:
1463 if match_count > 0 or diff_count > 1:
1464 # TODO no method to fix these right now
1465 self.report("ERROR: Can't fix missing "
1466 "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1468 self.err_missing_backlink(obj, attrname,
1469 obj.dn.extended_str(),
1474 self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1475 obj.dn.extended_str(), obj.dn,
1476 attrname, syntax_oid)
1481 def get_originating_time(self, val, attid):
1482 '''Read metadata properties and return the originating time for
1483 a given attributeId.
1485 :return: the originating time or 0 if not found
1488 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1491 for o in repl.ctr.array:
1492 if o.attid == attid:
1493 return o.originating_change_time
1497 def process_metadata(self, dn, val):
1498 '''Read metadata properties and list attributes in it.
1499 raises KeyError if the attid is unknown.'''
1502 wrong_attids = set()
1504 in_schema_nc = dn.is_child_of(self.schema_dn)
1506 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1509 for o in repl.ctr.array:
1510 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1511 set_att.add(att.lower())
1512 list_attid.append(o.attid)
1513 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1514 is_schema_nc=in_schema_nc)
1515 if correct_attid != o.attid:
1516 wrong_attids.add(o.attid)
1518 return (set_att, list_attid, wrong_attids)
1520 def fix_metadata(self, obj, attr):
1521 '''re-write replPropertyMetaData elements for a single attribute for a
1522 object. This is used to fix missing replPropertyMetaData elements'''
1523 guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1524 dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1525 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attr],
1526 controls=["search_options:1:2",
1529 nmsg = ldb.Message()
1531 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1532 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1533 "Failed to fix metadata for attribute %s" % attr):
1534 self.report("Fixed metadata for attribute %s" % attr)
1536 def ace_get_effective_inherited_type(self, ace):
1537 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1541 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1543 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1545 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1547 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1553 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1556 return str(ace.object.inherited_type)
1558 def lookup_class_schemaIDGUID(self, cls):
1559 if cls in self.class_schemaIDGUID:
1560 return self.class_schemaIDGUID[cls]
1562 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1563 res = self.samdb.search(base=self.schema_dn,
1565 attrs=["schemaIDGUID"])
1566 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1568 self.class_schemaIDGUID[cls] = t
1571 def process_sd(self, dn, obj):
1572 sd_attr = "nTSecurityDescriptor"
1573 sd_val = obj[sd_attr]
1575 sd = ndr_unpack(security.descriptor, sd_val[0])
1577 is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE'
1579 # we don't fix deleted objects
1582 sd_clean = security.descriptor()
1583 sd_clean.owner_sid = sd.owner_sid
1584 sd_clean.group_sid = sd.group_sid
1585 sd_clean.type = sd.type
1586 sd_clean.revision = sd.revision
1589 last_inherited_type = None
1592 if sd.sacl is not None:
1594 for i in range(0, len(aces)):
1597 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1598 sd_clean.sacl_add(ace)
1601 t = self.ace_get_effective_inherited_type(ace)
1605 if last_inherited_type is not None:
1606 if t != last_inherited_type:
1607 # if it inherited from more than
1608 # one type it's very likely to be broken
1610 # If not the recalculation will calculate
1615 last_inherited_type = t
1618 if sd.dacl is not None:
1620 for i in range(0, len(aces)):
1623 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1624 sd_clean.dacl_add(ace)
1627 t = self.ace_get_effective_inherited_type(ace)
1631 if last_inherited_type is not None:
1632 if t != last_inherited_type:
1633 # if it inherited from more than
1634 # one type it's very likely to be broken
1636 # If not the recalculation will calculate
1641 last_inherited_type = t
1644 return (sd_clean, sd)
1646 if last_inherited_type is None:
1652 cls = obj["objectClass"][-1]
1653 except KeyError as e:
1657 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1658 attrs=["isDeleted", "objectClass"],
1659 controls=["show_recycled:1"])
1661 is_deleted = 'isDeleted' in o and str(o['isDeleted'][0]).upper() == 'TRUE'
1663 # we don't fix deleted objects
1665 cls = o["objectClass"][-1]
1667 t = self.lookup_class_schemaIDGUID(cls)
1669 if t != last_inherited_type:
1671 return (sd_clean, sd)
1676 def err_wrong_sd(self, dn, sd, sd_broken):
1677 '''re-write the SD due to incorrect inherited ACEs'''
1678 sd_attr = "nTSecurityDescriptor"
1679 sd_val = ndr_pack(sd)
1680 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1682 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1683 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1686 nmsg = ldb.Message()
1688 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1689 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1690 "Failed to fix attribute %s" % sd_attr):
1691 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1693 def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1694 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1695 sd_attr = "nTSecurityDescriptor"
1696 sd_val = ndr_pack(sd)
1697 sd_old_val = ndr_pack(sd_old)
1698 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1699 if sd.owner_sid is not None:
1700 sd_flags |= security.SECINFO_OWNER
1701 if sd.group_sid is not None:
1702 sd_flags |= security.SECINFO_GROUP
1704 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1705 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1710 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1711 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1712 "Failed to reset attribute %s" % sd_attr):
1713 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1715 def err_missing_sd_owner(self, dn, sd):
1716 '''re-write the SD due to a missing owner or group'''
1717 sd_attr = "nTSecurityDescriptor"
1718 sd_val = ndr_pack(sd)
1719 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1721 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1722 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1725 nmsg = ldb.Message()
1727 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1729 # By setting the session_info to admin_session_info and
1730 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1731 # flags we cause the descriptor module to set the correct
1732 # owner and group on the SD, replacing the None/NULL values
1733 # for owner_sid and group_sid currently present.
1735 # The admin_session_info matches that used in provision, and
1736 # is the best guess we can make for an existing object that
1737 # hasn't had something specifically set.
1739 # This is important for the dns related naming contexts.
1740 self.samdb.set_session_info(self.admin_session_info)
1741 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1742 "Failed to fix metadata for attribute %s" % sd_attr):
1743 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1744 self.samdb.set_session_info(self.system_session_info)
1746 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1747 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1752 # Search for a zero invocationID
1753 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1757 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1758 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1759 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1760 % (dn, o.attid, o.version,
1761 time.ctime(samba.nttime2unix(o.originating_change_time)),
1762 self.samdb.get_invocation_id()))
1766 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1767 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1770 now = samba.unix2nttime(int(time.time()))
1773 # Search for a zero invocationID
1774 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1778 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1779 o.version = o.version + 1
1780 o.originating_change_time = now
1781 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1782 o.originating_usn = seq
1786 replBlob = ndr_pack(repl)
1790 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1791 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1792 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1795 nmsg = ldb.Message()
1797 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1798 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1799 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1800 "Failed to fix attribute %s" % attr):
1801 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1803 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1804 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1808 # Search for an invalid attid
1810 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1812 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1815 def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1816 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1821 remove_attid = set()
1824 in_schema_nc = dn.is_child_of(self.schema_dn)
1827 # Sort the array, except for the last element. This strange
1828 # construction, creating a new list, due to bugs in samba's
1829 # array handling in IDL generated objects.
1830 ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1831 # Now walk it in reverse, so we see the low (and so incorrect,
1832 # the correct values are above 0x80000000) values first and
1833 # remove the 'second' value we see.
1834 for o in reversed(ctr.array):
1835 print("%s: 0x%08x" % (dn, o.attid))
1836 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1837 if att.lower() in set_att:
1838 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1839 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1840 % (attr, dn, o.attid, att, hash_att[att].attid),
1841 'fix_replmetadata_duplicate_attid'):
1842 self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1843 % (o.attid, att, attr, dn))
1846 remove_attid.add(o.attid)
1847 # We want to set the metadata for the most recent
1848 # update to have been applied locally, that is the metadata
1849 # matching the (eg string) value in the attribute
1850 if o.local_usn > hash_att[att].local_usn:
1851 # This is always what we would have sent over DRS,
1852 # because the DRS server will have sent the
1853 # msDS-IntID, but with the values from both
1854 # attribute entries.
1855 hash_att[att].version = o.version
1856 hash_att[att].originating_change_time = o.originating_change_time
1857 hash_att[att].originating_invocation_id = o.originating_invocation_id
1858 hash_att[att].originating_usn = o.originating_usn
1859 hash_att[att].local_usn = o.local_usn
1861 # Do not re-add the value to the set or overwrite the hash value
1865 set_att.add(att.lower())
1867 # Generate a real list we can sort on properly
1868 new_list = [o for o in ctr.array if o.attid not in remove_attid]
1870 if (len(wrong_attids) > 0):
1872 if o.attid in wrong_attids:
1873 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1874 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1875 self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1876 if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1877 % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1878 self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1879 % (o.attid, correct_attid, att, attr, dn))
1882 o.attid = correct_attid
1884 # Sort the array, (we changed the value so must re-sort)
1885 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1887 # If we did not already need to fix it, then ask about sorting
1889 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1890 if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1891 % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1892 self.report('Not fixing %s on %s\n' % (attr, dn))
1895 # The actual sort done is done at the top of the function
1897 ctr.count = len(new_list)
1898 ctr.array = new_list
1899 replBlob = ndr_pack(repl)
1901 nmsg = ldb.Message()
1903 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1904 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1905 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1906 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1907 "Failed to fix attribute %s" % attr):
1908 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1910 def is_deleted_deleted_objects(self, obj):
1912 if "description" not in obj:
1913 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1915 if "showInAdvancedViewOnly" not in obj or str(obj['showInAdvancedViewOnly'][0]).upper() == 'FALSE':
1916 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1918 if "objectCategory" not in obj:
1919 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1921 if "isCriticalSystemObject" not in obj or str(obj['isCriticalSystemObject'][0]).upper() == 'FALSE':
1922 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1924 if "isRecycled" in obj:
1925 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1927 if "isDeleted" in obj and str(obj['isDeleted'][0]).upper() == 'FALSE':
1928 self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1930 if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1931 str(obj['objectClass'][0]) != 'top' or
1932 str(obj['objectClass'][1]) != 'container'):
1933 self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1935 if "systemFlags" not in obj or str(obj['systemFlags'][0]) != '-1946157056':
1936 self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1940 def err_deleted_deleted_objects(self, obj):
1941 nmsg = ldb.Message()
1942 nmsg.dn = dn = obj.dn
1944 if "description" not in obj:
1945 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1946 if "showInAdvancedViewOnly" not in obj:
1947 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1948 if "objectCategory" not in obj:
1949 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1950 if "isCriticalSystemObject" not in obj:
1951 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1952 if "isRecycled" in obj:
1953 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1955 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1956 nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1957 nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1959 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1960 % (dn), 'fix_deleted_deleted_objects'):
1961 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1964 if self.do_modify(nmsg, ["relax:0"],
1965 "Failed to fix Deleted Objects container %s" % dn):
1966 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1968 def err_replica_locations(self, obj, cross_ref, attr):
1969 nmsg = ldb.Message()
1971 target = self.samdb.get_dsServiceName()
1973 if self.samdb.am_rodc():
1974 self.report('Not fixing %s %s for the RODC' % (attr, obj.dn))
1977 if not self.confirm_all('Add yourself to the replica locations for %s?'
1978 % (obj.dn), 'fix_replica_locations'):
1979 self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1982 nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1983 if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1984 self.report("Fixed %s for %s" % (attr, obj.dn))
1986 def is_fsmo_role(self, dn):
1987 if dn == self.samdb.domain_dn:
1989 if dn == self.infrastructure_dn:
1991 if dn == self.naming_dn:
1993 if dn == self.schema_dn:
1995 if dn == self.rid_dn:
2000 def calculate_instancetype(self, dn):
2002 nc_root = self.samdb.get_nc_root(dn)
2004 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
2006 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
2007 except ldb.LdbError as e4:
2008 (enum, estr) = e4.args
2009 if enum != ldb.ERR_NO_SUCH_OBJECT:
2012 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
2013 if self.write_ncs is not None and str(nc_root) in [str(x) for x in self.write_ncs]:
2014 instancetype |= dsdb.INSTANCE_TYPE_WRITE
2018 def get_wellknown_sd(self, dn):
2019 for [sd_dn, descriptor_fn] in self.wellknown_sds:
2021 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
2022 return ndr_unpack(security.descriptor,
2023 descriptor_fn(domain_sid,
2024 name_map=self.name_map))
2028 def check_object(self, dn, attrs=['*']):
2029 '''check one object'''
2031 self.report("Checking object %s" % dn)
2033 # If we modify the pass-by-reference attrs variable, then we get a
2034 # replPropertyMetadata for every object that we check.
2036 if "dn" in map(str.lower, attrs):
2037 attrs.append("name")
2038 if "distinguishedname" in map(str.lower, attrs):
2039 attrs.append("name")
2040 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
2041 attrs.append("name")
2042 if 'name' in map(str.lower, attrs):
2043 attrs.append(dn.get_rdn_name())
2044 attrs.append("isDeleted")
2045 attrs.append("systemFlags")
2046 need_replPropertyMetaData = False
2048 need_replPropertyMetaData = True
2051 linkID, _ = self.get_attr_linkID_and_reverse_name(a)
2056 need_replPropertyMetaData = True
2058 if need_replPropertyMetaData:
2059 attrs.append("replPropertyMetaData")
2060 attrs.append("objectGUID")
2064 sd_flags |= security.SECINFO_OWNER
2065 sd_flags |= security.SECINFO_GROUP
2066 sd_flags |= security.SECINFO_DACL
2067 sd_flags |= security.SECINFO_SACL
2069 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
2074 "sd_flags:1:%d" % sd_flags,
2075 "reveal_internals:0",
2078 except ldb.LdbError as e10:
2079 (enum, estr) = e10.args
2080 if enum == ldb.ERR_NO_SUCH_OBJECT:
2081 if self.in_transaction:
2082 self.report("ERROR: Object %s disappeared during check" % dn)
2087 self.report("ERROR: Object %s failed to load during check" % dn)
2091 set_attrs_from_md = set()
2092 set_attrs_seen = set()
2093 got_repl_property_meta_data = False
2094 got_objectclass = False
2096 nc_dn = self.samdb.get_nc_root(obj.dn)
2098 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
2099 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
2101 # We have no deleted objects DN for schema, and we check for this above for the other
2103 deleted_objects_dn = None
2105 object_rdn_attr = None
2106 object_rdn_val = None
2111 for attrname in obj:
2112 if attrname == 'dn' or attrname == "distinguishedName":
2115 if str(attrname).lower() == 'objectclass':
2116 got_objectclass = True
2118 if str(attrname).lower() == "name":
2119 if len(obj[attrname]) != 1:
2121 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2122 (len(obj[attrname]), attrname, str(obj.dn)))
2124 name_val = obj[attrname][0]
2126 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
2127 object_rdn_attr = attrname
2128 if len(obj[attrname]) != 1:
2130 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2131 (len(obj[attrname]), attrname, str(obj.dn)))
2133 object_rdn_val = str(obj[attrname][0])
2135 if str(attrname).lower() == 'isdeleted':
2136 if str(obj[attrname][0]) != "FALSE":
2139 if str(attrname).lower() == 'systemflags':
2140 systemFlags = int(obj[attrname][0])
2142 if str(attrname).lower() == 'replpropertymetadata':
2143 if self.has_replmetadata_zero_invocationid(dn, obj[attrname][0]):
2145 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname][0])
2146 # We don't continue, as we may also have other fixes for this attribute
2147 # based on what other attributes we see.
2150 (set_attrs_from_md, list_attid_from_md, wrong_attids) \
2151 = self.process_metadata(dn, obj[attrname][0])
2154 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
2157 if len(set_attrs_from_md) < len(list_attid_from_md) \
2158 or len(wrong_attids) > 0 \
2159 or sorted(list_attid_from_md) != list_attid_from_md:
2161 self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname][0], wrong_attids)
2164 # Here we check that the first attid is 0
2166 if list_attid_from_md[0] != 0:
2168 self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2169 (attrname, str(dn)))
2171 got_repl_property_meta_data = True
2174 if str(attrname).lower() == 'ntsecuritydescriptor':
2175 (sd, sd_broken) = self.process_sd(dn, obj)
2176 if sd_broken is not None:
2177 self.err_wrong_sd(dn, sd, sd_broken)
2181 if sd.owner_sid is None or sd.group_sid is None:
2182 self.err_missing_sd_owner(dn, sd)
2186 if self.reset_well_known_acls:
2188 well_known_sd = self.get_wellknown_sd(dn)
2192 current_sd = ndr_unpack(security.descriptor,
2195 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
2197 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
2202 if str(attrname).lower() == 'objectclass':
2203 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
2204 # Do not consider the attribute incorrect if:
2205 # - The sorted (alphabetically) list is the same, inclding case
2206 # - The first and last elements are the same
2208 # This avoids triggering an error due to
2209 # non-determinism in the sort routine in (at least)
2210 # 4.3 and earlier, and the fact that any AUX classes
2211 # in these attributes are also not sorted when
2212 # imported from Windows (they are just in the reverse
2213 # order of last set)
2214 if sorted(normalised) != sorted(obj[attrname]) \
2215 or normalised[0] != obj[attrname][0] \
2216 or normalised[-1] != obj[attrname][-1]:
2217 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
2221 if str(attrname).lower() == 'userparameters':
2222 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == b'\x20'[0]:
2224 self.err_short_userParameters(obj, attrname, obj[attrname])
2227 elif obj[attrname][0][:16] == b'\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2228 # This is the correct, normal prefix
2231 elif obj[attrname][0][:20] == b'IAAgACAAIAAgACAAIAAg':
2232 # this is the typical prefix from a windows migration
2234 self.err_base64_userParameters(obj, attrname, obj[attrname])
2237 #43:00:00:00:74:00:00:00:78
2238 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]:
2239 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2241 self.err_utf8_userParameters(obj, attrname, obj[attrname])
2244 elif len(obj[attrname][0]) % 2 != 0:
2245 # This is a value that isn't even in length
2247 self.err_odd_userParameters(obj, attrname)
2250 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]:
2251 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2253 self.err_doubled_userParameters(obj, attrname, obj[attrname])
2256 if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2257 if obj[attrname][0] in self.attribute_or_class_ids:
2259 self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2260 % (attrname, obj.dn, obj[attrname][0]))
2262 self.attribute_or_class_ids.add(obj[attrname][0])
2264 # check for empty attributes
2265 for val in obj[attrname]:
2267 self.err_empty_attribute(dn, attrname)
2271 # get the syntax oid for the attribute, so we can can have
2272 # special handling for some specific attribute types
2274 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2275 except Exception as msg:
2276 self.err_unknown_attribute(obj, attrname)
2280 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2282 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2283 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2284 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2286 set_attrs_seen.add(str(attrname).lower())
2288 if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2289 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN]:
2290 # it's some form of DN, do specialised checking on those
2291 error_count += self.check_dn(obj, attrname, syntax_oid)
2295 # check for incorrectly normalised attributes
2296 for val in obj[attrname]:
2299 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2300 if len(normalised) != 1 or normalised[0] != val:
2301 self.err_normalise_mismatch(dn, attrname, obj[attrname])
2305 if len(obj[attrname]) != len(values):
2306 self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2310 if str(attrname).lower() == "instancetype":
2311 calculated_instancetype = self.calculate_instancetype(dn)
2312 if len(obj["instanceType"]) != 1 or int(obj["instanceType"][0]) != calculated_instancetype:
2314 self.err_wrong_instancetype(obj, calculated_instancetype)
2316 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
2318 self.err_missing_objectclass(dn)
2320 if ("*" in attrs or "name" in map(str.lower, attrs)):
2321 if name_val is None:
2323 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2324 if object_rdn_attr is None:
2326 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2328 if name_val is not None:
2331 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2332 parent_dn = deleted_objects_dn
2333 if parent_dn is None:
2334 parent_dn = obj.dn.parent()
2335 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2336 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2338 if obj.dn == deleted_objects_dn:
2339 expected_dn = obj.dn
2341 if expected_dn != obj.dn:
2343 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
2344 elif obj.dn.get_rdn_value() != object_rdn_val:
2346 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
2349 if got_repl_property_meta_data:
2350 if obj.dn == deleted_objects_dn:
2351 isDeletedAttId = 131120
2352 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2354 expectedTimeDo = 2650466015990000000
2355 originating = self.get_originating_time(obj["replPropertyMetaData"][0], isDeletedAttId)
2356 if originating != expectedTimeDo:
2357 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2358 nmsg = ldb.Message()
2360 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2362 self.samdb.modify(nmsg, controls=["provision:0"])
2365 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2367 for att in set_attrs_seen.difference(set_attrs_from_md):
2369 self.report("On object %s" % dn)
2372 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2373 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2374 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2376 self.fix_metadata(obj, att)
2378 if self.is_fsmo_role(dn):
2379 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
2380 self.err_no_fsmoRoleOwner(obj)
2384 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2385 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2386 controls=["show_recycled:1", "show_deleted:1"])
2387 except ldb.LdbError as e11:
2388 (enum, estr) = e11.args
2389 if enum == ldb.ERR_NO_SUCH_OBJECT:
2390 self.err_missing_parent(obj)
2395 if dn in self.deleted_objects_containers and '*' in attrs:
2396 if self.is_deleted_deleted_objects(obj):
2397 self.err_deleted_deleted_objects(obj)
2400 for (dns_part, msg) in self.dns_partitions:
2401 if dn == dns_part and 'repsFrom' in obj:
2402 location = "msDS-NC-Replica-Locations"
2403 if self.samdb.am_rodc():
2404 location = "msDS-NC-RO-Replica-Locations"
2406 if location not in msg:
2407 # There are no replica locations!
2408 self.err_replica_locations(obj, msg.dn, location)
2413 for loc in msg[location]:
2414 if str(loc) == self.samdb.get_dsServiceName():
2417 # This DC is not in the replica locations
2418 self.err_replica_locations(obj, msg.dn, location)
2421 if dn == self.server_ref_dn:
2422 # Check we have a valid RID Set
2423 if "*" in attrs or "rIDSetReferences" in attrs:
2424 if "rIDSetReferences" not in obj:
2425 # NO RID SET reference
2426 # We are RID master, allocate it.
2429 if self.is_rid_master:
2430 # Allocate a RID Set
2431 if self.confirm_all('Allocate the missing RID set for RID master?',
2432 'fix_missing_rid_set_master'):
2434 # We don't have auto-transaction logic on
2435 # extended operations, so we have to do it
2438 self.samdb.transaction_start()
2441 self.samdb.create_own_rid_set()
2444 self.samdb.transaction_cancel()
2447 self.samdb.transaction_commit()
2449 elif not self.samdb.am_rodc():
2450 self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2452 # Check some details of our own RID Set
2453 if dn == self.rid_set_dn:
2454 res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2455 attrs=["rIDAllocationPool",
2456 "rIDPreviousAllocationPool",
2459 if "rIDAllocationPool" not in res[0]:
2460 self.report("No rIDAllocationPool found in %s" % dn)
2463 next_pool = int(res[0]["rIDAllocationPool"][0])
2465 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2466 low = 0x00000000FFFFFFFF & next_pool
2469 self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2472 if "rIDNextRID" in res[0]:
2473 next_free_rid = int(res[0]["rIDNextRID"][0])
2477 if next_free_rid == 0:
2482 # Check the remainder of this pool for conflicts. If
2483 # ridalloc_allocate_rid() moves to a new pool, this
2484 # will be above high, so we will stop.
2485 while next_free_rid <= high:
2486 sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2488 res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2490 except ldb.LdbError as e:
2491 (enum, estr) = e.args
2492 if enum != ldb.ERR_NO_SUCH_OBJECT:
2496 self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2499 if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2501 'fix_sid_rid_set_conflict'):
2502 self.samdb.transaction_start()
2504 # This will burn RIDs, which will move
2505 # past the conflict. We then check again
2506 # to see if the new RID conflicts, until
2507 # the end of the current pool. We don't
2508 # look at the next pool to avoid burning
2509 # all RIDs in one go in some strange
2513 allocated_rid = self.samdb.allocate_rid()
2514 if allocated_rid >= next_free_rid:
2515 next_free_rid = allocated_rid + 1
2518 self.samdb.transaction_cancel()
2521 self.samdb.transaction_commit()
2529 ################################################################
2530 # check special @ROOTDSE attributes
2531 def check_rootdse(self):
2532 '''check the @ROOTDSE special object'''
2533 dn = ldb.Dn(self.samdb, '@ROOTDSE')
2535 self.report("Checking object %s" % dn)
2536 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2538 self.report("Object %s disappeared during check" % dn)
2543 # check that the dsServiceName is in GUID form
2544 if 'dsServiceName' not in obj:
2545 self.report('ERROR: dsServiceName missing in @ROOTDSE')
2546 return error_count + 1
2548 if not str(obj['dsServiceName'][0]).startswith('<GUID='):
2549 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2551 if not self.confirm('Change dsServiceName to GUID form?'):
2553 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0].decode('utf8')),
2554 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2555 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2558 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2559 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2560 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2561 self.report("Changed dsServiceName to GUID form")
2564 ###############################################
2565 # re-index the database
2567 def reindex_database(self):
2568 '''re-index the whole database'''
2570 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2571 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2572 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2573 return self.do_modify(m, [], 're-indexed database', validate=False)
2575 ###############################################
2577 def reset_modules(self):
2578 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2580 m.dn = ldb.Dn(self.samdb, "@MODULES")
2581 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2582 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)