dbchecker: improve verbose output of do_modify()
[Samba.git] / python / samba / dbchecker.py
blob189230503aca7808c1ddc618d7c41cc9a330d8f9
1 # Samba4 AD database checker
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 import ldb
21 import samba
22 import time
23 from base64 import b64decode
24 from samba import dsdb
25 from samba import common
26 from samba.dcerpc import misc
27 from samba.dcerpc import drsuapi
28 from samba.ndr import ndr_unpack, ndr_pack
29 from samba.dcerpc import drsblobs
30 from samba.common import dsdb_Dn
31 from samba.dcerpc import security
32 from samba.descriptor import get_wellknown_sds, get_diff_sds
33 from samba.auth import system_session, admin_session
34 from samba.netcmd import CommandError
35 from samba.netcmd.fsmo import get_fsmo_roleowner
38 class dbcheck(object):
39 """check a SAM database for errors"""
41 def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
42 yes=False, quiet=False, in_transaction=False,
43 reset_well_known_acls=False):
44 self.samdb = samdb
45 self.dict_oid_name = None
46 self.samdb_schema = (samdb_schema or samdb)
47 self.verbose = verbose
48 self.fix = fix
49 self.yes = yes
50 self.quiet = quiet
51 self.remove_all_unknown_attributes = False
52 self.remove_all_empty_attributes = False
53 self.fix_all_normalisation = False
54 self.fix_all_duplicates = False
55 self.fix_all_DN_GUIDs = False
56 self.fix_all_binary_dn = False
57 self.remove_implausible_deleted_DN_links = False
58 self.remove_plausible_deleted_DN_links = False
59 self.fix_all_string_dn_component_mismatch = False
60 self.fix_all_GUID_dn_component_mismatch = False
61 self.fix_all_SID_dn_component_mismatch = False
62 self.fix_all_old_dn_string_component_mismatch = False
63 self.fix_all_metadata = False
64 self.fix_time_metadata = False
65 self.fix_undead_linked_attributes = False
66 self.fix_all_missing_backlinks = False
67 self.fix_all_orphaned_backlinks = False
68 self.fix_all_missing_forward_links = False
69 self.duplicate_link_cache = dict()
70 self.recover_all_forward_links = False
71 self.fix_rmd_flags = False
72 self.fix_ntsecuritydescriptor = False
73 self.fix_ntsecuritydescriptor_owner_group = False
74 self.seize_fsmo_role = False
75 self.move_to_lost_and_found = False
76 self.fix_instancetype = False
77 self.fix_replmetadata_zero_invocationid = False
78 self.fix_replmetadata_duplicate_attid = False
79 self.fix_replmetadata_wrong_attid = False
80 self.fix_replmetadata_unsorted_attid = False
81 self.fix_deleted_deleted_objects = False
82 self.fix_incorrect_deleted_objects = False
83 self.fix_dn = False
84 self.fix_base64_userparameters = False
85 self.fix_utf8_userparameters = False
86 self.fix_doubled_userparameters = False
87 self.fix_sid_rid_set_conflict = False
88 self.reset_well_known_acls = reset_well_known_acls
89 self.reset_all_well_known_acls = False
90 self.in_transaction = in_transaction
91 self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
92 self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
93 self.schema_dn = samdb.get_schema_basedn()
94 self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
95 self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
96 self.class_schemaIDGUID = {}
97 self.wellknown_sds = get_wellknown_sds(self.samdb)
98 self.fix_all_missing_objectclass = False
99 self.fix_missing_deleted_objects = False
100 self.fix_replica_locations = False
101 self.fix_missing_rid_set_master = False
103 self.dn_set = set()
104 self.link_id_cache = {}
105 self.name_map = {}
106 try:
107 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE,
108 attrs=["objectSid"])
109 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
110 self.name_map['DnsAdmins'] = str(dnsadmins_sid)
111 except ldb.LdbError, (enum, estr):
112 if enum != ldb.ERR_NO_SUCH_OBJECT:
113 raise
114 pass
116 self.system_session_info = system_session()
117 self.admin_session_info = admin_session(None, samdb.get_domain_sid())
119 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
120 if "msDS-hasMasterNCs" in res[0]:
121 self.write_ncs = res[0]["msDS-hasMasterNCs"]
122 else:
123 # If the Forest Level is less than 2003 then there is no
124 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
125 # no need to merge as all the NCs that are in hasMasterNCs must
126 # also be in msDS-hasMasterNCs (but not the opposite)
127 if "hasMasterNCs" in res[0]:
128 self.write_ncs = res[0]["hasMasterNCs"]
129 else:
130 self.write_ncs = None
132 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
133 self.deleted_objects_containers = []
134 self.ncs_lacking_deleted_containers = []
135 self.dns_partitions = []
136 try:
137 self.ncs = res[0]["namingContexts"]
138 except KeyError:
139 pass
140 except IndexError:
141 pass
143 for nc in self.ncs:
144 try:
145 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc),
146 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
147 self.deleted_objects_containers.append(dn)
148 except KeyError:
149 self.ncs_lacking_deleted_containers.append(ldb.Dn(self.samdb, nc))
151 domaindns_zone = 'DC=DomainDnsZones,%s' % self.samdb.get_default_basedn()
152 forestdns_zone = 'DC=ForestDnsZones,%s' % self.samdb.get_root_basedn()
153 domain = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
154 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
155 base=self.samdb.get_partitions_dn(),
156 expression="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone)
157 if len(domain) == 1:
158 self.dns_partitions.append((ldb.Dn(self.samdb, forestdns_zone), domain[0]))
160 forest = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
161 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
162 base=self.samdb.get_partitions_dn(),
163 expression="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone)
164 if len(forest) == 1:
165 self.dns_partitions.append((ldb.Dn(self.samdb, domaindns_zone), forest[0]))
167 fsmo_dn = ldb.Dn(self.samdb, "CN=RID Manager$,CN=System," + self.samdb.domain_dn())
168 rid_master = get_fsmo_roleowner(self.samdb, fsmo_dn, "rid")
169 if ldb.Dn(self.samdb, self.samdb.get_dsServiceName()) == rid_master:
170 self.is_rid_master = True
171 else:
172 self.is_rid_master = False
174 # To get your rid set
175 # 1. Get server name
176 res = self.samdb.search(base=ldb.Dn(self.samdb, self.samdb.get_serverName()),
177 scope=ldb.SCOPE_BASE, attrs=["serverReference"])
178 # 2. Get server reference
179 self.server_ref_dn = ldb.Dn(self.samdb, res[0]['serverReference'][0])
181 # 3. Get RID Set
182 res = self.samdb.search(base=self.server_ref_dn,
183 scope=ldb.SCOPE_BASE, attrs=['rIDSetReferences'])
184 if "rIDSetReferences" in res[0]:
185 self.rid_set_dn = ldb.Dn(self.samdb, res[0]['rIDSetReferences'][0])
186 else:
187 self.rid_set_dn = None
189 self.compatibleFeatures = []
190 self.requiredFeatures = []
192 try:
193 res = self.samdb.search(scope=ldb.SCOPE_BASE,
194 base="@SAMBA_DSDB",
195 attrs=["compatibleFeatures",
196 "requiredFeatures"])
197 if "compatibleFeatures" in res[0]:
198 self.compatibleFeatures = res[0]["compatibleFeatures"]
199 if "requiredFeatures" in res[0]:
200 self.requiredFeatures = res[0]["requiredFeatures"]
201 except ldb.LdbError as (enum, estr):
202 if enum != ldb.ERR_NO_SUCH_OBJECT:
203 raise
204 pass
206 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
207 '''perform a database check, returning the number of errors found'''
208 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
209 self.report('Checking %u objects' % len(res))
210 error_count = 0
212 error_count += self.check_deleted_objects_containers()
214 self.attribute_or_class_ids = set()
216 for object in res:
217 self.dn_set.add(str(object.dn))
218 error_count += self.check_object(object.dn, attrs=attrs)
220 if DN is None:
221 error_count += self.check_rootdse()
223 if error_count != 0 and not self.fix:
224 self.report("Please use --fix to fix these errors")
226 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
227 return error_count
229 def check_deleted_objects_containers(self):
230 """This function only fixes conflicts on the Deleted Objects
231 containers, not the attributes"""
232 error_count = 0
233 for nc in self.ncs_lacking_deleted_containers:
234 if nc == self.schema_dn:
235 continue
236 error_count += 1
237 self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc)
238 if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'):
239 continue
241 dn = ldb.Dn(self.samdb, "CN=Deleted Objects")
242 dn.add_base(nc)
244 conflict_dn = None
245 try:
246 # If something already exists here, add a conflict
247 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[],
248 controls=["show_deleted:1", "extended_dn:1:1",
249 "show_recycled:1", "reveal_internals:0"])
250 if len(res) != 0:
251 guid = res[0].dn.get_extended_component("GUID")
252 conflict_dn = ldb.Dn(self.samdb,
253 "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid)))
254 conflict_dn.add_base(nc)
256 except ldb.LdbError, (enum, estr):
257 if enum == ldb.ERR_NO_SUCH_OBJECT:
258 pass
259 else:
260 self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr)
261 return 1
263 if conflict_dn is not None:
264 try:
265 self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"])
266 except ldb.LdbError, (enum, estr):
267 self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr))
268 return 1
270 # Refresh wellKnownObjects links
271 res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE,
272 attrs=['wellKnownObjects'],
273 controls=["show_deleted:1", "extended_dn:0",
274 "show_recycled:1", "reveal_internals:0"])
275 if len(res) != 1:
276 self.report("wellKnownObjects was not found for NC %s" % nc)
277 return 1
279 # Prevent duplicate deleted objects containers just in case
280 wko = res[0]["wellKnownObjects"]
281 listwko = []
282 proposed_objectguid = None
283 for o in wko:
284 dsdb_dn = dsdb_Dn(self.samdb, o, dsdb.DSDB_SYNTAX_BINARY_DN)
285 if self.is_deleted_objects_dn(dsdb_dn):
286 self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o)
287 # We really want to put this back in the same spot
288 # as the original one, so that on replication we
289 # merge, rather than conflict.
290 proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID")
291 listwko.append(o)
293 if proposed_objectguid is not None:
294 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid))
295 else:
296 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
297 listwko.append('%s:%s' % (wko_prefix, dn))
298 guid_suffix = ""
300 # Insert a brand new Deleted Objects container
301 self.samdb.add_ldif("""dn: %s
302 objectClass: top
303 objectClass: container
304 description: Container for deleted objects
305 isDeleted: TRUE
306 isCriticalSystemObject: TRUE
307 showInAdvancedViewOnly: TRUE
308 systemFlags: -1946157056%s""" % (dn, guid_suffix),
309 controls=["relax:0", "provision:0"])
311 delta = ldb.Message()
312 delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"]))
313 delta["wellKnownObjects"] = ldb.MessageElement(listwko,
314 ldb.FLAG_MOD_REPLACE,
315 "wellKnownObjects")
317 # Insert the link to the brand new container
318 if self.do_modify(delta, ["relax:0"],
319 "NC %s lacks Deleted Objects WKGUID" % nc,
320 validate=False):
321 self.report("Added %s well known guid link" % dn)
323 self.deleted_objects_containers.append(dn)
325 return error_count
327 def report(self, msg):
328 '''print a message unless quiet is set'''
329 if not self.quiet:
330 print(msg)
332 def confirm(self, msg, allow_all=False, forced=False):
333 '''confirm a change'''
334 if not self.fix:
335 return False
336 if self.quiet:
337 return self.yes
338 if self.yes:
339 forced = True
340 return common.confirm(msg, forced=forced, allow_all=allow_all)
342 ################################################################
343 # a local confirm function with support for 'all'
344 def confirm_all(self, msg, all_attr):
345 '''confirm a change with support for "all" '''
346 if not self.fix:
347 return False
348 if getattr(self, all_attr) == 'NONE':
349 return False
350 if getattr(self, all_attr) == 'ALL':
351 forced = True
352 else:
353 forced = self.yes
354 if self.quiet:
355 return forced
356 c = common.confirm(msg, forced=forced, allow_all=True)
357 if c == 'ALL':
358 setattr(self, all_attr, 'ALL')
359 return True
360 if c == 'NONE':
361 setattr(self, all_attr, 'NONE')
362 return False
363 return c
365 def do_delete(self, dn, controls, msg):
366 '''delete dn with optional verbose output'''
367 if self.verbose:
368 self.report("delete DN %s" % dn)
369 try:
370 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
371 self.samdb.delete(dn, controls=controls)
372 except Exception, err:
373 if self.in_transaction:
374 raise CommandError("%s : %s" % (msg, err))
375 self.report("%s : %s" % (msg, err))
376 return False
377 return True
379 def do_modify(self, m, controls, msg, validate=True):
380 '''perform a modify with optional verbose output'''
381 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
382 if self.verbose:
383 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
384 self.report("controls: %r" % controls)
385 try:
386 self.samdb.modify(m, controls=controls, validate=validate)
387 except Exception, err:
388 if self.in_transaction:
389 raise CommandError("%s : %s" % (msg, err))
390 self.report("%s : %s" % (msg, err))
391 return False
392 return True
394 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
395 '''perform a modify with optional verbose output'''
396 if self.verbose:
397 self.report("""dn: %s
398 changeType: modrdn
399 newrdn: %s
400 deleteOldRdn: 1
401 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
402 try:
403 to_dn = to_rdn + to_base
404 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
405 self.samdb.rename(from_dn, to_dn, controls=controls)
406 except Exception, err:
407 if self.in_transaction:
408 raise CommandError("%s : %s" % (msg, err))
409 self.report("%s : %s" % (msg, err))
410 return False
411 return True
413 def get_attr_linkID_and_reverse_name(self, attrname):
414 if attrname in self.link_id_cache:
415 return self.link_id_cache[attrname]
416 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
417 if linkID:
418 revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
419 else:
420 revname = None
421 self.link_id_cache[attrname] = (linkID, revname)
422 return linkID, revname
424 def err_empty_attribute(self, dn, attrname):
425 '''fix empty attributes'''
426 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
427 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
428 self.report("Not fixing empty attribute %s" % attrname)
429 return
431 m = ldb.Message()
432 m.dn = dn
433 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
434 if self.do_modify(m, ["relax:0", "show_recycled:1"],
435 "Failed to remove empty attribute %s" % attrname, validate=False):
436 self.report("Removed empty attribute %s" % attrname)
438 def err_normalise_mismatch(self, dn, attrname, values):
439 '''fix attribute normalisation errors'''
440 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
441 mod_list = []
442 for val in values:
443 normalised = self.samdb.dsdb_normalise_attributes(
444 self.samdb_schema, attrname, [val])
445 if len(normalised) != 1:
446 self.report("Unable to normalise value '%s'" % val)
447 mod_list.append((val, ''))
448 elif (normalised[0] != val):
449 self.report("value '%s' should be '%s'" % (val, normalised[0]))
450 mod_list.append((val, normalised[0]))
451 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
452 self.report("Not fixing attribute %s" % attrname)
453 return
455 m = ldb.Message()
456 m.dn = dn
457 for i in range(0, len(mod_list)):
458 (val, nval) = mod_list[i]
459 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
460 if nval != '':
461 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
462 attrname)
464 if self.do_modify(m, ["relax:0", "show_recycled:1"],
465 "Failed to normalise attribute %s" % attrname,
466 validate=False):
467 self.report("Normalised attribute %s" % attrname)
469 def err_normalise_mismatch_replace(self, dn, attrname, values):
470 '''fix attribute normalisation errors'''
471 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
472 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
473 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
474 if list(normalised) == values:
475 return
476 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
477 self.report("Not fixing attribute '%s'" % attrname)
478 return
480 m = ldb.Message()
481 m.dn = dn
482 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
484 if self.do_modify(m, ["relax:0", "show_recycled:1"],
485 "Failed to normalise attribute %s" % attrname,
486 validate=False):
487 self.report("Normalised attribute %s" % attrname)
489 def err_duplicate_values(self, dn, attrname, dup_values, values):
490 '''fix attribute normalisation errors'''
491 self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
492 self.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values), ','.join(values)))
493 if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
494 self.report("Not fixing attribute '%s'" % attrname)
495 return
497 m = ldb.Message()
498 m.dn = dn
499 m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
501 if self.do_modify(m, ["relax:0", "show_recycled:1"],
502 "Failed to remove duplicate value on attribute %s" % attrname,
503 validate=False):
504 self.report("Removed duplicate value on attribute %s" % attrname)
506 def is_deleted_objects_dn(self, dsdb_dn):
507 '''see if a dsdb_Dn is the special Deleted Objects DN'''
508 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
510 def err_missing_objectclass(self, dn):
511 """handle object without objectclass"""
512 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)))
513 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'):
514 self.report("Not deleting object with missing objectclass '%s'" % dn)
515 return
516 if self.do_delete(dn, ["relax:0"],
517 "Failed to remove DN %s" % dn):
518 self.report("Removed DN %s" % dn)
520 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False):
521 """handle a DN pointing to a deleted object"""
522 if not remove_plausible:
523 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
524 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
525 if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
526 self.report("Not removing")
527 return
528 else:
529 self.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
530 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
531 if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
532 self.report("Not removing")
533 return
535 m = ldb.Message()
536 m.dn = dn
537 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
538 if self.do_modify(m, ["show_recycled:1",
539 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
540 "Failed to remove deleted DN attribute %s" % attrname):
541 self.report("Removed deleted DN on attribute %s" % attrname)
543 def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
544 """handle a missing target DN (if specified, GUID form can't be found,
545 and otherwise DN string form can't be found)"""
546 # check if its a backlink
547 linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
548 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
550 linkID, reverse_link_name \
551 = self.get_attr_linkID_and_reverse_name(attrname)
552 if reverse_link_name is not None:
553 self.report("WARNING: no target object found for GUID "
554 "component for one-way forward link "
555 "%s in object "
556 "%s - %s" % (attrname, dn, val))
557 self.report("Not removing dangling forward link")
558 return 0
560 nc_root = self.samdb.get_nc_root(dn)
561 target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn)
562 if nc_root != target_nc_root:
563 # We don't bump the error count as Samba produces these
564 # in normal operation
565 self.report("WARNING: no target object found for GUID "
566 "component for cross-partition link "
567 "%s in object "
568 "%s - %s" % (attrname, dn, val))
569 self.report("Not removing dangling one-way "
570 "cross-partition link "
571 "(we might be mid-replication)")
572 return 0
574 # Due to our link handling one-way links pointing to
575 # missing objects are plausible.
577 # We don't bump the error count as Samba produces these
578 # in normal operation
579 self.report("WARNING: no target object found for GUID "
580 "component for DN value %s in object "
581 "%s - %s" % (attrname, dn, val))
582 self.err_deleted_dn(dn, attrname, val,
583 dsdb_dn, dsdb_dn, True)
584 return 0
586 # We bump the error count here, as we should have deleted this
587 self.report("ERROR: no target object found for GUID "
588 "component for link %s in object "
589 "%s - %s" % (attrname, dn, val))
590 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
591 return 1
593 def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
594 """handle a missing GUID extended DN component"""
595 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
596 controls=["extended_dn:1:1", "show_recycled:1"]
597 try:
598 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
599 attrs=[], controls=controls)
600 except ldb.LdbError, (enum, estr):
601 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
602 if enum != ldb.ERR_NO_SUCH_OBJECT:
603 raise
604 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
605 return
606 if len(res) == 0:
607 self.report("unable to find object for DN %s" % dsdb_dn.dn)
608 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
609 return
610 dsdb_dn.dn = res[0].dn
612 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
613 self.report("Not fixing %s" % errstr)
614 return
615 m = ldb.Message()
616 m.dn = dn
617 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
618 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
620 if self.do_modify(m, ["show_recycled:1"],
621 "Failed to fix %s on attribute %s" % (errstr, attrname)):
622 self.report("Fixed %s on attribute %s" % (errstr, attrname))
624 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
625 """handle an incorrect binary DN component"""
626 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
627 controls=["extended_dn:1:1", "show_recycled:1"]
629 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
630 self.report("Not fixing %s" % errstr)
631 return
632 m = ldb.Message()
633 m.dn = dn
634 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
635 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
637 if self.do_modify(m, ["show_recycled:1"],
638 "Failed to fix %s on attribute %s" % (errstr, attrname)):
639 self.report("Fixed %s on attribute %s" % (errstr, attrname))
641 def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn):
642 """handle a DN string being incorrect"""
643 self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val))
644 dsdb_dn.dn = correct_dn
646 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
647 'fix_all_old_dn_string_component_mismatch'):
648 self.report("Not fixing old string component")
649 return
650 m = ldb.Message()
651 m.dn = dn
652 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
653 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
654 if self.do_modify(m, ["show_recycled:1",
655 "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME],
656 "Failed to fix old DN string on attribute %s" % (attrname)):
657 self.report("Fixed old DN string on attribute %s" % (attrname))
659 def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
660 """handle a DN string being incorrect"""
661 self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
662 dsdb_dn.dn = correct_dn
664 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
665 'fix_all_%s_dn_component_mismatch' % mismatch_type):
666 self.report("Not fixing %s component mismatch" % mismatch_type)
667 return
668 m = ldb.Message()
669 m.dn = dn
670 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
671 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
672 if self.do_modify(m, ["show_recycled:1"],
673 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
674 self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
676 def err_unknown_attribute(self, obj, attrname):
677 '''handle an unknown attribute error'''
678 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
679 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
680 self.report("Not removing %s" % attrname)
681 return
682 m = ldb.Message()
683 m.dn = obj.dn
684 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
685 if self.do_modify(m, ["relax:0", "show_recycled:1"],
686 "Failed to remove unknown attribute %s" % attrname):
687 self.report("Removed unknown attribute %s" % (attrname))
689 def err_undead_linked_attribute(self, obj, attrname, val):
690 '''handle a link that should not be there on a deleted object'''
691 self.report("ERROR: linked attribute '%s' to '%s' is present on "
692 "deleted object %s" % (attrname, val, obj.dn))
693 if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
694 self.report("Not removing linked attribute %s" % attrname)
695 return
696 m = ldb.Message()
697 m.dn = obj.dn
698 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
700 if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
701 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
702 "Failed to delete forward link %s" % attrname):
703 self.report("Fixed undead forward link %s" % (attrname))
705 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
706 '''handle a missing backlink value'''
707 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
708 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
709 self.report("Not fixing missing backlink %s" % backlink_name)
710 return
711 m = ldb.Message()
712 m.dn = target_dn
713 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
714 if self.do_modify(m, ["show_recycled:1", "relax:0"],
715 "Failed to fix missing backlink %s" % backlink_name):
716 self.report("Fixed missing backlink %s" % (backlink_name))
718 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
719 '''handle a incorrect RMD_FLAGS value'''
720 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
721 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()))
722 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
723 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
724 return
725 m = ldb.Message()
726 m.dn = obj.dn
727 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
728 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
729 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
730 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
732 def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val,
733 target_dn, forward_attr, forward_syntax,
734 check_duplicates=True):
735 '''handle a orphaned backlink value'''
736 if check_duplicates is True and self.has_duplicate_links(target_dn, forward_attr, forward_syntax):
737 self.report("WARNING: Keep orphaned backlink attribute " + \
738 "'%s' in '%s' for link '%s' in '%s'" % (
739 backlink_attr, obj_dn, forward_attr, target_dn))
740 return
741 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn))
742 if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'):
743 self.report("Not removing orphaned backlink %s" % backlink_attr)
744 return
745 m = ldb.Message()
746 m.dn = obj_dn
747 m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr)
748 if self.do_modify(m, ["show_recycled:1", "relax:0"],
749 "Failed to fix orphaned backlink %s" % backlink_attr):
750 self.report("Fixed orphaned backlink %s" % (backlink_attr))
752 def err_recover_forward_links(self, obj, forward_attr, forward_vals):
753 '''handle a duplicate links value'''
755 self.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn))
757 if not self.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'):
758 self.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
759 forward_attr, obj.dn))
760 return
761 m = ldb.Message()
762 m.dn = obj.dn
763 m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr)
764 if self.do_modify(m, ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS],
765 "Failed to fix duplicate links in attribute '%s'" % forward_attr):
766 self.report("Fixed duplicate links in attribute '%s'" % (forward_attr))
767 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
768 assert duplicate_cache_key in self.duplicate_link_cache
769 self.duplicate_link_cache[duplicate_cache_key] = False
771 def err_no_fsmoRoleOwner(self, obj):
772 '''handle a missing fSMORoleOwner'''
773 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
774 res = self.samdb.search("",
775 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
776 assert len(res) == 1
777 serviceName = res[0]["dsServiceName"][0]
778 if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
779 self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
780 return
781 m = ldb.Message()
782 m.dn = obj.dn
783 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
784 if self.do_modify(m, [],
785 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
786 self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
788 def err_missing_parent(self, obj):
789 '''handle a missing parent'''
790 self.report("ERROR: parent object not found for %s" % (obj.dn))
791 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
792 self.report('Not moving object %s into LostAndFound' % (obj.dn))
793 return
795 keep_transaction = False
796 self.samdb.transaction_start()
797 try:
798 nc_root = self.samdb.get_nc_root(obj.dn);
799 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
800 new_dn = ldb.Dn(self.samdb, str(obj.dn))
801 new_dn.remove_base_components(len(new_dn) - 1)
802 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
803 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
804 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
806 m = ldb.Message()
807 m.dn = obj.dn
808 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
810 if self.do_modify(m, [],
811 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
812 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
813 keep_transaction = True
814 except:
815 self.samdb.transaction_cancel()
816 raise
818 if keep_transaction:
819 self.samdb.transaction_commit()
820 else:
821 self.samdb.transaction_cancel()
823 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
824 '''handle a wrong dn'''
826 new_rdn = ldb.Dn(self.samdb, str(new_dn))
827 new_rdn.remove_base_components(len(new_rdn) - 1)
828 new_parent = new_dn.parent()
830 attributes = ""
831 if rdn_val != name_val:
832 attributes += "%s=%r " % (rdn_attr, rdn_val)
833 attributes += "name=%r" % (name_val)
835 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
836 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
837 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
838 return
840 if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
841 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
842 self.report("Renamed %s into %s" % (obj.dn, new_dn))
844 def err_wrong_instancetype(self, obj, calculated_instancetype):
845 '''handle a wrong instanceType'''
846 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
847 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
848 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
849 return
851 m = ldb.Message()
852 m.dn = obj.dn
853 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
854 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
855 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
856 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
858 def err_short_userParameters(self, obj, attrname, value):
859 # This is a truncated userParameters due to a pre 4.1 replication bug
860 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)))
862 def err_base64_userParameters(self, obj, attrname, value):
863 '''handle a wrong userParameters'''
864 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
865 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
866 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
867 return
869 m = ldb.Message()
870 m.dn = obj.dn
871 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
872 if self.do_modify(m, [],
873 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
874 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
876 def err_utf8_userParameters(self, obj, attrname, value):
877 '''handle a wrong userParameters'''
878 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
879 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
880 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
881 return
883 m = ldb.Message()
884 m.dn = obj.dn
885 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
886 ldb.FLAG_MOD_REPLACE, 'userParameters')
887 if self.do_modify(m, [],
888 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
889 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
891 def err_doubled_userParameters(self, obj, attrname, value):
892 '''handle a wrong userParameters'''
893 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
894 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
895 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
896 return
898 m = ldb.Message()
899 m.dn = obj.dn
900 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
901 ldb.FLAG_MOD_REPLACE, 'userParameters')
902 if self.do_modify(m, [],
903 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
904 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
906 def err_odd_userParameters(self, obj, attrname):
907 # This is a truncated userParameters due to a pre 4.1 replication bug
908 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)))
910 def find_revealed_link(self, dn, attrname, guid):
911 '''return a revealed link in an object'''
912 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
913 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
914 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
915 for val in res[0][attrname]:
916 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
917 guid2 = dsdb_dn.dn.get_extended_component("GUID")
918 if guid == guid2:
919 return dsdb_dn
920 return None
922 def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr):
923 '''check a linked values for duplicate forward links'''
924 error_count = 0
926 duplicate_dict = dict()
927 unique_dict = dict()
929 # Only forward links can have this problem
930 if forward_linkID & 1:
931 # If we got the reverse, skip it
932 return (error_count, duplicate_dict, unique_dict)
934 if backlink_attr is None:
935 return (error_count, duplicate_dict, unique_dict)
937 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
938 if duplicate_cache_key not in self.duplicate_link_cache:
939 self.duplicate_link_cache[duplicate_cache_key] = False
941 for val in obj[forward_attr]:
942 dsdb_dn = dsdb_Dn(self.samdb, val, forward_syntax)
944 # all DNs should have a GUID component
945 guid = dsdb_dn.dn.get_extended_component("GUID")
946 if guid is None:
947 continue
948 guidstr = str(misc.GUID(guid))
949 keystr = guidstr + dsdb_dn.prefix
950 if keystr not in unique_dict:
951 unique_dict[keystr] = dsdb_dn
952 continue
953 error_count += 1
954 if keystr not in duplicate_dict:
955 duplicate_dict[keystr] = dict()
956 duplicate_dict[keystr]["keep"] = None
957 duplicate_dict[keystr]["delete"] = list()
959 # Now check for the highest RMD_VERSION
960 v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION"))
961 v2 = int(dsdb_dn.dn.get_extended_component("RMD_VERSION"))
962 if v1 > v2:
963 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
964 duplicate_dict[keystr]["delete"].append(dsdb_dn)
965 continue
966 if v1 < v2:
967 duplicate_dict[keystr]["keep"] = dsdb_dn
968 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
969 unique_dict[keystr] = dsdb_dn
970 continue
971 # Fallback to the highest RMD_LOCAL_USN
972 u1 = int(unique_dict[keystr].dn.get_extended_component("RMD_LOCAL_USN"))
973 u2 = int(dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN"))
974 if u1 >= u2:
975 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
976 duplicate_dict[keystr]["delete"].append(dsdb_dn)
977 continue
978 duplicate_dict[keystr]["keep"] = dsdb_dn
979 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
980 unique_dict[keystr] = dsdb_dn
982 if error_count != 0:
983 self.duplicate_link_cache[duplicate_cache_key] = True
985 return (error_count, duplicate_dict, unique_dict)
987 def has_duplicate_links(self, dn, forward_attr, forward_syntax):
988 '''check a linked values for duplicate forward links'''
989 error_count = 0
991 duplicate_cache_key = "%s:%s" % (str(dn), forward_attr)
992 if duplicate_cache_key in self.duplicate_link_cache:
993 return self.duplicate_link_cache[duplicate_cache_key]
995 forward_linkID, backlink_attr = self.get_attr_linkID_and_reverse_name(forward_attr)
997 attrs = [forward_attr]
998 controls = ["extended_dn:1:1", "reveal_internals:0"]
1000 # check its the right GUID
1001 try:
1002 res = self.samdb.search(base=str(dn), scope=ldb.SCOPE_BASE,
1003 attrs=attrs, controls=controls)
1004 except ldb.LdbError, (enum, estr):
1005 if enum != ldb.ERR_NO_SUCH_OBJECT:
1006 raise
1008 return False
1010 obj = res[0]
1011 error_count, duplicate_dict, unique_dict = \
1012 self.check_duplicate_links(obj, forward_attr, forward_syntax, forward_linkID, backlink_attr)
1014 if duplicate_cache_key in self.duplicate_link_cache:
1015 return self.duplicate_link_cache[duplicate_cache_key]
1017 return False
1019 def find_missing_forward_links_from_backlinks(self, obj,
1020 forward_attr,
1021 forward_syntax,
1022 backlink_attr,
1023 forward_unique_dict):
1024 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1025 missing_forward_links = []
1026 error_count = 0
1028 if backlink_attr is None:
1029 return (missing_forward_links, error_count)
1031 if forward_syntax != ldb.SYNTAX_DN:
1032 self.report("Not checking for missing forward links for syntax: %s",
1033 forward_syntax)
1034 return (missing_forward_links, error_count)
1036 if "sortedLinks" in self.compatibleFeatures:
1037 self.report("Not checking for missing forward links because the db " + \
1038 "has the sortedLinks feature")
1039 return (missing_forward_links, error_count)
1041 try:
1042 obj_guid = obj['objectGUID'][0]
1043 obj_guid_str = str(ndr_unpack(misc.GUID, obj_guid))
1044 filter = "(%s=<GUID=%s>)" % (backlink_attr, obj_guid_str)
1046 res = self.samdb.search(expression=filter,
1047 scope=ldb.SCOPE_SUBTREE, attrs=["objectGUID"],
1048 controls=["extended_dn:1:1",
1049 "search_options:1:2",
1050 "paged_results:1:1000"])
1051 except ldb.LdbError, (enum, estr):
1052 raise
1054 for r in res:
1055 target_dn = dsdb_Dn(self.samdb, r.dn.extended_str(), forward_syntax)
1057 guid = target_dn.dn.get_extended_component("GUID")
1058 guidstr = str(misc.GUID(guid))
1059 if guidstr in forward_unique_dict:
1060 continue
1062 # A valid forward link looks like this:
1064 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1065 # <RMD_ADDTIME=131607546230000000>;
1066 # <RMD_CHANGETIME=131607546230000000>;
1067 # <RMD_FLAGS=0>;
1068 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1069 # <RMD_LOCAL_USN=3765>;
1070 # <RMD_ORIGINATING_USN=3765>;
1071 # <RMD_VERSION=1>;
1072 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1073 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1075 # Note that versions older than Samba 4.8 create
1076 # links with RMD_VERSION=0.
1078 # Try to get the local_usn and time from objectClass
1079 # if possible and fallback to any other one.
1080 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1081 obj['replPropertyMetadata'][0])
1082 for o in repl.ctr.array:
1083 local_usn = o.local_usn
1084 t = o.originating_change_time
1085 if o.attid == drsuapi.DRSUAPI_ATTID_objectClass:
1086 break
1088 # We use a magic invocationID for restoring missing
1089 # forward links to recover from bug #13228.
1090 # This should allow some more future magic to fix the
1091 # problem.
1093 # It also means it looses the conflict resolution
1094 # against almost every real invocation, if the
1095 # version is also 0.
1096 originating_invocid = misc.GUID("ffffffff-4700-4700-4700-000000b13228")
1097 originating_usn = 1
1099 rmd_addtime = t
1100 rmd_changetime = t
1101 rmd_flags = 0
1102 rmd_invocid = originating_invocid
1103 rmd_originating_usn = originating_usn
1104 rmd_local_usn = local_usn
1105 rmd_version = 0
1107 target_dn.dn.set_extended_component("RMD_ADDTIME", str(rmd_addtime))
1108 target_dn.dn.set_extended_component("RMD_CHANGETIME", str(rmd_changetime))
1109 target_dn.dn.set_extended_component("RMD_FLAGS", str(rmd_flags))
1110 target_dn.dn.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid))
1111 target_dn.dn.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn))
1112 target_dn.dn.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn))
1113 target_dn.dn.set_extended_component("RMD_VERSION", str(rmd_version))
1115 error_count += 1
1116 missing_forward_links.append(target_dn)
1118 return (missing_forward_links, error_count)
1120 def check_dn(self, obj, attrname, syntax_oid):
1121 '''check a DN attribute for correctness'''
1122 error_count = 0
1123 obj_guid = obj['objectGUID'][0]
1125 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1126 if reverse_link_name is not None:
1127 reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
1128 else:
1129 reverse_syntax_oid = None
1131 error_count, duplicate_dict, unique_dict = \
1132 self.check_duplicate_links(obj, attrname, syntax_oid, linkID, reverse_link_name)
1134 if len(duplicate_dict) != 0:
1136 missing_forward_links, missing_error_count = \
1137 self.find_missing_forward_links_from_backlinks(obj,
1138 attrname, syntax_oid,
1139 reverse_link_name,
1140 unique_dict)
1141 error_count += missing_error_count
1143 forward_links = [dn for dn in unique_dict.values()]
1145 if missing_error_count != 0:
1146 self.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1147 attrname, obj.dn))
1148 else:
1149 self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
1150 for m in missing_forward_links:
1151 self.report("Missing link '%s'" % (m))
1152 if not self.confirm_all("Schedule readding missing forward link for attribute %s" % attrname,
1153 'fix_all_missing_forward_links'):
1154 self.err_orphaned_backlink(m.dn, reverse_link_name,
1155 obj.dn.extended_str(), obj.dn,
1156 attrname, syntax_oid,
1157 check_duplicates=False)
1158 continue
1159 forward_links += [m]
1160 for keystr in duplicate_dict.keys():
1161 d = duplicate_dict[keystr]
1162 for dd in d["delete"]:
1163 self.report("Duplicate link '%s'" % dd)
1164 self.report("Correct link '%s'" % d["keep"])
1166 # We now construct the sorted dn values.
1167 # They're sorted by the objectGUID of the target
1168 # See dsdb_Dn.__cmp__()
1169 vals = [str(dn) for dn in sorted(forward_links)]
1170 self.err_recover_forward_links(obj, attrname, vals)
1171 # We should continue with the fixed values
1172 obj[attrname] = ldb.MessageElement(vals, 0, attrname)
1174 for val in obj[attrname]:
1175 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
1177 # all DNs should have a GUID component
1178 guid = dsdb_dn.dn.get_extended_component("GUID")
1179 if guid is None:
1180 error_count += 1
1181 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
1182 "missing GUID")
1183 continue
1185 guidstr = str(misc.GUID(guid))
1186 attrs = ['isDeleted', 'replPropertyMetaData']
1188 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
1189 fixing_msDS_HasInstantiatedNCs = True
1190 attrs.append("instanceType")
1191 else:
1192 fixing_msDS_HasInstantiatedNCs = False
1194 if reverse_link_name is not None:
1195 attrs.append(reverse_link_name)
1197 # check its the right GUID
1198 try:
1199 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
1200 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
1201 "reveal_internals:0"
1203 except ldb.LdbError, (enum, estr):
1204 if enum != ldb.ERR_NO_SUCH_OBJECT:
1205 raise
1207 # We don't always want to
1208 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
1209 attrname,
1210 val,
1211 dsdb_dn)
1212 continue
1214 if fixing_msDS_HasInstantiatedNCs:
1215 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
1216 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
1218 if str(dsdb_dn) != val:
1219 error_count +=1
1220 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
1221 continue
1223 # now we have two cases - the source object might or might not be deleted
1224 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1225 target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
1228 if is_deleted and not obj.dn in self.deleted_objects_containers and linkID:
1229 # A fully deleted object should not have any linked
1230 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1231 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1232 # Requirements)
1233 self.err_undead_linked_attribute(obj, attrname, val)
1234 error_count += 1
1235 continue
1236 elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
1237 # the target DN is not allowed to be deleted, unless the target DN is the
1238 # special Deleted Objects container
1239 error_count += 1
1240 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
1241 if local_usn:
1242 if 'replPropertyMetaData' in res[0]:
1243 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1244 str(res[0]['replPropertyMetadata']))
1245 found_data = False
1246 for o in repl.ctr.array:
1247 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1248 deleted_usn = o.local_usn
1249 if deleted_usn >= int(local_usn):
1250 # If the object was deleted after the link
1251 # was last modified then, clean it up here
1252 found_data = True
1253 break
1255 if found_data:
1256 self.err_deleted_dn(obj.dn, attrname,
1257 val, dsdb_dn, res[0].dn, True)
1258 continue
1260 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
1261 continue
1263 # We should not check for incorrect
1264 # components on deleted links, as these are allowed to
1265 # go stale (we just need the GUID, not the name)
1266 rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
1267 rmd_flags = 0
1268 if rmd_blob is not None:
1269 rmd_flags = int(rmd_blob)
1271 # assert the DN matches in string form, where a reverse
1272 # link exists, otherwise (below) offer to fix it as a non-error.
1273 # The string form is essentially only kept for forensics,
1274 # as we always re-resolve by GUID in normal operations.
1275 if not rmd_flags & 1 and reverse_link_name is not None:
1276 if str(res[0].dn) != str(dsdb_dn.dn):
1277 error_count += 1
1278 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1279 res[0].dn, "string")
1280 continue
1282 if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
1283 error_count += 1
1284 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1285 res[0].dn, "GUID")
1286 continue
1288 if res[0].dn.get_extended_component("SID") != dsdb_dn.dn.get_extended_component("SID"):
1289 error_count += 1
1290 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1291 res[0].dn, "SID")
1292 continue
1294 # Only for non-links, not even forward-only links
1295 # (otherwise this breaks repl_meta_data):
1297 # Now we have checked the GUID and SID, offer to fix old
1298 # DN strings as a non-error (DNs, not links so no
1299 # backlink). Samba does not maintain this string
1300 # otherwise, so we don't increment error_count.
1301 if reverse_link_name is None:
1302 if linkID == 0 and str(res[0].dn) != str(dsdb_dn.dn):
1303 # Pass in the old/bad DN without the <GUID=...> part,
1304 # otherwise the LDB code will correct it on the way through
1305 # (Note: we still want to preserve the DSDB DN prefix in the
1306 # case of binary DNs)
1307 bad_dn = dsdb_dn.prefix + dsdb_dn.dn.get_linearized()
1308 self.err_dn_string_component_old(obj.dn, attrname, bad_dn,
1309 dsdb_dn, res[0].dn)
1310 continue
1312 # check the reverse_link is correct if there should be one
1313 match_count = 0
1314 if reverse_link_name in res[0]:
1315 for v in res[0][reverse_link_name]:
1316 v_dn = dsdb_Dn(self.samdb, v)
1317 v_guid = v_dn.dn.get_extended_component("GUID")
1318 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1319 v_rmd_flags = 0
1320 if v_blob is not None:
1321 v_rmd_flags = int(v_blob)
1322 if v_rmd_flags & 1:
1323 continue
1324 if v_guid == obj_guid:
1325 match_count += 1
1327 if match_count != 1:
1328 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1329 if not linkID & 1:
1330 # Forward binary multi-valued linked attribute
1331 forward_count = 0
1332 for w in obj[attrname]:
1333 w_guid = dsdb_Dn(self.samdb, w).dn.get_extended_component("GUID")
1334 if w_guid == guid:
1335 forward_count += 1
1337 if match_count == forward_count:
1338 continue
1339 expected_count = 0
1340 for v in obj[attrname]:
1341 v_dn = dsdb_Dn(self.samdb, v)
1342 v_guid = v_dn.dn.get_extended_component("GUID")
1343 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1344 v_rmd_flags = 0
1345 if v_blob is not None:
1346 v_rmd_flags = int(v_blob)
1347 if v_rmd_flags & 1:
1348 continue
1349 if v_guid == guid:
1350 expected_count += 1
1352 if match_count == expected_count:
1353 continue
1355 diff_count = expected_count - match_count
1357 if linkID & 1:
1358 # If there's a backward link on binary multi-valued linked attribute,
1359 # let the check on the forward link remedy the value.
1360 # UNLESS, there is no forward link detected.
1361 if match_count == 0:
1362 error_count += 1
1363 self.err_orphaned_backlink(obj.dn, attrname,
1364 val, dsdb_dn.dn,
1365 reverse_link_name,
1366 reverse_syntax_oid)
1367 continue
1368 # Only warn here and let the forward link logic fix it.
1369 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1370 attrname, expected_count, str(obj.dn),
1371 reverse_link_name, match_count, str(dsdb_dn.dn)))
1372 continue
1374 assert not target_is_deleted
1376 self.report("ERROR: Link (forward) 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)))
1380 # Loop until the difference between the forward and
1381 # the backward links is resolved.
1382 while diff_count != 0:
1383 error_count += 1
1384 if diff_count > 0:
1385 if match_count > 0 or diff_count > 1:
1386 # TODO no method to fix these right now
1387 self.report("ERROR: Can't fix missing "
1388 "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1389 break
1390 self.err_missing_backlink(obj, attrname,
1391 obj.dn.extended_str(),
1392 reverse_link_name,
1393 dsdb_dn.dn)
1394 diff_count -= 1
1395 else:
1396 self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1397 obj.dn.extended_str(), obj.dn,
1398 attrname, syntax_oid)
1399 diff_count += 1
1402 return error_count
1405 def get_originating_time(self, val, attid):
1406 '''Read metadata properties and return the originating time for
1407 a given attributeId.
1409 :return: the originating time or 0 if not found
1412 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1413 obj = repl.ctr
1415 for o in repl.ctr.array:
1416 if o.attid == attid:
1417 return o.originating_change_time
1419 return 0
1421 def process_metadata(self, dn, val):
1422 '''Read metadata properties and list attributes in it.
1423 raises KeyError if the attid is unknown.'''
1425 set_att = set()
1426 wrong_attids = set()
1427 list_attid = []
1428 in_schema_nc = dn.is_child_of(self.schema_dn)
1430 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1431 obj = repl.ctr
1433 for o in repl.ctr.array:
1434 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1435 set_att.add(att.lower())
1436 list_attid.append(o.attid)
1437 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1438 is_schema_nc=in_schema_nc)
1439 if correct_attid != o.attid:
1440 wrong_attids.add(o.attid)
1442 return (set_att, list_attid, wrong_attids)
1445 def fix_metadata(self, obj, attr):
1446 '''re-write replPropertyMetaData elements for a single attribute for a
1447 object. This is used to fix missing replPropertyMetaData elements'''
1448 guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1449 dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1450 res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
1451 controls = ["search_options:1:2",
1452 "show_recycled:1"])
1453 msg = res[0]
1454 nmsg = ldb.Message()
1455 nmsg.dn = dn
1456 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1457 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1458 "Failed to fix metadata for attribute %s" % attr):
1459 self.report("Fixed metadata for attribute %s" % attr)
1461 def ace_get_effective_inherited_type(self, ace):
1462 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1463 return None
1465 check = False
1466 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1467 check = True
1468 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1469 check = True
1470 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1471 check = True
1472 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1473 check = True
1475 if not check:
1476 return None
1478 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1479 return None
1481 return str(ace.object.inherited_type)
1483 def lookup_class_schemaIDGUID(self, cls):
1484 if cls in self.class_schemaIDGUID:
1485 return self.class_schemaIDGUID[cls]
1487 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1488 res = self.samdb.search(base=self.schema_dn,
1489 expression=flt,
1490 attrs=["schemaIDGUID"])
1491 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1493 self.class_schemaIDGUID[cls] = t
1494 return t
1496 def process_sd(self, dn, obj):
1497 sd_attr = "nTSecurityDescriptor"
1498 sd_val = obj[sd_attr]
1500 sd = ndr_unpack(security.descriptor, str(sd_val))
1502 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1503 if is_deleted:
1504 # we don't fix deleted objects
1505 return (sd, None)
1507 sd_clean = security.descriptor()
1508 sd_clean.owner_sid = sd.owner_sid
1509 sd_clean.group_sid = sd.group_sid
1510 sd_clean.type = sd.type
1511 sd_clean.revision = sd.revision
1513 broken = False
1514 last_inherited_type = None
1516 aces = []
1517 if sd.sacl is not None:
1518 aces = sd.sacl.aces
1519 for i in range(0, len(aces)):
1520 ace = aces[i]
1522 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1523 sd_clean.sacl_add(ace)
1524 continue
1526 t = self.ace_get_effective_inherited_type(ace)
1527 if t is None:
1528 continue
1530 if last_inherited_type is not None:
1531 if t != last_inherited_type:
1532 # if it inherited from more than
1533 # one type it's very likely to be broken
1535 # If not the recalculation will calculate
1536 # the same result.
1537 broken = True
1538 continue
1540 last_inherited_type = t
1542 aces = []
1543 if sd.dacl is not None:
1544 aces = sd.dacl.aces
1545 for i in range(0, len(aces)):
1546 ace = aces[i]
1548 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1549 sd_clean.dacl_add(ace)
1550 continue
1552 t = self.ace_get_effective_inherited_type(ace)
1553 if t is None:
1554 continue
1556 if last_inherited_type is not None:
1557 if t != last_inherited_type:
1558 # if it inherited from more than
1559 # one type it's very likely to be broken
1561 # If not the recalculation will calculate
1562 # the same result.
1563 broken = True
1564 continue
1566 last_inherited_type = t
1568 if broken:
1569 return (sd_clean, sd)
1571 if last_inherited_type is None:
1572 # ok
1573 return (sd, None)
1575 cls = None
1576 try:
1577 cls = obj["objectClass"][-1]
1578 except KeyError, e:
1579 pass
1581 if cls is None:
1582 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1583 attrs=["isDeleted", "objectClass"],
1584 controls=["show_recycled:1"])
1585 o = res[0]
1586 is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
1587 if is_deleted:
1588 # we don't fix deleted objects
1589 return (sd, None)
1590 cls = o["objectClass"][-1]
1592 t = self.lookup_class_schemaIDGUID(cls)
1594 if t != last_inherited_type:
1595 # broken
1596 return (sd_clean, sd)
1598 # ok
1599 return (sd, None)
1601 def err_wrong_sd(self, dn, sd, sd_broken):
1602 '''re-write the SD due to incorrect inherited ACEs'''
1603 sd_attr = "nTSecurityDescriptor"
1604 sd_val = ndr_pack(sd)
1605 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1607 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1608 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1609 return
1611 nmsg = ldb.Message()
1612 nmsg.dn = dn
1613 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1614 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1615 "Failed to fix attribute %s" % sd_attr):
1616 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1618 def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1619 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1620 sd_attr = "nTSecurityDescriptor"
1621 sd_val = ndr_pack(sd)
1622 sd_old_val = ndr_pack(sd_old)
1623 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1624 if sd.owner_sid is not None:
1625 sd_flags |= security.SECINFO_OWNER
1626 if sd.group_sid is not None:
1627 sd_flags |= security.SECINFO_GROUP
1629 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1630 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1631 return
1633 m = ldb.Message()
1634 m.dn = dn
1635 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1636 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1637 "Failed to reset attribute %s" % sd_attr):
1638 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1640 def err_missing_sd_owner(self, dn, sd):
1641 '''re-write the SD due to a missing owner or group'''
1642 sd_attr = "nTSecurityDescriptor"
1643 sd_val = ndr_pack(sd)
1644 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1646 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1647 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1648 return
1650 nmsg = ldb.Message()
1651 nmsg.dn = dn
1652 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1654 # By setting the session_info to admin_session_info and
1655 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1656 # flags we cause the descriptor module to set the correct
1657 # owner and group on the SD, replacing the None/NULL values
1658 # for owner_sid and group_sid currently present.
1660 # The admin_session_info matches that used in provision, and
1661 # is the best guess we can make for an existing object that
1662 # hasn't had something specifically set.
1664 # This is important for the dns related naming contexts.
1665 self.samdb.set_session_info(self.admin_session_info)
1666 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1667 "Failed to fix metadata for attribute %s" % sd_attr):
1668 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1669 self.samdb.set_session_info(self.system_session_info)
1672 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1673 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1674 str(repl_meta_data))
1675 ctr = repl.ctr
1676 found = False
1677 for o in ctr.array:
1678 # Search for a zero invocationID
1679 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1680 continue
1682 found = True
1683 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1684 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1685 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1686 % (dn, o.attid, o.version,
1687 time.ctime(samba.nttime2unix(o.originating_change_time)),
1688 self.samdb.get_invocation_id()))
1690 return found
1693 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1694 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1695 str(repl_meta_data))
1696 ctr = repl.ctr
1697 now = samba.unix2nttime(int(time.time()))
1698 found = False
1699 for o in ctr.array:
1700 # Search for a zero invocationID
1701 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1702 continue
1704 found = True
1705 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1706 o.version = o.version + 1
1707 o.originating_change_time = now
1708 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1709 o.originating_usn = seq
1710 o.local_usn = seq
1712 if found:
1713 replBlob = ndr_pack(repl)
1714 msg = ldb.Message()
1715 msg.dn = dn
1717 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1718 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1719 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1720 return
1722 nmsg = ldb.Message()
1723 nmsg.dn = dn
1724 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1725 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1726 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1727 "Failed to fix attribute %s" % attr):
1728 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1731 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1732 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1733 str(repl_meta_data))
1734 ctr = repl.ctr
1735 for o in ctr.array:
1736 # Search for an invalid attid
1737 try:
1738 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1739 except KeyError:
1740 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1741 return
1744 def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1745 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1746 str(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))
1840 def is_deleted_deleted_objects(self, obj):
1841 faulty = False
1842 if "description" not in obj:
1843 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1844 faulty = True
1845 if "showInAdvancedViewOnly" not in obj or obj['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1846 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1847 faulty = True
1848 if "objectCategory" not in obj:
1849 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1850 faulty = True
1851 if "isCriticalSystemObject" not in obj or obj['isCriticalSystemObject'][0].upper() == 'FALSE':
1852 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1853 faulty = True
1854 if "isRecycled" in obj:
1855 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1856 faulty = True
1857 if "isDeleted" in obj and obj['isDeleted'][0].upper() == 'FALSE':
1858 self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1859 faulty = True
1860 if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1861 obj['objectClass'][0] != 'top' or
1862 obj['objectClass'][1] != 'container'):
1863 self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1864 faulty = True
1865 if "systemFlags" not in obj or obj['systemFlags'][0] != '-1946157056':
1866 self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1867 faulty = True
1868 return faulty
1870 def err_deleted_deleted_objects(self, obj):
1871 nmsg = ldb.Message()
1872 nmsg.dn = dn = obj.dn
1874 if "description" not in obj:
1875 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1876 if "showInAdvancedViewOnly" not in obj:
1877 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1878 if "objectCategory" not in obj:
1879 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1880 if "isCriticalSystemObject" not in obj:
1881 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1882 if "isRecycled" in obj:
1883 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1885 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1886 nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1887 nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1889 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1890 % (dn), 'fix_deleted_deleted_objects'):
1891 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1892 return
1894 if self.do_modify(nmsg, ["relax:0"],
1895 "Failed to fix Deleted Objects container %s" % dn):
1896 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1898 def err_replica_locations(self, obj, cross_ref, attr):
1899 nmsg = ldb.Message()
1900 nmsg.dn = cross_ref
1901 target = self.samdb.get_dsServiceName()
1903 if self.samdb.am_rodc():
1904 self.report('Not fixing %s for the RODC' % (attr, obj.dn))
1905 return
1907 if not self.confirm_all('Add yourself to the replica locations for %s?'
1908 % (obj.dn), 'fix_replica_locations'):
1909 self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1910 return
1912 nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1913 if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1914 self.report("Fixed %s for %s" % (attr, obj.dn))
1916 def is_fsmo_role(self, dn):
1917 if dn == self.samdb.domain_dn:
1918 return True
1919 if dn == self.infrastructure_dn:
1920 return True
1921 if dn == self.naming_dn:
1922 return True
1923 if dn == self.schema_dn:
1924 return True
1925 if dn == self.rid_dn:
1926 return True
1928 return False
1930 def calculate_instancetype(self, dn):
1931 instancetype = 0
1932 nc_root = self.samdb.get_nc_root(dn)
1933 if dn == nc_root:
1934 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1935 try:
1936 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1937 except ldb.LdbError, (enum, estr):
1938 if enum != ldb.ERR_NO_SUCH_OBJECT:
1939 raise
1940 else:
1941 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1943 if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1944 instancetype |= dsdb.INSTANCE_TYPE_WRITE
1946 return instancetype
1948 def get_wellknown_sd(self, dn):
1949 for [sd_dn, descriptor_fn] in self.wellknown_sds:
1950 if dn == sd_dn:
1951 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1952 return ndr_unpack(security.descriptor,
1953 descriptor_fn(domain_sid,
1954 name_map=self.name_map))
1956 raise KeyError
1958 def check_object(self, dn, attrs=['*']):
1959 '''check one object'''
1960 if self.verbose:
1961 self.report("Checking object %s" % dn)
1963 # If we modify the pass-by-reference attrs variable, then we get a
1964 # replPropertyMetadata for every object that we check.
1965 attrs = list(attrs)
1966 if "dn" in map(str.lower, attrs):
1967 attrs.append("name")
1968 if "distinguishedname" in map(str.lower, attrs):
1969 attrs.append("name")
1970 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1971 attrs.append("name")
1972 if 'name' in map(str.lower, attrs):
1973 attrs.append(dn.get_rdn_name())
1974 attrs.append("isDeleted")
1975 attrs.append("systemFlags")
1976 need_replPropertyMetaData = False
1977 if '*' in attrs:
1978 need_replPropertyMetaData = True
1979 else:
1980 for a in attrs:
1981 linkID, _ = self.get_attr_linkID_and_reverse_name(a)
1982 if linkID == 0:
1983 continue
1984 if linkID & 1:
1985 continue
1986 need_replPropertyMetaData = True
1987 break
1988 if need_replPropertyMetaData:
1989 attrs.append("replPropertyMetaData")
1990 attrs.append("objectGUID")
1992 try:
1993 sd_flags = 0
1994 sd_flags |= security.SECINFO_OWNER
1995 sd_flags |= security.SECINFO_GROUP
1996 sd_flags |= security.SECINFO_DACL
1997 sd_flags |= security.SECINFO_SACL
1999 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
2000 controls=[
2001 "extended_dn:1:1",
2002 "show_recycled:1",
2003 "show_deleted:1",
2004 "sd_flags:1:%d" % sd_flags,
2005 "reveal_internals:0",
2007 attrs=attrs)
2008 except ldb.LdbError, (enum, estr):
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
2035 object_rdn_attr = None
2036 object_rdn_val = None
2037 name_val = None
2038 isDeleted = False
2039 systemFlags = 0
2041 for attrname in obj:
2042 if attrname == 'dn' or attrname == "distinguishedName":
2043 continue
2045 if str(attrname).lower() == 'objectclass':
2046 got_objectclass = True
2048 if str(attrname).lower() == "name":
2049 if len(obj[attrname]) != 1:
2050 error_count += 1
2051 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2052 (len(obj[attrname]), attrname, str(obj.dn)))
2053 else:
2054 name_val = obj[attrname][0]
2056 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
2057 object_rdn_attr = attrname
2058 if len(obj[attrname]) != 1:
2059 error_count += 1
2060 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2061 (len(obj[attrname]), attrname, str(obj.dn)))
2062 else:
2063 object_rdn_val = obj[attrname][0]
2065 if str(attrname).lower() == 'isdeleted':
2066 if obj[attrname][0] != "FALSE":
2067 isDeleted = True
2069 if str(attrname).lower() == 'systemflags':
2070 systemFlags = int(obj[attrname][0])
2072 if str(attrname).lower() == 'replpropertymetadata':
2073 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
2074 error_count += 1
2075 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
2076 # We don't continue, as we may also have other fixes for this attribute
2077 # based on what other attributes we see.
2079 try:
2080 (set_attrs_from_md, list_attid_from_md, wrong_attids) \
2081 = self.process_metadata(dn, obj[attrname])
2082 except KeyError:
2083 error_count += 1
2084 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
2085 continue
2087 if len(set_attrs_from_md) < len(list_attid_from_md) \
2088 or len(wrong_attids) > 0 \
2089 or sorted(list_attid_from_md) != list_attid_from_md:
2090 error_count +=1
2091 self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname], wrong_attids)
2093 else:
2094 # Here we check that the first attid is 0
2095 # (objectClass).
2096 if list_attid_from_md[0] != 0:
2097 error_count += 1
2098 self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2099 (attrname, str(dn)))
2101 got_repl_property_meta_data = True
2102 continue
2104 if str(attrname).lower() == 'ntsecuritydescriptor':
2105 (sd, sd_broken) = self.process_sd(dn, obj)
2106 if sd_broken is not None:
2107 self.err_wrong_sd(dn, sd, sd_broken)
2108 error_count += 1
2109 continue
2111 if sd.owner_sid is None or sd.group_sid is None:
2112 self.err_missing_sd_owner(dn, sd)
2113 error_count += 1
2114 continue
2116 if self.reset_well_known_acls:
2117 try:
2118 well_known_sd = self.get_wellknown_sd(dn)
2119 except KeyError:
2120 continue
2122 current_sd = ndr_unpack(security.descriptor,
2123 str(obj[attrname][0]))
2125 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
2126 if diff != "":
2127 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
2128 error_count += 1
2129 continue
2130 continue
2132 if str(attrname).lower() == 'objectclass':
2133 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
2134 # Do not consider the attribute incorrect if:
2135 # - The sorted (alphabetically) list is the same, inclding case
2136 # - The first and last elements are the same
2138 # This avoids triggering an error due to
2139 # non-determinism in the sort routine in (at least)
2140 # 4.3 and earlier, and the fact that any AUX classes
2141 # in these attributes are also not sorted when
2142 # imported from Windows (they are just in the reverse
2143 # order of last set)
2144 if sorted(normalised) != sorted(obj[attrname]) \
2145 or normalised[0] != obj[attrname][0] \
2146 or normalised[-1] != obj[attrname][-1]:
2147 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
2148 error_count += 1
2149 continue
2151 if str(attrname).lower() == 'userparameters':
2152 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
2153 error_count += 1
2154 self.err_short_userParameters(obj, attrname, obj[attrname])
2155 continue
2157 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2158 # This is the correct, normal prefix
2159 continue
2161 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
2162 # this is the typical prefix from a windows migration
2163 error_count += 1
2164 self.err_base64_userParameters(obj, attrname, obj[attrname])
2165 continue
2167 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':
2168 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2169 error_count += 1
2170 self.err_utf8_userParameters(obj, attrname, obj[attrname])
2171 continue
2173 elif len(obj[attrname][0]) % 2 != 0:
2174 # This is a value that isn't even in length
2175 error_count += 1
2176 self.err_odd_userParameters(obj, attrname, obj[attrname])
2177 continue
2179 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':
2180 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2181 error_count += 1
2182 self.err_doubled_userParameters(obj, attrname, obj[attrname])
2183 continue
2185 if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2186 if obj[attrname][0] in self.attribute_or_class_ids:
2187 error_count += 1
2188 self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2189 % (attrname, obj.dn, obj[attrname][0]))
2190 else:
2191 self.attribute_or_class_ids.add(obj[attrname][0])
2193 # check for empty attributes
2194 for val in obj[attrname]:
2195 if val == '':
2196 self.err_empty_attribute(dn, attrname)
2197 error_count += 1
2198 continue
2200 # get the syntax oid for the attribute, so we can can have
2201 # special handling for some specific attribute types
2202 try:
2203 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2204 except Exception, msg:
2205 self.err_unknown_attribute(obj, attrname)
2206 error_count += 1
2207 continue
2209 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2211 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2212 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2213 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2214 and not linkID):
2215 set_attrs_seen.add(str(attrname).lower())
2217 if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2218 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
2219 # it's some form of DN, do specialised checking on those
2220 error_count += self.check_dn(obj, attrname, syntax_oid)
2221 else:
2223 values = set()
2224 # check for incorrectly normalised attributes
2225 for val in obj[attrname]:
2226 values.add(str(val))
2228 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2229 if len(normalised) != 1 or normalised[0] != val:
2230 self.err_normalise_mismatch(dn, attrname, obj[attrname])
2231 error_count += 1
2232 break
2234 if len(obj[attrname]) != len(values):
2235 self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2236 error_count += 1
2237 break
2239 if str(attrname).lower() == "instancetype":
2240 calculated_instancetype = self.calculate_instancetype(dn)
2241 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
2242 error_count += 1
2243 self.err_wrong_instancetype(obj, calculated_instancetype)
2245 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
2246 error_count += 1
2247 self.err_missing_objectclass(dn)
2249 if ("*" in attrs or "name" in map(str.lower, attrs)):
2250 if name_val is None:
2251 error_count += 1
2252 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2253 if object_rdn_attr is None:
2254 error_count += 1
2255 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2257 if name_val is not None:
2258 parent_dn = None
2259 if isDeleted:
2260 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2261 parent_dn = deleted_objects_dn
2262 if parent_dn is None:
2263 parent_dn = obj.dn.parent()
2264 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2265 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2267 if obj.dn == deleted_objects_dn:
2268 expected_dn = obj.dn
2270 if expected_dn != obj.dn:
2271 error_count += 1
2272 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
2273 elif obj.dn.get_rdn_value() != object_rdn_val:
2274 error_count += 1
2275 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
2277 show_dn = True
2278 if got_repl_property_meta_data:
2279 if obj.dn == deleted_objects_dn:
2280 isDeletedAttId = 131120
2281 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2283 expectedTimeDo = 2650466015990000000
2284 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
2285 if originating != expectedTimeDo:
2286 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2287 nmsg = ldb.Message()
2288 nmsg.dn = dn
2289 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2290 error_count += 1
2291 self.samdb.modify(nmsg, controls=["provision:0"])
2293 else:
2294 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2296 for att in set_attrs_seen.difference(set_attrs_from_md):
2297 if show_dn:
2298 self.report("On object %s" % dn)
2299 show_dn = False
2300 error_count += 1
2301 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2302 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2303 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2304 continue
2305 self.fix_metadata(obj, att)
2307 if self.is_fsmo_role(dn):
2308 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
2309 self.err_no_fsmoRoleOwner(obj)
2310 error_count += 1
2312 try:
2313 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2314 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2315 controls=["show_recycled:1", "show_deleted:1"])
2316 except ldb.LdbError, (enum, estr):
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()
2378 elif not self.samdb.am_rodc():
2379 self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2382 # Check some details of our own RID Set
2383 if dn == self.rid_set_dn:
2384 res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2385 attrs=["rIDAllocationPool",
2386 "rIDPreviousAllocationPool",
2387 "rIDUsedPool",
2388 "rIDNextRID"])
2389 if "rIDAllocationPool" not in res[0]:
2390 self.report("No rIDAllocationPool found in %s" % dn)
2391 error_count += 1
2392 else:
2393 next_pool = int(res[0]["rIDAllocationPool"][0])
2395 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2396 low = 0x00000000FFFFFFFF & next_pool
2398 if high <= low:
2399 self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2400 error_count += 1
2402 if "rIDNextRID" in res[0]:
2403 next_free_rid = int(res[0]["rIDNextRID"][0])
2404 else:
2405 next_free_rid = 0
2407 if next_free_rid == 0:
2408 next_free_rid = low
2409 else:
2410 next_free_rid += 1
2412 # Check the remainder of this pool for conflicts. If
2413 # ridalloc_allocate_rid() moves to a new pool, this
2414 # will be above high, so we will stop.
2415 while next_free_rid <= high:
2416 sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2417 try:
2418 res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2419 attrs=[])
2420 except ldb.LdbError, (enum, estr):
2421 if enum != ldb.ERR_NO_SUCH_OBJECT:
2422 raise
2423 res = None
2424 if res is not None:
2425 self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2426 error_count += 1
2428 if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2429 % (sid, dn),
2430 'fix_sid_rid_set_conflict'):
2431 self.samdb.transaction_start()
2433 # This will burn RIDs, which will move
2434 # past the conflict. We then check again
2435 # to see if the new RID conflicts, until
2436 # the end of the current pool. We don't
2437 # look at the next pool to avoid burning
2438 # all RIDs in one go in some strange
2439 # failure case.
2440 try:
2441 while True:
2442 allocated_rid = self.samdb.allocate_rid()
2443 if allocated_rid >= next_free_rid:
2444 next_free_rid = allocated_rid + 1
2445 break
2446 except:
2447 self.samdb.transaction_cancel()
2448 raise
2450 self.samdb.transaction_commit()
2451 else:
2452 break
2453 else:
2454 next_free_rid += 1
2457 return error_count
2459 ################################################################
2460 # check special @ROOTDSE attributes
2461 def check_rootdse(self):
2462 '''check the @ROOTDSE special object'''
2463 dn = ldb.Dn(self.samdb, '@ROOTDSE')
2464 if self.verbose:
2465 self.report("Checking object %s" % dn)
2466 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2467 if len(res) != 1:
2468 self.report("Object %s disappeared during check" % dn)
2469 return 1
2470 obj = res[0]
2471 error_count = 0
2473 # check that the dsServiceName is in GUID form
2474 if not 'dsServiceName' in obj:
2475 self.report('ERROR: dsServiceName missing in @ROOTDSE')
2476 return error_count+1
2478 if not obj['dsServiceName'][0].startswith('<GUID='):
2479 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2480 error_count += 1
2481 if not self.confirm('Change dsServiceName to GUID form?'):
2482 return error_count
2483 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
2484 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2485 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2486 m = ldb.Message()
2487 m.dn = dn
2488 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2489 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2490 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2491 self.report("Changed dsServiceName to GUID form")
2492 return error_count
2495 ###############################################
2496 # re-index the database
2497 def reindex_database(self):
2498 '''re-index the whole database'''
2499 m = ldb.Message()
2500 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2501 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2502 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2503 return self.do_modify(m, [], 're-indexed database', validate=False)
2505 ###############################################
2506 # reset @MODULES
2507 def reset_modules(self):
2508 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2509 m = ldb.Message()
2510 m.dn = ldb.Dn(self.samdb, "@MODULES")
2511 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2512 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)