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