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