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