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