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=None,
231 '''perform a database check, returning the number of errors found'''
232 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
233 self.report('Checking %u objects' % len(res))
236 error_count += self.check_deleted_objects_containers()
238 self.attribute_or_class_ids = set()
241 self.dn_set.add(str(object.dn))
242 error_count += self.check_object(object.dn, attrs=attrs)
245 error_count += self.check_rootdse()
247 if error_count != 0 and not self.fix:
248 self.report("Please use --fix to fix these errors")
250 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
253 def check_deleted_objects_containers(self):
254 """This function only fixes conflicts on the Deleted Objects
255 containers, not the attributes"""
257 for nc in self.ncs_lacking_deleted_containers:
258 if nc == self.schema_dn:
261 self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc)
262 if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'):
265 dn = ldb.Dn(self.samdb, "CN=Deleted Objects")
270 # If something already exists here, add a conflict
271 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[],
272 controls=["show_deleted:1", "extended_dn:1:1",
273 "show_recycled:1", "reveal_internals:0"])
275 guid = res[0].dn.get_extended_component("GUID")
276 conflict_dn = ldb.Dn(self.samdb,
277 "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid)))
278 conflict_dn.add_base(nc)
280 except ldb.LdbError as e2:
281 (enum, estr) = e2.args
282 if enum == ldb.ERR_NO_SUCH_OBJECT:
285 self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr)
288 if conflict_dn is not None:
290 self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"])
291 except ldb.LdbError as e1:
292 (enum, estr) = e1.args
293 self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr))
296 # Refresh wellKnownObjects links
297 res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE,
298 attrs=['wellKnownObjects'],
299 controls=["show_deleted:1", "extended_dn:0",
300 "show_recycled:1", "reveal_internals:0"])
302 self.report("wellKnownObjects was not found for NC %s" % nc)
305 # Prevent duplicate deleted objects containers just in case
306 wko = res[0]["wellKnownObjects"]
308 proposed_objectguid = None
310 dsdb_dn = dsdb_Dn(self.samdb, o.decode('utf8'), dsdb.DSDB_SYNTAX_BINARY_DN)
311 if self.is_deleted_objects_dn(dsdb_dn):
312 self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o)
313 # We really want to put this back in the same spot
314 # as the original one, so that on replication we
315 # merge, rather than conflict.
316 proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID")
317 listwko.append(str(o))
319 if proposed_objectguid is not None:
320 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid))
322 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
323 listwko.append('%s:%s' % (wko_prefix, dn))
326 # Insert a brand new Deleted Objects container
327 self.samdb.add_ldif("""dn: %s
329 objectClass: container
330 description: Container for deleted objects
332 isCriticalSystemObject: TRUE
333 showInAdvancedViewOnly: TRUE
334 systemFlags: -1946157056%s""" % (dn, guid_suffix),
335 controls=["relax:0", "provision:0"])
337 delta = ldb.Message()
338 delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"]))
339 delta["wellKnownObjects"] = ldb.MessageElement(listwko,
340 ldb.FLAG_MOD_REPLACE,
343 # Insert the link to the brand new container
344 if self.do_modify(delta, ["relax:0"],
345 "NC %s lacks Deleted Objects WKGUID" % nc,
347 self.report("Added %s well known guid link" % dn)
349 self.deleted_objects_containers.append(dn)
353 def report(self, msg):
354 '''print a message unless quiet is set'''
358 def confirm(self, msg, allow_all=False, forced=False):
359 '''confirm a change'''
366 return common.confirm(msg, forced=forced, allow_all=allow_all)
368 ################################################################
369 # a local confirm function with support for 'all'
370 def confirm_all(self, msg, all_attr):
371 '''confirm a change with support for "all" '''
374 if getattr(self, all_attr) == 'NONE':
376 if getattr(self, all_attr) == 'ALL':
382 c = common.confirm(msg, forced=forced, allow_all=True)
384 setattr(self, all_attr, 'ALL')
387 setattr(self, all_attr, 'NONE')
391 def do_delete(self, dn, controls, msg):
392 '''delete dn with optional verbose output'''
394 self.report("delete DN %s" % dn)
396 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
397 self.samdb.delete(dn, controls=controls)
398 except Exception as err:
399 if self.in_transaction:
400 raise CommandError("%s : %s" % (msg, err))
401 self.report("%s : %s" % (msg, err))
405 def do_modify(self, m, controls, msg, validate=True):
406 '''perform a modify with optional verbose output'''
407 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
409 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
410 self.report("controls: %r" % controls)
412 self.samdb.modify(m, controls=controls, validate=validate)
413 except Exception as err:
414 if self.in_transaction:
415 raise CommandError("%s : %s" % (msg, err))
416 self.report("%s : %s" % (msg, err))
420 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
421 '''perform a modify with optional verbose output'''
423 self.report("""dn: %s
427 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
429 to_dn = to_rdn + to_base
430 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
431 self.samdb.rename(from_dn, to_dn, controls=controls)
432 except Exception as err:
433 if self.in_transaction:
434 raise CommandError("%s : %s" % (msg, err))
435 self.report("%s : %s" % (msg, err))
439 def get_attr_linkID_and_reverse_name(self, attrname):
440 if attrname in self.link_id_cache:
441 return self.link_id_cache[attrname]
442 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
444 revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
447 self.link_id_cache[attrname] = (linkID, revname)
448 return linkID, revname
450 def err_empty_attribute(self, dn, attrname):
451 '''fix empty attributes'''
452 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
453 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
454 self.report("Not fixing empty attribute %s" % attrname)
459 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
460 if self.do_modify(m, ["relax:0", "show_recycled:1"],
461 "Failed to remove empty attribute %s" % attrname, validate=False):
462 self.report("Removed empty attribute %s" % attrname)
464 def err_normalise_mismatch(self, dn, attrname, values):
465 '''fix attribute normalisation errors'''
466 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
469 normalised = self.samdb.dsdb_normalise_attributes(
470 self.samdb_schema, attrname, [val])
471 if len(normalised) != 1:
472 self.report("Unable to normalise value '%s'" % val)
473 mod_list.append((val, ''))
474 elif (normalised[0] != val):
475 self.report("value '%s' should be '%s'" % (val, normalised[0]))
476 mod_list.append((val, normalised[0]))
477 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
478 self.report("Not fixing attribute %s" % attrname)
483 for i in range(0, len(mod_list)):
484 (val, nval) = mod_list[i]
485 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
487 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
490 if self.do_modify(m, ["relax:0", "show_recycled:1"],
491 "Failed to normalise attribute %s" % attrname,
493 self.report("Normalised attribute %s" % attrname)
495 def err_normalise_mismatch_replace(self, dn, attrname, values):
496 '''fix attribute normalisation errors'''
497 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
498 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
499 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
500 if list(normalised) == values:
502 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
503 self.report("Not fixing attribute '%s'" % attrname)
508 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
510 if self.do_modify(m, ["relax:0", "show_recycled:1"],
511 "Failed to normalise attribute %s" % attrname,
513 self.report("Normalised attribute %s" % attrname)
515 def err_duplicate_values(self, dn, attrname, dup_values, values):
516 '''fix attribute normalisation errors'''
517 self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
518 self.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dump_attr_values(dup_values)), ','.join(dump_attr_values(values))))
519 if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
520 self.report("Not fixing attribute '%s'" % attrname)
525 m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
527 if self.do_modify(m, ["relax:0", "show_recycled:1"],
528 "Failed to remove duplicate value on attribute %s" % attrname,
530 self.report("Removed duplicate value on attribute %s" % attrname)
532 def is_deleted_objects_dn(self, dsdb_dn):
533 '''see if a dsdb_Dn is the special Deleted Objects DN'''
534 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
536 def err_missing_objectclass(self, dn):
537 """handle object without objectclass"""
538 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)))
539 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'):
540 self.report("Not deleting object with missing objectclass '%s'" % dn)
542 if self.do_delete(dn, ["relax:0"],
543 "Failed to remove DN %s" % dn):
544 self.report("Removed DN %s" % dn)
546 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False):
547 """handle a DN pointing to a deleted object"""
548 if not remove_plausible:
549 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
550 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
551 if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
552 self.report("Not removing")
555 self.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
556 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
557 if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
558 self.report("Not removing")
563 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
564 if self.do_modify(m, ["show_recycled:1",
565 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
566 "Failed to remove deleted DN attribute %s" % attrname):
567 self.report("Removed deleted DN on attribute %s" % attrname)
569 def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
570 """handle a missing target DN (if specified, GUID form can't be found,
571 and otherwise DN string form can't be found)"""
572 # check if its a backlink
573 linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
574 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
576 linkID, reverse_link_name \
577 = self.get_attr_linkID_and_reverse_name(attrname)
578 if reverse_link_name is not None:
579 self.report("WARNING: no target object found for GUID "
580 "component for one-way forward link "
582 "%s - %s" % (attrname, dn, val))
583 self.report("Not removing dangling forward link")
586 nc_root = self.samdb.get_nc_root(dn)
587 target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn)
588 if nc_root != target_nc_root:
589 # We don't bump the error count as Samba produces these
590 # in normal operation
591 self.report("WARNING: no target object found for GUID "
592 "component for cross-partition link "
594 "%s - %s" % (attrname, dn, val))
595 self.report("Not removing dangling one-way "
596 "cross-partition link "
597 "(we might be mid-replication)")
600 # Due to our link handling one-way links pointing to
601 # missing objects are plausible.
603 # We don't bump the error count as Samba produces these
604 # in normal operation
605 self.report("WARNING: no target object found for GUID "
606 "component for DN value %s in object "
607 "%s - %s" % (attrname, dn, val))
608 self.err_deleted_dn(dn, attrname, val,
609 dsdb_dn, dsdb_dn, True)
612 # We bump the error count here, as we should have deleted this
613 self.report("ERROR: no target object found for GUID "
614 "component for link %s in object "
615 "%s - %s" % (attrname, dn, val))
616 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
619 def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
620 """handle a missing GUID extended DN component"""
621 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
622 controls = ["extended_dn:1:1", "show_recycled:1"]
624 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
625 attrs=[], controls=controls)
626 except ldb.LdbError as e7:
627 (enum, estr) = e7.args
628 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
629 if enum != ldb.ERR_NO_SUCH_OBJECT:
631 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
634 self.report("unable to find object for DN %s" % dsdb_dn.dn)
635 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
637 dsdb_dn.dn = res[0].dn
639 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
640 self.report("Not fixing %s" % errstr)
644 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
645 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
647 if self.do_modify(m, ["show_recycled:1"],
648 "Failed to fix %s on attribute %s" % (errstr, attrname)):
649 self.report("Fixed %s on attribute %s" % (errstr, attrname))
651 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
652 """handle an incorrect binary DN component"""
653 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
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)
1490 for o in repl.ctr.array:
1491 if o.attid == attid:
1492 return o.originating_change_time
1496 def process_metadata(self, dn, val):
1497 '''Read metadata properties and list attributes in it.
1498 raises KeyError if the attid is unknown.'''
1501 wrong_attids = set()
1503 in_schema_nc = dn.is_child_of(self.schema_dn)
1505 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1507 for o in repl.ctr.array:
1508 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1509 set_att.add(att.lower())
1510 list_attid.append(o.attid)
1511 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1512 is_schema_nc=in_schema_nc)
1513 if correct_attid != o.attid:
1514 wrong_attids.add(o.attid)
1516 return (set_att, list_attid, wrong_attids)
1518 def fix_metadata(self, obj, attr):
1519 '''re-write replPropertyMetaData elements for a single attribute for a
1520 object. This is used to fix missing replPropertyMetaData elements'''
1521 guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1522 dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1523 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attr],
1524 controls=["search_options:1:2",
1527 nmsg = ldb.Message()
1529 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1530 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1531 "Failed to fix metadata for attribute %s" % attr):
1532 self.report("Fixed metadata for attribute %s" % attr)
1534 def ace_get_effective_inherited_type(self, ace):
1535 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1539 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1541 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1543 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1545 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1551 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1554 return str(ace.object.inherited_type)
1556 def lookup_class_schemaIDGUID(self, cls):
1557 if cls in self.class_schemaIDGUID:
1558 return self.class_schemaIDGUID[cls]
1560 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1561 res = self.samdb.search(base=self.schema_dn,
1563 attrs=["schemaIDGUID"])
1564 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1566 self.class_schemaIDGUID[cls] = t
1569 def process_sd(self, dn, obj):
1570 sd_attr = "nTSecurityDescriptor"
1571 sd_val = obj[sd_attr]
1573 sd = ndr_unpack(security.descriptor, sd_val[0])
1575 is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE'
1577 # we don't fix deleted objects
1580 sd_clean = security.descriptor()
1581 sd_clean.owner_sid = sd.owner_sid
1582 sd_clean.group_sid = sd.group_sid
1583 sd_clean.type = sd.type
1584 sd_clean.revision = sd.revision
1587 last_inherited_type = None
1590 if sd.sacl is not None:
1592 for i in range(0, len(aces)):
1595 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1596 sd_clean.sacl_add(ace)
1599 t = self.ace_get_effective_inherited_type(ace)
1603 if last_inherited_type is not None:
1604 if t != last_inherited_type:
1605 # if it inherited from more than
1606 # one type it's very likely to be broken
1608 # If not the recalculation will calculate
1613 last_inherited_type = t
1616 if sd.dacl is not None:
1618 for i in range(0, len(aces)):
1621 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1622 sd_clean.dacl_add(ace)
1625 t = self.ace_get_effective_inherited_type(ace)
1629 if last_inherited_type is not None:
1630 if t != last_inherited_type:
1631 # if it inherited from more than
1632 # one type it's very likely to be broken
1634 # If not the recalculation will calculate
1639 last_inherited_type = t
1642 return (sd_clean, sd)
1644 if last_inherited_type is None:
1650 cls = obj["objectClass"][-1]
1651 except KeyError as e:
1655 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1656 attrs=["isDeleted", "objectClass"],
1657 controls=["show_recycled:1"])
1659 is_deleted = 'isDeleted' in o and str(o['isDeleted'][0]).upper() == 'TRUE'
1661 # we don't fix deleted objects
1663 cls = o["objectClass"][-1]
1665 t = self.lookup_class_schemaIDGUID(cls)
1667 if t != last_inherited_type:
1669 return (sd_clean, sd)
1674 def err_wrong_sd(self, dn, sd, sd_broken):
1675 '''re-write the SD due to incorrect inherited ACEs'''
1676 sd_attr = "nTSecurityDescriptor"
1677 sd_val = ndr_pack(sd)
1678 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1680 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1681 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1684 nmsg = ldb.Message()
1686 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1687 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1688 "Failed to fix attribute %s" % sd_attr):
1689 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1691 def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1692 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1693 sd_attr = "nTSecurityDescriptor"
1694 sd_val = ndr_pack(sd)
1695 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1696 if sd.owner_sid is not None:
1697 sd_flags |= security.SECINFO_OWNER
1698 if sd.group_sid is not None:
1699 sd_flags |= security.SECINFO_GROUP
1701 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1702 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1707 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1708 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1709 "Failed to reset attribute %s" % sd_attr):
1710 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1712 def err_missing_sd_owner(self, dn, sd):
1713 '''re-write the SD due to a missing owner or group'''
1714 sd_attr = "nTSecurityDescriptor"
1715 sd_val = ndr_pack(sd)
1716 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1718 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1719 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1722 nmsg = ldb.Message()
1724 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1726 # By setting the session_info to admin_session_info and
1727 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1728 # flags we cause the descriptor module to set the correct
1729 # owner and group on the SD, replacing the None/NULL values
1730 # for owner_sid and group_sid currently present.
1732 # The admin_session_info matches that used in provision, and
1733 # is the best guess we can make for an existing object that
1734 # hasn't had something specifically set.
1736 # This is important for the dns related naming contexts.
1737 self.samdb.set_session_info(self.admin_session_info)
1738 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1739 "Failed to fix metadata for attribute %s" % sd_attr):
1740 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1741 self.samdb.set_session_info(self.system_session_info)
1743 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1744 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1749 # Search for a zero invocationID
1750 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1754 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1755 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1756 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1757 % (dn, o.attid, o.version,
1758 time.ctime(samba.nttime2unix(o.originating_change_time)),
1759 self.samdb.get_invocation_id()))
1763 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1764 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1767 now = samba.unix2nttime(int(time.time()))
1770 # Search for a zero invocationID
1771 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1775 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1776 o.version = o.version + 1
1777 o.originating_change_time = now
1778 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1779 o.originating_usn = seq
1783 replBlob = ndr_pack(repl)
1787 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1788 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1789 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1792 nmsg = ldb.Message()
1794 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1795 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1796 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1797 "Failed to fix attribute %s" % attr):
1798 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1800 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1801 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1805 # Search for an invalid attid
1807 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1809 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1812 def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1813 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1818 remove_attid = set()
1821 in_schema_nc = dn.is_child_of(self.schema_dn)
1824 # Sort the array, except for the last element. This strange
1825 # construction, creating a new list, due to bugs in samba's
1826 # array handling in IDL generated objects.
1827 ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1828 # Now walk it in reverse, so we see the low (and so incorrect,
1829 # the correct values are above 0x80000000) values first and
1830 # remove the 'second' value we see.
1831 for o in reversed(ctr.array):
1832 print("%s: 0x%08x" % (dn, o.attid))
1833 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1834 if att.lower() in set_att:
1835 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1836 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1837 % (attr, dn, o.attid, att, hash_att[att].attid),
1838 'fix_replmetadata_duplicate_attid'):
1839 self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1840 % (o.attid, att, attr, dn))
1843 remove_attid.add(o.attid)
1844 # We want to set the metadata for the most recent
1845 # update to have been applied locally, that is the metadata
1846 # matching the (eg string) value in the attribute
1847 if o.local_usn > hash_att[att].local_usn:
1848 # This is always what we would have sent over DRS,
1849 # because the DRS server will have sent the
1850 # msDS-IntID, but with the values from both
1851 # attribute entries.
1852 hash_att[att].version = o.version
1853 hash_att[att].originating_change_time = o.originating_change_time
1854 hash_att[att].originating_invocation_id = o.originating_invocation_id
1855 hash_att[att].originating_usn = o.originating_usn
1856 hash_att[att].local_usn = o.local_usn
1858 # Do not re-add the value to the set or overwrite the hash value
1862 set_att.add(att.lower())
1864 # Generate a real list we can sort on properly
1865 new_list = [o for o in ctr.array if o.attid not in remove_attid]
1867 if (len(wrong_attids) > 0):
1869 if o.attid in wrong_attids:
1870 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1871 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1872 self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1873 if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1874 % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1875 self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1876 % (o.attid, correct_attid, att, attr, dn))
1879 o.attid = correct_attid
1881 # Sort the array, (we changed the value so must re-sort)
1882 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1884 # If we did not already need to fix it, then ask about sorting
1886 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1887 if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1888 % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1889 self.report('Not fixing %s on %s\n' % (attr, dn))
1892 # The actual sort done is done at the top of the function
1894 ctr.count = len(new_list)
1895 ctr.array = new_list
1896 replBlob = ndr_pack(repl)
1898 nmsg = ldb.Message()
1900 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1901 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1902 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1903 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1904 "Failed to fix attribute %s" % attr):
1905 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1907 def is_deleted_deleted_objects(self, obj):
1909 if "description" not in obj:
1910 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1912 if "showInAdvancedViewOnly" not in obj or str(obj['showInAdvancedViewOnly'][0]).upper() == 'FALSE':
1913 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1915 if "objectCategory" not in obj:
1916 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1918 if "isCriticalSystemObject" not in obj or str(obj['isCriticalSystemObject'][0]).upper() == 'FALSE':
1919 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1921 if "isRecycled" in obj:
1922 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1924 if "isDeleted" in obj and str(obj['isDeleted'][0]).upper() == 'FALSE':
1925 self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1927 if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1928 str(obj['objectClass'][0]) != 'top' or
1929 str(obj['objectClass'][1]) != 'container'):
1930 self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1932 if "systemFlags" not in obj or str(obj['systemFlags'][0]) != '-1946157056':
1933 self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1937 def err_deleted_deleted_objects(self, obj):
1938 nmsg = ldb.Message()
1939 nmsg.dn = dn = obj.dn
1941 if "description" not in obj:
1942 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1943 if "showInAdvancedViewOnly" not in obj:
1944 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1945 if "objectCategory" not in obj:
1946 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1947 if "isCriticalSystemObject" not in obj:
1948 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1949 if "isRecycled" in obj:
1950 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1952 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1953 nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1954 nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1956 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1957 % (dn), 'fix_deleted_deleted_objects'):
1958 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1961 if self.do_modify(nmsg, ["relax:0"],
1962 "Failed to fix Deleted Objects container %s" % dn):
1963 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1965 def err_replica_locations(self, obj, cross_ref, attr):
1966 nmsg = ldb.Message()
1968 target = self.samdb.get_dsServiceName()
1970 if self.samdb.am_rodc():
1971 self.report('Not fixing %s %s for the RODC' % (attr, obj.dn))
1974 if not self.confirm_all('Add yourself to the replica locations for %s?'
1975 % (obj.dn), 'fix_replica_locations'):
1976 self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1979 nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1980 if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1981 self.report("Fixed %s for %s" % (attr, obj.dn))
1983 def is_fsmo_role(self, dn):
1984 if dn == self.samdb.domain_dn:
1986 if dn == self.infrastructure_dn:
1988 if dn == self.naming_dn:
1990 if dn == self.schema_dn:
1992 if dn == self.rid_dn:
1997 def calculate_instancetype(self, dn):
1999 nc_root = self.samdb.get_nc_root(dn)
2001 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
2003 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
2004 except ldb.LdbError as e4:
2005 (enum, estr) = e4.args
2006 if enum != ldb.ERR_NO_SUCH_OBJECT:
2009 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
2010 if self.write_ncs is not None and str(nc_root) in [str(x) for x in self.write_ncs]:
2011 instancetype |= dsdb.INSTANCE_TYPE_WRITE
2015 def get_wellknown_sd(self, dn):
2016 for [sd_dn, descriptor_fn] in self.wellknown_sds:
2018 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
2019 return ndr_unpack(security.descriptor,
2020 descriptor_fn(domain_sid,
2021 name_map=self.name_map))
2025 def check_object(self, dn, attrs=None):
2026 '''check one object'''
2028 self.report("Checking object %s" % dn)
2032 # make a local copy to modify
2034 if "dn" in map(str.lower, attrs):
2035 attrs.append("name")
2036 if "distinguishedname" in map(str.lower, attrs):
2037 attrs.append("name")
2038 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
2039 attrs.append("name")
2040 if 'name' in map(str.lower, attrs):
2041 attrs.append(dn.get_rdn_name())
2042 attrs.append("isDeleted")
2043 attrs.append("systemFlags")
2044 need_replPropertyMetaData = False
2046 need_replPropertyMetaData = True
2049 linkID, _ = self.get_attr_linkID_and_reverse_name(a)
2054 need_replPropertyMetaData = True
2056 if need_replPropertyMetaData:
2057 attrs.append("replPropertyMetaData")
2058 attrs.append("objectGUID")
2062 sd_flags |= security.SECINFO_OWNER
2063 sd_flags |= security.SECINFO_GROUP
2064 sd_flags |= security.SECINFO_DACL
2065 sd_flags |= security.SECINFO_SACL
2067 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
2072 "sd_flags:1:%d" % sd_flags,
2073 "reveal_internals:0",
2076 except ldb.LdbError as e10:
2077 (enum, estr) = e10.args
2078 if enum == ldb.ERR_NO_SUCH_OBJECT:
2079 if self.in_transaction:
2080 self.report("ERROR: Object %s disappeared during check" % dn)
2085 self.report("ERROR: Object %s failed to load during check" % dn)
2089 set_attrs_from_md = set()
2090 set_attrs_seen = set()
2091 got_repl_property_meta_data = False
2092 got_objectclass = False
2094 nc_dn = self.samdb.get_nc_root(obj.dn)
2096 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
2097 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
2099 # We have no deleted objects DN for schema, and we check for this above for the other
2101 deleted_objects_dn = None
2103 object_rdn_attr = None
2104 object_rdn_val = None
2109 for attrname in obj:
2110 if attrname == 'dn' or attrname == "distinguishedName":
2113 if str(attrname).lower() == 'objectclass':
2114 got_objectclass = True
2116 if str(attrname).lower() == "name":
2117 if len(obj[attrname]) != 1:
2119 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2120 (len(obj[attrname]), attrname, str(obj.dn)))
2122 name_val = obj[attrname][0]
2124 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
2125 object_rdn_attr = attrname
2126 if len(obj[attrname]) != 1:
2128 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2129 (len(obj[attrname]), attrname, str(obj.dn)))
2131 object_rdn_val = str(obj[attrname][0])
2133 if str(attrname).lower() == 'isdeleted':
2134 if str(obj[attrname][0]) != "FALSE":
2137 if str(attrname).lower() == 'systemflags':
2138 systemFlags = int(obj[attrname][0])
2140 if str(attrname).lower() == 'replpropertymetadata':
2141 if self.has_replmetadata_zero_invocationid(dn, obj[attrname][0]):
2143 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname][0])
2144 # We don't continue, as we may also have other fixes for this attribute
2145 # based on what other attributes we see.
2148 (set_attrs_from_md, list_attid_from_md, wrong_attids) \
2149 = self.process_metadata(dn, obj[attrname][0])
2152 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
2155 if len(set_attrs_from_md) < len(list_attid_from_md) \
2156 or len(wrong_attids) > 0 \
2157 or sorted(list_attid_from_md) != list_attid_from_md:
2159 self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname][0], wrong_attids)
2162 # Here we check that the first attid is 0
2164 if list_attid_from_md[0] != 0:
2166 self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2167 (attrname, str(dn)))
2169 got_repl_property_meta_data = True
2172 if str(attrname).lower() == 'ntsecuritydescriptor':
2173 (sd, sd_broken) = self.process_sd(dn, obj)
2174 if sd_broken is not None:
2175 self.err_wrong_sd(dn, sd, sd_broken)
2179 if sd.owner_sid is None or sd.group_sid is None:
2180 self.err_missing_sd_owner(dn, sd)
2184 if self.reset_well_known_acls:
2186 well_known_sd = self.get_wellknown_sd(dn)
2190 current_sd = ndr_unpack(security.descriptor,
2193 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
2195 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
2200 if str(attrname).lower() == 'objectclass':
2201 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
2202 # Do not consider the attribute incorrect if:
2203 # - The sorted (alphabetically) list is the same, inclding case
2204 # - The first and last elements are the same
2206 # This avoids triggering an error due to
2207 # non-determinism in the sort routine in (at least)
2208 # 4.3 and earlier, and the fact that any AUX classes
2209 # in these attributes are also not sorted when
2210 # imported from Windows (they are just in the reverse
2211 # order of last set)
2212 if sorted(normalised) != sorted(obj[attrname]) \
2213 or normalised[0] != obj[attrname][0] \
2214 or normalised[-1] != obj[attrname][-1]:
2215 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
2219 if str(attrname).lower() == 'userparameters':
2220 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == b'\x20'[0]:
2222 self.err_short_userParameters(obj, attrname, obj[attrname])
2225 elif obj[attrname][0][:16] == b'\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2226 # This is the correct, normal prefix
2229 elif obj[attrname][0][:20] == b'IAAgACAAIAAgACAAIAAg':
2230 # this is the typical prefix from a windows migration
2232 self.err_base64_userParameters(obj, attrname, obj[attrname])
2235 #43:00:00:00:74:00:00:00:78
2236 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]:
2237 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2239 self.err_utf8_userParameters(obj, attrname, obj[attrname])
2242 elif len(obj[attrname][0]) % 2 != 0:
2243 # This is a value that isn't even in length
2245 self.err_odd_userParameters(obj, attrname)
2248 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]:
2249 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2251 self.err_doubled_userParameters(obj, attrname, obj[attrname])
2254 if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2255 if obj[attrname][0] in self.attribute_or_class_ids:
2257 self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2258 % (attrname, obj.dn, obj[attrname][0]))
2260 self.attribute_or_class_ids.add(obj[attrname][0])
2262 # check for empty attributes
2263 for val in obj[attrname]:
2265 self.err_empty_attribute(dn, attrname)
2269 # get the syntax oid for the attribute, so we can can have
2270 # special handling for some specific attribute types
2272 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2273 except Exception as msg:
2274 self.err_unknown_attribute(obj, attrname)
2278 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2280 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2281 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2282 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2284 set_attrs_seen.add(str(attrname).lower())
2286 if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2287 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN]:
2288 # it's some form of DN, do specialised checking on those
2289 error_count += self.check_dn(obj, attrname, syntax_oid)
2293 # check for incorrectly normalised attributes
2294 for val in obj[attrname]:
2297 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2298 if len(normalised) != 1 or normalised[0] != val:
2299 self.err_normalise_mismatch(dn, attrname, obj[attrname])
2303 if len(obj[attrname]) != len(values):
2304 self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2308 if str(attrname).lower() == "instancetype":
2309 calculated_instancetype = self.calculate_instancetype(dn)
2310 if len(obj["instanceType"]) != 1 or int(obj["instanceType"][0]) != calculated_instancetype:
2312 self.err_wrong_instancetype(obj, calculated_instancetype)
2314 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
2316 self.err_missing_objectclass(dn)
2318 if ("*" in attrs or "name" in map(str.lower, attrs)):
2319 if name_val is None:
2321 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2322 if object_rdn_attr is None:
2324 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2326 if name_val is not None:
2329 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2330 parent_dn = deleted_objects_dn
2331 if parent_dn is None:
2332 parent_dn = obj.dn.parent()
2333 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2334 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2336 if obj.dn == deleted_objects_dn:
2337 expected_dn = obj.dn
2339 if expected_dn != obj.dn:
2341 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
2342 elif obj.dn.get_rdn_value() != object_rdn_val:
2344 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
2347 if got_repl_property_meta_data:
2348 if obj.dn == deleted_objects_dn:
2349 isDeletedAttId = 131120
2350 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2352 expectedTimeDo = 2650466015990000000
2353 originating = self.get_originating_time(obj["replPropertyMetaData"][0], isDeletedAttId)
2354 if originating != expectedTimeDo:
2355 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2356 nmsg = ldb.Message()
2358 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2360 self.samdb.modify(nmsg, controls=["provision:0"])
2363 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2365 for att in set_attrs_seen.difference(set_attrs_from_md):
2367 self.report("On object %s" % dn)
2370 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2371 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2372 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2374 self.fix_metadata(obj, att)
2376 if self.is_fsmo_role(dn):
2377 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
2378 self.err_no_fsmoRoleOwner(obj)
2382 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2383 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2384 controls=["show_recycled:1", "show_deleted:1"])
2385 except ldb.LdbError as e11:
2386 (enum, estr) = e11.args
2387 if enum == ldb.ERR_NO_SUCH_OBJECT:
2388 self.err_missing_parent(obj)
2393 if dn in self.deleted_objects_containers and '*' in attrs:
2394 if self.is_deleted_deleted_objects(obj):
2395 self.err_deleted_deleted_objects(obj)
2398 for (dns_part, msg) in self.dns_partitions:
2399 if dn == dns_part and 'repsFrom' in obj:
2400 location = "msDS-NC-Replica-Locations"
2401 if self.samdb.am_rodc():
2402 location = "msDS-NC-RO-Replica-Locations"
2404 if location not in msg:
2405 # There are no replica locations!
2406 self.err_replica_locations(obj, msg.dn, location)
2411 for loc in msg[location]:
2412 if str(loc) == self.samdb.get_dsServiceName():
2415 # This DC is not in the replica locations
2416 self.err_replica_locations(obj, msg.dn, location)
2419 if dn == self.server_ref_dn:
2420 # Check we have a valid RID Set
2421 if "*" in attrs or "rIDSetReferences" in attrs:
2422 if "rIDSetReferences" not in obj:
2423 # NO RID SET reference
2424 # We are RID master, allocate it.
2427 if self.is_rid_master:
2428 # Allocate a RID Set
2429 if self.confirm_all('Allocate the missing RID set for RID master?',
2430 'fix_missing_rid_set_master'):
2432 # We don't have auto-transaction logic on
2433 # extended operations, so we have to do it
2436 self.samdb.transaction_start()
2439 self.samdb.create_own_rid_set()
2442 self.samdb.transaction_cancel()
2445 self.samdb.transaction_commit()
2447 elif not self.samdb.am_rodc():
2448 self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2450 # Check some details of our own RID Set
2451 if dn == self.rid_set_dn:
2452 res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2453 attrs=["rIDAllocationPool",
2454 "rIDPreviousAllocationPool",
2457 if "rIDAllocationPool" not in res[0]:
2458 self.report("No rIDAllocationPool found in %s" % dn)
2461 next_pool = int(res[0]["rIDAllocationPool"][0])
2463 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2464 low = 0x00000000FFFFFFFF & next_pool
2467 self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2470 if "rIDNextRID" in res[0]:
2471 next_free_rid = int(res[0]["rIDNextRID"][0])
2475 if next_free_rid == 0:
2480 # Check the remainder of this pool for conflicts. If
2481 # ridalloc_allocate_rid() moves to a new pool, this
2482 # will be above high, so we will stop.
2483 while next_free_rid <= high:
2484 sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2486 res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2488 except ldb.LdbError as e:
2489 (enum, estr) = e.args
2490 if enum != ldb.ERR_NO_SUCH_OBJECT:
2494 self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2497 if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2499 'fix_sid_rid_set_conflict'):
2500 self.samdb.transaction_start()
2502 # This will burn RIDs, which will move
2503 # past the conflict. We then check again
2504 # to see if the new RID conflicts, until
2505 # the end of the current pool. We don't
2506 # look at the next pool to avoid burning
2507 # all RIDs in one go in some strange
2511 allocated_rid = self.samdb.allocate_rid()
2512 if allocated_rid >= next_free_rid:
2513 next_free_rid = allocated_rid + 1
2516 self.samdb.transaction_cancel()
2519 self.samdb.transaction_commit()
2527 ################################################################
2528 # check special @ROOTDSE attributes
2529 def check_rootdse(self):
2530 '''check the @ROOTDSE special object'''
2531 dn = ldb.Dn(self.samdb, '@ROOTDSE')
2533 self.report("Checking object %s" % dn)
2534 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2536 self.report("Object %s disappeared during check" % dn)
2541 # check that the dsServiceName is in GUID form
2542 if 'dsServiceName' not in obj:
2543 self.report('ERROR: dsServiceName missing in @ROOTDSE')
2544 return error_count + 1
2546 if not str(obj['dsServiceName'][0]).startswith('<GUID='):
2547 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2549 if not self.confirm('Change dsServiceName to GUID form?'):
2551 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0].decode('utf8')),
2552 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2553 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2556 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2557 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2558 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2559 self.report("Changed dsServiceName to GUID form")
2562 ###############################################
2563 # re-index the database
2565 def reindex_database(self):
2566 '''re-index the whole database'''
2568 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2569 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2570 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2571 return self.do_modify(m, [], 're-indexed database', validate=False)
2573 ###############################################
2575 def reset_modules(self):
2576 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2578 m.dn = ldb.Dn(self.samdb, "@MODULES")
2579 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2580 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)