dbcheck: don't check expired tombstone objects by default anymore
[Samba.git] / python / samba / dbchecker.py
blob98508192c10bf5504ad334bdff3de882e8e22e60
1 # Samba4 AD database checker
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from __future__ import print_function
21 import ldb
22 import samba
23 import time
24 from base64 import b64decode
25 from samba import dsdb
26 from samba import common
27 from samba.dcerpc import misc
28 from samba.dcerpc import drsuapi
29 from samba.ndr import ndr_unpack, ndr_pack
30 from samba.dcerpc import drsblobs
31 from samba.common import dsdb_Dn
32 from samba.dcerpc import security
33 from samba.descriptor import get_wellknown_sds, get_diff_sds
34 from samba.auth import system_session, admin_session
35 from samba.netcmd import CommandError
36 from samba.netcmd.fsmo import get_fsmo_roleowner
38 # vals is a sequence of ldb.bytes objects which are a subclass
39 # of 'byte' type in python3 and just a str type in python2, to
40 # display as string these need to be converted to string via (str)
41 # function in python3 but that may generate a UnicodeDecode error,
42 # if so use repr instead. We need to at least try to get the 'str'
43 # value if possible to allow some tests which check the strings
44 # outputted to pass, these tests compare attr values logged to stdout
45 # against those in various results files.
47 def dump_attr_values(vals):
48 result = ""
49 for value in vals:
50 if len(result):
51 result = "," + result
52 try:
53 result = result + str(value)
54 except UnicodeDecodeError:
55 result = result + repr(value)
56 return result
58 class dbcheck(object):
59 """check a SAM database for errors"""
61 def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
62 yes=False, quiet=False, in_transaction=False,
63 quick_membership_checks=False,
64 reset_well_known_acls=False,
65 check_expired_tombstones=False):
66 self.samdb = samdb
67 self.dict_oid_name = None
68 self.samdb_schema = (samdb_schema or samdb)
69 self.verbose = verbose
70 self.fix = fix
71 self.yes = yes
72 self.quiet = quiet
73 self.remove_all_unknown_attributes = False
74 self.remove_all_empty_attributes = False
75 self.fix_all_normalisation = False
76 self.fix_all_duplicates = False
77 self.fix_all_DN_GUIDs = False
78 self.fix_all_binary_dn = False
79 self.remove_implausible_deleted_DN_links = False
80 self.remove_plausible_deleted_DN_links = False
81 self.fix_all_string_dn_component_mismatch = False
82 self.fix_all_GUID_dn_component_mismatch = False
83 self.fix_all_SID_dn_component_mismatch = False
84 self.fix_all_SID_dn_component_missing = False
85 self.fix_all_old_dn_string_component_mismatch = False
86 self.fix_all_metadata = False
87 self.fix_time_metadata = False
88 self.fix_undead_linked_attributes = False
89 self.fix_all_missing_backlinks = False
90 self.fix_all_orphaned_backlinks = False
91 self.fix_all_missing_forward_links = False
92 self.duplicate_link_cache = dict()
93 self.recover_all_forward_links = False
94 self.fix_rmd_flags = False
95 self.fix_ntsecuritydescriptor = False
96 self.fix_ntsecuritydescriptor_owner_group = False
97 self.seize_fsmo_role = False
98 self.move_to_lost_and_found = False
99 self.fix_instancetype = False
100 self.fix_replmetadata_zero_invocationid = False
101 self.fix_replmetadata_duplicate_attid = False
102 self.fix_replmetadata_wrong_attid = False
103 self.fix_replmetadata_unsorted_attid = False
104 self.fix_deleted_deleted_objects = False
105 self.fix_incorrect_deleted_objects = False
106 self.fix_dn = False
107 self.fix_base64_userparameters = False
108 self.fix_utf8_userparameters = False
109 self.fix_doubled_userparameters = False
110 self.fix_sid_rid_set_conflict = False
111 self.quick_membership_checks = quick_membership_checks
112 self.reset_well_known_acls = reset_well_known_acls
113 self.check_expired_tombstones = check_expired_tombstones
114 self.expired_tombstones = 0
115 self.reset_all_well_known_acls = False
116 self.in_transaction = in_transaction
117 self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
118 self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
119 self.schema_dn = samdb.get_schema_basedn()
120 self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
121 self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
122 self.class_schemaIDGUID = {}
123 self.wellknown_sds = get_wellknown_sds(self.samdb)
124 self.fix_all_missing_objectclass = False
125 self.fix_missing_deleted_objects = False
126 self.fix_replica_locations = False
127 self.fix_missing_rid_set_master = False
128 self.fix_changes_after_deletion_bug = False
130 self.dn_set = set()
131 self.link_id_cache = {}
132 self.name_map = {}
133 try:
134 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE,
135 attrs=["objectSid"])
136 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
137 self.name_map['DnsAdmins'] = str(dnsadmins_sid)
138 except ldb.LdbError as e5:
139 (enum, estr) = e5.args
140 if enum != ldb.ERR_NO_SUCH_OBJECT:
141 raise
142 pass
144 self.system_session_info = system_session()
145 self.admin_session_info = admin_session(None, samdb.get_domain_sid())
147 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
148 if "msDS-hasMasterNCs" in res[0]:
149 self.write_ncs = res[0]["msDS-hasMasterNCs"]
150 else:
151 # If the Forest Level is less than 2003 then there is no
152 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
153 # no need to merge as all the NCs that are in hasMasterNCs must
154 # also be in msDS-hasMasterNCs (but not the opposite)
155 if "hasMasterNCs" in res[0]:
156 self.write_ncs = res[0]["hasMasterNCs"]
157 else:
158 self.write_ncs = None
160 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
161 self.deleted_objects_containers = []
162 self.ncs_lacking_deleted_containers = []
163 self.dns_partitions = []
164 try:
165 self.ncs = res[0]["namingContexts"]
166 except KeyError:
167 pass
168 except IndexError:
169 pass
171 for nc in self.ncs:
172 try:
173 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc.decode('utf8')),
174 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
175 self.deleted_objects_containers.append(dn)
176 except KeyError:
177 self.ncs_lacking_deleted_containers.append(ldb.Dn(self.samdb, nc.decode('utf8')))
179 domaindns_zone = 'DC=DomainDnsZones,%s' % self.samdb.get_default_basedn()
180 forestdns_zone = 'DC=ForestDnsZones,%s' % self.samdb.get_root_basedn()
181 domain = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
182 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
183 base=self.samdb.get_partitions_dn(),
184 expression="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone)
185 if len(domain) == 1:
186 self.dns_partitions.append((ldb.Dn(self.samdb, forestdns_zone), domain[0]))
188 forest = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
189 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
190 base=self.samdb.get_partitions_dn(),
191 expression="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone)
192 if len(forest) == 1:
193 self.dns_partitions.append((ldb.Dn(self.samdb, domaindns_zone), forest[0]))
195 fsmo_dn = ldb.Dn(self.samdb, "CN=RID Manager$,CN=System," + self.samdb.domain_dn())
196 rid_master = get_fsmo_roleowner(self.samdb, fsmo_dn, "rid")
197 if ldb.Dn(self.samdb, self.samdb.get_dsServiceName()) == rid_master:
198 self.is_rid_master = True
199 else:
200 self.is_rid_master = False
202 # To get your rid set
203 # 1. Get server name
204 res = self.samdb.search(base=ldb.Dn(self.samdb, self.samdb.get_serverName()),
205 scope=ldb.SCOPE_BASE, attrs=["serverReference"])
206 # 2. Get server reference
207 self.server_ref_dn = ldb.Dn(self.samdb, res[0]['serverReference'][0].decode('utf8'))
209 # 3. Get RID Set
210 res = self.samdb.search(base=self.server_ref_dn,
211 scope=ldb.SCOPE_BASE, attrs=['rIDSetReferences'])
212 if "rIDSetReferences" in res[0]:
213 self.rid_set_dn = ldb.Dn(self.samdb, res[0]['rIDSetReferences'][0].decode('utf8'))
214 else:
215 self.rid_set_dn = None
217 ntds_service_dn = "CN=Directory Service,CN=Windows NT,CN=Services,%s" % \
218 self.samdb.get_config_basedn().get_linearized()
219 res = samdb.search(base=ntds_service_dn,
220 scope=ldb.SCOPE_BASE,
221 expression="(objectClass=nTDSService)",
222 attrs=["tombstoneLifetime"])
223 self.tombstoneLifetime = int(res[0]["tombstoneLifetime"][0])
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
241 pass
243 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=None,
244 attrs=None):
245 '''perform a database check, returning the number of errors found'''
246 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
247 self.report('Checking %u objects' % len(res))
248 error_count = 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, 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 if error_count != 0 and not self.fix:
269 self.report("Please use --fix to fix these errors")
271 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
272 return error_count
274 def check_deleted_objects_containers(self):
275 """This function only fixes conflicts on the Deleted Objects
276 containers, not the attributes"""
277 error_count = 0
278 for nc in self.ncs_lacking_deleted_containers:
279 if nc == self.schema_dn:
280 continue
281 error_count += 1
282 self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc)
283 if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'):
284 continue
286 dn = ldb.Dn(self.samdb, "CN=Deleted Objects")
287 dn.add_base(nc)
289 conflict_dn = None
290 try:
291 # If something already exists here, add a conflict
292 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[],
293 controls=["show_deleted:1", "extended_dn:1:1",
294 "show_recycled:1", "reveal_internals:0"])
295 if len(res) != 0:
296 guid = res[0].dn.get_extended_component("GUID")
297 conflict_dn = ldb.Dn(self.samdb,
298 "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid)))
299 conflict_dn.add_base(nc)
301 except ldb.LdbError as e2:
302 (enum, estr) = e2.args
303 if enum == ldb.ERR_NO_SUCH_OBJECT:
304 pass
305 else:
306 self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr)
307 return 1
309 if conflict_dn is not None:
310 try:
311 self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"])
312 except ldb.LdbError as e1:
313 (enum, estr) = e1.args
314 self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr))
315 return 1
317 # Refresh wellKnownObjects links
318 res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE,
319 attrs=['wellKnownObjects'],
320 controls=["show_deleted:1", "extended_dn:0",
321 "show_recycled:1", "reveal_internals:0"])
322 if len(res) != 1:
323 self.report("wellKnownObjects was not found for NC %s" % nc)
324 return 1
326 # Prevent duplicate deleted objects containers just in case
327 wko = res[0]["wellKnownObjects"]
328 listwko = []
329 proposed_objectguid = None
330 for o in wko:
331 dsdb_dn = dsdb_Dn(self.samdb, o.decode('utf8'), dsdb.DSDB_SYNTAX_BINARY_DN)
332 if self.is_deleted_objects_dn(dsdb_dn):
333 self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o)
334 # We really want to put this back in the same spot
335 # as the original one, so that on replication we
336 # merge, rather than conflict.
337 proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID")
338 listwko.append(str(o))
340 if proposed_objectguid is not None:
341 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid))
342 else:
343 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
344 listwko.append('%s:%s' % (wko_prefix, dn))
345 guid_suffix = ""
347 # Insert a brand new Deleted Objects container
348 self.samdb.add_ldif("""dn: %s
349 objectClass: top
350 objectClass: container
351 description: Container for deleted objects
352 isDeleted: TRUE
353 isCriticalSystemObject: TRUE
354 showInAdvancedViewOnly: TRUE
355 systemFlags: -1946157056%s""" % (dn, guid_suffix),
356 controls=["relax:0", "provision:0"])
358 delta = ldb.Message()
359 delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"]))
360 delta["wellKnownObjects"] = ldb.MessageElement(listwko,
361 ldb.FLAG_MOD_REPLACE,
362 "wellKnownObjects")
364 # Insert the link to the brand new container
365 if self.do_modify(delta, ["relax:0"],
366 "NC %s lacks Deleted Objects WKGUID" % nc,
367 validate=False):
368 self.report("Added %s well known guid link" % dn)
370 self.deleted_objects_containers.append(dn)
372 return error_count
374 def report(self, msg):
375 '''print a message unless quiet is set'''
376 if not self.quiet:
377 print(msg)
379 def confirm(self, msg, allow_all=False, forced=False):
380 '''confirm a change'''
381 if not self.fix:
382 return False
383 if self.quiet:
384 return self.yes
385 if self.yes:
386 forced = True
387 return common.confirm(msg, forced=forced, allow_all=allow_all)
389 ################################################################
390 # a local confirm function with support for 'all'
391 def confirm_all(self, msg, all_attr):
392 '''confirm a change with support for "all" '''
393 if not self.fix:
394 return False
395 if getattr(self, all_attr) == 'NONE':
396 return False
397 if getattr(self, all_attr) == 'ALL':
398 forced = True
399 else:
400 forced = self.yes
401 if self.quiet:
402 return forced
403 c = common.confirm(msg, forced=forced, allow_all=True)
404 if c == 'ALL':
405 setattr(self, all_attr, 'ALL')
406 return True
407 if c == 'NONE':
408 setattr(self, all_attr, 'NONE')
409 return False
410 return c
412 def do_delete(self, dn, controls, msg):
413 '''delete dn with optional verbose output'''
414 if self.verbose:
415 self.report("delete DN %s" % dn)
416 try:
417 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
418 self.samdb.delete(dn, controls=controls)
419 except Exception as err:
420 if self.in_transaction:
421 raise CommandError("%s : %s" % (msg, err))
422 self.report("%s : %s" % (msg, err))
423 return False
424 return True
426 def do_modify(self, m, controls, msg, validate=True):
427 '''perform a modify with optional verbose output'''
428 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
429 if self.verbose:
430 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
431 self.report("controls: %r" % controls)
432 try:
433 self.samdb.modify(m, controls=controls, validate=validate)
434 except Exception as err:
435 if self.in_transaction:
436 raise CommandError("%s : %s" % (msg, err))
437 self.report("%s : %s" % (msg, err))
438 return False
439 return True
441 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
442 '''perform a modify with optional verbose output'''
443 if self.verbose:
444 self.report("""dn: %s
445 changeType: modrdn
446 newrdn: %s
447 deleteOldRdn: 1
448 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
449 try:
450 to_dn = to_rdn + to_base
451 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
452 self.samdb.rename(from_dn, to_dn, controls=controls)
453 except Exception as err:
454 if self.in_transaction:
455 raise CommandError("%s : %s" % (msg, err))
456 self.report("%s : %s" % (msg, err))
457 return False
458 return True
460 def get_attr_linkID_and_reverse_name(self, attrname):
461 if attrname in self.link_id_cache:
462 return self.link_id_cache[attrname]
463 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
464 if linkID:
465 revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
466 else:
467 revname = None
468 self.link_id_cache[attrname] = (linkID, revname)
469 return linkID, revname
471 def err_empty_attribute(self, dn, attrname):
472 '''fix empty attributes'''
473 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
474 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
475 self.report("Not fixing empty attribute %s" % attrname)
476 return
478 m = ldb.Message()
479 m.dn = dn
480 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
481 if self.do_modify(m, ["relax:0", "show_recycled:1"],
482 "Failed to remove empty attribute %s" % attrname, validate=False):
483 self.report("Removed empty attribute %s" % attrname)
485 def err_normalise_mismatch(self, dn, attrname, values):
486 '''fix attribute normalisation errors'''
487 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
488 mod_list = []
489 for val in values:
490 normalised = self.samdb.dsdb_normalise_attributes(
491 self.samdb_schema, attrname, [val])
492 if len(normalised) != 1:
493 self.report("Unable to normalise value '%s'" % val)
494 mod_list.append((val, ''))
495 elif (normalised[0] != val):
496 self.report("value '%s' should be '%s'" % (val, normalised[0]))
497 mod_list.append((val, normalised[0]))
498 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
499 self.report("Not fixing attribute %s" % attrname)
500 return
502 m = ldb.Message()
503 m.dn = dn
504 for i in range(0, len(mod_list)):
505 (val, nval) = mod_list[i]
506 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
507 if nval != '':
508 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
509 attrname)
511 if self.do_modify(m, ["relax:0", "show_recycled:1"],
512 "Failed to normalise attribute %s" % attrname,
513 validate=False):
514 self.report("Normalised attribute %s" % attrname)
516 def err_normalise_mismatch_replace(self, dn, attrname, values):
517 '''fix attribute normalisation errors'''
518 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
519 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
520 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
521 if list(normalised) == values:
522 return
523 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
524 self.report("Not fixing attribute '%s'" % attrname)
525 return
527 m = ldb.Message()
528 m.dn = dn
529 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
531 if self.do_modify(m, ["relax:0", "show_recycled:1"],
532 "Failed to normalise attribute %s" % attrname,
533 validate=False):
534 self.report("Normalised attribute %s" % attrname)
536 def err_duplicate_values(self, dn, attrname, dup_values, values):
537 '''fix attribute normalisation errors'''
538 self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
539 self.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dump_attr_values(dup_values)), ','.join(dump_attr_values(values))))
540 if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
541 self.report("Not fixing attribute '%s'" % attrname)
542 return
544 m = ldb.Message()
545 m.dn = dn
546 m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
548 if self.do_modify(m, ["relax:0", "show_recycled:1"],
549 "Failed to remove duplicate value on attribute %s" % attrname,
550 validate=False):
551 self.report("Removed duplicate value on attribute %s" % attrname)
553 def is_deleted_objects_dn(self, dsdb_dn):
554 '''see if a dsdb_Dn is the special Deleted Objects DN'''
555 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
557 def err_missing_objectclass(self, dn):
558 """handle object without objectclass"""
559 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)))
560 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'):
561 self.report("Not deleting object with missing objectclass '%s'" % dn)
562 return
563 if self.do_delete(dn, ["relax:0"],
564 "Failed to remove DN %s" % dn):
565 self.report("Removed DN %s" % dn)
567 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False):
568 """handle a DN pointing to a deleted object"""
569 if not remove_plausible:
570 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
571 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
572 if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
573 self.report("Not removing")
574 return
575 else:
576 self.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
577 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
578 if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
579 self.report("Not removing")
580 return
582 m = ldb.Message()
583 m.dn = dn
584 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
585 if self.do_modify(m, ["show_recycled:1",
586 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
587 "Failed to remove deleted DN attribute %s" % attrname):
588 self.report("Removed deleted DN on attribute %s" % attrname)
590 def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
591 """handle a missing target DN (if specified, GUID form can't be found,
592 and otherwise DN string form can't be found)"""
594 # Don't change anything if the object itself is deleted
595 if str(dn).find('\\0ADEL') != -1:
596 # We don't bump the error count as Samba produces these
597 # in normal operation
598 self.report("WARNING: no target object found for GUID "
599 "component link %s in deleted object "
600 "%s - %s" % (attrname, dn, val))
601 self.report("Not removing dangling one-way "
602 "link on deleted object "
603 "(tombstone garbage collection in progress?)")
604 return 0
606 # check if its a backlink
607 linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
608 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
610 linkID, reverse_link_name \
611 = self.get_attr_linkID_and_reverse_name(attrname)
612 if reverse_link_name is not None:
613 self.report("WARNING: no target object found for GUID "
614 "component for one-way forward link "
615 "%s in object "
616 "%s - %s" % (attrname, dn, val))
617 self.report("Not removing dangling forward link")
618 return 0
620 nc_root = self.samdb.get_nc_root(dn)
621 target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn)
622 if nc_root != target_nc_root:
623 # We don't bump the error count as Samba produces these
624 # in normal operation
625 self.report("WARNING: no target object found for GUID "
626 "component for cross-partition link "
627 "%s in object "
628 "%s - %s" % (attrname, dn, val))
629 self.report("Not removing dangling one-way "
630 "cross-partition link "
631 "(we might be mid-replication)")
632 return 0
634 # Due to our link handling one-way links pointing to
635 # missing objects are plausible.
637 # We don't bump the error count as Samba produces these
638 # in normal operation
639 self.report("WARNING: no target object found for GUID "
640 "component for DN value %s in object "
641 "%s - %s" % (attrname, dn, val))
642 self.err_deleted_dn(dn, attrname, val,
643 dsdb_dn, dsdb_dn, True)
644 return 0
646 # We bump the error count here, as we should have deleted this
647 self.report("ERROR: no target object found for GUID "
648 "component for link %s in object "
649 "%s - %s" % (attrname, dn, val))
650 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
651 return 1
653 def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
654 """handle a missing GUID extended DN component"""
655 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
656 controls = ["extended_dn:1:1", "show_recycled:1"]
657 try:
658 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
659 attrs=[], controls=controls)
660 except ldb.LdbError as e7:
661 (enum, estr) = e7.args
662 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
663 if enum != ldb.ERR_NO_SUCH_OBJECT:
664 raise
665 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
666 return
667 if len(res) == 0:
668 self.report("unable to find object for DN %s" % dsdb_dn.dn)
669 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
670 return
671 dsdb_dn.dn = res[0].dn
673 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
674 self.report("Not fixing %s" % errstr)
675 return
676 m = ldb.Message()
677 m.dn = dn
678 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
679 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
681 if self.do_modify(m, ["show_recycled:1"],
682 "Failed to fix %s on attribute %s" % (errstr, attrname)):
683 self.report("Fixed %s on attribute %s" % (errstr, attrname))
685 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
686 """handle an incorrect binary DN component"""
687 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
689 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
690 self.report("Not fixing %s" % errstr)
691 return
692 m = ldb.Message()
693 m.dn = dn
694 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
695 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
697 if self.do_modify(m, ["show_recycled:1"],
698 "Failed to fix %s on attribute %s" % (errstr, attrname)):
699 self.report("Fixed %s on attribute %s" % (errstr, attrname))
701 def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn):
702 """handle a DN string being incorrect"""
703 self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val))
704 dsdb_dn.dn = correct_dn
706 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
707 'fix_all_old_dn_string_component_mismatch'):
708 self.report("Not fixing old string component")
709 return
710 m = ldb.Message()
711 m.dn = dn
712 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
713 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
714 if self.do_modify(m, ["show_recycled:1",
715 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME],
716 "Failed to fix old DN string on attribute %s" % (attrname)):
717 self.report("Fixed old DN string on attribute %s" % (attrname))
719 def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
720 """handle a DN string being incorrect"""
721 self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
722 dsdb_dn.dn = correct_dn
724 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
725 'fix_all_%s_dn_component_mismatch' % mismatch_type):
726 self.report("Not fixing %s component mismatch" % mismatch_type)
727 return
728 m = ldb.Message()
729 m.dn = dn
730 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
731 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
732 if self.do_modify(m, ["show_recycled:1"],
733 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
734 self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
736 def err_dn_component_missing_target_sid(self, dn, attrname, val, dsdb_dn, target_sid_blob):
737 """handle a DN string being incorrect"""
738 self.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname, dn, val))
740 if len(dsdb_dn.prefix) != 0:
741 self.report("Not fixing missing DN SID on DN+BINARY or DN+STRING")
742 return
744 correct_dn = ldb.Dn(self.samdb, dsdb_dn.dn.extended_str())
745 correct_dn.set_extended_component("SID", target_sid_blob)
747 if not self.confirm_all('Change DN to %s?' % correct_dn.extended_str(),
748 'fix_all_SID_dn_component_missing'):
749 self.report("Not fixing missing DN SID component")
750 return
752 target_guid_blob = correct_dn.get_extended_component("GUID")
753 guid_sid_dn = ldb.Dn(self.samdb, "")
754 guid_sid_dn.set_extended_component("GUID", target_guid_blob)
755 guid_sid_dn.set_extended_component("SID", target_sid_blob)
757 m = ldb.Message()
758 m.dn = dn
759 m['new_value'] = ldb.MessageElement(guid_sid_dn.extended_str(), ldb.FLAG_MOD_ADD, attrname)
760 controls = [
761 "show_recycled:1",
762 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID
764 if self.do_modify(m, controls,
765 "Failed to ADD missing DN SID on attribute %s" % (attrname)):
766 self.report("Fixed missing DN SID on attribute %s" % (attrname))
768 def err_unknown_attribute(self, obj, attrname):
769 '''handle an unknown attribute error'''
770 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
771 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
772 self.report("Not removing %s" % attrname)
773 return
774 m = ldb.Message()
775 m.dn = obj.dn
776 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
777 if self.do_modify(m, ["relax:0", "show_recycled:1"],
778 "Failed to remove unknown attribute %s" % attrname):
779 self.report("Removed unknown attribute %s" % (attrname))
781 def err_undead_linked_attribute(self, obj, attrname, val):
782 '''handle a link that should not be there on a deleted object'''
783 self.report("ERROR: linked attribute '%s' to '%s' is present on "
784 "deleted object %s" % (attrname, val, obj.dn))
785 if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
786 self.report("Not removing linked attribute %s" % attrname)
787 return
788 m = ldb.Message()
789 m.dn = obj.dn
790 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
792 if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
793 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
794 "Failed to delete forward link %s" % attrname):
795 self.report("Fixed undead forward link %s" % (attrname))
797 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
798 '''handle a missing backlink value'''
799 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
800 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
801 self.report("Not fixing missing backlink %s" % backlink_name)
802 return
803 m = ldb.Message()
804 m.dn = target_dn
805 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
806 if self.do_modify(m, ["show_recycled:1", "relax:0"],
807 "Failed to fix missing backlink %s" % backlink_name):
808 self.report("Fixed missing backlink %s" % (backlink_name))
810 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
811 '''handle a incorrect RMD_FLAGS value'''
812 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
813 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()))
814 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
815 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
816 return
817 m = ldb.Message()
818 m.dn = obj.dn
819 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
820 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
821 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
822 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
824 def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val,
825 target_dn, forward_attr, forward_syntax,
826 check_duplicates=True):
827 '''handle a orphaned backlink value'''
828 if check_duplicates is True and self.has_duplicate_links(target_dn, forward_attr, forward_syntax):
829 self.report("WARNING: Keep orphaned backlink attribute " +
830 "'%s' in '%s' for link '%s' in '%s'" % (
831 backlink_attr, obj_dn, forward_attr, target_dn))
832 return
833 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn))
834 if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'):
835 self.report("Not removing orphaned backlink %s" % backlink_attr)
836 return
837 m = ldb.Message()
838 m.dn = obj_dn
839 m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr)
840 if self.do_modify(m, ["show_recycled:1", "relax:0"],
841 "Failed to fix orphaned backlink %s" % backlink_attr):
842 self.report("Fixed orphaned backlink %s" % (backlink_attr))
844 def err_recover_forward_links(self, obj, forward_attr, forward_vals):
845 '''handle a duplicate links value'''
847 self.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn))
849 if not self.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'):
850 self.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
851 forward_attr, obj.dn))
852 return
853 m = ldb.Message()
854 m.dn = obj.dn
855 m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr)
856 if self.do_modify(m, ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS],
857 "Failed to fix duplicate links in attribute '%s'" % forward_attr):
858 self.report("Fixed duplicate links in attribute '%s'" % (forward_attr))
859 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
860 assert duplicate_cache_key in self.duplicate_link_cache
861 self.duplicate_link_cache[duplicate_cache_key] = False
863 def err_no_fsmoRoleOwner(self, obj):
864 '''handle a missing fSMORoleOwner'''
865 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
866 res = self.samdb.search("",
867 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
868 assert len(res) == 1
869 serviceName = str(res[0]["dsServiceName"][0])
870 if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
871 self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
872 return
873 m = ldb.Message()
874 m.dn = obj.dn
875 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
876 if self.do_modify(m, [],
877 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
878 self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
880 def err_missing_parent(self, obj):
881 '''handle a missing parent'''
882 self.report("ERROR: parent object not found for %s" % (obj.dn))
883 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
884 self.report('Not moving object %s into LostAndFound' % (obj.dn))
885 return
887 keep_transaction = False
888 self.samdb.transaction_start()
889 try:
890 nc_root = self.samdb.get_nc_root(obj.dn)
891 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
892 new_dn = ldb.Dn(self.samdb, str(obj.dn))
893 new_dn.remove_base_components(len(new_dn) - 1)
894 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
895 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
896 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
898 m = ldb.Message()
899 m.dn = obj.dn
900 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
902 if self.do_modify(m, [],
903 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
904 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
905 keep_transaction = True
906 except:
907 self.samdb.transaction_cancel()
908 raise
910 if keep_transaction:
911 self.samdb.transaction_commit()
912 else:
913 self.samdb.transaction_cancel()
915 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val, controls):
916 '''handle a wrong dn'''
918 new_rdn = ldb.Dn(self.samdb, str(new_dn))
919 new_rdn.remove_base_components(len(new_rdn) - 1)
920 new_parent = new_dn.parent()
922 attributes = ""
923 if rdn_val != name_val:
924 attributes += "%s=%r " % (rdn_attr, rdn_val)
925 attributes += "name=%r" % (name_val)
927 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
928 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
929 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
930 return
932 if self.do_rename(obj.dn, new_rdn, new_parent, controls,
933 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
934 self.report("Renamed %s into %s" % (obj.dn, new_dn))
936 def err_wrong_instancetype(self, obj, calculated_instancetype):
937 '''handle a wrong instanceType'''
938 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
939 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
940 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
941 return
943 m = ldb.Message()
944 m.dn = obj.dn
945 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
946 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
947 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
948 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
950 def err_short_userParameters(self, obj, attrname, value):
951 # This is a truncated userParameters due to a pre 4.1 replication bug
952 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)))
954 def err_base64_userParameters(self, obj, attrname, value):
955 '''handle a wrong userParameters'''
956 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
957 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
958 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
959 return
961 m = ldb.Message()
962 m.dn = obj.dn
963 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
964 if self.do_modify(m, [],
965 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
966 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
968 def err_utf8_userParameters(self, obj, attrname, value):
969 '''handle a wrong userParameters'''
970 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
971 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
972 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
973 return
975 m = ldb.Message()
976 m.dn = obj.dn
977 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
978 ldb.FLAG_MOD_REPLACE, 'userParameters')
979 if self.do_modify(m, [],
980 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
981 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
983 def err_doubled_userParameters(self, obj, attrname, value):
984 '''handle a wrong userParameters'''
985 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
986 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
987 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
988 return
990 m = ldb.Message()
991 m.dn = obj.dn
992 # m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
993 # hmm the above old python2 code doesn't make sense to me and cannot
994 # work in python3 because a string doesn't have a decode method.
995 # However in python2 for some unknown reason this double decode
996 # followed by encode seems to result in what looks like utf8.
997 # In python2 just .decode('utf-16-le').encode('utf-16-le') does nothing
998 # but trigger the 'double UTF16 encoded' condition again :/
1000 # In python2 and python3 value.decode('utf-16-le').encode('utf8') seems
1001 # to do the trick and work as expected.
1002 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').encode('utf8'),
1003 ldb.FLAG_MOD_REPLACE, 'userParameters')
1005 if self.do_modify(m, [],
1006 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
1007 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
1009 def err_odd_userParameters(self, obj, attrname):
1010 # This is a truncated userParameters due to a pre 4.1 replication bug
1011 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)))
1013 def find_revealed_link(self, dn, attrname, guid):
1014 '''return a revealed link in an object'''
1015 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
1016 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
1017 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1018 for val in res[0][attrname]:
1019 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
1020 guid2 = dsdb_dn.dn.get_extended_component("GUID")
1021 if guid == guid2:
1022 return dsdb_dn
1023 return None
1025 def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr):
1026 '''check a linked values for duplicate forward links'''
1027 error_count = 0
1029 duplicate_dict = dict()
1030 unique_dict = dict()
1032 # Only forward links can have this problem
1033 if forward_linkID & 1:
1034 # If we got the reverse, skip it
1035 return (error_count, duplicate_dict, unique_dict)
1037 if backlink_attr is None:
1038 return (error_count, duplicate_dict, unique_dict)
1040 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
1041 if duplicate_cache_key not in self.duplicate_link_cache:
1042 self.duplicate_link_cache[duplicate_cache_key] = False
1044 for val in obj[forward_attr]:
1045 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), forward_syntax)
1047 # all DNs should have a GUID component
1048 guid = dsdb_dn.dn.get_extended_component("GUID")
1049 if guid is None:
1050 continue
1051 guidstr = str(misc.GUID(guid))
1052 keystr = guidstr + dsdb_dn.prefix
1053 if keystr not in unique_dict:
1054 unique_dict[keystr] = dsdb_dn
1055 continue
1056 error_count += 1
1057 if keystr not in duplicate_dict:
1058 duplicate_dict[keystr] = dict()
1059 duplicate_dict[keystr]["keep"] = None
1060 duplicate_dict[keystr]["delete"] = list()
1062 # Now check for the highest RMD_VERSION
1063 v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION"))
1064 v2 = int(dsdb_dn.dn.get_extended_component("RMD_VERSION"))
1065 if v1 > v2:
1066 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
1067 duplicate_dict[keystr]["delete"].append(dsdb_dn)
1068 continue
1069 if v1 < v2:
1070 duplicate_dict[keystr]["keep"] = dsdb_dn
1071 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
1072 unique_dict[keystr] = dsdb_dn
1073 continue
1074 # Fallback to the highest RMD_LOCAL_USN
1075 u1 = int(unique_dict[keystr].dn.get_extended_component("RMD_LOCAL_USN"))
1076 u2 = int(dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN"))
1077 if u1 >= u2:
1078 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
1079 duplicate_dict[keystr]["delete"].append(dsdb_dn)
1080 continue
1081 duplicate_dict[keystr]["keep"] = dsdb_dn
1082 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
1083 unique_dict[keystr] = dsdb_dn
1085 if error_count != 0:
1086 self.duplicate_link_cache[duplicate_cache_key] = True
1088 return (error_count, duplicate_dict, unique_dict)
1090 def has_duplicate_links(self, dn, forward_attr, forward_syntax):
1091 '''check a linked values for duplicate forward links'''
1092 error_count = 0
1094 duplicate_cache_key = "%s:%s" % (str(dn), forward_attr)
1095 if duplicate_cache_key in self.duplicate_link_cache:
1096 return self.duplicate_link_cache[duplicate_cache_key]
1098 forward_linkID, backlink_attr = self.get_attr_linkID_and_reverse_name(forward_attr)
1100 attrs = [forward_attr]
1101 controls = ["extended_dn:1:1", "reveal_internals:0"]
1103 # check its the right GUID
1104 try:
1105 res = self.samdb.search(base=str(dn), scope=ldb.SCOPE_BASE,
1106 attrs=attrs, controls=controls)
1107 except ldb.LdbError as e8:
1108 (enum, estr) = e8.args
1109 if enum != ldb.ERR_NO_SUCH_OBJECT:
1110 raise
1112 return False
1114 obj = res[0]
1115 error_count, duplicate_dict, unique_dict = \
1116 self.check_duplicate_links(obj, forward_attr, forward_syntax, forward_linkID, backlink_attr)
1118 if duplicate_cache_key in self.duplicate_link_cache:
1119 return self.duplicate_link_cache[duplicate_cache_key]
1121 return False
1123 def find_missing_forward_links_from_backlinks(self, obj,
1124 forward_attr,
1125 forward_syntax,
1126 backlink_attr,
1127 forward_unique_dict):
1128 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1129 missing_forward_links = []
1130 error_count = 0
1132 if backlink_attr is None:
1133 return (missing_forward_links, error_count)
1135 if forward_syntax != ldb.SYNTAX_DN:
1136 self.report("Not checking for missing forward links for syntax: %s" %
1137 forward_syntax)
1138 return (missing_forward_links, error_count)
1140 if "sortedLinks" in self.compatibleFeatures:
1141 self.report("Not checking for missing forward links because the db " +
1142 "has the sortedLinks feature")
1143 return (missing_forward_links, error_count)
1145 try:
1146 obj_guid = obj['objectGUID'][0]
1147 obj_guid_str = str(ndr_unpack(misc.GUID, obj_guid))
1148 filter = "(%s=<GUID=%s>)" % (backlink_attr, obj_guid_str)
1150 res = self.samdb.search(expression=filter,
1151 scope=ldb.SCOPE_SUBTREE, attrs=["objectGUID"],
1152 controls=["extended_dn:1:1",
1153 "search_options:1:2",
1154 "paged_results:1:1000"])
1155 except ldb.LdbError as e9:
1156 (enum, estr) = e9.args
1157 raise
1159 for r in res:
1160 target_dn = dsdb_Dn(self.samdb, r.dn.extended_str(), forward_syntax)
1162 guid = target_dn.dn.get_extended_component("GUID")
1163 guidstr = str(misc.GUID(guid))
1164 if guidstr in forward_unique_dict:
1165 continue
1167 # A valid forward link looks like this:
1169 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1170 # <RMD_ADDTIME=131607546230000000>;
1171 # <RMD_CHANGETIME=131607546230000000>;
1172 # <RMD_FLAGS=0>;
1173 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1174 # <RMD_LOCAL_USN=3765>;
1175 # <RMD_ORIGINATING_USN=3765>;
1176 # <RMD_VERSION=1>;
1177 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1178 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1180 # Note that versions older than Samba 4.8 create
1181 # links with RMD_VERSION=0.
1183 # Try to get the local_usn and time from objectClass
1184 # if possible and fallback to any other one.
1185 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1186 obj['replPropertyMetadata'][0])
1187 for o in repl.ctr.array:
1188 local_usn = o.local_usn
1189 t = o.originating_change_time
1190 if o.attid == drsuapi.DRSUAPI_ATTID_objectClass:
1191 break
1193 # We use a magic invocationID for restoring missing
1194 # forward links to recover from bug #13228.
1195 # This should allow some more future magic to fix the
1196 # problem.
1198 # It also means it looses the conflict resolution
1199 # against almost every real invocation, if the
1200 # version is also 0.
1201 originating_invocid = misc.GUID("ffffffff-4700-4700-4700-000000b13228")
1202 originating_usn = 1
1204 rmd_addtime = t
1205 rmd_changetime = t
1206 rmd_flags = 0
1207 rmd_invocid = originating_invocid
1208 rmd_originating_usn = originating_usn
1209 rmd_local_usn = local_usn
1210 rmd_version = 0
1212 target_dn.dn.set_extended_component("RMD_ADDTIME", str(rmd_addtime))
1213 target_dn.dn.set_extended_component("RMD_CHANGETIME", str(rmd_changetime))
1214 target_dn.dn.set_extended_component("RMD_FLAGS", str(rmd_flags))
1215 target_dn.dn.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid))
1216 target_dn.dn.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn))
1217 target_dn.dn.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn))
1218 target_dn.dn.set_extended_component("RMD_VERSION", str(rmd_version))
1220 error_count += 1
1221 missing_forward_links.append(target_dn)
1223 return (missing_forward_links, error_count)
1225 def check_dn(self, obj, attrname, syntax_oid):
1226 '''check a DN attribute for correctness'''
1227 error_count = 0
1228 obj_guid = obj['objectGUID'][0]
1230 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1231 if reverse_link_name is not None:
1232 reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
1233 else:
1234 reverse_syntax_oid = None
1236 is_member_link = attrname in ("member", "memberOf")
1237 if is_member_link and self.quick_membership_checks:
1238 duplicate_dict = {}
1239 else:
1240 error_count, duplicate_dict, unique_dict = \
1241 self.check_duplicate_links(obj, attrname, syntax_oid,
1242 linkID, reverse_link_name)
1244 if len(duplicate_dict) != 0:
1246 missing_forward_links, missing_error_count = \
1247 self.find_missing_forward_links_from_backlinks(obj,
1248 attrname, syntax_oid,
1249 reverse_link_name,
1250 unique_dict)
1251 error_count += missing_error_count
1253 forward_links = [dn for dn in unique_dict.values()]
1255 if missing_error_count != 0:
1256 self.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1257 attrname, obj.dn))
1258 else:
1259 self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
1260 for m in missing_forward_links:
1261 self.report("Missing link '%s'" % (m))
1262 if not self.confirm_all("Schedule readding missing forward link for attribute %s" % attrname,
1263 'fix_all_missing_forward_links'):
1264 self.err_orphaned_backlink(m.dn, reverse_link_name,
1265 obj.dn.extended_str(), obj.dn,
1266 attrname, syntax_oid,
1267 check_duplicates=False)
1268 continue
1269 forward_links += [m]
1270 for keystr in duplicate_dict.keys():
1271 d = duplicate_dict[keystr]
1272 for dd in d["delete"]:
1273 self.report("Duplicate link '%s'" % dd)
1274 self.report("Correct link '%s'" % d["keep"])
1276 # We now construct the sorted dn values.
1277 # They're sorted by the objectGUID of the target
1278 # See dsdb_Dn.__cmp__()
1279 vals = [str(dn) for dn in sorted(forward_links)]
1280 self.err_recover_forward_links(obj, attrname, vals)
1281 # We should continue with the fixed values
1282 obj[attrname] = ldb.MessageElement(vals, 0, attrname)
1284 for val in obj[attrname]:
1285 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
1287 # all DNs should have a GUID component
1288 guid = dsdb_dn.dn.get_extended_component("GUID")
1289 if guid is None:
1290 error_count += 1
1291 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
1292 "missing GUID")
1293 continue
1295 guidstr = str(misc.GUID(guid))
1296 attrs = ['isDeleted', 'replPropertyMetaData']
1298 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
1299 fixing_msDS_HasInstantiatedNCs = True
1300 attrs.append("instanceType")
1301 else:
1302 fixing_msDS_HasInstantiatedNCs = False
1304 if reverse_link_name is not None:
1305 attrs.append(reverse_link_name)
1307 # check its the right GUID
1308 try:
1309 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
1310 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
1311 "reveal_internals:0"
1313 except ldb.LdbError as e3:
1314 (enum, estr) = e3.args
1315 if enum != ldb.ERR_NO_SUCH_OBJECT:
1316 raise
1318 # We don't always want to
1319 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
1320 attrname,
1321 val,
1322 dsdb_dn)
1323 continue
1325 if fixing_msDS_HasInstantiatedNCs:
1326 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
1327 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
1329 if str(dsdb_dn) != str(val):
1330 error_count += 1
1331 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
1332 continue
1334 # now we have two cases - the source object might or might not be deleted
1335 is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE'
1336 target_is_deleted = 'isDeleted' in res[0] and str(res[0]['isDeleted'][0]).upper() == 'TRUE'
1338 if is_deleted and obj.dn not in self.deleted_objects_containers and linkID:
1339 # A fully deleted object should not have any linked
1340 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1341 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1342 # Requirements)
1343 self.err_undead_linked_attribute(obj, attrname, val)
1344 error_count += 1
1345 continue
1346 elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
1347 # the target DN is not allowed to be deleted, unless the target DN is the
1348 # special Deleted Objects container
1349 error_count += 1
1350 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
1351 if local_usn:
1352 if 'replPropertyMetaData' in res[0]:
1353 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1354 res[0]['replPropertyMetadata'][0])
1355 found_data = False
1356 for o in repl.ctr.array:
1357 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1358 deleted_usn = o.local_usn
1359 if deleted_usn >= int(local_usn):
1360 # If the object was deleted after the link
1361 # was last modified then, clean it up here
1362 found_data = True
1363 break
1365 if found_data:
1366 self.err_deleted_dn(obj.dn, attrname,
1367 val, dsdb_dn, res[0].dn, True)
1368 continue
1370 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
1371 continue
1373 # We should not check for incorrect
1374 # components on deleted links, as these are allowed to
1375 # go stale (we just need the GUID, not the name)
1376 rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
1377 rmd_flags = 0
1378 if rmd_blob is not None:
1379 rmd_flags = int(rmd_blob)
1381 # assert the DN matches in string form, where a reverse
1382 # link exists, otherwise (below) offer to fix it as a non-error.
1383 # The string form is essentially only kept for forensics,
1384 # as we always re-resolve by GUID in normal operations.
1385 if not rmd_flags & 1 and reverse_link_name is not None:
1386 if str(res[0].dn) != str(dsdb_dn.dn):
1387 error_count += 1
1388 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1389 res[0].dn, "string")
1390 continue
1392 if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
1393 error_count += 1
1394 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1395 res[0].dn, "GUID")
1396 continue
1398 target_sid = res[0].dn.get_extended_component("SID")
1399 link_sid = dsdb_dn.dn.get_extended_component("SID")
1400 if link_sid is None and target_sid is not None:
1401 error_count += 1
1402 self.err_dn_component_missing_target_sid(obj.dn, attrname, val,
1403 dsdb_dn, target_sid)
1404 continue
1405 if link_sid != target_sid:
1406 error_count += 1
1407 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1408 res[0].dn, "SID")
1409 continue
1411 # Only for non-links, not even forward-only links
1412 # (otherwise this breaks repl_meta_data):
1414 # Now we have checked the GUID and SID, offer to fix old
1415 # DN strings as a non-error (DNs, not links so no
1416 # backlink). Samba does not maintain this string
1417 # otherwise, so we don't increment error_count.
1418 if reverse_link_name is None:
1419 if linkID == 0 and str(res[0].dn) != str(dsdb_dn.dn):
1420 # Pass in the old/bad DN without the <GUID=...> part,
1421 # otherwise the LDB code will correct it on the way through
1422 # (Note: we still want to preserve the DSDB DN prefix in the
1423 # case of binary DNs)
1424 bad_dn = dsdb_dn.prefix + dsdb_dn.dn.get_linearized()
1425 self.err_dn_string_component_old(obj.dn, attrname, bad_dn,
1426 dsdb_dn, res[0].dn)
1427 continue
1429 if is_member_link and self.quick_membership_checks:
1430 continue
1432 # check the reverse_link is correct if there should be one
1433 match_count = 0
1434 if reverse_link_name in res[0]:
1435 for v in res[0][reverse_link_name]:
1436 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1437 v_guid = v_dn.dn.get_extended_component("GUID")
1438 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1439 v_rmd_flags = 0
1440 if v_blob is not None:
1441 v_rmd_flags = int(v_blob)
1442 if v_rmd_flags & 1:
1443 continue
1444 if v_guid == obj_guid:
1445 match_count += 1
1447 if match_count != 1:
1448 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1449 if not linkID & 1:
1450 # Forward binary multi-valued linked attribute
1451 forward_count = 0
1452 for w in obj[attrname]:
1453 w_guid = dsdb_Dn(self.samdb, w.decode('utf8')).dn.get_extended_component("GUID")
1454 if w_guid == guid:
1455 forward_count += 1
1457 if match_count == forward_count:
1458 continue
1459 expected_count = 0
1460 for v in obj[attrname]:
1461 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1462 v_guid = v_dn.dn.get_extended_component("GUID")
1463 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1464 v_rmd_flags = 0
1465 if v_blob is not None:
1466 v_rmd_flags = int(v_blob)
1467 if v_rmd_flags & 1:
1468 continue
1469 if v_guid == guid:
1470 expected_count += 1
1472 if match_count == expected_count:
1473 continue
1475 diff_count = expected_count - match_count
1477 if linkID & 1:
1478 # If there's a backward link on binary multi-valued linked attribute,
1479 # let the check on the forward link remedy the value.
1480 # UNLESS, there is no forward link detected.
1481 if match_count == 0:
1482 error_count += 1
1483 self.err_orphaned_backlink(obj.dn, attrname,
1484 val, dsdb_dn.dn,
1485 reverse_link_name,
1486 reverse_syntax_oid)
1487 continue
1488 # Only warn here and let the forward link logic fix it.
1489 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1490 attrname, expected_count, str(obj.dn),
1491 reverse_link_name, match_count, str(dsdb_dn.dn)))
1492 continue
1494 assert not target_is_deleted
1496 self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1497 attrname, expected_count, str(obj.dn),
1498 reverse_link_name, match_count, str(dsdb_dn.dn)))
1500 # Loop until the difference between the forward and
1501 # the backward links is resolved.
1502 while diff_count != 0:
1503 error_count += 1
1504 if diff_count > 0:
1505 if match_count > 0 or diff_count > 1:
1506 # TODO no method to fix these right now
1507 self.report("ERROR: Can't fix missing "
1508 "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1509 break
1510 self.err_missing_backlink(obj, attrname,
1511 obj.dn.extended_str(),
1512 reverse_link_name,
1513 dsdb_dn.dn)
1514 diff_count -= 1
1515 else:
1516 self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1517 obj.dn.extended_str(), obj.dn,
1518 attrname, syntax_oid)
1519 diff_count += 1
1521 return error_count
1523 def find_repl_attid(self, repl, attid):
1524 for o in repl.ctr.array:
1525 if o.attid == attid:
1526 return o
1528 return None
1530 def get_originating_time(self, val, attid):
1531 '''Read metadata properties and return the originating time for
1532 a given attributeId.
1534 :return: the originating time or 0 if not found
1537 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1538 o = self.find_repl_attid(repl, attid)
1539 if o is not None:
1540 return o.originating_change_time
1541 return 0
1543 def process_metadata(self, dn, val):
1544 '''Read metadata properties and list attributes in it.
1545 raises KeyError if the attid is unknown.'''
1547 set_att = set()
1548 wrong_attids = set()
1549 list_attid = []
1550 in_schema_nc = dn.is_child_of(self.schema_dn)
1552 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1554 for o in repl.ctr.array:
1555 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1556 set_att.add(att.lower())
1557 list_attid.append(o.attid)
1558 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1559 is_schema_nc=in_schema_nc)
1560 if correct_attid != o.attid:
1561 wrong_attids.add(o.attid)
1563 return (set_att, list_attid, wrong_attids)
1565 def fix_metadata(self, obj, attr):
1566 '''re-write replPropertyMetaData elements for a single attribute for a
1567 object. This is used to fix missing replPropertyMetaData elements'''
1568 guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1569 dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1570 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attr],
1571 controls=["search_options:1:2",
1572 "show_recycled:1"])
1573 msg = res[0]
1574 nmsg = ldb.Message()
1575 nmsg.dn = dn
1576 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1577 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1578 "Failed to fix metadata for attribute %s" % attr):
1579 self.report("Fixed metadata for attribute %s" % attr)
1581 def ace_get_effective_inherited_type(self, ace):
1582 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1583 return None
1585 check = False
1586 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1587 check = True
1588 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1589 check = True
1590 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1591 check = True
1592 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1593 check = True
1595 if not check:
1596 return None
1598 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1599 return None
1601 return str(ace.object.inherited_type)
1603 def lookup_class_schemaIDGUID(self, cls):
1604 if cls in self.class_schemaIDGUID:
1605 return self.class_schemaIDGUID[cls]
1607 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1608 res = self.samdb.search(base=self.schema_dn,
1609 expression=flt,
1610 attrs=["schemaIDGUID"])
1611 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1613 self.class_schemaIDGUID[cls] = t
1614 return t
1616 def process_sd(self, dn, obj):
1617 sd_attr = "nTSecurityDescriptor"
1618 sd_val = obj[sd_attr]
1620 sd = ndr_unpack(security.descriptor, sd_val[0])
1622 is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE'
1623 if is_deleted:
1624 # we don't fix deleted objects
1625 return (sd, None)
1627 sd_clean = security.descriptor()
1628 sd_clean.owner_sid = sd.owner_sid
1629 sd_clean.group_sid = sd.group_sid
1630 sd_clean.type = sd.type
1631 sd_clean.revision = sd.revision
1633 broken = False
1634 last_inherited_type = None
1636 aces = []
1637 if sd.sacl is not None:
1638 aces = sd.sacl.aces
1639 for i in range(0, len(aces)):
1640 ace = aces[i]
1642 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1643 sd_clean.sacl_add(ace)
1644 continue
1646 t = self.ace_get_effective_inherited_type(ace)
1647 if t is None:
1648 continue
1650 if last_inherited_type is not None:
1651 if t != last_inherited_type:
1652 # if it inherited from more than
1653 # one type it's very likely to be broken
1655 # If not the recalculation will calculate
1656 # the same result.
1657 broken = True
1658 continue
1660 last_inherited_type = t
1662 aces = []
1663 if sd.dacl is not None:
1664 aces = sd.dacl.aces
1665 for i in range(0, len(aces)):
1666 ace = aces[i]
1668 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1669 sd_clean.dacl_add(ace)
1670 continue
1672 t = self.ace_get_effective_inherited_type(ace)
1673 if t is None:
1674 continue
1676 if last_inherited_type is not None:
1677 if t != last_inherited_type:
1678 # if it inherited from more than
1679 # one type it's very likely to be broken
1681 # If not the recalculation will calculate
1682 # the same result.
1683 broken = True
1684 continue
1686 last_inherited_type = t
1688 if broken:
1689 return (sd_clean, sd)
1691 if last_inherited_type is None:
1692 # ok
1693 return (sd, None)
1695 cls = None
1696 try:
1697 cls = obj["objectClass"][-1]
1698 except KeyError as e:
1699 pass
1701 if cls is None:
1702 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1703 attrs=["isDeleted", "objectClass"],
1704 controls=["show_recycled:1"])
1705 o = res[0]
1706 is_deleted = 'isDeleted' in o and str(o['isDeleted'][0]).upper() == 'TRUE'
1707 if is_deleted:
1708 # we don't fix deleted objects
1709 return (sd, None)
1710 cls = o["objectClass"][-1]
1712 t = self.lookup_class_schemaIDGUID(cls)
1714 if t != last_inherited_type:
1715 # broken
1716 return (sd_clean, sd)
1718 # ok
1719 return (sd, None)
1721 def err_wrong_sd(self, dn, sd, sd_broken):
1722 '''re-write the SD due to incorrect inherited ACEs'''
1723 sd_attr = "nTSecurityDescriptor"
1724 sd_val = ndr_pack(sd)
1725 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1727 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1728 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1729 return
1731 nmsg = ldb.Message()
1732 nmsg.dn = dn
1733 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1734 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1735 "Failed to fix attribute %s" % sd_attr):
1736 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1738 def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1739 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1740 sd_attr = "nTSecurityDescriptor"
1741 sd_val = ndr_pack(sd)
1742 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1743 if sd.owner_sid is not None:
1744 sd_flags |= security.SECINFO_OWNER
1745 if sd.group_sid is not None:
1746 sd_flags |= security.SECINFO_GROUP
1748 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1749 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1750 return
1752 m = ldb.Message()
1753 m.dn = dn
1754 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1755 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1756 "Failed to reset attribute %s" % sd_attr):
1757 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1759 def err_missing_sd_owner(self, dn, sd):
1760 '''re-write the SD due to a missing owner or group'''
1761 sd_attr = "nTSecurityDescriptor"
1762 sd_val = ndr_pack(sd)
1763 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1765 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1766 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1767 return
1769 nmsg = ldb.Message()
1770 nmsg.dn = dn
1771 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1773 # By setting the session_info to admin_session_info and
1774 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1775 # flags we cause the descriptor module to set the correct
1776 # owner and group on the SD, replacing the None/NULL values
1777 # for owner_sid and group_sid currently present.
1779 # The admin_session_info matches that used in provision, and
1780 # is the best guess we can make for an existing object that
1781 # hasn't had something specifically set.
1783 # This is important for the dns related naming contexts.
1784 self.samdb.set_session_info(self.admin_session_info)
1785 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1786 "Failed to fix metadata for attribute %s" % sd_attr):
1787 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1788 self.samdb.set_session_info(self.system_session_info)
1790 def is_expired_tombstone(self, dn, repl_val):
1791 if self.check_expired_tombstones:
1792 # This is not the default, it's just
1793 # used to keep dbcheck tests work with
1794 # old static provision dumps
1795 return False
1797 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl_val)
1799 isDeleted = self.find_repl_attid(repl, drsuapi.DRSUAPI_ATTID_isDeleted)
1801 delete_time = samba.nttime2unix(isDeleted.originating_change_time)
1802 current_time = time.time()
1804 tombstone_delta = self.tombstoneLifetime * (24 * 60 * 60)
1806 delta = current_time - delete_time
1807 if delta <= tombstone_delta:
1808 return False
1810 self.report("SKIPING: object %s is an expired tombstone" % dn)
1811 self.report("isDeleted: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1812 isDeleted.attid,
1813 isDeleted.version,
1814 isDeleted.originating_invocation_id,
1815 isDeleted.originating_usn,
1816 isDeleted.local_usn,
1817 time.ctime(samba.nttime2unix(isDeleted.originating_change_time))))
1818 self.expired_tombstones += 1
1819 return True
1821 def find_changes_after_deletion(self, repl_val):
1822 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl_val)
1824 isDeleted = self.find_repl_attid(repl, drsuapi.DRSUAPI_ATTID_isDeleted)
1826 delete_time = samba.nttime2unix(isDeleted.originating_change_time)
1828 tombstone_delta = self.tombstoneLifetime * (24 * 60 * 60)
1830 found = []
1831 for o in repl.ctr.array:
1832 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1833 continue
1835 if o.local_usn <= isDeleted.local_usn:
1836 continue
1838 if o.originating_change_time <= isDeleted.originating_change_time:
1839 continue
1841 change_time = samba.nttime2unix(o.originating_change_time)
1843 delta = change_time - delete_time
1844 if delta <= tombstone_delta:
1845 continue
1847 # If the modification happened after the tombstone lifetime
1848 # has passed, we have a bug as the object might be deleted
1849 # already on other DCs and won't be able to replicate
1850 # back
1851 found.append(o)
1853 return found, isDeleted
1855 def has_changes_after_deletion(self, dn, repl_val):
1856 found, isDeleted = self.find_changes_after_deletion(repl_val)
1857 if len(found) == 0:
1858 return False
1860 def report_attid(o):
1861 try:
1862 attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1863 except KeyError:
1864 attname = "<unknown:0x%x08x>" % o.attid
1866 self.report("%s: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1867 attname, o.attid, o.version,
1868 o.originating_invocation_id,
1869 o.originating_usn,
1870 o.local_usn,
1871 time.ctime(samba.nttime2unix(o.originating_change_time))))
1873 self.report("ERROR: object %s, has changes after deletion" % dn)
1874 report_attid(isDeleted)
1875 for o in found:
1876 report_attid(o)
1878 return True
1880 def err_changes_after_deletion(self, dn, repl_val):
1881 found, isDeleted = self.find_changes_after_deletion(repl_val)
1883 in_schema_nc = dn.is_child_of(self.schema_dn)
1884 rdn_attr = dn.get_rdn_name()
1885 rdn_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(rdn_attr,
1886 is_schema_nc=in_schema_nc)
1888 unexpected = []
1889 for o in found:
1890 if o.attid == rdn_attid:
1891 continue
1892 if o.attid == drsuapi.DRSUAPI_ATTID_name:
1893 continue
1894 if o.attid == drsuapi.DRSUAPI_ATTID_lastKnownParent:
1895 continue
1896 try:
1897 attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1898 except KeyError:
1899 attname = "<unknown:0x%x08x>" % o.attid
1900 unexpected.append(attname)
1902 if len(unexpected) > 0:
1903 self.report('Unexpeted attributes: %s' % ",".join(unexpected))
1904 self.report('Not fixing changes after deletion bug')
1905 return
1907 if not self.confirm_all('Delete broken tombstone object %s deleted %s days ago?' % (
1908 dn, self.tombstoneLifetime), 'fix_changes_after_deletion_bug'):
1909 self.report('Not fixing changes after deletion bug')
1910 return
1912 if self.do_delete(dn, ["relax:0"],
1913 "Failed to remove DN %s" % dn):
1914 self.report("Removed DN %s" % dn)
1916 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1917 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1918 repl_meta_data)
1919 ctr = repl.ctr
1920 found = False
1921 for o in ctr.array:
1922 # Search for a zero invocationID
1923 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1924 continue
1926 found = True
1927 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1928 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1929 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1930 % (dn, o.attid, o.version,
1931 time.ctime(samba.nttime2unix(o.originating_change_time)),
1932 self.samdb.get_invocation_id()))
1934 return found
1936 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1937 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1938 repl_meta_data)
1939 ctr = repl.ctr
1940 now = samba.unix2nttime(int(time.time()))
1941 found = False
1942 for o in ctr.array:
1943 # Search for a zero invocationID
1944 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1945 continue
1947 found = True
1948 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1949 o.version = o.version + 1
1950 o.originating_change_time = now
1951 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1952 o.originating_usn = seq
1953 o.local_usn = seq
1955 if found:
1956 replBlob = ndr_pack(repl)
1957 msg = ldb.Message()
1958 msg.dn = dn
1960 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1961 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1962 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1963 return
1965 nmsg = ldb.Message()
1966 nmsg.dn = dn
1967 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1968 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1969 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1970 "Failed to fix attribute %s" % attr):
1971 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1973 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1974 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1975 repl_meta_data)
1976 ctr = repl.ctr
1977 for o in ctr.array:
1978 # Search for an invalid attid
1979 try:
1980 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1981 except KeyError:
1982 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1983 return
1985 def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1986 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1987 repl_meta_data)
1988 fix = False
1990 set_att = set()
1991 remove_attid = set()
1992 hash_att = {}
1994 in_schema_nc = dn.is_child_of(self.schema_dn)
1996 ctr = repl.ctr
1997 # Sort the array, except for the last element. This strange
1998 # construction, creating a new list, due to bugs in samba's
1999 # array handling in IDL generated objects.
2000 ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
2001 # Now walk it in reverse, so we see the low (and so incorrect,
2002 # the correct values are above 0x80000000) values first and
2003 # remove the 'second' value we see.
2004 for o in reversed(ctr.array):
2005 print("%s: 0x%08x" % (dn, o.attid))
2006 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
2007 if att.lower() in set_att:
2008 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
2009 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
2010 % (attr, dn, o.attid, att, hash_att[att].attid),
2011 'fix_replmetadata_duplicate_attid'):
2012 self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
2013 % (o.attid, att, attr, dn))
2014 return
2015 fix = True
2016 remove_attid.add(o.attid)
2017 # We want to set the metadata for the most recent
2018 # update to have been applied locally, that is the metadata
2019 # matching the (eg string) value in the attribute
2020 if o.local_usn > hash_att[att].local_usn:
2021 # This is always what we would have sent over DRS,
2022 # because the DRS server will have sent the
2023 # msDS-IntID, but with the values from both
2024 # attribute entries.
2025 hash_att[att].version = o.version
2026 hash_att[att].originating_change_time = o.originating_change_time
2027 hash_att[att].originating_invocation_id = o.originating_invocation_id
2028 hash_att[att].originating_usn = o.originating_usn
2029 hash_att[att].local_usn = o.local_usn
2031 # Do not re-add the value to the set or overwrite the hash value
2032 continue
2034 hash_att[att] = o
2035 set_att.add(att.lower())
2037 # Generate a real list we can sort on properly
2038 new_list = [o for o in ctr.array if o.attid not in remove_attid]
2040 if (len(wrong_attids) > 0):
2041 for o in new_list:
2042 if o.attid in wrong_attids:
2043 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
2044 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
2045 self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
2046 if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
2047 % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
2048 self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
2049 % (o.attid, correct_attid, att, attr, dn))
2050 return
2051 fix = True
2052 o.attid = correct_attid
2053 if fix:
2054 # Sort the array, (we changed the value so must re-sort)
2055 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
2057 # If we did not already need to fix it, then ask about sorting
2058 if not fix:
2059 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
2060 if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
2061 % (attr, dn), 'fix_replmetadata_unsorted_attid'):
2062 self.report('Not fixing %s on %s\n' % (attr, dn))
2063 return
2065 # The actual sort done is done at the top of the function
2067 ctr.count = len(new_list)
2068 ctr.array = new_list
2069 replBlob = ndr_pack(repl)
2071 nmsg = ldb.Message()
2072 nmsg.dn = dn
2073 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
2074 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
2075 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
2076 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
2077 "Failed to fix attribute %s" % attr):
2078 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
2080 def is_deleted_deleted_objects(self, obj):
2081 faulty = False
2082 if "description" not in obj:
2083 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
2084 faulty = True
2085 if "showInAdvancedViewOnly" not in obj or str(obj['showInAdvancedViewOnly'][0]).upper() == 'FALSE':
2086 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
2087 faulty = True
2088 if "objectCategory" not in obj:
2089 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
2090 faulty = True
2091 if "isCriticalSystemObject" not in obj or str(obj['isCriticalSystemObject'][0]).upper() == 'FALSE':
2092 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
2093 faulty = True
2094 if "isRecycled" in obj:
2095 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
2096 faulty = True
2097 if "isDeleted" in obj and str(obj['isDeleted'][0]).upper() == 'FALSE':
2098 self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
2099 faulty = True
2100 if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
2101 str(obj['objectClass'][0]) != 'top' or
2102 str(obj['objectClass'][1]) != 'container'):
2103 self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
2104 faulty = True
2105 if "systemFlags" not in obj or str(obj['systemFlags'][0]) != '-1946157056':
2106 self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
2107 faulty = True
2108 return faulty
2110 def err_deleted_deleted_objects(self, obj):
2111 nmsg = ldb.Message()
2112 nmsg.dn = dn = obj.dn
2114 if "description" not in obj:
2115 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
2116 if "showInAdvancedViewOnly" not in obj:
2117 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
2118 if "objectCategory" not in obj:
2119 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
2120 if "isCriticalSystemObject" not in obj:
2121 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
2122 if "isRecycled" in obj:
2123 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
2125 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2126 nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
2127 nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
2129 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
2130 % (dn), 'fix_deleted_deleted_objects'):
2131 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
2132 return
2134 if self.do_modify(nmsg, ["relax:0"],
2135 "Failed to fix Deleted Objects container %s" % dn):
2136 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
2138 def err_replica_locations(self, obj, cross_ref, attr):
2139 nmsg = ldb.Message()
2140 nmsg.dn = cross_ref
2141 target = self.samdb.get_dsServiceName()
2143 if self.samdb.am_rodc():
2144 self.report('Not fixing %s %s for the RODC' % (attr, obj.dn))
2145 return
2147 if not self.confirm_all('Add yourself to the replica locations for %s?'
2148 % (obj.dn), 'fix_replica_locations'):
2149 self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
2150 return
2152 nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
2153 if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
2154 self.report("Fixed %s for %s" % (attr, obj.dn))
2156 def is_fsmo_role(self, dn):
2157 if dn == self.samdb.domain_dn:
2158 return True
2159 if dn == self.infrastructure_dn:
2160 return True
2161 if dn == self.naming_dn:
2162 return True
2163 if dn == self.schema_dn:
2164 return True
2165 if dn == self.rid_dn:
2166 return True
2168 return False
2170 def calculate_instancetype(self, dn):
2171 instancetype = 0
2172 nc_root = self.samdb.get_nc_root(dn)
2173 if dn == nc_root:
2174 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
2175 try:
2176 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
2177 except ldb.LdbError as e4:
2178 (enum, estr) = e4.args
2179 if enum != ldb.ERR_NO_SUCH_OBJECT:
2180 raise
2181 else:
2182 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
2183 if self.write_ncs is not None and str(nc_root) in [str(x) for x in self.write_ncs]:
2184 instancetype |= dsdb.INSTANCE_TYPE_WRITE
2186 return instancetype
2188 def get_wellknown_sd(self, dn):
2189 for [sd_dn, descriptor_fn] in self.wellknown_sds:
2190 if dn == sd_dn:
2191 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
2192 return ndr_unpack(security.descriptor,
2193 descriptor_fn(domain_sid,
2194 name_map=self.name_map))
2196 raise KeyError
2198 def check_object(self, dn, attrs=None):
2199 '''check one object'''
2200 if self.verbose:
2201 self.report("Checking object %s" % dn)
2202 if attrs is None:
2203 attrs = ['*']
2204 else:
2205 # make a local copy to modify
2206 attrs = list(attrs)
2207 if "dn" in map(str.lower, attrs):
2208 attrs.append("name")
2209 if "distinguishedname" in map(str.lower, attrs):
2210 attrs.append("name")
2211 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
2212 attrs.append("name")
2213 if 'name' in map(str.lower, attrs):
2214 attrs.append(dn.get_rdn_name())
2215 attrs.append("isDeleted")
2216 attrs.append("systemFlags")
2217 need_replPropertyMetaData = False
2218 if '*' in attrs:
2219 need_replPropertyMetaData = True
2220 else:
2221 for a in attrs:
2222 linkID, _ = self.get_attr_linkID_and_reverse_name(a)
2223 if linkID == 0:
2224 continue
2225 if linkID & 1:
2226 continue
2227 need_replPropertyMetaData = True
2228 break
2229 if need_replPropertyMetaData:
2230 attrs.append("replPropertyMetaData")
2231 attrs.append("objectGUID")
2233 try:
2234 sd_flags = 0
2235 sd_flags |= security.SECINFO_OWNER
2236 sd_flags |= security.SECINFO_GROUP
2237 sd_flags |= security.SECINFO_DACL
2238 sd_flags |= security.SECINFO_SACL
2240 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
2241 controls=[
2242 "extended_dn:1:1",
2243 "show_recycled:1",
2244 "show_deleted:1",
2245 "sd_flags:1:%d" % sd_flags,
2246 "reveal_internals:0",
2248 attrs=attrs)
2249 except ldb.LdbError as e10:
2250 (enum, estr) = e10.args
2251 if enum == ldb.ERR_NO_SUCH_OBJECT:
2252 if self.in_transaction:
2253 self.report("ERROR: Object %s disappeared during check" % dn)
2254 return 1
2255 return 0
2256 raise
2257 if len(res) != 1:
2258 self.report("ERROR: Object %s failed to load during check" % dn)
2259 return 1
2260 obj = res[0]
2261 error_count = 0
2262 set_attrs_from_md = set()
2263 set_attrs_seen = set()
2264 got_objectclass = False
2266 nc_dn = self.samdb.get_nc_root(obj.dn)
2267 try:
2268 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
2269 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
2270 except KeyError:
2271 # We have no deleted objects DN for schema, and we check for this above for the other
2272 # NCs
2273 deleted_objects_dn = None
2275 object_rdn_attr = None
2276 object_rdn_val = None
2277 name_val = None
2278 isDeleted = False
2279 systemFlags = 0
2280 repl_meta_data_val = None
2282 for attrname in obj:
2283 if str(attrname).lower() == 'isdeleted':
2284 if str(obj[attrname][0]) != "FALSE":
2285 isDeleted = True
2287 if str(attrname).lower() == 'systemflags':
2288 systemFlags = int(obj[attrname][0])
2290 if str(attrname).lower() == 'replpropertymetadata':
2291 repl_meta_data_val = obj[attrname][0]
2293 if isDeleted and repl_meta_data_val:
2294 if self.has_changes_after_deletion(dn, repl_meta_data_val):
2295 error_count += 1
2296 self.err_changes_after_deletion(dn, repl_meta_data_val)
2297 return error_count
2298 if self.is_expired_tombstone(dn, repl_meta_data_val):
2299 return error_count
2301 for attrname in obj:
2302 if attrname == 'dn' or attrname == "distinguishedName":
2303 continue
2305 if str(attrname).lower() == 'objectclass':
2306 got_objectclass = True
2308 if str(attrname).lower() == "name":
2309 if len(obj[attrname]) != 1:
2310 error_count += 1
2311 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2312 (len(obj[attrname]), attrname, str(obj.dn)))
2313 else:
2314 name_val = obj[attrname][0]
2316 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
2317 object_rdn_attr = attrname
2318 if len(obj[attrname]) != 1:
2319 error_count += 1
2320 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2321 (len(obj[attrname]), attrname, str(obj.dn)))
2322 else:
2323 object_rdn_val = str(obj[attrname][0])
2325 if str(attrname).lower() == 'replpropertymetadata':
2326 if self.has_replmetadata_zero_invocationid(dn, obj[attrname][0]):
2327 error_count += 1
2328 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname][0])
2329 # We don't continue, as we may also have other fixes for this attribute
2330 # based on what other attributes we see.
2332 try:
2333 (set_attrs_from_md, list_attid_from_md, wrong_attids) \
2334 = self.process_metadata(dn, obj[attrname][0])
2335 except KeyError:
2336 error_count += 1
2337 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
2338 continue
2340 if len(set_attrs_from_md) < len(list_attid_from_md) \
2341 or len(wrong_attids) > 0 \
2342 or sorted(list_attid_from_md) != list_attid_from_md:
2343 error_count += 1
2344 self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname][0], wrong_attids)
2346 else:
2347 # Here we check that the first attid is 0
2348 # (objectClass).
2349 if list_attid_from_md[0] != 0:
2350 error_count += 1
2351 self.report("ERROR: Not fixing incorrect initial attributeID in '%s' on '%s', it should be objectClass" %
2352 (attrname, str(dn)))
2354 continue
2356 if str(attrname).lower() == 'ntsecuritydescriptor':
2357 (sd, sd_broken) = self.process_sd(dn, obj)
2358 if sd_broken is not None:
2359 self.err_wrong_sd(dn, sd, sd_broken)
2360 error_count += 1
2361 continue
2363 if sd.owner_sid is None or sd.group_sid is None:
2364 self.err_missing_sd_owner(dn, sd)
2365 error_count += 1
2366 continue
2368 if self.reset_well_known_acls:
2369 try:
2370 well_known_sd = self.get_wellknown_sd(dn)
2371 except KeyError:
2372 continue
2374 current_sd = ndr_unpack(security.descriptor,
2375 obj[attrname][0])
2377 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
2378 if diff != "":
2379 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
2380 error_count += 1
2381 continue
2382 continue
2384 if str(attrname).lower() == 'objectclass':
2385 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
2386 # Do not consider the attribute incorrect if:
2387 # - The sorted (alphabetically) list is the same, inclding case
2388 # - The first and last elements are the same
2390 # This avoids triggering an error due to
2391 # non-determinism in the sort routine in (at least)
2392 # 4.3 and earlier, and the fact that any AUX classes
2393 # in these attributes are also not sorted when
2394 # imported from Windows (they are just in the reverse
2395 # order of last set)
2396 if sorted(normalised) != sorted(obj[attrname]) \
2397 or normalised[0] != obj[attrname][0] \
2398 or normalised[-1] != obj[attrname][-1]:
2399 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
2400 error_count += 1
2401 continue
2403 if str(attrname).lower() == 'userparameters':
2404 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == b'\x20'[0]:
2405 error_count += 1
2406 self.err_short_userParameters(obj, attrname, obj[attrname])
2407 continue
2409 elif obj[attrname][0][:16] == b'\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2410 # This is the correct, normal prefix
2411 continue
2413 elif obj[attrname][0][:20] == b'IAAgACAAIAAgACAAIAAg':
2414 # this is the typical prefix from a windows migration
2415 error_count += 1
2416 self.err_base64_userParameters(obj, attrname, obj[attrname])
2417 continue
2419 #43:00:00:00:74:00:00:00:78
2420 elif obj[attrname][0][1] != b'\x00'[0] and obj[attrname][0][3] != b'\x00'[0] and obj[attrname][0][5] != b'\x00'[0] and obj[attrname][0][7] != b'\x00'[0] and obj[attrname][0][9] != b'\x00'[0]:
2421 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2422 error_count += 1
2423 self.err_utf8_userParameters(obj, attrname, obj[attrname])
2424 continue
2426 elif len(obj[attrname][0]) % 2 != 0:
2427 # This is a value that isn't even in length
2428 error_count += 1
2429 self.err_odd_userParameters(obj, attrname)
2430 continue
2432 elif obj[attrname][0][1] == b'\x00'[0] and obj[attrname][0][2] == b'\x00'[0] and obj[attrname][0][3] == b'\x00'[0] and obj[attrname][0][4] != b'\x00'[0] and obj[attrname][0][5] == b'\x00'[0]:
2433 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2434 error_count += 1
2435 self.err_doubled_userParameters(obj, attrname, obj[attrname])
2436 continue
2438 if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2439 if obj[attrname][0] in self.attribute_or_class_ids:
2440 error_count += 1
2441 self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2442 % (attrname, obj.dn, obj[attrname][0]))
2443 else:
2444 self.attribute_or_class_ids.add(obj[attrname][0])
2446 # check for empty attributes
2447 for val in obj[attrname]:
2448 if val == '':
2449 self.err_empty_attribute(dn, attrname)
2450 error_count += 1
2451 continue
2453 # get the syntax oid for the attribute, so we can can have
2454 # special handling for some specific attribute types
2455 try:
2456 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2457 except Exception as msg:
2458 self.err_unknown_attribute(obj, attrname)
2459 error_count += 1
2460 continue
2462 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2464 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2465 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2466 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2467 and not linkID):
2468 set_attrs_seen.add(str(attrname).lower())
2470 if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2471 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN]:
2472 # it's some form of DN, do specialised checking on those
2473 error_count += self.check_dn(obj, attrname, syntax_oid)
2474 else:
2476 values = set()
2477 # check for incorrectly normalised attributes
2478 for val in obj[attrname]:
2479 values.add(val)
2481 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2482 if len(normalised) != 1 or normalised[0] != val:
2483 self.err_normalise_mismatch(dn, attrname, obj[attrname])
2484 error_count += 1
2485 break
2487 if len(obj[attrname]) != len(values):
2488 self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2489 error_count += 1
2490 break
2492 if str(attrname).lower() == "instancetype":
2493 calculated_instancetype = self.calculate_instancetype(dn)
2494 if len(obj["instanceType"]) != 1 or int(obj["instanceType"][0]) != calculated_instancetype:
2495 error_count += 1
2496 self.err_wrong_instancetype(obj, calculated_instancetype)
2498 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
2499 error_count += 1
2500 self.err_missing_objectclass(dn)
2502 if ("*" in attrs or "name" in map(str.lower, attrs)):
2503 if name_val is None:
2504 error_count += 1
2505 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2506 if object_rdn_attr is None:
2507 error_count += 1
2508 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2510 if name_val is not None:
2511 parent_dn = None
2512 controls = ["show_recycled:1", "relax:0"]
2513 if isDeleted:
2514 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2515 parent_dn = deleted_objects_dn
2516 controls += ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME]
2517 if parent_dn is None:
2518 parent_dn = obj.dn.parent()
2519 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2520 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2522 if obj.dn == deleted_objects_dn:
2523 expected_dn = obj.dn
2525 if expected_dn != obj.dn:
2526 error_count += 1
2527 self.err_wrong_dn(obj, expected_dn, object_rdn_attr,
2528 object_rdn_val, name_val, controls)
2529 elif obj.dn.get_rdn_value() != object_rdn_val:
2530 error_count += 1
2531 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
2533 show_dn = True
2534 if repl_meta_data_val:
2535 if obj.dn == deleted_objects_dn:
2536 isDeletedAttId = 131120
2537 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2539 expectedTimeDo = 2650466015990000000
2540 originating = self.get_originating_time(repl_meta_data_val, isDeletedAttId)
2541 if originating != expectedTimeDo:
2542 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2543 nmsg = ldb.Message()
2544 nmsg.dn = dn
2545 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2546 error_count += 1
2547 self.samdb.modify(nmsg, controls=["provision:0"])
2549 else:
2550 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2552 for att in set_attrs_seen.difference(set_attrs_from_md):
2553 if show_dn:
2554 self.report("On object %s" % dn)
2555 show_dn = False
2556 error_count += 1
2557 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2558 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2559 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2560 continue
2561 self.fix_metadata(obj, att)
2563 if self.is_fsmo_role(dn):
2564 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
2565 self.err_no_fsmoRoleOwner(obj)
2566 error_count += 1
2568 try:
2569 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2570 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2571 controls=["show_recycled:1", "show_deleted:1"])
2572 except ldb.LdbError as e11:
2573 (enum, estr) = e11.args
2574 if enum == ldb.ERR_NO_SUCH_OBJECT:
2575 if isDeleted:
2576 self.report("WARNING: parent object not found for %s" % (obj.dn))
2577 self.report("Not moving to LostAndFound "
2578 "(tombstone garbage collection in progress?)")
2579 else:
2580 self.err_missing_parent(obj)
2581 error_count += 1
2582 else:
2583 raise
2585 if dn in self.deleted_objects_containers and '*' in attrs:
2586 if self.is_deleted_deleted_objects(obj):
2587 self.err_deleted_deleted_objects(obj)
2588 error_count += 1
2590 for (dns_part, msg) in self.dns_partitions:
2591 if dn == dns_part and 'repsFrom' in obj:
2592 location = "msDS-NC-Replica-Locations"
2593 if self.samdb.am_rodc():
2594 location = "msDS-NC-RO-Replica-Locations"
2596 if location not in msg:
2597 # There are no replica locations!
2598 self.err_replica_locations(obj, msg.dn, location)
2599 error_count += 1
2600 continue
2602 found = False
2603 for loc in msg[location]:
2604 if str(loc) == self.samdb.get_dsServiceName():
2605 found = True
2606 if not found:
2607 # This DC is not in the replica locations
2608 self.err_replica_locations(obj, msg.dn, location)
2609 error_count += 1
2611 if dn == self.server_ref_dn:
2612 # Check we have a valid RID Set
2613 if "*" in attrs or "rIDSetReferences" in attrs:
2614 if "rIDSetReferences" not in obj:
2615 # NO RID SET reference
2616 # We are RID master, allocate it.
2617 error_count += 1
2619 if self.is_rid_master:
2620 # Allocate a RID Set
2621 if self.confirm_all('Allocate the missing RID set for RID master?',
2622 'fix_missing_rid_set_master'):
2624 # We don't have auto-transaction logic on
2625 # extended operations, so we have to do it
2626 # here.
2628 self.samdb.transaction_start()
2630 try:
2631 self.samdb.create_own_rid_set()
2633 except:
2634 self.samdb.transaction_cancel()
2635 raise
2637 self.samdb.transaction_commit()
2639 elif not self.samdb.am_rodc():
2640 self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2642 # Check some details of our own RID Set
2643 if dn == self.rid_set_dn:
2644 res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2645 attrs=["rIDAllocationPool",
2646 "rIDPreviousAllocationPool",
2647 "rIDUsedPool",
2648 "rIDNextRID"])
2649 if "rIDAllocationPool" not in res[0]:
2650 self.report("No rIDAllocationPool found in %s" % dn)
2651 error_count += 1
2652 else:
2653 next_pool = int(res[0]["rIDAllocationPool"][0])
2655 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2656 low = 0x00000000FFFFFFFF & next_pool
2658 if high <= low:
2659 self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2660 error_count += 1
2662 if "rIDNextRID" in res[0]:
2663 next_free_rid = int(res[0]["rIDNextRID"][0])
2664 else:
2665 next_free_rid = 0
2667 if next_free_rid == 0:
2668 next_free_rid = low
2669 else:
2670 next_free_rid += 1
2672 # Check the remainder of this pool for conflicts. If
2673 # ridalloc_allocate_rid() moves to a new pool, this
2674 # will be above high, so we will stop.
2675 while next_free_rid <= high:
2676 sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2677 try:
2678 res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2679 attrs=[])
2680 except ldb.LdbError as e:
2681 (enum, estr) = e.args
2682 if enum != ldb.ERR_NO_SUCH_OBJECT:
2683 raise
2684 res = None
2685 if res is not None:
2686 self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2687 error_count += 1
2689 if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2690 % (sid, dn),
2691 'fix_sid_rid_set_conflict'):
2692 self.samdb.transaction_start()
2694 # This will burn RIDs, which will move
2695 # past the conflict. We then check again
2696 # to see if the new RID conflicts, until
2697 # the end of the current pool. We don't
2698 # look at the next pool to avoid burning
2699 # all RIDs in one go in some strange
2700 # failure case.
2701 try:
2702 while True:
2703 allocated_rid = self.samdb.allocate_rid()
2704 if allocated_rid >= next_free_rid:
2705 next_free_rid = allocated_rid + 1
2706 break
2707 except:
2708 self.samdb.transaction_cancel()
2709 raise
2711 self.samdb.transaction_commit()
2712 else:
2713 break
2714 else:
2715 next_free_rid += 1
2717 return error_count
2719 ################################################################
2720 # check special @ROOTDSE attributes
2721 def check_rootdse(self):
2722 '''check the @ROOTDSE special object'''
2723 dn = ldb.Dn(self.samdb, '@ROOTDSE')
2724 if self.verbose:
2725 self.report("Checking object %s" % dn)
2726 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2727 if len(res) != 1:
2728 self.report("Object %s disappeared during check" % dn)
2729 return 1
2730 obj = res[0]
2731 error_count = 0
2733 # check that the dsServiceName is in GUID form
2734 if 'dsServiceName' not in obj:
2735 self.report('ERROR: dsServiceName missing in @ROOTDSE')
2736 return error_count + 1
2738 if not str(obj['dsServiceName'][0]).startswith('<GUID='):
2739 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2740 error_count += 1
2741 if not self.confirm('Change dsServiceName to GUID form?'):
2742 return error_count
2743 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0].decode('utf8')),
2744 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2745 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2746 m = ldb.Message()
2747 m.dn = dn
2748 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2749 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2750 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2751 self.report("Changed dsServiceName to GUID form")
2752 return error_count
2754 ###############################################
2755 # re-index the database
2757 def reindex_database(self):
2758 '''re-index the whole database'''
2759 m = ldb.Message()
2760 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2761 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2762 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2763 return self.do_modify(m, [], 're-indexed database', validate=False)
2765 ###############################################
2766 # reset @MODULES
2767 def reset_modules(self):
2768 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2769 m = ldb.Message()
2770 m.dn = ldb.Dn(self.samdb, "@MODULES")
2771 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2772 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)