lib: Move the "expired" for gencache_parse calculation into gencache.c
[Samba.git] / python / samba / dbchecker.py
blobd1251a262997e2877473f0f3ad108f98a09d4b56
1 # Samba4 AD database checker
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from __future__ import print_function
21 import ldb
22 import samba
23 import time
24 from base64 import b64decode
25 from samba import dsdb
26 from samba import common
27 from samba.dcerpc import misc
28 from samba.dcerpc import drsuapi
29 from samba.ndr import ndr_unpack, ndr_pack
30 from samba.dcerpc import drsblobs
31 from samba.common import dsdb_Dn
32 from samba.dcerpc import security
33 from samba.descriptor import get_wellknown_sds, get_diff_sds
34 from samba.auth import system_session, admin_session
35 from samba.netcmd import CommandError
36 from samba.netcmd.fsmo import get_fsmo_roleowner
39 class dbcheck(object):
40 """check a SAM database for errors"""
42 def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
43 yes=False, quiet=False, in_transaction=False,
44 reset_well_known_acls=False):
45 self.samdb = samdb
46 self.dict_oid_name = None
47 self.samdb_schema = (samdb_schema or samdb)
48 self.verbose = verbose
49 self.fix = fix
50 self.yes = yes
51 self.quiet = quiet
52 self.remove_all_unknown_attributes = False
53 self.remove_all_empty_attributes = False
54 self.fix_all_normalisation = False
55 self.fix_all_duplicates = False
56 self.fix_all_DN_GUIDs = False
57 self.fix_all_binary_dn = False
58 self.remove_implausible_deleted_DN_links = False
59 self.remove_plausible_deleted_DN_links = False
60 self.fix_all_string_dn_component_mismatch = False
61 self.fix_all_GUID_dn_component_mismatch = False
62 self.fix_all_SID_dn_component_mismatch = False
63 self.fix_all_old_dn_string_component_mismatch = False
64 self.fix_all_metadata = False
65 self.fix_time_metadata = False
66 self.fix_undead_linked_attributes = False
67 self.fix_all_missing_backlinks = False
68 self.fix_all_orphaned_backlinks = False
69 self.fix_all_missing_forward_links = False
70 self.duplicate_link_cache = dict()
71 self.recover_all_forward_links = False
72 self.fix_rmd_flags = False
73 self.fix_ntsecuritydescriptor = False
74 self.fix_ntsecuritydescriptor_owner_group = False
75 self.seize_fsmo_role = False
76 self.move_to_lost_and_found = False
77 self.fix_instancetype = False
78 self.fix_replmetadata_zero_invocationid = False
79 self.fix_replmetadata_duplicate_attid = False
80 self.fix_replmetadata_wrong_attid = False
81 self.fix_replmetadata_unsorted_attid = False
82 self.fix_deleted_deleted_objects = False
83 self.fix_incorrect_deleted_objects = False
84 self.fix_dn = False
85 self.fix_base64_userparameters = False
86 self.fix_utf8_userparameters = False
87 self.fix_doubled_userparameters = False
88 self.fix_sid_rid_set_conflict = False
89 self.reset_well_known_acls = reset_well_known_acls
90 self.reset_all_well_known_acls = False
91 self.in_transaction = in_transaction
92 self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
93 self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
94 self.schema_dn = samdb.get_schema_basedn()
95 self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
96 self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
97 self.class_schemaIDGUID = {}
98 self.wellknown_sds = get_wellknown_sds(self.samdb)
99 self.fix_all_missing_objectclass = False
100 self.fix_missing_deleted_objects = False
101 self.fix_replica_locations = False
102 self.fix_missing_rid_set_master = False
104 self.dn_set = set()
105 self.link_id_cache = {}
106 self.name_map = {}
107 try:
108 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE,
109 attrs=["objectSid"])
110 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
111 self.name_map['DnsAdmins'] = str(dnsadmins_sid)
112 except ldb.LdbError as e5:
113 (enum, estr) = e5.args
114 if enum != ldb.ERR_NO_SUCH_OBJECT:
115 raise
116 pass
118 self.system_session_info = system_session()
119 self.admin_session_info = admin_session(None, samdb.get_domain_sid())
121 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
122 if "msDS-hasMasterNCs" in res[0]:
123 self.write_ncs = res[0]["msDS-hasMasterNCs"]
124 else:
125 # If the Forest Level is less than 2003 then there is no
126 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
127 # no need to merge as all the NCs that are in hasMasterNCs must
128 # also be in msDS-hasMasterNCs (but not the opposite)
129 if "hasMasterNCs" in res[0]:
130 self.write_ncs = res[0]["hasMasterNCs"]
131 else:
132 self.write_ncs = None
134 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
135 self.deleted_objects_containers = []
136 self.ncs_lacking_deleted_containers = []
137 self.dns_partitions = []
138 try:
139 self.ncs = res[0]["namingContexts"]
140 except KeyError:
141 pass
142 except IndexError:
143 pass
145 for nc in self.ncs:
146 try:
147 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc.decode('utf8')),
148 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
149 self.deleted_objects_containers.append(dn)
150 except KeyError:
151 self.ncs_lacking_deleted_containers.append(ldb.Dn(self.samdb, nc.decode('utf8')))
153 domaindns_zone = 'DC=DomainDnsZones,%s' % self.samdb.get_default_basedn()
154 forestdns_zone = 'DC=ForestDnsZones,%s' % self.samdb.get_root_basedn()
155 domain = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
156 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
157 base=self.samdb.get_partitions_dn(),
158 expression="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone)
159 if len(domain) == 1:
160 self.dns_partitions.append((ldb.Dn(self.samdb, forestdns_zone), domain[0]))
162 forest = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
163 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
164 base=self.samdb.get_partitions_dn(),
165 expression="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone)
166 if len(forest) == 1:
167 self.dns_partitions.append((ldb.Dn(self.samdb, domaindns_zone), forest[0]))
169 fsmo_dn = ldb.Dn(self.samdb, "CN=RID Manager$,CN=System," + self.samdb.domain_dn())
170 rid_master = get_fsmo_roleowner(self.samdb, fsmo_dn, "rid")
171 if ldb.Dn(self.samdb, self.samdb.get_dsServiceName()) == rid_master:
172 self.is_rid_master = True
173 else:
174 self.is_rid_master = False
176 # To get your rid set
177 # 1. Get server name
178 res = self.samdb.search(base=ldb.Dn(self.samdb, self.samdb.get_serverName()),
179 scope=ldb.SCOPE_BASE, attrs=["serverReference"])
180 # 2. Get server reference
181 self.server_ref_dn = ldb.Dn(self.samdb, res[0]['serverReference'][0].decode('utf8'))
183 # 3. Get RID Set
184 res = self.samdb.search(base=self.server_ref_dn,
185 scope=ldb.SCOPE_BASE, attrs=['rIDSetReferences'])
186 if "rIDSetReferences" in res[0]:
187 self.rid_set_dn = ldb.Dn(self.samdb, res[0]['rIDSetReferences'][0].decode('utf8'))
188 else:
189 self.rid_set_dn = None
191 self.compatibleFeatures = []
192 self.requiredFeatures = []
194 try:
195 res = self.samdb.search(scope=ldb.SCOPE_BASE,
196 base="@SAMBA_DSDB",
197 attrs=["compatibleFeatures",
198 "requiredFeatures"])
199 if "compatibleFeatures" in res[0]:
200 self.compatibleFeatures = res[0]["compatibleFeatures"]
201 if "requiredFeatures" in res[0]:
202 self.requiredFeatures = res[0]["requiredFeatures"]
203 except ldb.LdbError as e6:
204 (enum, estr) = e6.args
205 if enum != ldb.ERR_NO_SUCH_OBJECT:
206 raise
207 pass
209 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
210 '''perform a database check, returning the number of errors found'''
211 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
212 self.report('Checking %u objects' % len(res))
213 error_count = 0
215 error_count += self.check_deleted_objects_containers()
217 self.attribute_or_class_ids = set()
219 for object in res:
220 self.dn_set.add(str(object.dn))
221 error_count += self.check_object(object.dn, attrs=attrs)
223 if DN is None:
224 error_count += self.check_rootdse()
226 if error_count != 0 and not self.fix:
227 self.report("Please use --fix to fix these errors")
229 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
230 return error_count
232 def check_deleted_objects_containers(self):
233 """This function only fixes conflicts on the Deleted Objects
234 containers, not the attributes"""
235 error_count = 0
236 for nc in self.ncs_lacking_deleted_containers:
237 if nc == self.schema_dn:
238 continue
239 error_count += 1
240 self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc)
241 if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'):
242 continue
244 dn = ldb.Dn(self.samdb, "CN=Deleted Objects")
245 dn.add_base(nc)
247 conflict_dn = None
248 try:
249 # If something already exists here, add a conflict
250 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[],
251 controls=["show_deleted:1", "extended_dn:1:1",
252 "show_recycled:1", "reveal_internals:0"])
253 if len(res) != 0:
254 guid = res[0].dn.get_extended_component("GUID")
255 conflict_dn = ldb.Dn(self.samdb,
256 "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid)))
257 conflict_dn.add_base(nc)
259 except ldb.LdbError as e2:
260 (enum, estr) = e2.args
261 if enum == ldb.ERR_NO_SUCH_OBJECT:
262 pass
263 else:
264 self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr)
265 return 1
267 if conflict_dn is not None:
268 try:
269 self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"])
270 except ldb.LdbError as e1:
271 (enum, estr) = e1.args
272 self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr))
273 return 1
275 # Refresh wellKnownObjects links
276 res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE,
277 attrs=['wellKnownObjects'],
278 controls=["show_deleted:1", "extended_dn:0",
279 "show_recycled:1", "reveal_internals:0"])
280 if len(res) != 1:
281 self.report("wellKnownObjects was not found for NC %s" % nc)
282 return 1
284 # Prevent duplicate deleted objects containers just in case
285 wko = res[0]["wellKnownObjects"]
286 listwko = []
287 proposed_objectguid = None
288 for o in wko:
289 dsdb_dn = dsdb_Dn(self.samdb, o.decode('utf8'), dsdb.DSDB_SYNTAX_BINARY_DN)
290 if self.is_deleted_objects_dn(dsdb_dn):
291 self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o)
292 # We really want to put this back in the same spot
293 # as the original one, so that on replication we
294 # merge, rather than conflict.
295 proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID")
296 listwko.append(str(o))
298 if proposed_objectguid is not None:
299 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid))
300 else:
301 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
302 listwko.append('%s:%s' % (wko_prefix, dn))
303 guid_suffix = ""
305 # Insert a brand new Deleted Objects container
306 self.samdb.add_ldif("""dn: %s
307 objectClass: top
308 objectClass: container
309 description: Container for deleted objects
310 isDeleted: TRUE
311 isCriticalSystemObject: TRUE
312 showInAdvancedViewOnly: TRUE
313 systemFlags: -1946157056%s""" % (dn, guid_suffix),
314 controls=["relax:0", "provision:0"])
316 delta = ldb.Message()
317 delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"]))
318 delta["wellKnownObjects"] = ldb.MessageElement(listwko,
319 ldb.FLAG_MOD_REPLACE,
320 "wellKnownObjects")
322 # Insert the link to the brand new container
323 if self.do_modify(delta, ["relax:0"],
324 "NC %s lacks Deleted Objects WKGUID" % nc,
325 validate=False):
326 self.report("Added %s well known guid link" % dn)
328 self.deleted_objects_containers.append(dn)
330 return error_count
332 def report(self, msg):
333 '''print a message unless quiet is set'''
334 if not self.quiet:
335 print(msg)
337 def confirm(self, msg, allow_all=False, forced=False):
338 '''confirm a change'''
339 if not self.fix:
340 return False
341 if self.quiet:
342 return self.yes
343 if self.yes:
344 forced = True
345 return common.confirm(msg, forced=forced, allow_all=allow_all)
347 ################################################################
348 # a local confirm function with support for 'all'
349 def confirm_all(self, msg, all_attr):
350 '''confirm a change with support for "all" '''
351 if not self.fix:
352 return False
353 if getattr(self, all_attr) == 'NONE':
354 return False
355 if getattr(self, all_attr) == 'ALL':
356 forced = True
357 else:
358 forced = self.yes
359 if self.quiet:
360 return forced
361 c = common.confirm(msg, forced=forced, allow_all=True)
362 if c == 'ALL':
363 setattr(self, all_attr, 'ALL')
364 return True
365 if c == 'NONE':
366 setattr(self, all_attr, 'NONE')
367 return False
368 return c
370 def do_delete(self, dn, controls, msg):
371 '''delete dn with optional verbose output'''
372 if self.verbose:
373 self.report("delete DN %s" % dn)
374 try:
375 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
376 self.samdb.delete(dn, controls=controls)
377 except Exception as err:
378 if self.in_transaction:
379 raise CommandError("%s : %s" % (msg, err))
380 self.report("%s : %s" % (msg, err))
381 return False
382 return True
384 def do_modify(self, m, controls, msg, validate=True):
385 '''perform a modify with optional verbose output'''
386 if self.verbose:
387 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
388 try:
389 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
390 self.samdb.modify(m, controls=controls, validate=validate)
391 except Exception as err:
392 if self.in_transaction:
393 raise CommandError("%s : %s" % (msg, err))
394 self.report("%s : %s" % (msg, err))
395 return False
396 return True
398 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
399 '''perform a modify with optional verbose output'''
400 if self.verbose:
401 self.report("""dn: %s
402 changeType: modrdn
403 newrdn: %s
404 deleteOldRdn: 1
405 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
406 try:
407 to_dn = to_rdn + to_base
408 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
409 self.samdb.rename(from_dn, to_dn, controls=controls)
410 except Exception as err:
411 if self.in_transaction:
412 raise CommandError("%s : %s" % (msg, err))
413 self.report("%s : %s" % (msg, err))
414 return False
415 return True
417 def get_attr_linkID_and_reverse_name(self, attrname):
418 if attrname in self.link_id_cache:
419 return self.link_id_cache[attrname]
420 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
421 if linkID:
422 revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
423 else:
424 revname = None
425 self.link_id_cache[attrname] = (linkID, revname)
426 return linkID, revname
428 def err_empty_attribute(self, dn, attrname):
429 '''fix empty attributes'''
430 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
431 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
432 self.report("Not fixing empty attribute %s" % attrname)
433 return
435 m = ldb.Message()
436 m.dn = dn
437 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
438 if self.do_modify(m, ["relax:0", "show_recycled:1"],
439 "Failed to remove empty attribute %s" % attrname, validate=False):
440 self.report("Removed empty attribute %s" % attrname)
442 def err_normalise_mismatch(self, dn, attrname, values):
443 '''fix attribute normalisation errors'''
444 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
445 mod_list = []
446 for val in values:
447 normalised = self.samdb.dsdb_normalise_attributes(
448 self.samdb_schema, attrname, [val])
449 if len(normalised) != 1:
450 self.report("Unable to normalise value '%s'" % val)
451 mod_list.append((val, ''))
452 elif (normalised[0] != val):
453 self.report("value '%s' should be '%s'" % (val, normalised[0]))
454 mod_list.append((val, normalised[0]))
455 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
456 self.report("Not fixing attribute %s" % attrname)
457 return
459 m = ldb.Message()
460 m.dn = dn
461 for i in range(0, len(mod_list)):
462 (val, nval) = mod_list[i]
463 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
464 if nval != '':
465 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
466 attrname)
468 if self.do_modify(m, ["relax:0", "show_recycled:1"],
469 "Failed to normalise attribute %s" % attrname,
470 validate=False):
471 self.report("Normalised attribute %s" % attrname)
473 def err_normalise_mismatch_replace(self, dn, attrname, values):
474 '''fix attribute normalisation errors'''
475 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
476 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
477 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
478 if list(normalised) == values:
479 return
480 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
481 self.report("Not fixing attribute '%s'" % attrname)
482 return
484 m = ldb.Message()
485 m.dn = dn
486 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
488 if self.do_modify(m, ["relax:0", "show_recycled:1"],
489 "Failed to normalise attribute %s" % attrname,
490 validate=False):
491 self.report("Normalised attribute %s" % attrname)
493 def err_duplicate_values(self, dn, attrname, dup_values, values):
494 '''fix attribute normalisation errors'''
495 self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
496 self.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values), ','.join(values)))
497 if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
498 self.report("Not fixing attribute '%s'" % attrname)
499 return
501 m = ldb.Message()
502 m.dn = dn
503 m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
505 if self.do_modify(m, ["relax:0", "show_recycled:1"],
506 "Failed to remove duplicate value on attribute %s" % attrname,
507 validate=False):
508 self.report("Removed duplicate value on attribute %s" % attrname)
510 def is_deleted_objects_dn(self, dsdb_dn):
511 '''see if a dsdb_Dn is the special Deleted Objects DN'''
512 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
514 def err_missing_objectclass(self, dn):
515 """handle object without objectclass"""
516 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)))
517 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'):
518 self.report("Not deleting object with missing objectclass '%s'" % dn)
519 return
520 if self.do_delete(dn, ["relax:0"],
521 "Failed to remove DN %s" % dn):
522 self.report("Removed DN %s" % dn)
524 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False):
525 """handle a DN pointing to a deleted object"""
526 if not remove_plausible:
527 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
528 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
529 if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
530 self.report("Not removing")
531 return
532 else:
533 self.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
534 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
535 if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
536 self.report("Not removing")
537 return
539 m = ldb.Message()
540 m.dn = dn
541 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
542 if self.do_modify(m, ["show_recycled:1",
543 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
544 "Failed to remove deleted DN attribute %s" % attrname):
545 self.report("Removed deleted DN on attribute %s" % attrname)
547 def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
548 """handle a missing target DN (if specified, GUID form can't be found,
549 and otherwise DN string form can't be found)"""
550 # check if its a backlink
551 linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
552 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
554 linkID, reverse_link_name \
555 = self.get_attr_linkID_and_reverse_name(attrname)
556 if reverse_link_name is not None:
557 self.report("WARNING: no target object found for GUID "
558 "component for one-way forward link "
559 "%s in object "
560 "%s - %s" % (attrname, dn, val))
561 self.report("Not removing dangling forward link")
562 return 0
564 nc_root = self.samdb.get_nc_root(dn)
565 target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn)
566 if nc_root != target_nc_root:
567 # We don't bump the error count as Samba produces these
568 # in normal operation
569 self.report("WARNING: no target object found for GUID "
570 "component for cross-partition link "
571 "%s in object "
572 "%s - %s" % (attrname, dn, val))
573 self.report("Not removing dangling one-way "
574 "cross-partition link "
575 "(we might be mid-replication)")
576 return 0
578 # Due to our link handling one-way links pointing to
579 # missing objects are plausible.
581 # We don't bump the error count as Samba produces these
582 # in normal operation
583 self.report("WARNING: no target object found for GUID "
584 "component for DN value %s in object "
585 "%s - %s" % (attrname, dn, val))
586 self.err_deleted_dn(dn, attrname, val,
587 dsdb_dn, dsdb_dn, True)
588 return 0
590 # We bump the error count here, as we should have deleted this
591 self.report("ERROR: no target object found for GUID "
592 "component for link %s in object "
593 "%s - %s" % (attrname, dn, val))
594 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
595 return 1
597 def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
598 """handle a missing GUID extended DN component"""
599 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
600 controls = ["extended_dn:1:1", "show_recycled:1"]
601 try:
602 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
603 attrs=[], controls=controls)
604 except ldb.LdbError as e7:
605 (enum, estr) = e7.args
606 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
607 if enum != ldb.ERR_NO_SUCH_OBJECT:
608 raise
609 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
610 return
611 if len(res) == 0:
612 self.report("unable to find object for DN %s" % dsdb_dn.dn)
613 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
614 return
615 dsdb_dn.dn = res[0].dn
617 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
618 self.report("Not fixing %s" % errstr)
619 return
620 m = ldb.Message()
621 m.dn = dn
622 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
623 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
625 if self.do_modify(m, ["show_recycled:1"],
626 "Failed to fix %s on attribute %s" % (errstr, attrname)):
627 self.report("Fixed %s on attribute %s" % (errstr, attrname))
629 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
630 """handle an incorrect binary DN component"""
631 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
632 controls = ["extended_dn:1:1", "show_recycled:1"]
634 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
635 self.report("Not fixing %s" % errstr)
636 return
637 m = ldb.Message()
638 m.dn = dn
639 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
640 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
642 if self.do_modify(m, ["show_recycled:1"],
643 "Failed to fix %s on attribute %s" % (errstr, attrname)):
644 self.report("Fixed %s on attribute %s" % (errstr, attrname))
646 def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn):
647 """handle a DN string being incorrect"""
648 self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val))
649 dsdb_dn.dn = correct_dn
651 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
652 'fix_all_old_dn_string_component_mismatch'):
653 self.report("Not fixing old string component")
654 return
655 m = ldb.Message()
656 m.dn = dn
657 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
658 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
659 if self.do_modify(m, ["show_recycled:1",
660 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME],
661 "Failed to fix old DN string on attribute %s" % (attrname)):
662 self.report("Fixed old DN string on attribute %s" % (attrname))
664 def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
665 """handle a DN string being incorrect"""
666 self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
667 dsdb_dn.dn = correct_dn
669 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
670 'fix_all_%s_dn_component_mismatch' % mismatch_type):
671 self.report("Not fixing %s component mismatch" % mismatch_type)
672 return
673 m = ldb.Message()
674 m.dn = dn
675 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
676 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
677 if self.do_modify(m, ["show_recycled:1"],
678 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
679 self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
681 def err_unknown_attribute(self, obj, attrname):
682 '''handle an unknown attribute error'''
683 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
684 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
685 self.report("Not removing %s" % attrname)
686 return
687 m = ldb.Message()
688 m.dn = obj.dn
689 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
690 if self.do_modify(m, ["relax:0", "show_recycled:1"],
691 "Failed to remove unknown attribute %s" % attrname):
692 self.report("Removed unknown attribute %s" % (attrname))
694 def err_undead_linked_attribute(self, obj, attrname, val):
695 '''handle a link that should not be there on a deleted object'''
696 self.report("ERROR: linked attribute '%s' to '%s' is present on "
697 "deleted object %s" % (attrname, val, obj.dn))
698 if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
699 self.report("Not removing linked attribute %s" % attrname)
700 return
701 m = ldb.Message()
702 m.dn = obj.dn
703 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
705 if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
706 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
707 "Failed to delete forward link %s" % attrname):
708 self.report("Fixed undead forward link %s" % (attrname))
710 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
711 '''handle a missing backlink value'''
712 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
713 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
714 self.report("Not fixing missing backlink %s" % backlink_name)
715 return
716 m = ldb.Message()
717 m.dn = target_dn
718 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
719 if self.do_modify(m, ["show_recycled:1", "relax:0"],
720 "Failed to fix missing backlink %s" % backlink_name):
721 self.report("Fixed missing backlink %s" % (backlink_name))
723 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
724 '''handle a incorrect RMD_FLAGS value'''
725 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
726 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()))
727 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
728 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
729 return
730 m = ldb.Message()
731 m.dn = obj.dn
732 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
733 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
734 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
735 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
737 def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val,
738 target_dn, forward_attr, forward_syntax,
739 check_duplicates=True):
740 '''handle a orphaned backlink value'''
741 if check_duplicates is True and self.has_duplicate_links(target_dn, forward_attr, forward_syntax):
742 self.report("WARNING: Keep orphaned backlink attribute " +
743 "'%s' in '%s' for link '%s' in '%s'" % (
744 backlink_attr, obj_dn, forward_attr, target_dn))
745 return
746 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn))
747 if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'):
748 self.report("Not removing orphaned backlink %s" % backlink_attr)
749 return
750 m = ldb.Message()
751 m.dn = obj_dn
752 m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr)
753 if self.do_modify(m, ["show_recycled:1", "relax:0"],
754 "Failed to fix orphaned backlink %s" % backlink_attr):
755 self.report("Fixed orphaned backlink %s" % (backlink_attr))
757 def err_recover_forward_links(self, obj, forward_attr, forward_vals):
758 '''handle a duplicate links value'''
760 self.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn))
762 if not self.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'):
763 self.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
764 forward_attr, obj.dn))
765 return
766 m = ldb.Message()
767 m.dn = obj.dn
768 m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr)
769 if self.do_modify(m, ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS],
770 "Failed to fix duplicate links in attribute '%s'" % forward_attr):
771 self.report("Fixed duplicate links in attribute '%s'" % (forward_attr))
772 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
773 assert duplicate_cache_key in self.duplicate_link_cache
774 self.duplicate_link_cache[duplicate_cache_key] = False
776 def err_no_fsmoRoleOwner(self, obj):
777 '''handle a missing fSMORoleOwner'''
778 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
779 res = self.samdb.search("",
780 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
781 assert len(res) == 1
782 serviceName = res[0]["dsServiceName"][0]
783 if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
784 self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
785 return
786 m = ldb.Message()
787 m.dn = obj.dn
788 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
789 if self.do_modify(m, [],
790 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
791 self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
793 def err_missing_parent(self, obj):
794 '''handle a missing parent'''
795 self.report("ERROR: parent object not found for %s" % (obj.dn))
796 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
797 self.report('Not moving object %s into LostAndFound' % (obj.dn))
798 return
800 keep_transaction = False
801 self.samdb.transaction_start()
802 try:
803 nc_root = self.samdb.get_nc_root(obj.dn)
804 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
805 new_dn = ldb.Dn(self.samdb, str(obj.dn))
806 new_dn.remove_base_components(len(new_dn) - 1)
807 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
808 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
809 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
811 m = ldb.Message()
812 m.dn = obj.dn
813 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
815 if self.do_modify(m, [],
816 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
817 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
818 keep_transaction = True
819 except:
820 self.samdb.transaction_cancel()
821 raise
823 if keep_transaction:
824 self.samdb.transaction_commit()
825 else:
826 self.samdb.transaction_cancel()
828 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
829 '''handle a wrong dn'''
831 new_rdn = ldb.Dn(self.samdb, str(new_dn))
832 new_rdn.remove_base_components(len(new_rdn) - 1)
833 new_parent = new_dn.parent()
835 attributes = ""
836 if rdn_val != name_val:
837 attributes += "%s=%r " % (rdn_attr, rdn_val)
838 attributes += "name=%r" % (name_val)
840 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
841 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
842 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
843 return
845 if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
846 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
847 self.report("Renamed %s into %s" % (obj.dn, new_dn))
849 def err_wrong_instancetype(self, obj, calculated_instancetype):
850 '''handle a wrong instanceType'''
851 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
852 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
853 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
854 return
856 m = ldb.Message()
857 m.dn = obj.dn
858 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
859 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
860 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
861 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
863 def err_short_userParameters(self, obj, attrname, value):
864 # This is a truncated userParameters due to a pre 4.1 replication bug
865 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)))
867 def err_base64_userParameters(self, obj, attrname, value):
868 '''handle a wrong userParameters'''
869 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
870 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
871 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
872 return
874 m = ldb.Message()
875 m.dn = obj.dn
876 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
877 if self.do_modify(m, [],
878 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
879 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
881 def err_utf8_userParameters(self, obj, attrname, value):
882 '''handle a wrong userParameters'''
883 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
884 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
885 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
886 return
888 m = ldb.Message()
889 m.dn = obj.dn
890 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
891 ldb.FLAG_MOD_REPLACE, 'userParameters')
892 if self.do_modify(m, [],
893 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
894 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
896 def err_doubled_userParameters(self, obj, attrname, value):
897 '''handle a wrong userParameters'''
898 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
899 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
900 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
901 return
903 m = ldb.Message()
904 m.dn = obj.dn
905 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
906 ldb.FLAG_MOD_REPLACE, 'userParameters')
907 if self.do_modify(m, [],
908 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
909 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
911 def err_odd_userParameters(self, obj, attrname):
912 # This is a truncated userParameters due to a pre 4.1 replication bug
913 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)))
915 def find_revealed_link(self, dn, attrname, guid):
916 '''return a revealed link in an object'''
917 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
918 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
919 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
920 for val in res[0][attrname]:
921 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
922 guid2 = dsdb_dn.dn.get_extended_component("GUID")
923 if guid == guid2:
924 return dsdb_dn
925 return None
927 def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr):
928 '''check a linked values for duplicate forward links'''
929 error_count = 0
931 duplicate_dict = dict()
932 unique_dict = dict()
934 # Only forward links can have this problem
935 if forward_linkID & 1:
936 # If we got the reverse, skip it
937 return (error_count, duplicate_dict, unique_dict)
939 if backlink_attr is None:
940 return (error_count, duplicate_dict, unique_dict)
942 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
943 if duplicate_cache_key not in self.duplicate_link_cache:
944 self.duplicate_link_cache[duplicate_cache_key] = False
946 for val in obj[forward_attr]:
947 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), forward_syntax)
949 # all DNs should have a GUID component
950 guid = dsdb_dn.dn.get_extended_component("GUID")
951 if guid is None:
952 continue
953 guidstr = str(misc.GUID(guid))
954 keystr = guidstr + dsdb_dn.prefix
955 if keystr not in unique_dict:
956 unique_dict[keystr] = dsdb_dn
957 continue
958 error_count += 1
959 if keystr not in duplicate_dict:
960 duplicate_dict[keystr] = dict()
961 duplicate_dict[keystr]["keep"] = None
962 duplicate_dict[keystr]["delete"] = list()
964 # Now check for the highest RMD_VERSION
965 v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION"))
966 v2 = int(dsdb_dn.dn.get_extended_component("RMD_VERSION"))
967 if v1 > v2:
968 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
969 duplicate_dict[keystr]["delete"].append(dsdb_dn)
970 continue
971 if v1 < v2:
972 duplicate_dict[keystr]["keep"] = dsdb_dn
973 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
974 unique_dict[keystr] = dsdb_dn
975 continue
976 # Fallback to the highest RMD_LOCAL_USN
977 u1 = int(unique_dict[keystr].dn.get_extended_component("RMD_LOCAL_USN"))
978 u2 = int(dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN"))
979 if u1 >= u2:
980 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
981 duplicate_dict[keystr]["delete"].append(dsdb_dn)
982 continue
983 duplicate_dict[keystr]["keep"] = dsdb_dn
984 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
985 unique_dict[keystr] = dsdb_dn
987 if error_count != 0:
988 self.duplicate_link_cache[duplicate_cache_key] = True
990 return (error_count, duplicate_dict, unique_dict)
992 def has_duplicate_links(self, dn, forward_attr, forward_syntax):
993 '''check a linked values for duplicate forward links'''
994 error_count = 0
996 duplicate_cache_key = "%s:%s" % (str(dn), forward_attr)
997 if duplicate_cache_key in self.duplicate_link_cache:
998 return self.duplicate_link_cache[duplicate_cache_key]
1000 forward_linkID, backlink_attr = self.get_attr_linkID_and_reverse_name(forward_attr)
1002 attrs = [forward_attr]
1003 controls = ["extended_dn:1:1", "reveal_internals:0"]
1005 # check its the right GUID
1006 try:
1007 res = self.samdb.search(base=str(dn), scope=ldb.SCOPE_BASE,
1008 attrs=attrs, controls=controls)
1009 except ldb.LdbError as e8:
1010 (enum, estr) = e8.args
1011 if enum != ldb.ERR_NO_SUCH_OBJECT:
1012 raise
1014 return False
1016 obj = res[0]
1017 error_count, duplicate_dict, unique_dict = \
1018 self.check_duplicate_links(obj, forward_attr, forward_syntax, forward_linkID, backlink_attr)
1020 if duplicate_cache_key in self.duplicate_link_cache:
1021 return self.duplicate_link_cache[duplicate_cache_key]
1023 return False
1025 def find_missing_forward_links_from_backlinks(self, obj,
1026 forward_attr,
1027 forward_syntax,
1028 backlink_attr,
1029 forward_unique_dict):
1030 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1031 missing_forward_links = []
1032 error_count = 0
1034 if backlink_attr is None:
1035 return (missing_forward_links, error_count)
1037 if forward_syntax != ldb.SYNTAX_DN:
1038 self.report("Not checking for missing forward links for syntax: %s",
1039 forward_syntax)
1040 return (missing_forward_links, error_count)
1042 if "sortedLinks" in self.compatibleFeatures:
1043 self.report("Not checking for missing forward links because the db " +
1044 "has the sortedLinks feature")
1045 return (missing_forward_links, error_count)
1047 try:
1048 obj_guid = obj['objectGUID'][0]
1049 obj_guid_str = str(ndr_unpack(misc.GUID, obj_guid))
1050 filter = "(%s=<GUID=%s>)" % (backlink_attr, obj_guid_str)
1052 res = self.samdb.search(expression=filter,
1053 scope=ldb.SCOPE_SUBTREE, attrs=["objectGUID"],
1054 controls=["extended_dn:1:1",
1055 "search_options:1:2",
1056 "paged_results:1:1000"])
1057 except ldb.LdbError as e9:
1058 (enum, estr) = e9.args
1059 raise
1061 for r in res:
1062 target_dn = dsdb_Dn(self.samdb, r.dn.extended_str(), forward_syntax)
1064 guid = target_dn.dn.get_extended_component("GUID")
1065 guidstr = str(misc.GUID(guid))
1066 if guidstr in forward_unique_dict:
1067 continue
1069 # A valid forward link looks like this:
1071 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1072 # <RMD_ADDTIME=131607546230000000>;
1073 # <RMD_CHANGETIME=131607546230000000>;
1074 # <RMD_FLAGS=0>;
1075 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1076 # <RMD_LOCAL_USN=3765>;
1077 # <RMD_ORIGINATING_USN=3765>;
1078 # <RMD_VERSION=1>;
1079 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1080 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1082 # Note that versions older than Samba 4.8 create
1083 # links with RMD_VERSION=0.
1085 # Try to get the local_usn and time from objectClass
1086 # if possible and fallback to any other one.
1087 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1088 obj['replPropertyMetadata'][0])
1089 for o in repl.ctr.array:
1090 local_usn = o.local_usn
1091 t = o.originating_change_time
1092 if o.attid == drsuapi.DRSUAPI_ATTID_objectClass:
1093 break
1095 # We use a magic invocationID for restoring missing
1096 # forward links to recover from bug #13228.
1097 # This should allow some more future magic to fix the
1098 # problem.
1100 # It also means it looses the conflict resolution
1101 # against almost every real invocation, if the
1102 # version is also 0.
1103 originating_invocid = misc.GUID("ffffffff-4700-4700-4700-000000b13228")
1104 originating_usn = 1
1106 rmd_addtime = t
1107 rmd_changetime = t
1108 rmd_flags = 0
1109 rmd_invocid = originating_invocid
1110 rmd_originating_usn = originating_usn
1111 rmd_local_usn = local_usn
1112 rmd_version = 0
1114 target_dn.dn.set_extended_component("RMD_ADDTIME", str(rmd_addtime))
1115 target_dn.dn.set_extended_component("RMD_CHANGETIME", str(rmd_changetime))
1116 target_dn.dn.set_extended_component("RMD_FLAGS", str(rmd_flags))
1117 target_dn.dn.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid))
1118 target_dn.dn.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn))
1119 target_dn.dn.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn))
1120 target_dn.dn.set_extended_component("RMD_VERSION", str(rmd_version))
1122 error_count += 1
1123 missing_forward_links.append(target_dn)
1125 return (missing_forward_links, error_count)
1127 def check_dn(self, obj, attrname, syntax_oid):
1128 '''check a DN attribute for correctness'''
1129 error_count = 0
1130 obj_guid = obj['objectGUID'][0]
1132 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1133 if reverse_link_name is not None:
1134 reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
1135 else:
1136 reverse_syntax_oid = None
1138 error_count, duplicate_dict, unique_dict = \
1139 self.check_duplicate_links(obj, attrname, syntax_oid, linkID, reverse_link_name)
1141 if len(duplicate_dict) != 0:
1143 missing_forward_links, missing_error_count = \
1144 self.find_missing_forward_links_from_backlinks(obj,
1145 attrname, syntax_oid,
1146 reverse_link_name,
1147 unique_dict)
1148 error_count += missing_error_count
1150 forward_links = [dn for dn in unique_dict.values()]
1152 if missing_error_count != 0:
1153 self.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1154 attrname, obj.dn))
1155 else:
1156 self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
1157 for m in missing_forward_links:
1158 self.report("Missing link '%s'" % (m))
1159 if not self.confirm_all("Schedule readding missing forward link for attribute %s" % attrname,
1160 'fix_all_missing_forward_links'):
1161 self.err_orphaned_backlink(m.dn, reverse_link_name,
1162 obj.dn.extended_str(), obj.dn,
1163 attrname, syntax_oid,
1164 check_duplicates=False)
1165 continue
1166 forward_links += [m]
1167 for keystr in duplicate_dict.keys():
1168 d = duplicate_dict[keystr]
1169 for dd in d["delete"]:
1170 self.report("Duplicate link '%s'" % dd)
1171 self.report("Correct link '%s'" % d["keep"])
1173 # We now construct the sorted dn values.
1174 # They're sorted by the objectGUID of the target
1175 # See dsdb_Dn.__cmp__()
1176 vals = [str(dn) for dn in sorted(forward_links)]
1177 self.err_recover_forward_links(obj, attrname, vals)
1178 # We should continue with the fixed values
1179 obj[attrname] = ldb.MessageElement(vals, 0, attrname)
1181 for val in obj[attrname]:
1182 dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
1184 # all DNs should have a GUID component
1185 guid = dsdb_dn.dn.get_extended_component("GUID")
1186 if guid is None:
1187 error_count += 1
1188 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
1189 "missing GUID")
1190 continue
1192 guidstr = str(misc.GUID(guid))
1193 attrs = ['isDeleted', 'replPropertyMetaData']
1195 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
1196 fixing_msDS_HasInstantiatedNCs = True
1197 attrs.append("instanceType")
1198 else:
1199 fixing_msDS_HasInstantiatedNCs = False
1201 if reverse_link_name is not None:
1202 attrs.append(reverse_link_name)
1204 # check its the right GUID
1205 try:
1206 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
1207 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
1208 "reveal_internals:0"
1210 except ldb.LdbError as e3:
1211 (enum, estr) = e3.args
1212 if enum != ldb.ERR_NO_SUCH_OBJECT:
1213 raise
1215 # We don't always want to
1216 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
1217 attrname,
1218 val,
1219 dsdb_dn)
1220 continue
1222 if fixing_msDS_HasInstantiatedNCs:
1223 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
1224 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
1226 if str(dsdb_dn) != val:
1227 error_count += 1
1228 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
1229 continue
1231 # now we have two cases - the source object might or might not be deleted
1232 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1233 target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
1235 if is_deleted and obj.dn not in self.deleted_objects_containers and linkID:
1236 # A fully deleted object should not have any linked
1237 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1238 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1239 # Requirements)
1240 self.err_undead_linked_attribute(obj, attrname, val)
1241 error_count += 1
1242 continue
1243 elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
1244 # the target DN is not allowed to be deleted, unless the target DN is the
1245 # special Deleted Objects container
1246 error_count += 1
1247 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
1248 if local_usn:
1249 if 'replPropertyMetaData' in res[0]:
1250 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1251 res[0]['replPropertyMetadata'][0])
1252 found_data = False
1253 for o in repl.ctr.array:
1254 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1255 deleted_usn = o.local_usn
1256 if deleted_usn >= int(local_usn):
1257 # If the object was deleted after the link
1258 # was last modified then, clean it up here
1259 found_data = True
1260 break
1262 if found_data:
1263 self.err_deleted_dn(obj.dn, attrname,
1264 val, dsdb_dn, res[0].dn, True)
1265 continue
1267 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
1268 continue
1270 # We should not check for incorrect
1271 # components on deleted links, as these are allowed to
1272 # go stale (we just need the GUID, not the name)
1273 rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
1274 rmd_flags = 0
1275 if rmd_blob is not None:
1276 rmd_flags = int(rmd_blob)
1278 # assert the DN matches in string form, where a reverse
1279 # link exists, otherwise (below) offer to fix it as a non-error.
1280 # The string form is essentially only kept for forensics,
1281 # as we always re-resolve by GUID in normal operations.
1282 if not rmd_flags & 1 and reverse_link_name is not None:
1283 if str(res[0].dn) != str(dsdb_dn.dn):
1284 error_count += 1
1285 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1286 res[0].dn, "string")
1287 continue
1289 if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
1290 error_count += 1
1291 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1292 res[0].dn, "GUID")
1293 continue
1295 if res[0].dn.get_extended_component("SID") != dsdb_dn.dn.get_extended_component("SID"):
1296 error_count += 1
1297 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1298 res[0].dn, "SID")
1299 continue
1301 # Only for non-links, not even forward-only links
1302 # (otherwise this breaks repl_meta_data):
1304 # Now we have checked the GUID and SID, offer to fix old
1305 # DN strings as a non-error (DNs, not links so no
1306 # backlink). Samba does not maintain this string
1307 # otherwise, so we don't increment error_count.
1308 if reverse_link_name is None:
1309 if linkID == 0 and str(res[0].dn) != str(dsdb_dn.dn):
1310 # Pass in the old/bad DN without the <GUID=...> part,
1311 # otherwise the LDB code will correct it on the way through
1312 # (Note: we still want to preserve the DSDB DN prefix in the
1313 # case of binary DNs)
1314 bad_dn = dsdb_dn.prefix + dsdb_dn.dn.get_linearized()
1315 self.err_dn_string_component_old(obj.dn, attrname, bad_dn,
1316 dsdb_dn, res[0].dn)
1317 continue
1319 # check the reverse_link is correct if there should be one
1320 match_count = 0
1321 if reverse_link_name in res[0]:
1322 for v in res[0][reverse_link_name]:
1323 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1324 v_guid = v_dn.dn.get_extended_component("GUID")
1325 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1326 v_rmd_flags = 0
1327 if v_blob is not None:
1328 v_rmd_flags = int(v_blob)
1329 if v_rmd_flags & 1:
1330 continue
1331 if v_guid == obj_guid:
1332 match_count += 1
1334 if match_count != 1:
1335 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1336 if not linkID & 1:
1337 # Forward binary multi-valued linked attribute
1338 forward_count = 0
1339 for w in obj[attrname]:
1340 w_guid = dsdb_Dn(self.samdb, w.decode('utf8')).dn.get_extended_component("GUID")
1341 if w_guid == guid:
1342 forward_count += 1
1344 if match_count == forward_count:
1345 continue
1346 expected_count = 0
1347 for v in obj[attrname]:
1348 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1349 v_guid = v_dn.dn.get_extended_component("GUID")
1350 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1351 v_rmd_flags = 0
1352 if v_blob is not None:
1353 v_rmd_flags = int(v_blob)
1354 if v_rmd_flags & 1:
1355 continue
1356 if v_guid == guid:
1357 expected_count += 1
1359 if match_count == expected_count:
1360 continue
1362 diff_count = expected_count - match_count
1364 if linkID & 1:
1365 # If there's a backward link on binary multi-valued linked attribute,
1366 # let the check on the forward link remedy the value.
1367 # UNLESS, there is no forward link detected.
1368 if match_count == 0:
1369 error_count += 1
1370 self.err_orphaned_backlink(obj.dn, attrname,
1371 val, dsdb_dn.dn,
1372 reverse_link_name,
1373 reverse_syntax_oid)
1374 continue
1375 # Only warn here and let the forward link logic fix it.
1376 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1377 attrname, expected_count, str(obj.dn),
1378 reverse_link_name, match_count, str(dsdb_dn.dn)))
1379 continue
1381 assert not target_is_deleted
1383 self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1384 attrname, expected_count, str(obj.dn),
1385 reverse_link_name, match_count, str(dsdb_dn.dn)))
1387 # Loop until the difference between the forward and
1388 # the backward links is resolved.
1389 while diff_count != 0:
1390 error_count += 1
1391 if diff_count > 0:
1392 if match_count > 0 or diff_count > 1:
1393 # TODO no method to fix these right now
1394 self.report("ERROR: Can't fix missing "
1395 "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1396 break
1397 self.err_missing_backlink(obj, attrname,
1398 obj.dn.extended_str(),
1399 reverse_link_name,
1400 dsdb_dn.dn)
1401 diff_count -= 1
1402 else:
1403 self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1404 obj.dn.extended_str(), obj.dn,
1405 attrname, syntax_oid)
1406 diff_count += 1
1408 return error_count
1410 def get_originating_time(self, val, attid):
1411 '''Read metadata properties and return the originating time for
1412 a given attributeId.
1414 :return: the originating time or 0 if not found
1417 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1418 obj = repl.ctr
1420 for o in repl.ctr.array:
1421 if o.attid == attid:
1422 return o.originating_change_time
1424 return 0
1426 def process_metadata(self, dn, val):
1427 '''Read metadata properties and list attributes in it.
1428 raises KeyError if the attid is unknown.'''
1430 set_att = set()
1431 wrong_attids = set()
1432 list_attid = []
1433 in_schema_nc = dn.is_child_of(self.schema_dn)
1435 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1436 obj = repl.ctr
1438 for o in repl.ctr.array:
1439 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1440 set_att.add(att.lower())
1441 list_attid.append(o.attid)
1442 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1443 is_schema_nc=in_schema_nc)
1444 if correct_attid != o.attid:
1445 wrong_attids.add(o.attid)
1447 return (set_att, list_attid, wrong_attids)
1449 def fix_metadata(self, obj, attr):
1450 '''re-write replPropertyMetaData elements for a single attribute for a
1451 object. This is used to fix missing replPropertyMetaData elements'''
1452 guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1453 dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1454 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attr],
1455 controls=["search_options:1:2",
1456 "show_recycled:1"])
1457 msg = res[0]
1458 nmsg = ldb.Message()
1459 nmsg.dn = dn
1460 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1461 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1462 "Failed to fix metadata for attribute %s" % attr):
1463 self.report("Fixed metadata for attribute %s" % attr)
1465 def ace_get_effective_inherited_type(self, ace):
1466 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1467 return None
1469 check = False
1470 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1471 check = True
1472 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1473 check = True
1474 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1475 check = True
1476 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1477 check = True
1479 if not check:
1480 return None
1482 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1483 return None
1485 return str(ace.object.inherited_type)
1487 def lookup_class_schemaIDGUID(self, cls):
1488 if cls in self.class_schemaIDGUID:
1489 return self.class_schemaIDGUID[cls]
1491 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1492 res = self.samdb.search(base=self.schema_dn,
1493 expression=flt,
1494 attrs=["schemaIDGUID"])
1495 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1497 self.class_schemaIDGUID[cls] = t
1498 return t
1500 def process_sd(self, dn, obj):
1501 sd_attr = "nTSecurityDescriptor"
1502 sd_val = obj[sd_attr]
1504 sd = ndr_unpack(security.descriptor, sd_val[0])
1506 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1507 if is_deleted:
1508 # we don't fix deleted objects
1509 return (sd, None)
1511 sd_clean = security.descriptor()
1512 sd_clean.owner_sid = sd.owner_sid
1513 sd_clean.group_sid = sd.group_sid
1514 sd_clean.type = sd.type
1515 sd_clean.revision = sd.revision
1517 broken = False
1518 last_inherited_type = None
1520 aces = []
1521 if sd.sacl is not None:
1522 aces = sd.sacl.aces
1523 for i in range(0, len(aces)):
1524 ace = aces[i]
1526 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1527 sd_clean.sacl_add(ace)
1528 continue
1530 t = self.ace_get_effective_inherited_type(ace)
1531 if t is None:
1532 continue
1534 if last_inherited_type is not None:
1535 if t != last_inherited_type:
1536 # if it inherited from more than
1537 # one type it's very likely to be broken
1539 # If not the recalculation will calculate
1540 # the same result.
1541 broken = True
1542 continue
1544 last_inherited_type = t
1546 aces = []
1547 if sd.dacl is not None:
1548 aces = sd.dacl.aces
1549 for i in range(0, len(aces)):
1550 ace = aces[i]
1552 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1553 sd_clean.dacl_add(ace)
1554 continue
1556 t = self.ace_get_effective_inherited_type(ace)
1557 if t is None:
1558 continue
1560 if last_inherited_type is not None:
1561 if t != last_inherited_type:
1562 # if it inherited from more than
1563 # one type it's very likely to be broken
1565 # If not the recalculation will calculate
1566 # the same result.
1567 broken = True
1568 continue
1570 last_inherited_type = t
1572 if broken:
1573 return (sd_clean, sd)
1575 if last_inherited_type is None:
1576 # ok
1577 return (sd, None)
1579 cls = None
1580 try:
1581 cls = obj["objectClass"][-1]
1582 except KeyError as e:
1583 pass
1585 if cls is None:
1586 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1587 attrs=["isDeleted", "objectClass"],
1588 controls=["show_recycled:1"])
1589 o = res[0]
1590 is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
1591 if is_deleted:
1592 # we don't fix deleted objects
1593 return (sd, None)
1594 cls = o["objectClass"][-1]
1596 t = self.lookup_class_schemaIDGUID(cls)
1598 if t != last_inherited_type:
1599 # broken
1600 return (sd_clean, sd)
1602 # ok
1603 return (sd, None)
1605 def err_wrong_sd(self, dn, sd, sd_broken):
1606 '''re-write the SD due to incorrect inherited ACEs'''
1607 sd_attr = "nTSecurityDescriptor"
1608 sd_val = ndr_pack(sd)
1609 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1611 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1612 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1613 return
1615 nmsg = ldb.Message()
1616 nmsg.dn = dn
1617 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1618 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1619 "Failed to fix attribute %s" % sd_attr):
1620 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1622 def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1623 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1624 sd_attr = "nTSecurityDescriptor"
1625 sd_val = ndr_pack(sd)
1626 sd_old_val = ndr_pack(sd_old)
1627 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1628 if sd.owner_sid is not None:
1629 sd_flags |= security.SECINFO_OWNER
1630 if sd.group_sid is not None:
1631 sd_flags |= security.SECINFO_GROUP
1633 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1634 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1635 return
1637 m = ldb.Message()
1638 m.dn = dn
1639 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1640 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1641 "Failed to reset attribute %s" % sd_attr):
1642 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1644 def err_missing_sd_owner(self, dn, sd):
1645 '''re-write the SD due to a missing owner or group'''
1646 sd_attr = "nTSecurityDescriptor"
1647 sd_val = ndr_pack(sd)
1648 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1650 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1651 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1652 return
1654 nmsg = ldb.Message()
1655 nmsg.dn = dn
1656 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1658 # By setting the session_info to admin_session_info and
1659 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1660 # flags we cause the descriptor module to set the correct
1661 # owner and group on the SD, replacing the None/NULL values
1662 # for owner_sid and group_sid currently present.
1664 # The admin_session_info matches that used in provision, and
1665 # is the best guess we can make for an existing object that
1666 # hasn't had something specifically set.
1668 # This is important for the dns related naming contexts.
1669 self.samdb.set_session_info(self.admin_session_info)
1670 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1671 "Failed to fix metadata for attribute %s" % sd_attr):
1672 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1673 self.samdb.set_session_info(self.system_session_info)
1675 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1676 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1677 repl_meta_data)
1678 ctr = repl.ctr
1679 found = False
1680 for o in ctr.array:
1681 # Search for a zero invocationID
1682 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1683 continue
1685 found = True
1686 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1687 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1688 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1689 % (dn, o.attid, o.version,
1690 time.ctime(samba.nttime2unix(o.originating_change_time)),
1691 self.samdb.get_invocation_id()))
1693 return found
1695 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1696 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1697 repl_meta_data)
1698 ctr = repl.ctr
1699 now = samba.unix2nttime(int(time.time()))
1700 found = False
1701 for o in ctr.array:
1702 # Search for a zero invocationID
1703 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1704 continue
1706 found = True
1707 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1708 o.version = o.version + 1
1709 o.originating_change_time = now
1710 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1711 o.originating_usn = seq
1712 o.local_usn = seq
1714 if found:
1715 replBlob = ndr_pack(repl)
1716 msg = ldb.Message()
1717 msg.dn = dn
1719 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1720 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1721 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1722 return
1724 nmsg = ldb.Message()
1725 nmsg.dn = dn
1726 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1727 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1728 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1729 "Failed to fix attribute %s" % attr):
1730 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1732 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1733 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1734 repl_meta_data)
1735 ctr = repl.ctr
1736 for o in ctr.array:
1737 # Search for an invalid attid
1738 try:
1739 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1740 except KeyError:
1741 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1742 return
1744 def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1745 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1746 repl_meta_data)
1747 fix = False
1749 set_att = set()
1750 remove_attid = set()
1751 hash_att = {}
1753 in_schema_nc = dn.is_child_of(self.schema_dn)
1755 ctr = repl.ctr
1756 # Sort the array, except for the last element. This strange
1757 # construction, creating a new list, due to bugs in samba's
1758 # array handling in IDL generated objects.
1759 ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1760 # Now walk it in reverse, so we see the low (and so incorrect,
1761 # the correct values are above 0x80000000) values first and
1762 # remove the 'second' value we see.
1763 for o in reversed(ctr.array):
1764 print("%s: 0x%08x" % (dn, o.attid))
1765 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1766 if att.lower() in set_att:
1767 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1768 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1769 % (attr, dn, o.attid, att, hash_att[att].attid),
1770 'fix_replmetadata_duplicate_attid'):
1771 self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1772 % (o.attid, att, attr, dn))
1773 return
1774 fix = True
1775 remove_attid.add(o.attid)
1776 # We want to set the metadata for the most recent
1777 # update to have been applied locally, that is the metadata
1778 # matching the (eg string) value in the attribute
1779 if o.local_usn > hash_att[att].local_usn:
1780 # This is always what we would have sent over DRS,
1781 # because the DRS server will have sent the
1782 # msDS-IntID, but with the values from both
1783 # attribute entries.
1784 hash_att[att].version = o.version
1785 hash_att[att].originating_change_time = o.originating_change_time
1786 hash_att[att].originating_invocation_id = o.originating_invocation_id
1787 hash_att[att].originating_usn = o.originating_usn
1788 hash_att[att].local_usn = o.local_usn
1790 # Do not re-add the value to the set or overwrite the hash value
1791 continue
1793 hash_att[att] = o
1794 set_att.add(att.lower())
1796 # Generate a real list we can sort on properly
1797 new_list = [o for o in ctr.array if o.attid not in remove_attid]
1799 if (len(wrong_attids) > 0):
1800 for o in new_list:
1801 if o.attid in wrong_attids:
1802 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1803 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1804 self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1805 if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1806 % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1807 self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1808 % (o.attid, correct_attid, att, attr, dn))
1809 return
1810 fix = True
1811 o.attid = correct_attid
1812 if fix:
1813 # Sort the array, (we changed the value so must re-sort)
1814 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1816 # If we did not already need to fix it, then ask about sorting
1817 if not fix:
1818 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1819 if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1820 % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1821 self.report('Not fixing %s on %s\n' % (attr, dn))
1822 return
1824 # The actual sort done is done at the top of the function
1826 ctr.count = len(new_list)
1827 ctr.array = new_list
1828 replBlob = ndr_pack(repl)
1830 nmsg = ldb.Message()
1831 nmsg.dn = dn
1832 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1833 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1834 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1835 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1836 "Failed to fix attribute %s" % attr):
1837 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1839 def is_deleted_deleted_objects(self, obj):
1840 faulty = False
1841 if "description" not in obj:
1842 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1843 faulty = True
1844 if "showInAdvancedViewOnly" not in obj or obj['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1845 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1846 faulty = True
1847 if "objectCategory" not in obj:
1848 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1849 faulty = True
1850 if "isCriticalSystemObject" not in obj or obj['isCriticalSystemObject'][0].upper() == 'FALSE':
1851 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1852 faulty = True
1853 if "isRecycled" in obj:
1854 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1855 faulty = True
1856 if "isDeleted" in obj and obj['isDeleted'][0].upper() == 'FALSE':
1857 self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1858 faulty = True
1859 if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1860 obj['objectClass'][0] != 'top' or
1861 obj['objectClass'][1] != 'container'):
1862 self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1863 faulty = True
1864 if "systemFlags" not in obj or obj['systemFlags'][0] != '-1946157056':
1865 self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1866 faulty = True
1867 return faulty
1869 def err_deleted_deleted_objects(self, obj):
1870 nmsg = ldb.Message()
1871 nmsg.dn = dn = obj.dn
1873 if "description" not in obj:
1874 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1875 if "showInAdvancedViewOnly" not in obj:
1876 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1877 if "objectCategory" not in obj:
1878 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1879 if "isCriticalSystemObject" not in obj:
1880 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1881 if "isRecycled" in obj:
1882 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1884 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1885 nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1886 nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1888 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1889 % (dn), 'fix_deleted_deleted_objects'):
1890 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1891 return
1893 if self.do_modify(nmsg, ["relax:0"],
1894 "Failed to fix Deleted Objects container %s" % dn):
1895 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1897 def err_replica_locations(self, obj, cross_ref, attr):
1898 nmsg = ldb.Message()
1899 nmsg.dn = cross_ref
1900 target = self.samdb.get_dsServiceName()
1902 if self.samdb.am_rodc():
1903 self.report('Not fixing %s for the RODC' % (attr, obj.dn))
1904 return
1906 if not self.confirm_all('Add yourself to the replica locations for %s?'
1907 % (obj.dn), 'fix_replica_locations'):
1908 self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1909 return
1911 nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1912 if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1913 self.report("Fixed %s for %s" % (attr, obj.dn))
1915 def is_fsmo_role(self, dn):
1916 if dn == self.samdb.domain_dn:
1917 return True
1918 if dn == self.infrastructure_dn:
1919 return True
1920 if dn == self.naming_dn:
1921 return True
1922 if dn == self.schema_dn:
1923 return True
1924 if dn == self.rid_dn:
1925 return True
1927 return False
1929 def calculate_instancetype(self, dn):
1930 instancetype = 0
1931 nc_root = self.samdb.get_nc_root(dn)
1932 if dn == nc_root:
1933 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1934 try:
1935 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1936 except ldb.LdbError as e4:
1937 (enum, estr) = e4.args
1938 if enum != ldb.ERR_NO_SUCH_OBJECT:
1939 raise
1940 else:
1941 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1942 if self.write_ncs is not None and str(nc_root) in [str(x) for x in self.write_ncs]:
1943 instancetype |= dsdb.INSTANCE_TYPE_WRITE
1945 return instancetype
1947 def get_wellknown_sd(self, dn):
1948 for [sd_dn, descriptor_fn] in self.wellknown_sds:
1949 if dn == sd_dn:
1950 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1951 return ndr_unpack(security.descriptor,
1952 descriptor_fn(domain_sid,
1953 name_map=self.name_map))
1955 raise KeyError
1957 def check_object(self, dn, attrs=['*']):
1958 '''check one object'''
1959 if self.verbose:
1960 self.report("Checking object %s" % dn)
1962 # If we modify the pass-by-reference attrs variable, then we get a
1963 # replPropertyMetadata for every object that we check.
1964 attrs = list(attrs)
1965 if "dn" in map(str.lower, attrs):
1966 attrs.append("name")
1967 if "distinguishedname" in map(str.lower, attrs):
1968 attrs.append("name")
1969 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1970 attrs.append("name")
1971 if 'name' in map(str.lower, attrs):
1972 attrs.append(dn.get_rdn_name())
1973 attrs.append("isDeleted")
1974 attrs.append("systemFlags")
1975 need_replPropertyMetaData = False
1976 if '*' in attrs:
1977 need_replPropertyMetaData = True
1978 else:
1979 for a in attrs:
1980 linkID, _ = self.get_attr_linkID_and_reverse_name(a)
1981 if linkID == 0:
1982 continue
1983 if linkID & 1:
1984 continue
1985 need_replPropertyMetaData = True
1986 break
1987 if need_replPropertyMetaData:
1988 attrs.append("replPropertyMetaData")
1989 attrs.append("objectGUID")
1991 try:
1992 sd_flags = 0
1993 sd_flags |= security.SECINFO_OWNER
1994 sd_flags |= security.SECINFO_GROUP
1995 sd_flags |= security.SECINFO_DACL
1996 sd_flags |= security.SECINFO_SACL
1998 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1999 controls=[
2000 "extended_dn:1:1",
2001 "show_recycled:1",
2002 "show_deleted:1",
2003 "sd_flags:1:%d" % sd_flags,
2004 "reveal_internals:0",
2006 attrs=attrs)
2007 except ldb.LdbError as e10:
2008 (enum, estr) = e10.args
2009 if enum == ldb.ERR_NO_SUCH_OBJECT:
2010 if self.in_transaction:
2011 self.report("ERROR: Object %s disappeared during check" % dn)
2012 return 1
2013 return 0
2014 raise
2015 if len(res) != 1:
2016 self.report("ERROR: Object %s failed to load during check" % dn)
2017 return 1
2018 obj = res[0]
2019 error_count = 0
2020 set_attrs_from_md = set()
2021 set_attrs_seen = set()
2022 got_repl_property_meta_data = False
2023 got_objectclass = False
2025 nc_dn = self.samdb.get_nc_root(obj.dn)
2026 try:
2027 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
2028 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
2029 except KeyError:
2030 # We have no deleted objects DN for schema, and we check for this above for the other
2031 # NCs
2032 deleted_objects_dn = None
2034 object_rdn_attr = None
2035 object_rdn_val = None
2036 name_val = None
2037 isDeleted = False
2038 systemFlags = 0
2040 for attrname in obj:
2041 if attrname == 'dn' or attrname == "distinguishedName":
2042 continue
2044 if str(attrname).lower() == 'objectclass':
2045 got_objectclass = True
2047 if str(attrname).lower() == "name":
2048 if len(obj[attrname]) != 1:
2049 error_count += 1
2050 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2051 (len(obj[attrname]), attrname, str(obj.dn)))
2052 else:
2053 name_val = obj[attrname][0]
2055 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
2056 object_rdn_attr = attrname
2057 if len(obj[attrname]) != 1:
2058 error_count += 1
2059 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2060 (len(obj[attrname]), attrname, str(obj.dn)))
2061 else:
2062 object_rdn_val = str(obj[attrname][0])
2064 if str(attrname).lower() == 'isdeleted':
2065 if str(obj[attrname][0]) != "FALSE":
2066 isDeleted = True
2068 if str(attrname).lower() == 'systemflags':
2069 systemFlags = int(obj[attrname][0])
2071 if str(attrname).lower() == 'replpropertymetadata':
2072 if self.has_replmetadata_zero_invocationid(dn, obj[attrname][0]):
2073 error_count += 1
2074 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname][0])
2075 # We don't continue, as we may also have other fixes for this attribute
2076 # based on what other attributes we see.
2078 try:
2079 (set_attrs_from_md, list_attid_from_md, wrong_attids) \
2080 = self.process_metadata(dn, obj[attrname][0])
2081 except KeyError:
2082 error_count += 1
2083 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
2084 continue
2086 if len(set_attrs_from_md) < len(list_attid_from_md) \
2087 or len(wrong_attids) > 0 \
2088 or sorted(list_attid_from_md) != list_attid_from_md:
2089 error_count += 1
2090 self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname][0], wrong_attids)
2092 else:
2093 # Here we check that the first attid is 0
2094 # (objectClass).
2095 if list_attid_from_md[0] != 0:
2096 error_count += 1
2097 self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2098 (attrname, str(dn)))
2100 got_repl_property_meta_data = True
2101 continue
2103 if str(attrname).lower() == 'ntsecuritydescriptor':
2104 (sd, sd_broken) = self.process_sd(dn, obj)
2105 if sd_broken is not None:
2106 self.err_wrong_sd(dn, sd, sd_broken)
2107 error_count += 1
2108 continue
2110 if sd.owner_sid is None or sd.group_sid is None:
2111 self.err_missing_sd_owner(dn, sd)
2112 error_count += 1
2113 continue
2115 if self.reset_well_known_acls:
2116 try:
2117 well_known_sd = self.get_wellknown_sd(dn)
2118 except KeyError:
2119 continue
2121 current_sd = ndr_unpack(security.descriptor,
2122 obj[attrname][0])
2124 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
2125 if diff != "":
2126 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
2127 error_count += 1
2128 continue
2129 continue
2131 if str(attrname).lower() == 'objectclass':
2132 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
2133 # Do not consider the attribute incorrect if:
2134 # - The sorted (alphabetically) list is the same, inclding case
2135 # - The first and last elements are the same
2137 # This avoids triggering an error due to
2138 # non-determinism in the sort routine in (at least)
2139 # 4.3 and earlier, and the fact that any AUX classes
2140 # in these attributes are also not sorted when
2141 # imported from Windows (they are just in the reverse
2142 # order of last set)
2143 if sorted(normalised) != sorted(obj[attrname]) \
2144 or normalised[0] != obj[attrname][0] \
2145 or normalised[-1] != obj[attrname][-1]:
2146 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
2147 error_count += 1
2148 continue
2150 if str(attrname).lower() == 'userparameters':
2151 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
2152 error_count += 1
2153 self.err_short_userParameters(obj, attrname, obj[attrname])
2154 continue
2156 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2157 # This is the correct, normal prefix
2158 continue
2160 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
2161 # this is the typical prefix from a windows migration
2162 error_count += 1
2163 self.err_base64_userParameters(obj, attrname, obj[attrname])
2164 continue
2166 elif obj[attrname][0][1] != '\x00' and obj[attrname][0][3] != '\x00' and obj[attrname][0][5] != '\x00' and obj[attrname][0][7] != '\x00' and obj[attrname][0][9] != '\x00':
2167 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2168 error_count += 1
2169 self.err_utf8_userParameters(obj, attrname, obj[attrname])
2170 continue
2172 elif len(obj[attrname][0]) % 2 != 0:
2173 # This is a value that isn't even in length
2174 error_count += 1
2175 self.err_odd_userParameters(obj, attrname, obj[attrname])
2176 continue
2178 elif obj[attrname][0][1] == '\x00' and obj[attrname][0][2] == '\x00' and obj[attrname][0][3] == '\x00' and obj[attrname][0][4] != '\x00' and obj[attrname][0][5] == '\x00':
2179 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2180 error_count += 1
2181 self.err_doubled_userParameters(obj, attrname, obj[attrname])
2182 continue
2184 if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2185 if obj[attrname][0] in self.attribute_or_class_ids:
2186 error_count += 1
2187 self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2188 % (attrname, obj.dn, obj[attrname][0]))
2189 else:
2190 self.attribute_or_class_ids.add(obj[attrname][0])
2192 # check for empty attributes
2193 for val in obj[attrname]:
2194 if val == '':
2195 self.err_empty_attribute(dn, attrname)
2196 error_count += 1
2197 continue
2199 # get the syntax oid for the attribute, so we can can have
2200 # special handling for some specific attribute types
2201 try:
2202 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2203 except Exception as msg:
2204 self.err_unknown_attribute(obj, attrname)
2205 error_count += 1
2206 continue
2208 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2210 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2211 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2212 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2213 and not linkID):
2214 set_attrs_seen.add(str(attrname).lower())
2216 if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2217 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN]:
2218 # it's some form of DN, do specialised checking on those
2219 error_count += self.check_dn(obj, attrname, syntax_oid)
2220 else:
2222 values = set()
2223 # check for incorrectly normalised attributes
2224 for val in obj[attrname]:
2225 values.add(val)
2227 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2228 if len(normalised) != 1 or normalised[0] != val:
2229 self.err_normalise_mismatch(dn, attrname, obj[attrname])
2230 error_count += 1
2231 break
2233 if len(obj[attrname]) != len(values):
2234 self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2235 error_count += 1
2236 break
2238 if str(attrname).lower() == "instancetype":
2239 calculated_instancetype = self.calculate_instancetype(dn)
2240 if len(obj["instanceType"]) != 1 or int(obj["instanceType"][0]) != calculated_instancetype:
2241 error_count += 1
2242 self.err_wrong_instancetype(obj, calculated_instancetype)
2244 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
2245 error_count += 1
2246 self.err_missing_objectclass(dn)
2248 if ("*" in attrs or "name" in map(str.lower, attrs)):
2249 if name_val is None:
2250 error_count += 1
2251 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2252 if object_rdn_attr is None:
2253 error_count += 1
2254 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2256 if name_val is not None:
2257 parent_dn = None
2258 if isDeleted:
2259 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2260 parent_dn = deleted_objects_dn
2261 if parent_dn is None:
2262 parent_dn = obj.dn.parent()
2263 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2264 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2266 if obj.dn == deleted_objects_dn:
2267 expected_dn = obj.dn
2269 if expected_dn != obj.dn:
2270 error_count += 1
2271 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
2272 elif obj.dn.get_rdn_value() != object_rdn_val:
2273 error_count += 1
2274 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
2276 show_dn = True
2277 if got_repl_property_meta_data:
2278 if obj.dn == deleted_objects_dn:
2279 isDeletedAttId = 131120
2280 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2282 expectedTimeDo = 2650466015990000000
2283 originating = self.get_originating_time(obj["replPropertyMetaData"][0], isDeletedAttId)
2284 if originating != expectedTimeDo:
2285 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2286 nmsg = ldb.Message()
2287 nmsg.dn = dn
2288 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2289 error_count += 1
2290 self.samdb.modify(nmsg, controls=["provision:0"])
2292 else:
2293 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2295 for att in set_attrs_seen.difference(set_attrs_from_md):
2296 if show_dn:
2297 self.report("On object %s" % dn)
2298 show_dn = False
2299 error_count += 1
2300 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2301 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2302 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2303 continue
2304 self.fix_metadata(obj, att)
2306 if self.is_fsmo_role(dn):
2307 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
2308 self.err_no_fsmoRoleOwner(obj)
2309 error_count += 1
2311 try:
2312 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2313 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2314 controls=["show_recycled:1", "show_deleted:1"])
2315 except ldb.LdbError as e11:
2316 (enum, estr) = e11.args
2317 if enum == ldb.ERR_NO_SUCH_OBJECT:
2318 self.err_missing_parent(obj)
2319 error_count += 1
2320 else:
2321 raise
2323 if dn in self.deleted_objects_containers and '*' in attrs:
2324 if self.is_deleted_deleted_objects(obj):
2325 self.err_deleted_deleted_objects(obj)
2326 error_count += 1
2328 for (dns_part, msg) in self.dns_partitions:
2329 if dn == dns_part and 'repsFrom' in obj:
2330 location = "msDS-NC-Replica-Locations"
2331 if self.samdb.am_rodc():
2332 location = "msDS-NC-RO-Replica-Locations"
2334 if location not in msg:
2335 # There are no replica locations!
2336 self.err_replica_locations(obj, msg.dn, location)
2337 error_count += 1
2338 continue
2340 found = False
2341 for loc in msg[location]:
2342 if loc == self.samdb.get_dsServiceName():
2343 found = True
2344 if not found:
2345 # This DC is not in the replica locations
2346 self.err_replica_locations(obj, msg.dn, location)
2347 error_count += 1
2349 if dn == self.server_ref_dn:
2350 # Check we have a valid RID Set
2351 if "*" in attrs or "rIDSetReferences" in attrs:
2352 if "rIDSetReferences" not in obj:
2353 # NO RID SET reference
2354 # We are RID master, allocate it.
2355 error_count += 1
2357 if self.is_rid_master:
2358 # Allocate a RID Set
2359 if self.confirm_all('Allocate the missing RID set for RID master?',
2360 'fix_missing_rid_set_master'):
2362 # We don't have auto-transaction logic on
2363 # extended operations, so we have to do it
2364 # here.
2366 self.samdb.transaction_start()
2368 try:
2369 self.samdb.create_own_rid_set()
2371 except:
2372 self.samdb.transaction_cancel()
2373 raise
2375 self.samdb.transaction_commit()
2377 elif not self.samdb.am_rodc():
2378 self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2380 # Check some details of our own RID Set
2381 if dn == self.rid_set_dn:
2382 res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2383 attrs=["rIDAllocationPool",
2384 "rIDPreviousAllocationPool",
2385 "rIDUsedPool",
2386 "rIDNextRID"])
2387 if "rIDAllocationPool" not in res[0]:
2388 self.report("No rIDAllocationPool found in %s" % dn)
2389 error_count += 1
2390 else:
2391 next_pool = int(res[0]["rIDAllocationPool"][0])
2393 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2394 low = 0x00000000FFFFFFFF & next_pool
2396 if high <= low:
2397 self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2398 error_count += 1
2400 if "rIDNextRID" in res[0]:
2401 next_free_rid = int(res[0]["rIDNextRID"][0])
2402 else:
2403 next_free_rid = 0
2405 if next_free_rid == 0:
2406 next_free_rid = low
2407 else:
2408 next_free_rid += 1
2410 # Check the remainder of this pool for conflicts. If
2411 # ridalloc_allocate_rid() moves to a new pool, this
2412 # will be above high, so we will stop.
2413 while next_free_rid <= high:
2414 sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2415 try:
2416 res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2417 attrs=[])
2418 except ldb.LdbError as e:
2419 (enum, estr) = e.args
2420 if enum != ldb.ERR_NO_SUCH_OBJECT:
2421 raise
2422 res = None
2423 if res is not None:
2424 self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2425 error_count += 1
2427 if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2428 % (sid, dn),
2429 'fix_sid_rid_set_conflict'):
2430 self.samdb.transaction_start()
2432 # This will burn RIDs, which will move
2433 # past the conflict. We then check again
2434 # to see if the new RID conflicts, until
2435 # the end of the current pool. We don't
2436 # look at the next pool to avoid burning
2437 # all RIDs in one go in some strange
2438 # failure case.
2439 try:
2440 while True:
2441 allocated_rid = self.samdb.allocate_rid()
2442 if allocated_rid >= next_free_rid:
2443 next_free_rid = allocated_rid + 1
2444 break
2445 except:
2446 self.samdb.transaction_cancel()
2447 raise
2449 self.samdb.transaction_commit()
2450 else:
2451 break
2452 else:
2453 next_free_rid += 1
2455 return error_count
2457 ################################################################
2458 # check special @ROOTDSE attributes
2459 def check_rootdse(self):
2460 '''check the @ROOTDSE special object'''
2461 dn = ldb.Dn(self.samdb, '@ROOTDSE')
2462 if self.verbose:
2463 self.report("Checking object %s" % dn)
2464 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2465 if len(res) != 1:
2466 self.report("Object %s disappeared during check" % dn)
2467 return 1
2468 obj = res[0]
2469 error_count = 0
2471 # check that the dsServiceName is in GUID form
2472 if 'dsServiceName' not in obj:
2473 self.report('ERROR: dsServiceName missing in @ROOTDSE')
2474 return error_count + 1
2476 if not obj['dsServiceName'][0].startswith('<GUID='):
2477 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2478 error_count += 1
2479 if not self.confirm('Change dsServiceName to GUID form?'):
2480 return error_count
2481 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0].decode('utf8')),
2482 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2483 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2484 m = ldb.Message()
2485 m.dn = dn
2486 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2487 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2488 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2489 self.report("Changed dsServiceName to GUID form")
2490 return error_count
2492 ###############################################
2493 # re-index the database
2495 def reindex_database(self):
2496 '''re-index the whole database'''
2497 m = ldb.Message()
2498 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2499 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2500 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2501 return self.do_modify(m, [], 're-indexed database', validate=False)
2503 ###############################################
2504 # reset @MODULES
2505 def reset_modules(self):
2506 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2507 m = ldb.Message()
2508 m.dn = ldb.Dn(self.samdb, "@MODULES")
2509 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2510 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)