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