dbcheck: add support for restoring missing forward links
[Samba.git] / python / samba / dbchecker.py
blobc2c95a2e8593855b3d8f2abe3a4d3d2a924cf4f8
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 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
190 '''perform a database check, returning the number of errors found'''
191 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
192 self.report('Checking %u objects' % len(res))
193 error_count = 0
195 error_count += self.check_deleted_objects_containers()
197 self.attribute_or_class_ids = set()
199 for object in res:
200 self.dn_set.add(str(object.dn))
201 error_count += self.check_object(object.dn, attrs=attrs)
203 if DN is None:
204 error_count += self.check_rootdse()
206 if error_count != 0 and not self.fix:
207 self.report("Please use --fix to fix these errors")
209 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
210 return error_count
212 def check_deleted_objects_containers(self):
213 """This function only fixes conflicts on the Deleted Objects
214 containers, not the attributes"""
215 error_count = 0
216 for nc in self.ncs_lacking_deleted_containers:
217 if nc == self.schema_dn:
218 continue
219 error_count += 1
220 self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc)
221 if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'):
222 continue
224 dn = ldb.Dn(self.samdb, "CN=Deleted Objects")
225 dn.add_base(nc)
227 conflict_dn = None
228 try:
229 # If something already exists here, add a conflict
230 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[],
231 controls=["show_deleted:1", "extended_dn:1:1",
232 "show_recycled:1", "reveal_internals:0"])
233 if len(res) != 0:
234 guid = res[0].dn.get_extended_component("GUID")
235 conflict_dn = ldb.Dn(self.samdb,
236 "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid)))
237 conflict_dn.add_base(nc)
239 except ldb.LdbError, (enum, estr):
240 if enum == ldb.ERR_NO_SUCH_OBJECT:
241 pass
242 else:
243 self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr)
244 return 1
246 if conflict_dn is not None:
247 try:
248 self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"])
249 except ldb.LdbError, (enum, estr):
250 self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr))
251 return 1
253 # Refresh wellKnownObjects links
254 res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE,
255 attrs=['wellKnownObjects'],
256 controls=["show_deleted:1", "extended_dn:0",
257 "show_recycled:1", "reveal_internals:0"])
258 if len(res) != 1:
259 self.report("wellKnownObjects was not found for NC %s" % nc)
260 return 1
262 # Prevent duplicate deleted objects containers just in case
263 wko = res[0]["wellKnownObjects"]
264 listwko = []
265 proposed_objectguid = None
266 for o in wko:
267 dsdb_dn = dsdb_Dn(self.samdb, o, dsdb.DSDB_SYNTAX_BINARY_DN)
268 if self.is_deleted_objects_dn(dsdb_dn):
269 self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o)
270 # We really want to put this back in the same spot
271 # as the original one, so that on replication we
272 # merge, rather than conflict.
273 proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID")
274 listwko.append(o)
276 if proposed_objectguid is not None:
277 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid))
278 else:
279 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
280 listwko.append('%s:%s' % (wko_prefix, dn))
281 guid_suffix = ""
283 # Insert a brand new Deleted Objects container
284 self.samdb.add_ldif("""dn: %s
285 objectClass: top
286 objectClass: container
287 description: Container for deleted objects
288 isDeleted: TRUE
289 isCriticalSystemObject: TRUE
290 showInAdvancedViewOnly: TRUE
291 systemFlags: -1946157056%s""" % (dn, guid_suffix),
292 controls=["relax:0", "provision:0"])
294 delta = ldb.Message()
295 delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"]))
296 delta["wellKnownObjects"] = ldb.MessageElement(listwko,
297 ldb.FLAG_MOD_REPLACE,
298 "wellKnownObjects")
300 # Insert the link to the brand new container
301 if self.do_modify(delta, ["relax:0"],
302 "NC %s lacks Deleted Objects WKGUID" % nc,
303 validate=False):
304 self.report("Added %s well known guid link" % dn)
306 self.deleted_objects_containers.append(dn)
308 return error_count
310 def report(self, msg):
311 '''print a message unless quiet is set'''
312 if not self.quiet:
313 print(msg)
315 def confirm(self, msg, allow_all=False, forced=False):
316 '''confirm a change'''
317 if not self.fix:
318 return False
319 if self.quiet:
320 return self.yes
321 if self.yes:
322 forced = True
323 return common.confirm(msg, forced=forced, allow_all=allow_all)
325 ################################################################
326 # a local confirm function with support for 'all'
327 def confirm_all(self, msg, all_attr):
328 '''confirm a change with support for "all" '''
329 if not self.fix:
330 return False
331 if getattr(self, all_attr) == 'NONE':
332 return False
333 if getattr(self, all_attr) == 'ALL':
334 forced = True
335 else:
336 forced = self.yes
337 if self.quiet:
338 return forced
339 c = common.confirm(msg, forced=forced, allow_all=True)
340 if c == 'ALL':
341 setattr(self, all_attr, 'ALL')
342 return True
343 if c == 'NONE':
344 setattr(self, all_attr, 'NONE')
345 return False
346 return c
348 def do_delete(self, dn, controls, msg):
349 '''delete dn with optional verbose output'''
350 if self.verbose:
351 self.report("delete DN %s" % dn)
352 try:
353 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
354 self.samdb.delete(dn, controls=controls)
355 except Exception, err:
356 if self.in_transaction:
357 raise CommandError("%s : %s" % (msg, err))
358 self.report("%s : %s" % (msg, err))
359 return False
360 return True
362 def do_modify(self, m, controls, msg, validate=True):
363 '''perform a modify with optional verbose output'''
364 if self.verbose:
365 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
366 try:
367 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
368 self.samdb.modify(m, controls=controls, validate=validate)
369 except Exception, err:
370 if self.in_transaction:
371 raise CommandError("%s : %s" % (msg, err))
372 self.report("%s : %s" % (msg, err))
373 return False
374 return True
376 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
377 '''perform a modify with optional verbose output'''
378 if self.verbose:
379 self.report("""dn: %s
380 changeType: modrdn
381 newrdn: %s
382 deleteOldRdn: 1
383 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
384 try:
385 to_dn = to_rdn + to_base
386 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
387 self.samdb.rename(from_dn, to_dn, controls=controls)
388 except Exception, err:
389 if self.in_transaction:
390 raise CommandError("%s : %s" % (msg, err))
391 self.report("%s : %s" % (msg, err))
392 return False
393 return True
395 def get_attr_linkID_and_reverse_name(self, attrname):
396 if attrname in self.link_id_cache:
397 return self.link_id_cache[attrname]
398 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
399 if linkID:
400 revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
401 else:
402 revname = None
403 self.link_id_cache[attrname] = (linkID, revname)
404 return linkID, revname
406 def err_empty_attribute(self, dn, attrname):
407 '''fix empty attributes'''
408 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
409 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
410 self.report("Not fixing empty attribute %s" % attrname)
411 return
413 m = ldb.Message()
414 m.dn = dn
415 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
416 if self.do_modify(m, ["relax:0", "show_recycled:1"],
417 "Failed to remove empty attribute %s" % attrname, validate=False):
418 self.report("Removed empty attribute %s" % attrname)
420 def err_normalise_mismatch(self, dn, attrname, values):
421 '''fix attribute normalisation errors'''
422 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
423 mod_list = []
424 for val in values:
425 normalised = self.samdb.dsdb_normalise_attributes(
426 self.samdb_schema, attrname, [val])
427 if len(normalised) != 1:
428 self.report("Unable to normalise value '%s'" % val)
429 mod_list.append((val, ''))
430 elif (normalised[0] != val):
431 self.report("value '%s' should be '%s'" % (val, normalised[0]))
432 mod_list.append((val, normalised[0]))
433 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
434 self.report("Not fixing attribute %s" % attrname)
435 return
437 m = ldb.Message()
438 m.dn = dn
439 for i in range(0, len(mod_list)):
440 (val, nval) = mod_list[i]
441 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
442 if nval != '':
443 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
444 attrname)
446 if self.do_modify(m, ["relax:0", "show_recycled:1"],
447 "Failed to normalise attribute %s" % attrname,
448 validate=False):
449 self.report("Normalised attribute %s" % attrname)
451 def err_normalise_mismatch_replace(self, dn, attrname, values):
452 '''fix attribute normalisation errors'''
453 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
454 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
455 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
456 if list(normalised) == values:
457 return
458 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
459 self.report("Not fixing attribute '%s'" % attrname)
460 return
462 m = ldb.Message()
463 m.dn = dn
464 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
466 if self.do_modify(m, ["relax:0", "show_recycled:1"],
467 "Failed to normalise attribute %s" % attrname,
468 validate=False):
469 self.report("Normalised attribute %s" % attrname)
471 def err_duplicate_values(self, dn, attrname, dup_values, values):
472 '''fix attribute normalisation errors'''
473 self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
474 self.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values), ','.join(values)))
475 if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
476 self.report("Not fixing attribute '%s'" % attrname)
477 return
479 m = ldb.Message()
480 m.dn = dn
481 m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
483 if self.do_modify(m, ["relax:0", "show_recycled:1"],
484 "Failed to remove duplicate value on attribute %s" % attrname,
485 validate=False):
486 self.report("Removed duplicate value on attribute %s" % attrname)
488 def is_deleted_objects_dn(self, dsdb_dn):
489 '''see if a dsdb_Dn is the special Deleted Objects DN'''
490 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
492 def err_missing_objectclass(self, dn):
493 """handle object without objectclass"""
494 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)))
495 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'):
496 self.report("Not deleting object with missing objectclass '%s'" % dn)
497 return
498 if self.do_delete(dn, ["relax:0"],
499 "Failed to remove DN %s" % dn):
500 self.report("Removed DN %s" % dn)
502 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False):
503 """handle a DN pointing to a deleted object"""
504 if not remove_plausible:
505 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
506 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
507 if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
508 self.report("Not removing")
509 return
510 else:
511 self.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
512 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
513 if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
514 self.report("Not removing")
515 return
517 m = ldb.Message()
518 m.dn = dn
519 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
520 if self.do_modify(m, ["show_recycled:1",
521 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
522 "Failed to remove deleted DN attribute %s" % attrname):
523 self.report("Removed deleted DN on attribute %s" % attrname)
525 def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
526 """handle a missing target DN (if specified, GUID form can't be found,
527 and otherwise DN string form can't be found)"""
528 # check if its a backlink
529 linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
530 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
532 linkID, reverse_link_name \
533 = self.get_attr_linkID_and_reverse_name(attrname)
534 if reverse_link_name is not None:
535 self.report("WARNING: no target object found for GUID "
536 "component for one-way forward link "
537 "%s in object "
538 "%s - %s" % (attrname, dn, val))
539 self.report("Not removing dangling forward link")
540 return 0
542 nc_root = self.samdb.get_nc_root(dn)
543 target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn)
544 if nc_root != target_nc_root:
545 # We don't bump the error count as Samba produces these
546 # in normal operation
547 self.report("WARNING: no target object found for GUID "
548 "component for cross-partition link "
549 "%s in object "
550 "%s - %s" % (attrname, dn, val))
551 self.report("Not removing dangling one-way "
552 "cross-partition link "
553 "(we might be mid-replication)")
554 return 0
556 # Due to our link handling one-way links pointing to
557 # missing objects are plausible.
559 # We don't bump the error count as Samba produces these
560 # in normal operation
561 self.report("WARNING: no target object found for GUID "
562 "component for DN value %s in object "
563 "%s - %s" % (attrname, dn, val))
564 self.err_deleted_dn(dn, attrname, val,
565 dsdb_dn, dsdb_dn, True)
566 return 0
568 # We bump the error count here, as we should have deleted this
569 self.report("ERROR: no target object found for GUID "
570 "component for link %s in object "
571 "%s - %s" % (attrname, dn, val))
572 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
573 return 1
575 def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
576 """handle a missing GUID extended DN component"""
577 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
578 controls=["extended_dn:1:1", "show_recycled:1"]
579 try:
580 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
581 attrs=[], controls=controls)
582 except ldb.LdbError, (enum, estr):
583 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
584 if enum != ldb.ERR_NO_SUCH_OBJECT:
585 raise
586 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
587 return
588 if len(res) == 0:
589 self.report("unable to find object for DN %s" % dsdb_dn.dn)
590 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
591 return
592 dsdb_dn.dn = res[0].dn
594 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
595 self.report("Not fixing %s" % errstr)
596 return
597 m = ldb.Message()
598 m.dn = dn
599 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
600 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
602 if self.do_modify(m, ["show_recycled:1"],
603 "Failed to fix %s on attribute %s" % (errstr, attrname)):
604 self.report("Fixed %s on attribute %s" % (errstr, attrname))
606 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
607 """handle an incorrect binary DN component"""
608 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
609 controls=["extended_dn:1:1", "show_recycled:1"]
611 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
612 self.report("Not fixing %s" % errstr)
613 return
614 m = ldb.Message()
615 m.dn = dn
616 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
617 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
619 if self.do_modify(m, ["show_recycled:1"],
620 "Failed to fix %s on attribute %s" % (errstr, attrname)):
621 self.report("Fixed %s on attribute %s" % (errstr, attrname))
623 def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn):
624 """handle a DN string being incorrect"""
625 self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val))
626 dsdb_dn.dn = correct_dn
628 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
629 'fix_all_old_dn_string_component_mismatch'):
630 self.report("Not fixing old string component")
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)
636 if self.do_modify(m, ["show_recycled:1"],
637 "Failed to fix old DN string on attribute %s" % (attrname)):
638 self.report("Fixed old DN string on attribute %s" % (attrname))
640 def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
641 """handle a DN string being incorrect"""
642 self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
643 dsdb_dn.dn = correct_dn
645 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
646 'fix_all_%s_dn_component_mismatch' % mismatch_type):
647 self.report("Not fixing %s component mismatch" % mismatch_type)
648 return
649 m = ldb.Message()
650 m.dn = dn
651 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
652 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
653 if self.do_modify(m, ["show_recycled:1"],
654 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
655 self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
657 def err_unknown_attribute(self, obj, attrname):
658 '''handle an unknown attribute error'''
659 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
660 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
661 self.report("Not removing %s" % attrname)
662 return
663 m = ldb.Message()
664 m.dn = obj.dn
665 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
666 if self.do_modify(m, ["relax:0", "show_recycled:1"],
667 "Failed to remove unknown attribute %s" % attrname):
668 self.report("Removed unknown attribute %s" % (attrname))
670 def err_undead_linked_attribute(self, obj, attrname, val):
671 '''handle a link that should not be there on a deleted object'''
672 self.report("ERROR: linked attribute '%s' to '%s' is present on "
673 "deleted object %s" % (attrname, val, obj.dn))
674 if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
675 self.report("Not removing linked attribute %s" % attrname)
676 return
677 m = ldb.Message()
678 m.dn = obj.dn
679 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
681 if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
682 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
683 "Failed to delete forward link %s" % attrname):
684 self.report("Fixed undead forward link %s" % (attrname))
686 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
687 '''handle a missing backlink value'''
688 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
689 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
690 self.report("Not fixing missing backlink %s" % backlink_name)
691 return
692 m = ldb.Message()
693 m.dn = target_dn
694 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
695 if self.do_modify(m, ["show_recycled:1", "relax:0"],
696 "Failed to fix missing backlink %s" % backlink_name):
697 self.report("Fixed missing backlink %s" % (backlink_name))
699 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
700 '''handle a incorrect RMD_FLAGS value'''
701 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
702 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()))
703 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
704 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
705 return
706 m = ldb.Message()
707 m.dn = obj.dn
708 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
709 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
710 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
711 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
713 def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val,
714 target_dn, forward_attr, forward_syntax,
715 check_duplicates=True):
716 '''handle a orphaned backlink value'''
717 if check_duplicates is True and self.has_duplicate_links(target_dn, forward_attr, forward_syntax):
718 self.report("WARNING: Keep orphaned backlink attribute " + \
719 "'%s' in '%s' for link '%s' in '%s'" % (
720 backlink_attr, obj_dn, forward_attr, target_dn))
721 return
722 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn))
723 if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'):
724 self.report("Not removing orphaned backlink %s" % backlink_attr)
725 return
726 m = ldb.Message()
727 m.dn = obj_dn
728 m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr)
729 if self.do_modify(m, ["show_recycled:1", "relax:0"],
730 "Failed to fix orphaned backlink %s" % backlink_attr):
731 self.report("Fixed orphaned backlink %s" % (backlink_attr))
733 def err_recover_forward_links(self, obj, forward_attr, forward_vals):
734 '''handle a duplicate links value'''
736 self.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn))
738 if not self.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'):
739 self.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
740 forward_attr, obj.dn))
741 return
742 m = ldb.Message()
743 m.dn = obj.dn
744 m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr)
745 if self.do_modify(m, ["local_oid:1.3.6.1.4.1.7165.4.3.19.2:1"],
746 "Failed to fix duplicate links in attribute '%s'" % forward_attr):
747 self.report("Fixed duplicate links in attribute '%s'" % (forward_attr))
749 def err_no_fsmoRoleOwner(self, obj):
750 '''handle a missing fSMORoleOwner'''
751 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
752 res = self.samdb.search("",
753 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
754 assert len(res) == 1
755 serviceName = res[0]["dsServiceName"][0]
756 if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
757 self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
758 return
759 m = ldb.Message()
760 m.dn = obj.dn
761 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
762 if self.do_modify(m, [],
763 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
764 self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
766 def err_missing_parent(self, obj):
767 '''handle a missing parent'''
768 self.report("ERROR: parent object not found for %s" % (obj.dn))
769 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
770 self.report('Not moving object %s into LostAndFound' % (obj.dn))
771 return
773 keep_transaction = False
774 self.samdb.transaction_start()
775 try:
776 nc_root = self.samdb.get_nc_root(obj.dn);
777 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
778 new_dn = ldb.Dn(self.samdb, str(obj.dn))
779 new_dn.remove_base_components(len(new_dn) - 1)
780 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
781 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
782 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
784 m = ldb.Message()
785 m.dn = obj.dn
786 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
788 if self.do_modify(m, [],
789 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
790 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
791 keep_transaction = True
792 except:
793 self.samdb.transaction_cancel()
794 raise
796 if keep_transaction:
797 self.samdb.transaction_commit()
798 else:
799 self.samdb.transaction_cancel()
801 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
802 '''handle a wrong dn'''
804 new_rdn = ldb.Dn(self.samdb, str(new_dn))
805 new_rdn.remove_base_components(len(new_rdn) - 1)
806 new_parent = new_dn.parent()
808 attributes = ""
809 if rdn_val != name_val:
810 attributes += "%s=%r " % (rdn_attr, rdn_val)
811 attributes += "name=%r" % (name_val)
813 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
814 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
815 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
816 return
818 if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
819 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
820 self.report("Renamed %s into %s" % (obj.dn, new_dn))
822 def err_wrong_instancetype(self, obj, calculated_instancetype):
823 '''handle a wrong instanceType'''
824 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
825 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
826 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
827 return
829 m = ldb.Message()
830 m.dn = obj.dn
831 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
832 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
833 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
834 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
836 def err_short_userParameters(self, obj, attrname, value):
837 # This is a truncated userParameters due to a pre 4.1 replication bug
838 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)))
840 def err_base64_userParameters(self, obj, attrname, value):
841 '''handle a wrong userParameters'''
842 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
843 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
844 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
845 return
847 m = ldb.Message()
848 m.dn = obj.dn
849 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
850 if self.do_modify(m, [],
851 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
852 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
854 def err_utf8_userParameters(self, obj, attrname, value):
855 '''handle a wrong userParameters'''
856 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
857 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
858 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
859 return
861 m = ldb.Message()
862 m.dn = obj.dn
863 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
864 ldb.FLAG_MOD_REPLACE, 'userParameters')
865 if self.do_modify(m, [],
866 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
867 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
869 def err_doubled_userParameters(self, obj, attrname, value):
870 '''handle a wrong userParameters'''
871 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
872 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
873 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
874 return
876 m = ldb.Message()
877 m.dn = obj.dn
878 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
879 ldb.FLAG_MOD_REPLACE, 'userParameters')
880 if self.do_modify(m, [],
881 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
882 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
884 def err_odd_userParameters(self, obj, attrname):
885 # This is a truncated userParameters due to a pre 4.1 replication bug
886 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)))
888 def find_revealed_link(self, dn, attrname, guid):
889 '''return a revealed link in an object'''
890 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
891 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
892 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
893 for val in res[0][attrname]:
894 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
895 guid2 = dsdb_dn.dn.get_extended_component("GUID")
896 if guid == guid2:
897 return dsdb_dn
898 return None
900 def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr):
901 '''check a linked values for duplicate forward links'''
902 error_count = 0
904 duplicate_dict = dict()
905 unique_dict = dict()
907 # Only forward links can have this problem
908 if forward_linkID & 1:
909 # If we got the reverse, skip it
910 return (error_count, duplicate_dict, unique_dict)
912 if backlink_attr is None:
913 return (error_count, duplicate_dict, unique_dict)
915 duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
916 if duplicate_cache_key not in self.duplicate_link_cache:
917 self.duplicate_link_cache[duplicate_cache_key] = False
919 for val in obj[forward_attr]:
920 dsdb_dn = dsdb_Dn(self.samdb, val, forward_syntax)
922 # all DNs should have a GUID component
923 guid = dsdb_dn.dn.get_extended_component("GUID")
924 if guid is None:
925 continue
926 guidstr = str(misc.GUID(guid))
927 keystr = guidstr + dsdb_dn.prefix
928 if keystr not in unique_dict:
929 unique_dict[keystr] = dsdb_dn
930 continue
931 error_count += 1
932 if keystr not in duplicate_dict:
933 duplicate_dict[keystr] = dict()
934 duplicate_dict[keystr]["keep"] = None
935 duplicate_dict[keystr]["delete"] = list()
937 # Now check for the highest RMD_VERSION
938 v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION"))
939 v2 = int(dsdb_dn.dn.get_extended_component("RMD_VERSION"))
940 if v1 > v2:
941 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
942 duplicate_dict[keystr]["delete"].append(dsdb_dn)
943 continue
944 if v1 < v2:
945 duplicate_dict[keystr]["keep"] = dsdb_dn
946 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
947 unique_dict[keystr] = dsdb_dn
948 continue
949 # Fallback to the highest RMD_LOCAL_USN
950 u1 = int(unique_dict[keystr].dn.get_extended_component("RMD_LOCAL_USN"))
951 u2 = int(dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN"))
952 if u1 >= u2:
953 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
954 duplicate_dict[keystr]["delete"].append(dsdb_dn)
955 continue
956 duplicate_dict[keystr]["keep"] = dsdb_dn
957 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
958 unique_dict[keystr] = dsdb_dn
960 if error_count != 0:
961 self.duplicate_link_cache[duplicate_cache_key] = True
963 return (error_count, duplicate_dict, unique_dict)
965 def has_duplicate_links(self, dn, forward_attr, forward_syntax):
966 '''check a linked values for duplicate forward links'''
967 error_count = 0
969 duplicate_cache_key = "%s:%s" % (str(dn), forward_attr)
970 if duplicate_cache_key in self.duplicate_link_cache:
971 return self.duplicate_link_cache[duplicate_cache_key]
973 forward_linkID, backlink_attr = self.get_attr_linkID_and_reverse_name(forward_attr)
975 attrs = [forward_attr]
976 controls = ["extended_dn:1:1", "reveal_internals:0"]
978 # check its the right GUID
979 try:
980 res = self.samdb.search(base=str(dn), scope=ldb.SCOPE_BASE,
981 attrs=attrs, controls=controls)
982 except ldb.LdbError, (enum, estr):
983 if enum != ldb.ERR_NO_SUCH_OBJECT:
984 raise
986 return False
988 obj = res[0]
989 error_count, duplicate_dict, unique_dict = \
990 self.check_duplicate_links(obj, forward_attr, forward_syntax, forward_linkID, backlink_attr)
992 if duplicate_cache_key in self.duplicate_link_cache:
993 return self.duplicate_link_cache[duplicate_cache_key]
995 return False
997 def find_missing_forward_links_from_backlinks(self, obj,
998 forward_attr,
999 forward_syntax,
1000 backlink_attr,
1001 forward_unique_dict):
1002 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1003 missing_forward_links = []
1004 error_count = 0
1006 if backlink_attr is None:
1007 return (missing_forward_links, error_count)
1009 if forward_syntax != ldb.SYNTAX_DN:
1010 self.report("Not checking for missing forward links for syntax: %s",
1011 forward_syntax)
1012 return (missing_forward_links, error_count)
1014 try:
1015 obj_guid = obj['objectGUID'][0]
1016 obj_guid_str = str(ndr_unpack(misc.GUID, obj_guid))
1017 filter = "(%s=<GUID=%s>)" % (backlink_attr, obj_guid_str)
1019 res = self.samdb.search(expression=filter,
1020 scope=ldb.SCOPE_SUBTREE, attrs=["objectGUID"],
1021 controls=["extended_dn:1:1",
1022 "search_options:1:2",
1023 "paged_results:1:1000"])
1024 except ldb.LdbError, (enum, estr):
1025 raise
1027 for r in res:
1028 target_dn = dsdb_Dn(self.samdb, r.dn.extended_str(), forward_syntax)
1030 guid = target_dn.dn.get_extended_component("GUID")
1031 guidstr = str(misc.GUID(guid))
1032 if guidstr in forward_unique_dict:
1033 continue
1035 # A valid forward link looks like this:
1037 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1038 # <RMD_ADDTIME=131607546230000000>;
1039 # <RMD_CHANGETIME=131607546230000000>;
1040 # <RMD_FLAGS=0>;
1041 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1042 # <RMD_LOCAL_USN=3765>;
1043 # <RMD_ORIGINATING_USN=3765>;
1044 # <RMD_VERSION=1>;
1045 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1046 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1048 # Note that versions older than Samba 4.8 create
1049 # links with RMD_VERSION=0.
1051 # Try to get the local_usn and time from objectClass
1052 # if possible and fallback to any other one.
1053 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1054 obj['replPropertyMetadata'][0])
1055 for o in repl.ctr.array:
1056 local_usn = o.local_usn
1057 t = o.originating_change_time
1058 if o.attid == drsuapi.DRSUAPI_ATTID_objectClass:
1059 break
1061 # We use a magic invocationID for restoring missing
1062 # forward links to recover from bug #13228.
1063 # This should allow some more future magic to fix the
1064 # problem.
1066 # It also means it looses the conflict resolution
1067 # against almost every real invocation, if the
1068 # version is also 0.
1069 originating_invocid = misc.GUID("ffffffff-4700-4700-4700-000000b13228")
1070 originating_usn = 1
1072 rmd_addtime = t
1073 rmd_changetime = t
1074 rmd_flags = 0
1075 rmd_invocid = originating_invocid
1076 rmd_originating_usn = originating_usn
1077 rmd_local_usn = local_usn
1078 rmd_version = 0
1080 target_dn.dn.set_extended_component("RMD_ADDTIME", str(rmd_addtime))
1081 target_dn.dn.set_extended_component("RMD_CHANGETIME", str(rmd_changetime))
1082 target_dn.dn.set_extended_component("RMD_FLAGS", str(rmd_flags))
1083 target_dn.dn.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid))
1084 target_dn.dn.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn))
1085 target_dn.dn.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn))
1086 target_dn.dn.set_extended_component("RMD_VERSION", str(rmd_version))
1088 error_count += 1
1089 missing_forward_links.append(target_dn)
1091 return (missing_forward_links, error_count)
1093 def check_dn(self, obj, attrname, syntax_oid):
1094 '''check a DN attribute for correctness'''
1095 error_count = 0
1096 obj_guid = obj['objectGUID'][0]
1098 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1099 if reverse_link_name is not None:
1100 reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
1101 else:
1102 reverse_syntax_oid = None
1104 error_count, duplicate_dict, unique_dict = \
1105 self.check_duplicate_links(obj, attrname, syntax_oid, linkID, reverse_link_name)
1107 if len(duplicate_dict) != 0:
1109 missing_forward_links, missing_error_count = \
1110 self.find_missing_forward_links_from_backlinks(obj,
1111 attrname, syntax_oid,
1112 reverse_link_name,
1113 unique_dict)
1114 error_count += missing_error_count
1116 forward_links = [dn for dn in unique_dict.values()]
1118 if missing_error_count != 0:
1119 self.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1120 attrname, obj.dn))
1121 else:
1122 self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
1123 for m in missing_forward_links:
1124 self.report("Missing link '%s'" % (m))
1125 if not self.confirm_all("Schedule readding missing forward link for attribute %s" % attrname,
1126 'fix_all_missing_forward_links'):
1127 self.err_orphaned_backlink(m.dn, reverse_link_name,
1128 obj.dn.extended_str(), obj.dn,
1129 attrname, syntax_oid,
1130 check_duplicates=False)
1131 continue
1132 forward_links += [m]
1133 for keystr in duplicate_dict.keys():
1134 d = duplicate_dict[keystr]
1135 for dd in d["delete"]:
1136 self.report("Duplicate link '%s'" % dd)
1137 self.report("Correct link '%s'" % d["keep"])
1139 # We now construct the sorted dn values.
1140 # They're sorted by the objectGUID of the target
1141 # See dsdb_Dn.__cmp__()
1142 vals = [str(dn) for dn in sorted(forward_links)]
1143 self.err_recover_forward_links(obj, attrname, vals)
1144 # We should continue with the fixed values
1145 obj[attrname] = ldb.MessageElement(vals, 0, attrname)
1147 for val in obj[attrname]:
1148 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
1150 # all DNs should have a GUID component
1151 guid = dsdb_dn.dn.get_extended_component("GUID")
1152 if guid is None:
1153 error_count += 1
1154 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
1155 "missing GUID")
1156 continue
1158 guidstr = str(misc.GUID(guid))
1159 attrs = ['isDeleted', 'replPropertyMetaData']
1161 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
1162 fixing_msDS_HasInstantiatedNCs = True
1163 attrs.append("instanceType")
1164 else:
1165 fixing_msDS_HasInstantiatedNCs = False
1167 if reverse_link_name is not None:
1168 attrs.append(reverse_link_name)
1170 # check its the right GUID
1171 try:
1172 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
1173 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
1174 "reveal_internals:0"
1176 except ldb.LdbError, (enum, estr):
1177 if enum != ldb.ERR_NO_SUCH_OBJECT:
1178 raise
1180 # We don't always want to
1181 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
1182 attrname,
1183 val,
1184 dsdb_dn)
1185 continue
1187 if fixing_msDS_HasInstantiatedNCs:
1188 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
1189 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
1191 if str(dsdb_dn) != val:
1192 error_count +=1
1193 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
1194 continue
1196 # now we have two cases - the source object might or might not be deleted
1197 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1198 target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
1201 if is_deleted and not obj.dn in self.deleted_objects_containers and linkID:
1202 # A fully deleted object should not have any linked
1203 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1204 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1205 # Requirements)
1206 self.err_undead_linked_attribute(obj, attrname, val)
1207 error_count += 1
1208 continue
1209 elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
1210 # the target DN is not allowed to be deleted, unless the target DN is the
1211 # special Deleted Objects container
1212 error_count += 1
1213 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
1214 if local_usn:
1215 if 'replPropertyMetaData' in res[0]:
1216 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1217 str(res[0]['replPropertyMetadata']))
1218 found_data = False
1219 for o in repl.ctr.array:
1220 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1221 deleted_usn = o.local_usn
1222 if deleted_usn >= int(local_usn):
1223 # If the object was deleted after the link
1224 # was last modified then, clean it up here
1225 found_data = True
1226 break
1228 if found_data:
1229 self.err_deleted_dn(obj.dn, attrname,
1230 val, dsdb_dn, res[0].dn, True)
1231 continue
1233 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
1234 continue
1236 # We should not check for incorrect
1237 # components on deleted links, as these are allowed to
1238 # go stale (we just need the GUID, not the name)
1239 rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
1240 rmd_flags = 0
1241 if rmd_blob is not None:
1242 rmd_flags = int(rmd_blob)
1244 # assert the DN matches in string form, where a reverse
1245 # link exists, otherwise (below) offer to fix it as a non-error.
1246 # The string form is essentially only kept for forensics,
1247 # as we always re-resolve by GUID in normal operations.
1248 if not rmd_flags & 1 and reverse_link_name is not None:
1249 if str(res[0].dn) != str(dsdb_dn.dn):
1250 error_count += 1
1251 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1252 res[0].dn, "string")
1253 continue
1255 if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
1256 error_count += 1
1257 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1258 res[0].dn, "GUID")
1259 continue
1261 if res[0].dn.get_extended_component("SID") != dsdb_dn.dn.get_extended_component("SID"):
1262 error_count += 1
1263 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1264 res[0].dn, "SID")
1265 continue
1267 # Now we have checked the GUID and SID, offer to fix old
1268 # DN strings as a non-error (for forward links with no
1269 # backlink). Samba does not maintain this string
1270 # otherwise, so we don't increment error_count.
1271 if reverse_link_name is None:
1272 if str(res[0].dn) != str(dsdb_dn.dn):
1273 self.err_dn_string_component_old(obj.dn, attrname, val, dsdb_dn,
1274 res[0].dn)
1275 continue
1277 # check the reverse_link is correct if there should be one
1278 match_count = 0
1279 if reverse_link_name in res[0]:
1280 for v in res[0][reverse_link_name]:
1281 v_dn = dsdb_Dn(self.samdb, v)
1282 v_guid = v_dn.dn.get_extended_component("GUID")
1283 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1284 v_rmd_flags = 0
1285 if v_blob is not None:
1286 v_rmd_flags = int(v_blob)
1287 if v_rmd_flags & 1:
1288 continue
1289 if v_guid == obj_guid:
1290 match_count += 1
1292 if match_count != 1:
1293 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1294 if not linkID & 1:
1295 # Forward binary multi-valued linked attribute
1296 forward_count = 0
1297 for w in obj[attrname]:
1298 w_guid = dsdb_Dn(self.samdb, w).dn.get_extended_component("GUID")
1299 if w_guid == guid:
1300 forward_count += 1
1302 if match_count == forward_count:
1303 continue
1304 expected_count = 0
1305 for v in obj[attrname]:
1306 v_dn = dsdb_Dn(self.samdb, v)
1307 v_guid = v_dn.dn.get_extended_component("GUID")
1308 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1309 v_rmd_flags = 0
1310 if v_blob is not None:
1311 v_rmd_flags = int(v_blob)
1312 if v_rmd_flags & 1:
1313 continue
1314 if v_guid == guid:
1315 expected_count += 1
1317 if match_count == expected_count:
1318 continue
1320 diff_count = expected_count - match_count
1322 if linkID & 1:
1323 # If there's a backward link on binary multi-valued linked attribute,
1324 # let the check on the forward link remedy the value.
1325 # UNLESS, there is no forward link detected.
1326 if match_count == 0:
1327 error_count += 1
1328 self.err_orphaned_backlink(obj.dn, attrname,
1329 val, dsdb_dn.dn,
1330 reverse_link_name,
1331 reverse_syntax_oid)
1332 continue
1333 # Only warn here and let the forward link logic fix it.
1334 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1335 attrname, expected_count, str(obj.dn),
1336 reverse_link_name, match_count, str(dsdb_dn.dn)))
1337 continue
1339 assert not target_is_deleted
1341 self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1342 attrname, expected_count, str(obj.dn),
1343 reverse_link_name, match_count, str(dsdb_dn.dn)))
1345 # Loop until the difference between the forward and
1346 # the backward links is resolved.
1347 while diff_count != 0:
1348 error_count += 1
1349 if diff_count > 0:
1350 if match_count > 0 or diff_count > 1:
1351 # TODO no method to fix these right now
1352 self.report("ERROR: Can't fix missing "
1353 "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1354 break
1355 self.err_missing_backlink(obj, attrname,
1356 obj.dn.extended_str(),
1357 reverse_link_name,
1358 dsdb_dn.dn)
1359 diff_count -= 1
1360 else:
1361 self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1362 obj.dn.extended_str(), obj.dn,
1363 attrname, syntax_oid)
1364 diff_count += 1
1367 return error_count
1370 def get_originating_time(self, val, attid):
1371 '''Read metadata properties and return the originating time for
1372 a given attributeId.
1374 :return: the originating time or 0 if not found
1377 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1378 obj = repl.ctr
1380 for o in repl.ctr.array:
1381 if o.attid == attid:
1382 return o.originating_change_time
1384 return 0
1386 def process_metadata(self, dn, val):
1387 '''Read metadata properties and list attributes in it.
1388 raises KeyError if the attid is unknown.'''
1390 set_att = set()
1391 wrong_attids = set()
1392 list_attid = []
1393 in_schema_nc = dn.is_child_of(self.schema_dn)
1395 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1396 obj = repl.ctr
1398 for o in repl.ctr.array:
1399 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1400 set_att.add(att.lower())
1401 list_attid.append(o.attid)
1402 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1403 is_schema_nc=in_schema_nc)
1404 if correct_attid != o.attid:
1405 wrong_attids.add(o.attid)
1407 return (set_att, list_attid, wrong_attids)
1410 def fix_metadata(self, obj, attr):
1411 '''re-write replPropertyMetaData elements for a single attribute for a
1412 object. This is used to fix missing replPropertyMetaData elements'''
1413 guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1414 dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1415 res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
1416 controls = ["search_options:1:2",
1417 "show_recycled:1"])
1418 msg = res[0]
1419 nmsg = ldb.Message()
1420 nmsg.dn = dn
1421 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1422 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1423 "Failed to fix metadata for attribute %s" % attr):
1424 self.report("Fixed metadata for attribute %s" % attr)
1426 def ace_get_effective_inherited_type(self, ace):
1427 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1428 return None
1430 check = False
1431 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1432 check = True
1433 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1434 check = True
1435 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1436 check = True
1437 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1438 check = True
1440 if not check:
1441 return None
1443 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1444 return None
1446 return str(ace.object.inherited_type)
1448 def lookup_class_schemaIDGUID(self, cls):
1449 if cls in self.class_schemaIDGUID:
1450 return self.class_schemaIDGUID[cls]
1452 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1453 res = self.samdb.search(base=self.schema_dn,
1454 expression=flt,
1455 attrs=["schemaIDGUID"])
1456 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1458 self.class_schemaIDGUID[cls] = t
1459 return t
1461 def process_sd(self, dn, obj):
1462 sd_attr = "nTSecurityDescriptor"
1463 sd_val = obj[sd_attr]
1465 sd = ndr_unpack(security.descriptor, str(sd_val))
1467 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1468 if is_deleted:
1469 # we don't fix deleted objects
1470 return (sd, None)
1472 sd_clean = security.descriptor()
1473 sd_clean.owner_sid = sd.owner_sid
1474 sd_clean.group_sid = sd.group_sid
1475 sd_clean.type = sd.type
1476 sd_clean.revision = sd.revision
1478 broken = False
1479 last_inherited_type = None
1481 aces = []
1482 if sd.sacl is not None:
1483 aces = sd.sacl.aces
1484 for i in range(0, len(aces)):
1485 ace = aces[i]
1487 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1488 sd_clean.sacl_add(ace)
1489 continue
1491 t = self.ace_get_effective_inherited_type(ace)
1492 if t is None:
1493 continue
1495 if last_inherited_type is not None:
1496 if t != last_inherited_type:
1497 # if it inherited from more than
1498 # one type it's very likely to be broken
1500 # If not the recalculation will calculate
1501 # the same result.
1502 broken = True
1503 continue
1505 last_inherited_type = t
1507 aces = []
1508 if sd.dacl is not None:
1509 aces = sd.dacl.aces
1510 for i in range(0, len(aces)):
1511 ace = aces[i]
1513 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1514 sd_clean.dacl_add(ace)
1515 continue
1517 t = self.ace_get_effective_inherited_type(ace)
1518 if t is None:
1519 continue
1521 if last_inherited_type is not None:
1522 if t != last_inherited_type:
1523 # if it inherited from more than
1524 # one type it's very likely to be broken
1526 # If not the recalculation will calculate
1527 # the same result.
1528 broken = True
1529 continue
1531 last_inherited_type = t
1533 if broken:
1534 return (sd_clean, sd)
1536 if last_inherited_type is None:
1537 # ok
1538 return (sd, None)
1540 cls = None
1541 try:
1542 cls = obj["objectClass"][-1]
1543 except KeyError, e:
1544 pass
1546 if cls is None:
1547 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1548 attrs=["isDeleted", "objectClass"],
1549 controls=["show_recycled:1"])
1550 o = res[0]
1551 is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
1552 if is_deleted:
1553 # we don't fix deleted objects
1554 return (sd, None)
1555 cls = o["objectClass"][-1]
1557 t = self.lookup_class_schemaIDGUID(cls)
1559 if t != last_inherited_type:
1560 # broken
1561 return (sd_clean, sd)
1563 # ok
1564 return (sd, None)
1566 def err_wrong_sd(self, dn, sd, sd_broken):
1567 '''re-write the SD due to incorrect inherited ACEs'''
1568 sd_attr = "nTSecurityDescriptor"
1569 sd_val = ndr_pack(sd)
1570 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1572 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1573 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1574 return
1576 nmsg = ldb.Message()
1577 nmsg.dn = dn
1578 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1579 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1580 "Failed to fix attribute %s" % sd_attr):
1581 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1583 def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1584 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1585 sd_attr = "nTSecurityDescriptor"
1586 sd_val = ndr_pack(sd)
1587 sd_old_val = ndr_pack(sd_old)
1588 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1589 if sd.owner_sid is not None:
1590 sd_flags |= security.SECINFO_OWNER
1591 if sd.group_sid is not None:
1592 sd_flags |= security.SECINFO_GROUP
1594 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1595 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1596 return
1598 m = ldb.Message()
1599 m.dn = dn
1600 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1601 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1602 "Failed to reset attribute %s" % sd_attr):
1603 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1605 def err_missing_sd_owner(self, dn, sd):
1606 '''re-write the SD due to a missing owner or group'''
1607 sd_attr = "nTSecurityDescriptor"
1608 sd_val = ndr_pack(sd)
1609 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1611 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1612 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1613 return
1615 nmsg = ldb.Message()
1616 nmsg.dn = dn
1617 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1619 # By setting the session_info to admin_session_info and
1620 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1621 # flags we cause the descriptor module to set the correct
1622 # owner and group on the SD, replacing the None/NULL values
1623 # for owner_sid and group_sid currently present.
1625 # The admin_session_info matches that used in provision, and
1626 # is the best guess we can make for an existing object that
1627 # hasn't had something specifically set.
1629 # This is important for the dns related naming contexts.
1630 self.samdb.set_session_info(self.admin_session_info)
1631 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1632 "Failed to fix metadata for attribute %s" % sd_attr):
1633 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1634 self.samdb.set_session_info(self.system_session_info)
1637 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1638 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1639 str(repl_meta_data))
1640 ctr = repl.ctr
1641 found = False
1642 for o in ctr.array:
1643 # Search for a zero invocationID
1644 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1645 continue
1647 found = True
1648 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1649 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1650 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1651 % (dn, o.attid, o.version,
1652 time.ctime(samba.nttime2unix(o.originating_change_time)),
1653 self.samdb.get_invocation_id()))
1655 return found
1658 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1659 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1660 str(repl_meta_data))
1661 ctr = repl.ctr
1662 now = samba.unix2nttime(int(time.time()))
1663 found = False
1664 for o in ctr.array:
1665 # Search for a zero invocationID
1666 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1667 continue
1669 found = True
1670 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1671 o.version = o.version + 1
1672 o.originating_change_time = now
1673 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1674 o.originating_usn = seq
1675 o.local_usn = seq
1677 if found:
1678 replBlob = ndr_pack(repl)
1679 msg = ldb.Message()
1680 msg.dn = dn
1682 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1683 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1684 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1685 return
1687 nmsg = ldb.Message()
1688 nmsg.dn = dn
1689 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1690 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1691 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1692 "Failed to fix attribute %s" % attr):
1693 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1696 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1697 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1698 str(repl_meta_data))
1699 ctr = repl.ctr
1700 for o in ctr.array:
1701 # Search for an invalid attid
1702 try:
1703 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1704 except KeyError:
1705 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1706 return
1709 def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1710 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1711 str(repl_meta_data))
1712 fix = False
1714 set_att = set()
1715 remove_attid = set()
1716 hash_att = {}
1718 in_schema_nc = dn.is_child_of(self.schema_dn)
1720 ctr = repl.ctr
1721 # Sort the array, except for the last element. This strange
1722 # construction, creating a new list, due to bugs in samba's
1723 # array handling in IDL generated objects.
1724 ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1725 # Now walk it in reverse, so we see the low (and so incorrect,
1726 # the correct values are above 0x80000000) values first and
1727 # remove the 'second' value we see.
1728 for o in reversed(ctr.array):
1729 print "%s: 0x%08x" % (dn, o.attid)
1730 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1731 if att.lower() in set_att:
1732 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1733 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1734 % (attr, dn, o.attid, att, hash_att[att].attid),
1735 'fix_replmetadata_duplicate_attid'):
1736 self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1737 % (o.attid, att, attr, dn))
1738 return
1739 fix = True
1740 remove_attid.add(o.attid)
1741 # We want to set the metadata for the most recent
1742 # update to have been applied locally, that is the metadata
1743 # matching the (eg string) value in the attribute
1744 if o.local_usn > hash_att[att].local_usn:
1745 # This is always what we would have sent over DRS,
1746 # because the DRS server will have sent the
1747 # msDS-IntID, but with the values from both
1748 # attribute entries.
1749 hash_att[att].version = o.version
1750 hash_att[att].originating_change_time = o.originating_change_time
1751 hash_att[att].originating_invocation_id = o.originating_invocation_id
1752 hash_att[att].originating_usn = o.originating_usn
1753 hash_att[att].local_usn = o.local_usn
1755 # Do not re-add the value to the set or overwrite the hash value
1756 continue
1758 hash_att[att] = o
1759 set_att.add(att.lower())
1761 # Generate a real list we can sort on properly
1762 new_list = [o for o in ctr.array if o.attid not in remove_attid]
1764 if (len(wrong_attids) > 0):
1765 for o in new_list:
1766 if o.attid in wrong_attids:
1767 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1768 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1769 self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1770 if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1771 % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1772 self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1773 % (o.attid, correct_attid, att, attr, dn))
1774 return
1775 fix = True
1776 o.attid = correct_attid
1777 if fix:
1778 # Sort the array, (we changed the value so must re-sort)
1779 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1781 # If we did not already need to fix it, then ask about sorting
1782 if not fix:
1783 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1784 if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1785 % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1786 self.report('Not fixing %s on %s\n' % (attr, dn))
1787 return
1789 # The actual sort done is done at the top of the function
1791 ctr.count = len(new_list)
1792 ctr.array = new_list
1793 replBlob = ndr_pack(repl)
1795 nmsg = ldb.Message()
1796 nmsg.dn = dn
1797 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1798 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1799 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1800 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1801 "Failed to fix attribute %s" % attr):
1802 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1805 def is_deleted_deleted_objects(self, obj):
1806 faulty = False
1807 if "description" not in obj:
1808 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1809 faulty = True
1810 if "showInAdvancedViewOnly" not in obj or obj['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1811 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1812 faulty = True
1813 if "objectCategory" not in obj:
1814 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1815 faulty = True
1816 if "isCriticalSystemObject" not in obj or obj['isCriticalSystemObject'][0].upper() == 'FALSE':
1817 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1818 faulty = True
1819 if "isRecycled" in obj:
1820 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1821 faulty = True
1822 if "isDeleted" in obj and obj['isDeleted'][0].upper() == 'FALSE':
1823 self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1824 faulty = True
1825 if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1826 obj['objectClass'][0] != 'top' or
1827 obj['objectClass'][1] != 'container'):
1828 self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1829 faulty = True
1830 if "systemFlags" not in obj or obj['systemFlags'][0] != '-1946157056':
1831 self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1832 faulty = True
1833 return faulty
1835 def err_deleted_deleted_objects(self, obj):
1836 nmsg = ldb.Message()
1837 nmsg.dn = dn = obj.dn
1839 if "description" not in obj:
1840 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1841 if "showInAdvancedViewOnly" not in obj:
1842 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1843 if "objectCategory" not in obj:
1844 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1845 if "isCriticalSystemObject" not in obj:
1846 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1847 if "isRecycled" in obj:
1848 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1850 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1851 nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1852 nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1854 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1855 % (dn), 'fix_deleted_deleted_objects'):
1856 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1857 return
1859 if self.do_modify(nmsg, ["relax:0"],
1860 "Failed to fix Deleted Objects container %s" % dn):
1861 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1863 def err_replica_locations(self, obj, cross_ref, attr):
1864 nmsg = ldb.Message()
1865 nmsg.dn = cross_ref
1866 target = self.samdb.get_dsServiceName()
1868 if self.samdb.am_rodc():
1869 self.report('Not fixing %s for the RODC' % (attr, obj.dn))
1870 return
1872 if not self.confirm_all('Add yourself to the replica locations for %s?'
1873 % (obj.dn), 'fix_replica_locations'):
1874 self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1875 return
1877 nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1878 if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1879 self.report("Fixed %s for %s" % (attr, obj.dn))
1881 def is_fsmo_role(self, dn):
1882 if dn == self.samdb.domain_dn:
1883 return True
1884 if dn == self.infrastructure_dn:
1885 return True
1886 if dn == self.naming_dn:
1887 return True
1888 if dn == self.schema_dn:
1889 return True
1890 if dn == self.rid_dn:
1891 return True
1893 return False
1895 def calculate_instancetype(self, dn):
1896 instancetype = 0
1897 nc_root = self.samdb.get_nc_root(dn)
1898 if dn == nc_root:
1899 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1900 try:
1901 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1902 except ldb.LdbError, (enum, estr):
1903 if enum != ldb.ERR_NO_SUCH_OBJECT:
1904 raise
1905 else:
1906 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1908 if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1909 instancetype |= dsdb.INSTANCE_TYPE_WRITE
1911 return instancetype
1913 def get_wellknown_sd(self, dn):
1914 for [sd_dn, descriptor_fn] in self.wellknown_sds:
1915 if dn == sd_dn:
1916 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1917 return ndr_unpack(security.descriptor,
1918 descriptor_fn(domain_sid,
1919 name_map=self.name_map))
1921 raise KeyError
1923 def check_object(self, dn, attrs=['*']):
1924 '''check one object'''
1925 if self.verbose:
1926 self.report("Checking object %s" % dn)
1928 # If we modify the pass-by-reference attrs variable, then we get a
1929 # replPropertyMetadata for every object that we check.
1930 attrs = list(attrs)
1931 if "dn" in map(str.lower, attrs):
1932 attrs.append("name")
1933 if "distinguishedname" in map(str.lower, attrs):
1934 attrs.append("name")
1935 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1936 attrs.append("name")
1937 if 'name' in map(str.lower, attrs):
1938 attrs.append(dn.get_rdn_name())
1939 attrs.append("isDeleted")
1940 attrs.append("systemFlags")
1941 need_replPropertyMetaData = False
1942 if '*' in attrs:
1943 need_replPropertyMetaData = True
1944 else:
1945 for a in attrs:
1946 linkID, _ = self.get_attr_linkID_and_reverse_name(a)
1947 if linkID == 0:
1948 continue
1949 if linkID & 1:
1950 continue
1951 need_replPropertyMetaData = True
1952 break
1953 if need_replPropertyMetaData:
1954 attrs.append("replPropertyMetaData")
1955 attrs.append("objectGUID")
1957 try:
1958 sd_flags = 0
1959 sd_flags |= security.SECINFO_OWNER
1960 sd_flags |= security.SECINFO_GROUP
1961 sd_flags |= security.SECINFO_DACL
1962 sd_flags |= security.SECINFO_SACL
1964 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1965 controls=[
1966 "extended_dn:1:1",
1967 "show_recycled:1",
1968 "show_deleted:1",
1969 "sd_flags:1:%d" % sd_flags,
1970 "reveal_internals:0",
1972 attrs=attrs)
1973 except ldb.LdbError, (enum, estr):
1974 if enum == ldb.ERR_NO_SUCH_OBJECT:
1975 if self.in_transaction:
1976 self.report("ERROR: Object %s disappeared during check" % dn)
1977 return 1
1978 return 0
1979 raise
1980 if len(res) != 1:
1981 self.report("ERROR: Object %s failed to load during check" % dn)
1982 return 1
1983 obj = res[0]
1984 error_count = 0
1985 set_attrs_from_md = set()
1986 set_attrs_seen = set()
1987 got_repl_property_meta_data = False
1988 got_objectclass = False
1990 nc_dn = self.samdb.get_nc_root(obj.dn)
1991 try:
1992 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
1993 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
1994 except KeyError:
1995 # We have no deleted objects DN for schema, and we check for this above for the other
1996 # NCs
1997 deleted_objects_dn = None
2000 object_rdn_attr = None
2001 object_rdn_val = None
2002 name_val = None
2003 isDeleted = False
2004 systemFlags = 0
2006 for attrname in obj:
2007 if attrname == 'dn' or attrname == "distinguishedName":
2008 continue
2010 if str(attrname).lower() == 'objectclass':
2011 got_objectclass = True
2013 if str(attrname).lower() == "name":
2014 if len(obj[attrname]) != 1:
2015 error_count += 1
2016 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2017 (len(obj[attrname]), attrname, str(obj.dn)))
2018 else:
2019 name_val = obj[attrname][0]
2021 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
2022 object_rdn_attr = attrname
2023 if len(obj[attrname]) != 1:
2024 error_count += 1
2025 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2026 (len(obj[attrname]), attrname, str(obj.dn)))
2027 else:
2028 object_rdn_val = obj[attrname][0]
2030 if str(attrname).lower() == 'isdeleted':
2031 if obj[attrname][0] != "FALSE":
2032 isDeleted = True
2034 if str(attrname).lower() == 'systemflags':
2035 systemFlags = int(obj[attrname][0])
2037 if str(attrname).lower() == 'replpropertymetadata':
2038 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
2039 error_count += 1
2040 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
2041 # We don't continue, as we may also have other fixes for this attribute
2042 # based on what other attributes we see.
2044 try:
2045 (set_attrs_from_md, list_attid_from_md, wrong_attids) \
2046 = self.process_metadata(dn, obj[attrname])
2047 except KeyError:
2048 error_count += 1
2049 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
2050 continue
2052 if len(set_attrs_from_md) < len(list_attid_from_md) \
2053 or len(wrong_attids) > 0 \
2054 or sorted(list_attid_from_md) != list_attid_from_md:
2055 error_count +=1
2056 self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname], wrong_attids)
2058 else:
2059 # Here we check that the first attid is 0
2060 # (objectClass).
2061 if list_attid_from_md[0] != 0:
2062 error_count += 1
2063 self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2064 (attrname, str(dn)))
2066 got_repl_property_meta_data = True
2067 continue
2069 if str(attrname).lower() == 'ntsecuritydescriptor':
2070 (sd, sd_broken) = self.process_sd(dn, obj)
2071 if sd_broken is not None:
2072 self.err_wrong_sd(dn, sd, sd_broken)
2073 error_count += 1
2074 continue
2076 if sd.owner_sid is None or sd.group_sid is None:
2077 self.err_missing_sd_owner(dn, sd)
2078 error_count += 1
2079 continue
2081 if self.reset_well_known_acls:
2082 try:
2083 well_known_sd = self.get_wellknown_sd(dn)
2084 except KeyError:
2085 continue
2087 current_sd = ndr_unpack(security.descriptor,
2088 str(obj[attrname][0]))
2090 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
2091 if diff != "":
2092 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
2093 error_count += 1
2094 continue
2095 continue
2097 if str(attrname).lower() == 'objectclass':
2098 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
2099 # Do not consider the attribute incorrect if:
2100 # - The sorted (alphabetically) list is the same, inclding case
2101 # - The first and last elements are the same
2103 # This avoids triggering an error due to
2104 # non-determinism in the sort routine in (at least)
2105 # 4.3 and earlier, and the fact that any AUX classes
2106 # in these attributes are also not sorted when
2107 # imported from Windows (they are just in the reverse
2108 # order of last set)
2109 if sorted(normalised) != sorted(obj[attrname]) \
2110 or normalised[0] != obj[attrname][0] \
2111 or normalised[-1] != obj[attrname][-1]:
2112 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
2113 error_count += 1
2114 continue
2116 if str(attrname).lower() == 'userparameters':
2117 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
2118 error_count += 1
2119 self.err_short_userParameters(obj, attrname, obj[attrname])
2120 continue
2122 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2123 # This is the correct, normal prefix
2124 continue
2126 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
2127 # this is the typical prefix from a windows migration
2128 error_count += 1
2129 self.err_base64_userParameters(obj, attrname, obj[attrname])
2130 continue
2132 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':
2133 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2134 error_count += 1
2135 self.err_utf8_userParameters(obj, attrname, obj[attrname])
2136 continue
2138 elif len(obj[attrname][0]) % 2 != 0:
2139 # This is a value that isn't even in length
2140 error_count += 1
2141 self.err_odd_userParameters(obj, attrname, obj[attrname])
2142 continue
2144 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':
2145 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2146 error_count += 1
2147 self.err_doubled_userParameters(obj, attrname, obj[attrname])
2148 continue
2150 if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2151 if obj[attrname][0] in self.attribute_or_class_ids:
2152 error_count += 1
2153 self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2154 % (attrname, obj.dn, obj[attrname][0]))
2155 else:
2156 self.attribute_or_class_ids.add(obj[attrname][0])
2158 # check for empty attributes
2159 for val in obj[attrname]:
2160 if val == '':
2161 self.err_empty_attribute(dn, attrname)
2162 error_count += 1
2163 continue
2165 # get the syntax oid for the attribute, so we can can have
2166 # special handling for some specific attribute types
2167 try:
2168 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2169 except Exception, msg:
2170 self.err_unknown_attribute(obj, attrname)
2171 error_count += 1
2172 continue
2174 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2176 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2177 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2178 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2179 and not linkID):
2180 set_attrs_seen.add(str(attrname).lower())
2182 if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2183 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
2184 # it's some form of DN, do specialised checking on those
2185 error_count += self.check_dn(obj, attrname, syntax_oid)
2186 else:
2188 values = set()
2189 # check for incorrectly normalised attributes
2190 for val in obj[attrname]:
2191 values.add(str(val))
2193 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2194 if len(normalised) != 1 or normalised[0] != val:
2195 self.err_normalise_mismatch(dn, attrname, obj[attrname])
2196 error_count += 1
2197 break
2199 if len(obj[attrname]) != len(values):
2200 self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2201 error_count += 1
2202 break
2204 if str(attrname).lower() == "instancetype":
2205 calculated_instancetype = self.calculate_instancetype(dn)
2206 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
2207 error_count += 1
2208 self.err_wrong_instancetype(obj, calculated_instancetype)
2210 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
2211 error_count += 1
2212 self.err_missing_objectclass(dn)
2214 if ("*" in attrs or "name" in map(str.lower, attrs)):
2215 if name_val is None:
2216 error_count += 1
2217 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2218 if object_rdn_attr is None:
2219 error_count += 1
2220 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2222 if name_val is not None:
2223 parent_dn = None
2224 if isDeleted:
2225 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2226 parent_dn = deleted_objects_dn
2227 if parent_dn is None:
2228 parent_dn = obj.dn.parent()
2229 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2230 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2232 if obj.dn == deleted_objects_dn:
2233 expected_dn = obj.dn
2235 if expected_dn != obj.dn:
2236 error_count += 1
2237 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
2238 elif obj.dn.get_rdn_value() != object_rdn_val:
2239 error_count += 1
2240 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
2242 show_dn = True
2243 if got_repl_property_meta_data:
2244 if obj.dn == deleted_objects_dn:
2245 isDeletedAttId = 131120
2246 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2248 expectedTimeDo = 2650466015990000000
2249 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
2250 if originating != expectedTimeDo:
2251 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2252 nmsg = ldb.Message()
2253 nmsg.dn = dn
2254 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2255 error_count += 1
2256 self.samdb.modify(nmsg, controls=["provision:0"])
2258 else:
2259 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2261 for att in set_attrs_seen.difference(set_attrs_from_md):
2262 if show_dn:
2263 self.report("On object %s" % dn)
2264 show_dn = False
2265 error_count += 1
2266 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2267 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2268 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2269 continue
2270 self.fix_metadata(obj, att)
2272 if self.is_fsmo_role(dn):
2273 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
2274 self.err_no_fsmoRoleOwner(obj)
2275 error_count += 1
2277 try:
2278 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2279 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2280 controls=["show_recycled:1", "show_deleted:1"])
2281 except ldb.LdbError, (enum, estr):
2282 if enum == ldb.ERR_NO_SUCH_OBJECT:
2283 self.err_missing_parent(obj)
2284 error_count += 1
2285 else:
2286 raise
2288 if dn in self.deleted_objects_containers and '*' in attrs:
2289 if self.is_deleted_deleted_objects(obj):
2290 self.err_deleted_deleted_objects(obj)
2291 error_count += 1
2293 for (dns_part, msg) in self.dns_partitions:
2294 if dn == dns_part and 'repsFrom' in obj:
2295 location = "msDS-NC-Replica-Locations"
2296 if self.samdb.am_rodc():
2297 location = "msDS-NC-RO-Replica-Locations"
2299 if location not in msg:
2300 # There are no replica locations!
2301 self.err_replica_locations(obj, msg.dn, location)
2302 error_count += 1
2303 continue
2305 found = False
2306 for loc in msg[location]:
2307 if loc == self.samdb.get_dsServiceName():
2308 found = True
2309 if not found:
2310 # This DC is not in the replica locations
2311 self.err_replica_locations(obj, msg.dn, location)
2312 error_count += 1
2314 if dn == self.server_ref_dn:
2315 # Check we have a valid RID Set
2316 if "*" in attrs or "rIDSetReferences" in attrs:
2317 if "rIDSetReferences" not in obj:
2318 # NO RID SET reference
2319 # We are RID master, allocate it.
2320 error_count += 1
2322 if self.is_rid_master:
2323 # Allocate a RID Set
2324 if self.confirm_all('Allocate the missing RID set for RID master?',
2325 'fix_missing_rid_set_master'):
2327 # We don't have auto-transaction logic on
2328 # extended operations, so we have to do it
2329 # here.
2331 self.samdb.transaction_start()
2333 try:
2334 self.samdb.create_own_rid_set()
2336 except:
2337 self.samdb.transaction_cancel()
2338 raise
2340 self.samdb.transaction_commit()
2343 elif not self.samdb.am_rodc():
2344 self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2347 # Check some details of our own RID Set
2348 if dn == self.rid_set_dn:
2349 res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2350 attrs=["rIDAllocationPool",
2351 "rIDPreviousAllocationPool",
2352 "rIDUsedPool",
2353 "rIDNextRID"])
2354 if "rIDAllocationPool" not in res[0]:
2355 self.report("No rIDAllocationPool found in %s" % dn)
2356 error_count += 1
2357 else:
2358 next_pool = int(res[0]["rIDAllocationPool"][0])
2360 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2361 low = 0x00000000FFFFFFFF & next_pool
2363 if high <= low:
2364 self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2365 error_count += 1
2367 if "rIDNextRID" in res[0]:
2368 next_free_rid = int(res[0]["rIDNextRID"][0])
2369 else:
2370 next_free_rid = 0
2372 if next_free_rid == 0:
2373 next_free_rid = low
2374 else:
2375 next_free_rid += 1
2377 # Check the remainder of this pool for conflicts. If
2378 # ridalloc_allocate_rid() moves to a new pool, this
2379 # will be above high, so we will stop.
2380 while next_free_rid <= high:
2381 sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2382 try:
2383 res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2384 attrs=[])
2385 except ldb.LdbError, (enum, estr):
2386 if enum != ldb.ERR_NO_SUCH_OBJECT:
2387 raise
2388 res = None
2389 if res is not None:
2390 self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2391 error_count += 1
2393 if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2394 % (sid, dn),
2395 'fix_sid_rid_set_conflict'):
2396 self.samdb.transaction_start()
2398 # This will burn RIDs, which will move
2399 # past the conflict. We then check again
2400 # to see if the new RID conflicts, until
2401 # the end of the current pool. We don't
2402 # look at the next pool to avoid burning
2403 # all RIDs in one go in some strange
2404 # failure case.
2405 try:
2406 while True:
2407 allocated_rid = self.samdb.allocate_rid()
2408 if allocated_rid >= next_free_rid:
2409 next_free_rid = allocated_rid + 1
2410 break
2411 except:
2412 self.samdb.transaction_cancel()
2413 raise
2415 self.samdb.transaction_commit()
2416 else:
2417 break
2418 else:
2419 next_free_rid += 1
2422 return error_count
2424 ################################################################
2425 # check special @ROOTDSE attributes
2426 def check_rootdse(self):
2427 '''check the @ROOTDSE special object'''
2428 dn = ldb.Dn(self.samdb, '@ROOTDSE')
2429 if self.verbose:
2430 self.report("Checking object %s" % dn)
2431 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2432 if len(res) != 1:
2433 self.report("Object %s disappeared during check" % dn)
2434 return 1
2435 obj = res[0]
2436 error_count = 0
2438 # check that the dsServiceName is in GUID form
2439 if not 'dsServiceName' in obj:
2440 self.report('ERROR: dsServiceName missing in @ROOTDSE')
2441 return error_count+1
2443 if not obj['dsServiceName'][0].startswith('<GUID='):
2444 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2445 error_count += 1
2446 if not self.confirm('Change dsServiceName to GUID form?'):
2447 return error_count
2448 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
2449 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2450 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2451 m = ldb.Message()
2452 m.dn = dn
2453 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2454 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2455 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2456 self.report("Changed dsServiceName to GUID form")
2457 return error_count
2460 ###############################################
2461 # re-index the database
2462 def reindex_database(self):
2463 '''re-index the whole database'''
2464 m = ldb.Message()
2465 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2466 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2467 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2468 return self.do_modify(m, [], 're-indexed database', validate=False)
2470 ###############################################
2471 # reset @MODULES
2472 def reset_modules(self):
2473 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2474 m = ldb.Message()
2475 m.dn = ldb.Dn(self.samdb, "@MODULES")
2476 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2477 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)