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