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