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