dbcheck: split out check_duplicate_links from check_dn
[Samba.git] / python / samba / dbchecker.py
blob9185a1d1bed0518b5e3e83c838a52af7820ed486
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.recover_all_forward_links = False
69 self.fix_rmd_flags = False
70 self.fix_ntsecuritydescriptor = False
71 self.fix_ntsecuritydescriptor_owner_group = False
72 self.seize_fsmo_role = False
73 self.move_to_lost_and_found = False
74 self.fix_instancetype = False
75 self.fix_replmetadata_zero_invocationid = False
76 self.fix_replmetadata_duplicate_attid = False
77 self.fix_replmetadata_wrong_attid = False
78 self.fix_replmetadata_unsorted_attid = False
79 self.fix_deleted_deleted_objects = False
80 self.fix_incorrect_deleted_objects = False
81 self.fix_dn = False
82 self.fix_base64_userparameters = False
83 self.fix_utf8_userparameters = False
84 self.fix_doubled_userparameters = False
85 self.fix_sid_rid_set_conflict = False
86 self.reset_well_known_acls = reset_well_known_acls
87 self.reset_all_well_known_acls = False
88 self.in_transaction = in_transaction
89 self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
90 self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
91 self.schema_dn = samdb.get_schema_basedn()
92 self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
93 self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
94 self.class_schemaIDGUID = {}
95 self.wellknown_sds = get_wellknown_sds(self.samdb)
96 self.fix_all_missing_objectclass = False
97 self.fix_missing_deleted_objects = False
98 self.fix_replica_locations = False
99 self.fix_missing_rid_set_master = False
101 self.dn_set = set()
102 self.link_id_cache = {}
103 self.name_map = {}
104 try:
105 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE,
106 attrs=["objectSid"])
107 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
108 self.name_map['DnsAdmins'] = str(dnsadmins_sid)
109 except ldb.LdbError, (enum, estr):
110 if enum != ldb.ERR_NO_SUCH_OBJECT:
111 raise
112 pass
114 self.system_session_info = system_session()
115 self.admin_session_info = admin_session(None, samdb.get_domain_sid())
117 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
118 if "msDS-hasMasterNCs" in res[0]:
119 self.write_ncs = res[0]["msDS-hasMasterNCs"]
120 else:
121 # If the Forest Level is less than 2003 then there is no
122 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
123 # no need to merge as all the NCs that are in hasMasterNCs must
124 # also be in msDS-hasMasterNCs (but not the opposite)
125 if "hasMasterNCs" in res[0]:
126 self.write_ncs = res[0]["hasMasterNCs"]
127 else:
128 self.write_ncs = None
130 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
131 self.deleted_objects_containers = []
132 self.ncs_lacking_deleted_containers = []
133 self.dns_partitions = []
134 try:
135 self.ncs = res[0]["namingContexts"]
136 except KeyError:
137 pass
138 except IndexError:
139 pass
141 for nc in self.ncs:
142 try:
143 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc),
144 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
145 self.deleted_objects_containers.append(dn)
146 except KeyError:
147 self.ncs_lacking_deleted_containers.append(ldb.Dn(self.samdb, nc))
149 domaindns_zone = 'DC=DomainDnsZones,%s' % self.samdb.get_default_basedn()
150 forestdns_zone = 'DC=ForestDnsZones,%s' % self.samdb.get_root_basedn()
151 domain = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
152 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
153 base=self.samdb.get_partitions_dn(),
154 expression="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone)
155 if len(domain) == 1:
156 self.dns_partitions.append((ldb.Dn(self.samdb, forestdns_zone), domain[0]))
158 forest = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
159 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
160 base=self.samdb.get_partitions_dn(),
161 expression="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone)
162 if len(forest) == 1:
163 self.dns_partitions.append((ldb.Dn(self.samdb, domaindns_zone), forest[0]))
165 fsmo_dn = ldb.Dn(self.samdb, "CN=RID Manager$,CN=System," + self.samdb.domain_dn())
166 rid_master = get_fsmo_roleowner(self.samdb, fsmo_dn, "rid")
167 if ldb.Dn(self.samdb, self.samdb.get_dsServiceName()) == rid_master:
168 self.is_rid_master = True
169 else:
170 self.is_rid_master = False
172 # To get your rid set
173 # 1. Get server name
174 res = self.samdb.search(base=ldb.Dn(self.samdb, self.samdb.get_serverName()),
175 scope=ldb.SCOPE_BASE, attrs=["serverReference"])
176 # 2. Get server reference
177 self.server_ref_dn = ldb.Dn(self.samdb, res[0]['serverReference'][0])
179 # 3. Get RID Set
180 res = self.samdb.search(base=self.server_ref_dn,
181 scope=ldb.SCOPE_BASE, attrs=['rIDSetReferences'])
182 if "rIDSetReferences" in res[0]:
183 self.rid_set_dn = ldb.Dn(self.samdb, res[0]['rIDSetReferences'][0])
184 else:
185 self.rid_set_dn = None
187 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
188 '''perform a database check, returning the number of errors found'''
189 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
190 self.report('Checking %u objects' % len(res))
191 error_count = 0
193 error_count += self.check_deleted_objects_containers()
195 self.attribute_or_class_ids = set()
197 for object in res:
198 self.dn_set.add(str(object.dn))
199 error_count += self.check_object(object.dn, attrs=attrs)
201 if DN is None:
202 error_count += self.check_rootdse()
204 if error_count != 0 and not self.fix:
205 self.report("Please use --fix to fix these errors")
207 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
208 return error_count
210 def check_deleted_objects_containers(self):
211 """This function only fixes conflicts on the Deleted Objects
212 containers, not the attributes"""
213 error_count = 0
214 for nc in self.ncs_lacking_deleted_containers:
215 if nc == self.schema_dn:
216 continue
217 error_count += 1
218 self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc)
219 if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'):
220 continue
222 dn = ldb.Dn(self.samdb, "CN=Deleted Objects")
223 dn.add_base(nc)
225 conflict_dn = None
226 try:
227 # If something already exists here, add a conflict
228 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[],
229 controls=["show_deleted:1", "extended_dn:1:1",
230 "show_recycled:1", "reveal_internals:0"])
231 if len(res) != 0:
232 guid = res[0].dn.get_extended_component("GUID")
233 conflict_dn = ldb.Dn(self.samdb,
234 "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid)))
235 conflict_dn.add_base(nc)
237 except ldb.LdbError, (enum, estr):
238 if enum == ldb.ERR_NO_SUCH_OBJECT:
239 pass
240 else:
241 self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr)
242 return 1
244 if conflict_dn is not None:
245 try:
246 self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"])
247 except ldb.LdbError, (enum, estr):
248 self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr))
249 return 1
251 # Refresh wellKnownObjects links
252 res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE,
253 attrs=['wellKnownObjects'],
254 controls=["show_deleted:1", "extended_dn:0",
255 "show_recycled:1", "reveal_internals:0"])
256 if len(res) != 1:
257 self.report("wellKnownObjects was not found for NC %s" % nc)
258 return 1
260 # Prevent duplicate deleted objects containers just in case
261 wko = res[0]["wellKnownObjects"]
262 listwko = []
263 proposed_objectguid = None
264 for o in wko:
265 dsdb_dn = dsdb_Dn(self.samdb, o, dsdb.DSDB_SYNTAX_BINARY_DN)
266 if self.is_deleted_objects_dn(dsdb_dn):
267 self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o)
268 # We really want to put this back in the same spot
269 # as the original one, so that on replication we
270 # merge, rather than conflict.
271 proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID")
272 listwko.append(o)
274 if proposed_objectguid is not None:
275 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid))
276 else:
277 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
278 listwko.append('%s:%s' % (wko_prefix, dn))
279 guid_suffix = ""
281 # Insert a brand new Deleted Objects container
282 self.samdb.add_ldif("""dn: %s
283 objectClass: top
284 objectClass: container
285 description: Container for deleted objects
286 isDeleted: TRUE
287 isCriticalSystemObject: TRUE
288 showInAdvancedViewOnly: TRUE
289 systemFlags: -1946157056%s""" % (dn, guid_suffix),
290 controls=["relax:0", "provision:0"])
292 delta = ldb.Message()
293 delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"]))
294 delta["wellKnownObjects"] = ldb.MessageElement(listwko,
295 ldb.FLAG_MOD_REPLACE,
296 "wellKnownObjects")
298 # Insert the link to the brand new container
299 if self.do_modify(delta, ["relax:0"],
300 "NC %s lacks Deleted Objects WKGUID" % nc,
301 validate=False):
302 self.report("Added %s well known guid link" % dn)
304 self.deleted_objects_containers.append(dn)
306 return error_count
308 def report(self, msg):
309 '''print a message unless quiet is set'''
310 if not self.quiet:
311 print(msg)
313 def confirm(self, msg, allow_all=False, forced=False):
314 '''confirm a change'''
315 if not self.fix:
316 return False
317 if self.quiet:
318 return self.yes
319 if self.yes:
320 forced = True
321 return common.confirm(msg, forced=forced, allow_all=allow_all)
323 ################################################################
324 # a local confirm function with support for 'all'
325 def confirm_all(self, msg, all_attr):
326 '''confirm a change with support for "all" '''
327 if not self.fix:
328 return False
329 if getattr(self, all_attr) == 'NONE':
330 return False
331 if getattr(self, all_attr) == 'ALL':
332 forced = True
333 else:
334 forced = self.yes
335 if self.quiet:
336 return forced
337 c = common.confirm(msg, forced=forced, allow_all=True)
338 if c == 'ALL':
339 setattr(self, all_attr, 'ALL')
340 return True
341 if c == 'NONE':
342 setattr(self, all_attr, 'NONE')
343 return False
344 return c
346 def do_delete(self, dn, controls, msg):
347 '''delete dn with optional verbose output'''
348 if self.verbose:
349 self.report("delete DN %s" % dn)
350 try:
351 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
352 self.samdb.delete(dn, controls=controls)
353 except Exception, err:
354 if self.in_transaction:
355 raise CommandError("%s : %s" % (msg, err))
356 self.report("%s : %s" % (msg, err))
357 return False
358 return True
360 def do_modify(self, m, controls, msg, validate=True):
361 '''perform a modify with optional verbose output'''
362 if self.verbose:
363 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
364 try:
365 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
366 self.samdb.modify(m, controls=controls, validate=validate)
367 except Exception, err:
368 if self.in_transaction:
369 raise CommandError("%s : %s" % (msg, err))
370 self.report("%s : %s" % (msg, err))
371 return False
372 return True
374 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
375 '''perform a modify with optional verbose output'''
376 if self.verbose:
377 self.report("""dn: %s
378 changeType: modrdn
379 newrdn: %s
380 deleteOldRdn: 1
381 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
382 try:
383 to_dn = to_rdn + to_base
384 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
385 self.samdb.rename(from_dn, to_dn, controls=controls)
386 except Exception, err:
387 if self.in_transaction:
388 raise CommandError("%s : %s" % (msg, err))
389 self.report("%s : %s" % (msg, err))
390 return False
391 return True
393 def get_attr_linkID_and_reverse_name(self, attrname):
394 if attrname in self.link_id_cache:
395 return self.link_id_cache[attrname]
396 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
397 if linkID:
398 revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
399 else:
400 revname = None
401 self.link_id_cache[attrname] = (linkID, revname)
402 return linkID, revname
404 def err_empty_attribute(self, dn, attrname):
405 '''fix empty attributes'''
406 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
407 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
408 self.report("Not fixing empty attribute %s" % attrname)
409 return
411 m = ldb.Message()
412 m.dn = dn
413 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
414 if self.do_modify(m, ["relax:0", "show_recycled:1"],
415 "Failed to remove empty attribute %s" % attrname, validate=False):
416 self.report("Removed empty attribute %s" % attrname)
418 def err_normalise_mismatch(self, dn, attrname, values):
419 '''fix attribute normalisation errors'''
420 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
421 mod_list = []
422 for val in values:
423 normalised = self.samdb.dsdb_normalise_attributes(
424 self.samdb_schema, attrname, [val])
425 if len(normalised) != 1:
426 self.report("Unable to normalise value '%s'" % val)
427 mod_list.append((val, ''))
428 elif (normalised[0] != val):
429 self.report("value '%s' should be '%s'" % (val, normalised[0]))
430 mod_list.append((val, normalised[0]))
431 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
432 self.report("Not fixing attribute %s" % attrname)
433 return
435 m = ldb.Message()
436 m.dn = dn
437 for i in range(0, len(mod_list)):
438 (val, nval) = mod_list[i]
439 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
440 if nval != '':
441 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
442 attrname)
444 if self.do_modify(m, ["relax:0", "show_recycled:1"],
445 "Failed to normalise attribute %s" % attrname,
446 validate=False):
447 self.report("Normalised attribute %s" % attrname)
449 def err_normalise_mismatch_replace(self, dn, attrname, values):
450 '''fix attribute normalisation errors'''
451 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
452 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
453 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
454 if list(normalised) == values:
455 return
456 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
457 self.report("Not fixing attribute '%s'" % attrname)
458 return
460 m = ldb.Message()
461 m.dn = dn
462 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
464 if self.do_modify(m, ["relax:0", "show_recycled:1"],
465 "Failed to normalise attribute %s" % attrname,
466 validate=False):
467 self.report("Normalised attribute %s" % attrname)
469 def err_duplicate_values(self, dn, attrname, dup_values, values):
470 '''fix attribute normalisation errors'''
471 self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
472 self.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values), ','.join(values)))
473 if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
474 self.report("Not fixing attribute '%s'" % attrname)
475 return
477 m = ldb.Message()
478 m.dn = dn
479 m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
481 if self.do_modify(m, ["relax:0", "show_recycled:1"],
482 "Failed to remove duplicate value on attribute %s" % attrname,
483 validate=False):
484 self.report("Removed duplicate value on attribute %s" % attrname)
486 def is_deleted_objects_dn(self, dsdb_dn):
487 '''see if a dsdb_Dn is the special Deleted Objects DN'''
488 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
490 def err_missing_objectclass(self, dn):
491 """handle object without objectclass"""
492 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)))
493 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'):
494 self.report("Not deleting object with missing objectclass '%s'" % dn)
495 return
496 if self.do_delete(dn, ["relax:0"],
497 "Failed to remove DN %s" % dn):
498 self.report("Removed DN %s" % dn)
500 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False):
501 """handle a DN pointing to a deleted object"""
502 if not remove_plausible:
503 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
504 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
505 if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
506 self.report("Not removing")
507 return
508 else:
509 self.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
510 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
511 if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
512 self.report("Not removing")
513 return
515 m = ldb.Message()
516 m.dn = dn
517 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
518 if self.do_modify(m, ["show_recycled:1",
519 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
520 "Failed to remove deleted DN attribute %s" % attrname):
521 self.report("Removed deleted DN on attribute %s" % attrname)
523 def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
524 """handle a missing target DN (if specified, GUID form can't be found,
525 and otherwise DN string form can't be found)"""
526 # check if its a backlink
527 linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
528 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
530 linkID, reverse_link_name \
531 = self.get_attr_linkID_and_reverse_name(attrname)
532 if reverse_link_name is not None:
533 self.report("WARNING: no target object found for GUID "
534 "component for one-way forward link "
535 "%s in object "
536 "%s - %s" % (attrname, dn, val))
537 self.report("Not removing dangling forward link")
538 return 0
540 nc_root = self.samdb.get_nc_root(dn)
541 target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn)
542 if nc_root != target_nc_root:
543 # We don't bump the error count as Samba produces these
544 # in normal operation
545 self.report("WARNING: no target object found for GUID "
546 "component for cross-partition link "
547 "%s in object "
548 "%s - %s" % (attrname, dn, val))
549 self.report("Not removing dangling one-way "
550 "cross-partition link "
551 "(we might be mid-replication)")
552 return 0
554 # Due to our link handling one-way links pointing to
555 # missing objects are plausible.
557 # We don't bump the error count as Samba produces these
558 # in normal operation
559 self.report("WARNING: no target object found for GUID "
560 "component for DN value %s in object "
561 "%s - %s" % (attrname, dn, val))
562 self.err_deleted_dn(dn, attrname, val,
563 dsdb_dn, dsdb_dn, True)
564 return 0
566 # We bump the error count here, as we should have deleted this
567 self.report("ERROR: no target object found for GUID "
568 "component for link %s in object "
569 "%s - %s" % (attrname, dn, val))
570 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
571 return 1
573 def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
574 """handle a missing GUID extended DN component"""
575 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
576 controls=["extended_dn:1:1", "show_recycled:1"]
577 try:
578 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
579 attrs=[], controls=controls)
580 except ldb.LdbError, (enum, estr):
581 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
582 if enum != ldb.ERR_NO_SUCH_OBJECT:
583 raise
584 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
585 return
586 if len(res) == 0:
587 self.report("unable to find object for DN %s" % dsdb_dn.dn)
588 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
589 return
590 dsdb_dn.dn = res[0].dn
592 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
593 self.report("Not fixing %s" % errstr)
594 return
595 m = ldb.Message()
596 m.dn = dn
597 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
598 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
600 if self.do_modify(m, ["show_recycled:1"],
601 "Failed to fix %s on attribute %s" % (errstr, attrname)):
602 self.report("Fixed %s on attribute %s" % (errstr, attrname))
604 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
605 """handle an incorrect binary DN component"""
606 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
607 controls=["extended_dn:1:1", "show_recycled:1"]
609 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
610 self.report("Not fixing %s" % errstr)
611 return
612 m = ldb.Message()
613 m.dn = dn
614 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
615 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
617 if self.do_modify(m, ["show_recycled:1"],
618 "Failed to fix %s on attribute %s" % (errstr, attrname)):
619 self.report("Fixed %s on attribute %s" % (errstr, attrname))
621 def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn):
622 """handle a DN string being incorrect"""
623 self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val))
624 dsdb_dn.dn = correct_dn
626 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
627 'fix_all_old_dn_string_component_mismatch'):
628 self.report("Not fixing old string component")
629 return
630 m = ldb.Message()
631 m.dn = dn
632 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
633 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
634 if self.do_modify(m, ["show_recycled:1"],
635 "Failed to fix old DN string on attribute %s" % (attrname)):
636 self.report("Fixed old DN string on attribute %s" % (attrname))
638 def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
639 """handle a DN string being incorrect"""
640 self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
641 dsdb_dn.dn = correct_dn
643 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
644 'fix_all_%s_dn_component_mismatch' % mismatch_type):
645 self.report("Not fixing %s component mismatch" % mismatch_type)
646 return
647 m = ldb.Message()
648 m.dn = dn
649 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
650 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
651 if self.do_modify(m, ["show_recycled:1"],
652 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
653 self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
655 def err_unknown_attribute(self, obj, attrname):
656 '''handle an unknown attribute error'''
657 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
658 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
659 self.report("Not removing %s" % attrname)
660 return
661 m = ldb.Message()
662 m.dn = obj.dn
663 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
664 if self.do_modify(m, ["relax:0", "show_recycled:1"],
665 "Failed to remove unknown attribute %s" % attrname):
666 self.report("Removed unknown attribute %s" % (attrname))
668 def err_undead_linked_attribute(self, obj, attrname, val):
669 '''handle a link that should not be there on a deleted object'''
670 self.report("ERROR: linked attribute '%s' to '%s' is present on "
671 "deleted object %s" % (attrname, val, obj.dn))
672 if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
673 self.report("Not removing linked attribute %s" % attrname)
674 return
675 m = ldb.Message()
676 m.dn = obj.dn
677 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
679 if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
680 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
681 "Failed to delete forward link %s" % attrname):
682 self.report("Fixed undead forward link %s" % (attrname))
684 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
685 '''handle a missing backlink value'''
686 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
687 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
688 self.report("Not fixing missing backlink %s" % backlink_name)
689 return
690 m = ldb.Message()
691 m.dn = target_dn
692 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
693 if self.do_modify(m, ["show_recycled:1", "relax:0"],
694 "Failed to fix missing backlink %s" % backlink_name):
695 self.report("Fixed missing backlink %s" % (backlink_name))
697 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
698 '''handle a incorrect RMD_FLAGS value'''
699 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
700 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()))
701 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
702 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
703 return
704 m = ldb.Message()
705 m.dn = obj.dn
706 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
707 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
708 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
709 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
711 def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val,
712 target_dn, forward_attr, forward_syntax):
713 '''handle a orphaned backlink value'''
714 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn))
715 if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'):
716 self.report("Not removing orphaned backlink %s" % backlink_attr)
717 return
718 m = ldb.Message()
719 m.dn = obj_dn
720 m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr)
721 if self.do_modify(m, ["show_recycled:1", "relax:0"],
722 "Failed to fix orphaned backlink %s" % backlink_attr):
723 self.report("Fixed orphaned backlink %s" % (backlink_attr))
725 def err_recover_forward_links(self, obj, forward_attr, forward_vals):
726 '''handle a duplicate links value'''
728 self.report("RECHECK: 'Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn))
730 if not self.confirm_all("Commit fixes for (duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'):
731 self.report("Not fixing corrupted (duplicate) forward links in attribute '%s' of '%s'" % (
732 forward_attr, obj.dn))
733 return
734 m = ldb.Message()
735 m.dn = obj.dn
736 m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr)
737 if self.do_modify(m, ["local_oid:1.3.6.1.4.1.7165.4.3.19.2:1"],
738 "Failed to fix duplicate links in attribute '%s'" % forward_attr):
739 self.report("Fixed duplicate links in attribute '%s'" % (forward_attr))
741 def err_no_fsmoRoleOwner(self, obj):
742 '''handle a missing fSMORoleOwner'''
743 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
744 res = self.samdb.search("",
745 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
746 assert len(res) == 1
747 serviceName = res[0]["dsServiceName"][0]
748 if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
749 self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
750 return
751 m = ldb.Message()
752 m.dn = obj.dn
753 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
754 if self.do_modify(m, [],
755 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
756 self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
758 def err_missing_parent(self, obj):
759 '''handle a missing parent'''
760 self.report("ERROR: parent object not found for %s" % (obj.dn))
761 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
762 self.report('Not moving object %s into LostAndFound' % (obj.dn))
763 return
765 keep_transaction = False
766 self.samdb.transaction_start()
767 try:
768 nc_root = self.samdb.get_nc_root(obj.dn);
769 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
770 new_dn = ldb.Dn(self.samdb, str(obj.dn))
771 new_dn.remove_base_components(len(new_dn) - 1)
772 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
773 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
774 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
776 m = ldb.Message()
777 m.dn = obj.dn
778 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
780 if self.do_modify(m, [],
781 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
782 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
783 keep_transaction = True
784 except:
785 self.samdb.transaction_cancel()
786 raise
788 if keep_transaction:
789 self.samdb.transaction_commit()
790 else:
791 self.samdb.transaction_cancel()
793 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
794 '''handle a wrong dn'''
796 new_rdn = ldb.Dn(self.samdb, str(new_dn))
797 new_rdn.remove_base_components(len(new_rdn) - 1)
798 new_parent = new_dn.parent()
800 attributes = ""
801 if rdn_val != name_val:
802 attributes += "%s=%r " % (rdn_attr, rdn_val)
803 attributes += "name=%r" % (name_val)
805 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
806 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
807 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
808 return
810 if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
811 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
812 self.report("Renamed %s into %s" % (obj.dn, new_dn))
814 def err_wrong_instancetype(self, obj, calculated_instancetype):
815 '''handle a wrong instanceType'''
816 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
817 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
818 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
819 return
821 m = ldb.Message()
822 m.dn = obj.dn
823 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
824 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
825 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
826 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
828 def err_short_userParameters(self, obj, attrname, value):
829 # This is a truncated userParameters due to a pre 4.1 replication bug
830 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)))
832 def err_base64_userParameters(self, obj, attrname, value):
833 '''handle a wrong userParameters'''
834 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
835 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
836 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
837 return
839 m = ldb.Message()
840 m.dn = obj.dn
841 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
842 if self.do_modify(m, [],
843 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
844 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
846 def err_utf8_userParameters(self, obj, attrname, value):
847 '''handle a wrong userParameters'''
848 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
849 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
850 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
851 return
853 m = ldb.Message()
854 m.dn = obj.dn
855 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
856 ldb.FLAG_MOD_REPLACE, 'userParameters')
857 if self.do_modify(m, [],
858 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
859 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
861 def err_doubled_userParameters(self, obj, attrname, value):
862 '''handle a wrong userParameters'''
863 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
864 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
865 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
866 return
868 m = ldb.Message()
869 m.dn = obj.dn
870 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
871 ldb.FLAG_MOD_REPLACE, 'userParameters')
872 if self.do_modify(m, [],
873 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
874 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
876 def err_odd_userParameters(self, obj, attrname):
877 # This is a truncated userParameters due to a pre 4.1 replication bug
878 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)))
880 def find_revealed_link(self, dn, attrname, guid):
881 '''return a revealed link in an object'''
882 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
883 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
884 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
885 for val in res[0][attrname]:
886 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
887 guid2 = dsdb_dn.dn.get_extended_component("GUID")
888 if guid == guid2:
889 return dsdb_dn
890 return None
892 def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr):
893 '''check a linked values for duplicate forward links'''
894 error_count = 0
896 duplicate_dict = dict()
897 unique_dict = dict()
899 # Only forward links can have this problem
900 if forward_linkID & 1:
901 # If we got the reverse, skip it
902 return (error_count, duplicate_dict, unique_dict)
904 if backlink_attr is None:
905 return (error_count, duplicate_dict, unique_dict)
907 for val in obj[forward_attr]:
908 dsdb_dn = dsdb_Dn(self.samdb, val, forward_syntax)
910 # all DNs should have a GUID component
911 guid = dsdb_dn.dn.get_extended_component("GUID")
912 if guid is None:
913 continue
914 guidstr = str(misc.GUID(guid))
915 keystr = guidstr + dsdb_dn.prefix
916 if keystr not in unique_dict:
917 unique_dict[keystr] = dsdb_dn
918 continue
919 error_count += 1
920 if keystr not in duplicate_dict:
921 duplicate_dict[keystr] = dict()
922 duplicate_dict[keystr]["keep"] = None
923 duplicate_dict[keystr]["delete"] = list()
925 # Now check for the highest RMD_VERSION
926 v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION"))
927 v2 = int(dsdb_dn.dn.get_extended_component("RMD_VERSION"))
928 if v1 > v2:
929 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
930 duplicate_dict[keystr]["delete"].append(dsdb_dn)
931 continue
932 if v1 < v2:
933 duplicate_dict[keystr]["keep"] = dsdb_dn
934 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
935 unique_dict[keystr] = dsdb_dn
936 continue
937 # Fallback to the highest RMD_LOCAL_USN
938 u1 = int(unique_dict[keystr].dn.get_extended_component("RMD_LOCAL_USN"))
939 u2 = int(dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN"))
940 if u1 >= u2:
941 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
942 duplicate_dict[keystr]["delete"].append(dsdb_dn)
943 continue
944 duplicate_dict[keystr]["keep"] = dsdb_dn
945 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
946 unique_dict[keystr] = dsdb_dn
949 return (error_count, duplicate_dict, unique_dict)
951 def check_dn(self, obj, attrname, syntax_oid):
952 '''check a DN attribute for correctness'''
953 error_count = 0
954 obj_guid = obj['objectGUID'][0]
956 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
957 if reverse_link_name is not None:
958 reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
959 else:
960 reverse_syntax_oid = None
962 error_count, duplicate_dict, unique_dict = \
963 self.check_duplicate_links(obj, attrname, syntax_oid, linkID, reverse_link_name)
965 if len(duplicate_dict) != 0:
966 self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
967 for keystr in duplicate_dict.keys():
968 d = duplicate_dict[keystr]
969 for dd in d["delete"]:
970 self.report("Duplicate link '%s'" % dd)
971 self.report("Correct link '%s'" % d["keep"])
973 # We now construct the sorted dn values.
974 # They're sorted by the objectGUID of the target
975 # See dsdb_Dn.__cmp__()
976 vals = [str(dn) for dn in sorted(unique_dict.values())]
977 self.err_recover_forward_links(obj, attrname, vals)
978 # We should continue with the fixed values
979 obj[attrname] = ldb.MessageElement(vals, 0, attrname)
981 for val in obj[attrname]:
982 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
984 # all DNs should have a GUID component
985 guid = dsdb_dn.dn.get_extended_component("GUID")
986 if guid is None:
987 error_count += 1
988 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
989 "missing GUID")
990 continue
992 guidstr = str(misc.GUID(guid))
993 attrs = ['isDeleted', 'replPropertyMetaData']
995 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
996 fixing_msDS_HasInstantiatedNCs = True
997 attrs.append("instanceType")
998 else:
999 fixing_msDS_HasInstantiatedNCs = False
1001 if reverse_link_name is not None:
1002 attrs.append(reverse_link_name)
1004 # check its the right GUID
1005 try:
1006 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
1007 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
1008 "reveal_internals:0"
1010 except ldb.LdbError, (enum, estr):
1011 if enum != ldb.ERR_NO_SUCH_OBJECT:
1012 raise
1014 # We don't always want to
1015 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
1016 attrname,
1017 val,
1018 dsdb_dn)
1019 continue
1021 if fixing_msDS_HasInstantiatedNCs:
1022 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
1023 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
1025 if str(dsdb_dn) != val:
1026 error_count +=1
1027 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
1028 continue
1030 # now we have two cases - the source object might or might not be deleted
1031 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1032 target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
1035 if is_deleted and not obj.dn in self.deleted_objects_containers and linkID:
1036 # A fully deleted object should not have any linked
1037 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1038 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1039 # Requirements)
1040 self.err_undead_linked_attribute(obj, attrname, val)
1041 error_count += 1
1042 continue
1043 elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
1044 # the target DN is not allowed to be deleted, unless the target DN is the
1045 # special Deleted Objects container
1046 error_count += 1
1047 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
1048 if local_usn:
1049 if 'replPropertyMetaData' in res[0]:
1050 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1051 str(res[0]['replPropertyMetadata']))
1052 found_data = False
1053 for o in repl.ctr.array:
1054 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1055 deleted_usn = o.local_usn
1056 if deleted_usn >= int(local_usn):
1057 # If the object was deleted after the link
1058 # was last modified then, clean it up here
1059 found_data = True
1060 break
1062 if found_data:
1063 self.err_deleted_dn(obj.dn, attrname,
1064 val, dsdb_dn, res[0].dn, True)
1065 continue
1067 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
1068 continue
1070 # We should not check for incorrect
1071 # components on deleted links, as these are allowed to
1072 # go stale (we just need the GUID, not the name)
1073 rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
1074 rmd_flags = 0
1075 if rmd_blob is not None:
1076 rmd_flags = int(rmd_blob)
1078 # assert the DN matches in string form, where a reverse
1079 # link exists, otherwise (below) offer to fix it as a non-error.
1080 # The string form is essentially only kept for forensics,
1081 # as we always re-resolve by GUID in normal operations.
1082 if not rmd_flags & 1 and reverse_link_name is not None:
1083 if str(res[0].dn) != str(dsdb_dn.dn):
1084 error_count += 1
1085 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1086 res[0].dn, "string")
1087 continue
1089 if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
1090 error_count += 1
1091 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1092 res[0].dn, "GUID")
1093 continue
1095 if res[0].dn.get_extended_component("SID") != dsdb_dn.dn.get_extended_component("SID"):
1096 error_count += 1
1097 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1098 res[0].dn, "SID")
1099 continue
1101 # Now we have checked the GUID and SID, offer to fix old
1102 # DN strings as a non-error (for forward links with no
1103 # backlink). Samba does not maintain this string
1104 # otherwise, so we don't increment error_count.
1105 if reverse_link_name is None:
1106 if str(res[0].dn) != str(dsdb_dn.dn):
1107 self.err_dn_string_component_old(obj.dn, attrname, val, dsdb_dn,
1108 res[0].dn)
1109 continue
1111 # check the reverse_link is correct if there should be one
1112 match_count = 0
1113 if reverse_link_name in res[0]:
1114 for v in res[0][reverse_link_name]:
1115 v_dn = dsdb_Dn(self.samdb, v)
1116 v_guid = v_dn.dn.get_extended_component("GUID")
1117 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1118 v_rmd_flags = 0
1119 if v_blob is not None:
1120 v_rmd_flags = int(v_blob)
1121 if v_rmd_flags & 1:
1122 continue
1123 if v_guid == obj_guid:
1124 match_count += 1
1126 if match_count != 1:
1127 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1128 if not linkID & 1:
1129 # Forward binary multi-valued linked attribute
1130 forward_count = 0
1131 for w in obj[attrname]:
1132 w_guid = dsdb_Dn(self.samdb, w).dn.get_extended_component("GUID")
1133 if w_guid == guid:
1134 forward_count += 1
1136 if match_count == forward_count:
1137 continue
1138 expected_count = 0
1139 for v in obj[attrname]:
1140 v_dn = dsdb_Dn(self.samdb, v)
1141 v_guid = v_dn.dn.get_extended_component("GUID")
1142 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1143 v_rmd_flags = 0
1144 if v_blob is not None:
1145 v_rmd_flags = int(v_blob)
1146 if v_rmd_flags & 1:
1147 continue
1148 if v_guid == guid:
1149 expected_count += 1
1151 if match_count == expected_count:
1152 continue
1154 diff_count = expected_count - match_count
1156 if linkID & 1:
1157 # If there's a backward link on binary multi-valued linked attribute,
1158 # let the check on the forward link remedy the value.
1159 # UNLESS, there is no forward link detected.
1160 if match_count == 0:
1161 error_count += 1
1162 self.err_orphaned_backlink(obj.dn, attrname,
1163 val, dsdb_dn.dn,
1164 reverse_link_name,
1165 reverse_syntax_oid)
1166 continue
1167 # Only warn here and let the forward link logic fix it.
1168 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1169 attrname, expected_count, str(obj.dn),
1170 reverse_link_name, match_count, str(dsdb_dn.dn)))
1171 continue
1173 assert not target_is_deleted
1175 self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1176 attrname, expected_count, str(obj.dn),
1177 reverse_link_name, match_count, str(dsdb_dn.dn)))
1179 # Loop until the difference between the forward and
1180 # the backward links is resolved.
1181 while diff_count != 0:
1182 error_count += 1
1183 if diff_count > 0:
1184 if match_count > 0 or diff_count > 1:
1185 # TODO no method to fix these right now
1186 self.report("ERROR: Can't fix missing "
1187 "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1188 break
1189 self.err_missing_backlink(obj, attrname,
1190 obj.dn.extended_str(),
1191 reverse_link_name,
1192 dsdb_dn.dn)
1193 diff_count -= 1
1194 else:
1195 self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1196 obj.dn.extended_str(), obj.dn,
1197 attrname, syntax_oid)
1198 diff_count += 1
1201 return error_count
1204 def get_originating_time(self, val, attid):
1205 '''Read metadata properties and return the originating time for
1206 a given attributeId.
1208 :return: the originating time or 0 if not found
1211 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1212 obj = repl.ctr
1214 for o in repl.ctr.array:
1215 if o.attid == attid:
1216 return o.originating_change_time
1218 return 0
1220 def process_metadata(self, dn, val):
1221 '''Read metadata properties and list attributes in it.
1222 raises KeyError if the attid is unknown.'''
1224 set_att = set()
1225 wrong_attids = set()
1226 list_attid = []
1227 in_schema_nc = dn.is_child_of(self.schema_dn)
1229 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1230 obj = repl.ctr
1232 for o in repl.ctr.array:
1233 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1234 set_att.add(att.lower())
1235 list_attid.append(o.attid)
1236 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1237 is_schema_nc=in_schema_nc)
1238 if correct_attid != o.attid:
1239 wrong_attids.add(o.attid)
1241 return (set_att, list_attid, wrong_attids)
1244 def fix_metadata(self, obj, attr):
1245 '''re-write replPropertyMetaData elements for a single attribute for a
1246 object. This is used to fix missing replPropertyMetaData elements'''
1247 guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1248 dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1249 res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
1250 controls = ["search_options:1:2",
1251 "show_recycled:1"])
1252 msg = res[0]
1253 nmsg = ldb.Message()
1254 nmsg.dn = dn
1255 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1256 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1257 "Failed to fix metadata for attribute %s" % attr):
1258 self.report("Fixed metadata for attribute %s" % attr)
1260 def ace_get_effective_inherited_type(self, ace):
1261 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1262 return None
1264 check = False
1265 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1266 check = True
1267 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1268 check = True
1269 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1270 check = True
1271 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1272 check = True
1274 if not check:
1275 return None
1277 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1278 return None
1280 return str(ace.object.inherited_type)
1282 def lookup_class_schemaIDGUID(self, cls):
1283 if cls in self.class_schemaIDGUID:
1284 return self.class_schemaIDGUID[cls]
1286 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1287 res = self.samdb.search(base=self.schema_dn,
1288 expression=flt,
1289 attrs=["schemaIDGUID"])
1290 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1292 self.class_schemaIDGUID[cls] = t
1293 return t
1295 def process_sd(self, dn, obj):
1296 sd_attr = "nTSecurityDescriptor"
1297 sd_val = obj[sd_attr]
1299 sd = ndr_unpack(security.descriptor, str(sd_val))
1301 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1302 if is_deleted:
1303 # we don't fix deleted objects
1304 return (sd, None)
1306 sd_clean = security.descriptor()
1307 sd_clean.owner_sid = sd.owner_sid
1308 sd_clean.group_sid = sd.group_sid
1309 sd_clean.type = sd.type
1310 sd_clean.revision = sd.revision
1312 broken = False
1313 last_inherited_type = None
1315 aces = []
1316 if sd.sacl is not None:
1317 aces = sd.sacl.aces
1318 for i in range(0, len(aces)):
1319 ace = aces[i]
1321 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1322 sd_clean.sacl_add(ace)
1323 continue
1325 t = self.ace_get_effective_inherited_type(ace)
1326 if t is None:
1327 continue
1329 if last_inherited_type is not None:
1330 if t != last_inherited_type:
1331 # if it inherited from more than
1332 # one type it's very likely to be broken
1334 # If not the recalculation will calculate
1335 # the same result.
1336 broken = True
1337 continue
1339 last_inherited_type = t
1341 aces = []
1342 if sd.dacl is not None:
1343 aces = sd.dacl.aces
1344 for i in range(0, len(aces)):
1345 ace = aces[i]
1347 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1348 sd_clean.dacl_add(ace)
1349 continue
1351 t = self.ace_get_effective_inherited_type(ace)
1352 if t is None:
1353 continue
1355 if last_inherited_type is not None:
1356 if t != last_inherited_type:
1357 # if it inherited from more than
1358 # one type it's very likely to be broken
1360 # If not the recalculation will calculate
1361 # the same result.
1362 broken = True
1363 continue
1365 last_inherited_type = t
1367 if broken:
1368 return (sd_clean, sd)
1370 if last_inherited_type is None:
1371 # ok
1372 return (sd, None)
1374 cls = None
1375 try:
1376 cls = obj["objectClass"][-1]
1377 except KeyError, e:
1378 pass
1380 if cls is None:
1381 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1382 attrs=["isDeleted", "objectClass"],
1383 controls=["show_recycled:1"])
1384 o = res[0]
1385 is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
1386 if is_deleted:
1387 # we don't fix deleted objects
1388 return (sd, None)
1389 cls = o["objectClass"][-1]
1391 t = self.lookup_class_schemaIDGUID(cls)
1393 if t != last_inherited_type:
1394 # broken
1395 return (sd_clean, sd)
1397 # ok
1398 return (sd, None)
1400 def err_wrong_sd(self, dn, sd, sd_broken):
1401 '''re-write the SD due to incorrect inherited ACEs'''
1402 sd_attr = "nTSecurityDescriptor"
1403 sd_val = ndr_pack(sd)
1404 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1406 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1407 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1408 return
1410 nmsg = ldb.Message()
1411 nmsg.dn = dn
1412 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1413 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1414 "Failed to fix attribute %s" % sd_attr):
1415 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1417 def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1418 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1419 sd_attr = "nTSecurityDescriptor"
1420 sd_val = ndr_pack(sd)
1421 sd_old_val = ndr_pack(sd_old)
1422 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1423 if sd.owner_sid is not None:
1424 sd_flags |= security.SECINFO_OWNER
1425 if sd.group_sid is not None:
1426 sd_flags |= security.SECINFO_GROUP
1428 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1429 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1430 return
1432 m = ldb.Message()
1433 m.dn = dn
1434 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1435 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1436 "Failed to reset attribute %s" % sd_attr):
1437 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1439 def err_missing_sd_owner(self, dn, sd):
1440 '''re-write the SD due to a missing owner or group'''
1441 sd_attr = "nTSecurityDescriptor"
1442 sd_val = ndr_pack(sd)
1443 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1445 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1446 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1447 return
1449 nmsg = ldb.Message()
1450 nmsg.dn = dn
1451 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1453 # By setting the session_info to admin_session_info and
1454 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1455 # flags we cause the descriptor module to set the correct
1456 # owner and group on the SD, replacing the None/NULL values
1457 # for owner_sid and group_sid currently present.
1459 # The admin_session_info matches that used in provision, and
1460 # is the best guess we can make for an existing object that
1461 # hasn't had something specifically set.
1463 # This is important for the dns related naming contexts.
1464 self.samdb.set_session_info(self.admin_session_info)
1465 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1466 "Failed to fix metadata for attribute %s" % sd_attr):
1467 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1468 self.samdb.set_session_info(self.system_session_info)
1471 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1472 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1473 str(repl_meta_data))
1474 ctr = repl.ctr
1475 found = False
1476 for o in ctr.array:
1477 # Search for a zero invocationID
1478 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1479 continue
1481 found = True
1482 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1483 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1484 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1485 % (dn, o.attid, o.version,
1486 time.ctime(samba.nttime2unix(o.originating_change_time)),
1487 self.samdb.get_invocation_id()))
1489 return found
1492 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1493 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1494 str(repl_meta_data))
1495 ctr = repl.ctr
1496 now = samba.unix2nttime(int(time.time()))
1497 found = False
1498 for o in ctr.array:
1499 # Search for a zero invocationID
1500 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1501 continue
1503 found = True
1504 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1505 o.version = o.version + 1
1506 o.originating_change_time = now
1507 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1508 o.originating_usn = seq
1509 o.local_usn = seq
1511 if found:
1512 replBlob = ndr_pack(repl)
1513 msg = ldb.Message()
1514 msg.dn = dn
1516 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1517 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1518 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1519 return
1521 nmsg = ldb.Message()
1522 nmsg.dn = dn
1523 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1524 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1525 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1526 "Failed to fix attribute %s" % attr):
1527 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1530 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1531 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1532 str(repl_meta_data))
1533 ctr = repl.ctr
1534 for o in ctr.array:
1535 # Search for an invalid attid
1536 try:
1537 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1538 except KeyError:
1539 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1540 return
1543 def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1544 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1545 str(repl_meta_data))
1546 fix = False
1548 set_att = set()
1549 remove_attid = set()
1550 hash_att = {}
1552 in_schema_nc = dn.is_child_of(self.schema_dn)
1554 ctr = repl.ctr
1555 # Sort the array, except for the last element. This strange
1556 # construction, creating a new list, due to bugs in samba's
1557 # array handling in IDL generated objects.
1558 ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1559 # Now walk it in reverse, so we see the low (and so incorrect,
1560 # the correct values are above 0x80000000) values first and
1561 # remove the 'second' value we see.
1562 for o in reversed(ctr.array):
1563 print "%s: 0x%08x" % (dn, o.attid)
1564 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1565 if att.lower() in set_att:
1566 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1567 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1568 % (attr, dn, o.attid, att, hash_att[att].attid),
1569 'fix_replmetadata_duplicate_attid'):
1570 self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1571 % (o.attid, att, attr, dn))
1572 return
1573 fix = True
1574 remove_attid.add(o.attid)
1575 # We want to set the metadata for the most recent
1576 # update to have been applied locally, that is the metadata
1577 # matching the (eg string) value in the attribute
1578 if o.local_usn > hash_att[att].local_usn:
1579 # This is always what we would have sent over DRS,
1580 # because the DRS server will have sent the
1581 # msDS-IntID, but with the values from both
1582 # attribute entries.
1583 hash_att[att].version = o.version
1584 hash_att[att].originating_change_time = o.originating_change_time
1585 hash_att[att].originating_invocation_id = o.originating_invocation_id
1586 hash_att[att].originating_usn = o.originating_usn
1587 hash_att[att].local_usn = o.local_usn
1589 # Do not re-add the value to the set or overwrite the hash value
1590 continue
1592 hash_att[att] = o
1593 set_att.add(att.lower())
1595 # Generate a real list we can sort on properly
1596 new_list = [o for o in ctr.array if o.attid not in remove_attid]
1598 if (len(wrong_attids) > 0):
1599 for o in new_list:
1600 if o.attid in wrong_attids:
1601 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1602 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1603 self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1604 if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1605 % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1606 self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1607 % (o.attid, correct_attid, att, attr, dn))
1608 return
1609 fix = True
1610 o.attid = correct_attid
1611 if fix:
1612 # Sort the array, (we changed the value so must re-sort)
1613 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1615 # If we did not already need to fix it, then ask about sorting
1616 if not fix:
1617 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1618 if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1619 % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1620 self.report('Not fixing %s on %s\n' % (attr, dn))
1621 return
1623 # The actual sort done is done at the top of the function
1625 ctr.count = len(new_list)
1626 ctr.array = new_list
1627 replBlob = ndr_pack(repl)
1629 nmsg = ldb.Message()
1630 nmsg.dn = dn
1631 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1632 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1633 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1634 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1635 "Failed to fix attribute %s" % attr):
1636 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1639 def is_deleted_deleted_objects(self, obj):
1640 faulty = False
1641 if "description" not in obj:
1642 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1643 faulty = True
1644 if "showInAdvancedViewOnly" not in obj or obj['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1645 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1646 faulty = True
1647 if "objectCategory" not in obj:
1648 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1649 faulty = True
1650 if "isCriticalSystemObject" not in obj or obj['isCriticalSystemObject'][0].upper() == 'FALSE':
1651 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1652 faulty = True
1653 if "isRecycled" in obj:
1654 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1655 faulty = True
1656 if "isDeleted" in obj and obj['isDeleted'][0].upper() == 'FALSE':
1657 self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1658 faulty = True
1659 if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1660 obj['objectClass'][0] != 'top' or
1661 obj['objectClass'][1] != 'container'):
1662 self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1663 faulty = True
1664 if "systemFlags" not in obj or obj['systemFlags'][0] != '-1946157056':
1665 self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1666 faulty = True
1667 return faulty
1669 def err_deleted_deleted_objects(self, obj):
1670 nmsg = ldb.Message()
1671 nmsg.dn = dn = obj.dn
1673 if "description" not in obj:
1674 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1675 if "showInAdvancedViewOnly" not in obj:
1676 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1677 if "objectCategory" not in obj:
1678 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1679 if "isCriticalSystemObject" not in obj:
1680 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1681 if "isRecycled" in obj:
1682 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1684 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1685 nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1686 nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1688 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1689 % (dn), 'fix_deleted_deleted_objects'):
1690 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1691 return
1693 if self.do_modify(nmsg, ["relax:0"],
1694 "Failed to fix Deleted Objects container %s" % dn):
1695 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1697 def err_replica_locations(self, obj, cross_ref, attr):
1698 nmsg = ldb.Message()
1699 nmsg.dn = cross_ref
1700 target = self.samdb.get_dsServiceName()
1702 if self.samdb.am_rodc():
1703 self.report('Not fixing %s for the RODC' % (attr, obj.dn))
1704 return
1706 if not self.confirm_all('Add yourself to the replica locations for %s?'
1707 % (obj.dn), 'fix_replica_locations'):
1708 self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1709 return
1711 nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1712 if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1713 self.report("Fixed %s for %s" % (attr, obj.dn))
1715 def is_fsmo_role(self, dn):
1716 if dn == self.samdb.domain_dn:
1717 return True
1718 if dn == self.infrastructure_dn:
1719 return True
1720 if dn == self.naming_dn:
1721 return True
1722 if dn == self.schema_dn:
1723 return True
1724 if dn == self.rid_dn:
1725 return True
1727 return False
1729 def calculate_instancetype(self, dn):
1730 instancetype = 0
1731 nc_root = self.samdb.get_nc_root(dn)
1732 if dn == nc_root:
1733 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1734 try:
1735 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1736 except ldb.LdbError, (enum, estr):
1737 if enum != ldb.ERR_NO_SUCH_OBJECT:
1738 raise
1739 else:
1740 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1742 if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1743 instancetype |= dsdb.INSTANCE_TYPE_WRITE
1745 return instancetype
1747 def get_wellknown_sd(self, dn):
1748 for [sd_dn, descriptor_fn] in self.wellknown_sds:
1749 if dn == sd_dn:
1750 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1751 return ndr_unpack(security.descriptor,
1752 descriptor_fn(domain_sid,
1753 name_map=self.name_map))
1755 raise KeyError
1757 def check_object(self, dn, attrs=['*']):
1758 '''check one object'''
1759 if self.verbose:
1760 self.report("Checking object %s" % dn)
1762 # If we modify the pass-by-reference attrs variable, then we get a
1763 # replPropertyMetadata for every object that we check.
1764 attrs = list(attrs)
1765 if "dn" in map(str.lower, attrs):
1766 attrs.append("name")
1767 if "distinguishedname" in map(str.lower, attrs):
1768 attrs.append("name")
1769 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1770 attrs.append("name")
1771 if 'name' in map(str.lower, attrs):
1772 attrs.append(dn.get_rdn_name())
1773 attrs.append("isDeleted")
1774 attrs.append("systemFlags")
1775 if '*' in attrs:
1776 attrs.append("replPropertyMetaData")
1777 else:
1778 attrs.append("objectGUID")
1780 try:
1781 sd_flags = 0
1782 sd_flags |= security.SECINFO_OWNER
1783 sd_flags |= security.SECINFO_GROUP
1784 sd_flags |= security.SECINFO_DACL
1785 sd_flags |= security.SECINFO_SACL
1787 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1788 controls=[
1789 "extended_dn:1:1",
1790 "show_recycled:1",
1791 "show_deleted:1",
1792 "sd_flags:1:%d" % sd_flags,
1793 "reveal_internals:0",
1795 attrs=attrs)
1796 except ldb.LdbError, (enum, estr):
1797 if enum == ldb.ERR_NO_SUCH_OBJECT:
1798 if self.in_transaction:
1799 self.report("ERROR: Object %s disappeared during check" % dn)
1800 return 1
1801 return 0
1802 raise
1803 if len(res) != 1:
1804 self.report("ERROR: Object %s failed to load during check" % dn)
1805 return 1
1806 obj = res[0]
1807 error_count = 0
1808 set_attrs_from_md = set()
1809 set_attrs_seen = set()
1810 got_repl_property_meta_data = False
1811 got_objectclass = False
1813 nc_dn = self.samdb.get_nc_root(obj.dn)
1814 try:
1815 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
1816 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
1817 except KeyError:
1818 # We have no deleted objects DN for schema, and we check for this above for the other
1819 # NCs
1820 deleted_objects_dn = None
1823 object_rdn_attr = None
1824 object_rdn_val = None
1825 name_val = None
1826 isDeleted = False
1827 systemFlags = 0
1829 for attrname in obj:
1830 if attrname == 'dn' or attrname == "distinguishedName":
1831 continue
1833 if str(attrname).lower() == 'objectclass':
1834 got_objectclass = True
1836 if str(attrname).lower() == "name":
1837 if len(obj[attrname]) != 1:
1838 error_count += 1
1839 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1840 (len(obj[attrname]), attrname, str(obj.dn)))
1841 else:
1842 name_val = obj[attrname][0]
1844 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
1845 object_rdn_attr = attrname
1846 if len(obj[attrname]) != 1:
1847 error_count += 1
1848 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1849 (len(obj[attrname]), attrname, str(obj.dn)))
1850 else:
1851 object_rdn_val = obj[attrname][0]
1853 if str(attrname).lower() == 'isdeleted':
1854 if obj[attrname][0] != "FALSE":
1855 isDeleted = True
1857 if str(attrname).lower() == 'systemflags':
1858 systemFlags = int(obj[attrname][0])
1860 if str(attrname).lower() == 'replpropertymetadata':
1861 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
1862 error_count += 1
1863 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
1864 # We don't continue, as we may also have other fixes for this attribute
1865 # based on what other attributes we see.
1867 try:
1868 (set_attrs_from_md, list_attid_from_md, wrong_attids) \
1869 = self.process_metadata(dn, obj[attrname])
1870 except KeyError:
1871 error_count += 1
1872 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
1873 continue
1875 if len(set_attrs_from_md) < len(list_attid_from_md) \
1876 or len(wrong_attids) > 0 \
1877 or sorted(list_attid_from_md) != list_attid_from_md:
1878 error_count +=1
1879 self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname], wrong_attids)
1881 else:
1882 # Here we check that the first attid is 0
1883 # (objectClass).
1884 if list_attid_from_md[0] != 0:
1885 error_count += 1
1886 self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
1887 (attrname, str(dn)))
1889 got_repl_property_meta_data = True
1890 continue
1892 if str(attrname).lower() == 'ntsecuritydescriptor':
1893 (sd, sd_broken) = self.process_sd(dn, obj)
1894 if sd_broken is not None:
1895 self.err_wrong_sd(dn, sd, sd_broken)
1896 error_count += 1
1897 continue
1899 if sd.owner_sid is None or sd.group_sid is None:
1900 self.err_missing_sd_owner(dn, sd)
1901 error_count += 1
1902 continue
1904 if self.reset_well_known_acls:
1905 try:
1906 well_known_sd = self.get_wellknown_sd(dn)
1907 except KeyError:
1908 continue
1910 current_sd = ndr_unpack(security.descriptor,
1911 str(obj[attrname][0]))
1913 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
1914 if diff != "":
1915 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
1916 error_count += 1
1917 continue
1918 continue
1920 if str(attrname).lower() == 'objectclass':
1921 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
1922 # Do not consider the attribute incorrect if:
1923 # - The sorted (alphabetically) list is the same, inclding case
1924 # - The first and last elements are the same
1926 # This avoids triggering an error due to
1927 # non-determinism in the sort routine in (at least)
1928 # 4.3 and earlier, and the fact that any AUX classes
1929 # in these attributes are also not sorted when
1930 # imported from Windows (they are just in the reverse
1931 # order of last set)
1932 if sorted(normalised) != sorted(obj[attrname]) \
1933 or normalised[0] != obj[attrname][0] \
1934 or normalised[-1] != obj[attrname][-1]:
1935 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
1936 error_count += 1
1937 continue
1939 if str(attrname).lower() == 'userparameters':
1940 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
1941 error_count += 1
1942 self.err_short_userParameters(obj, attrname, obj[attrname])
1943 continue
1945 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1946 # This is the correct, normal prefix
1947 continue
1949 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1950 # this is the typical prefix from a windows migration
1951 error_count += 1
1952 self.err_base64_userParameters(obj, attrname, obj[attrname])
1953 continue
1955 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':
1956 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1957 error_count += 1
1958 self.err_utf8_userParameters(obj, attrname, obj[attrname])
1959 continue
1961 elif len(obj[attrname][0]) % 2 != 0:
1962 # This is a value that isn't even in length
1963 error_count += 1
1964 self.err_odd_userParameters(obj, attrname, obj[attrname])
1965 continue
1967 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':
1968 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
1969 error_count += 1
1970 self.err_doubled_userParameters(obj, attrname, obj[attrname])
1971 continue
1973 if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
1974 if obj[attrname][0] in self.attribute_or_class_ids:
1975 error_count += 1
1976 self.report('Error: %s %s on %s already exists as an attributeId or governsId'
1977 % (attrname, obj.dn, obj[attrname][0]))
1978 else:
1979 self.attribute_or_class_ids.add(obj[attrname][0])
1981 # check for empty attributes
1982 for val in obj[attrname]:
1983 if val == '':
1984 self.err_empty_attribute(dn, attrname)
1985 error_count += 1
1986 continue
1988 # get the syntax oid for the attribute, so we can can have
1989 # special handling for some specific attribute types
1990 try:
1991 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1992 except Exception, msg:
1993 self.err_unknown_attribute(obj, attrname)
1994 error_count += 1
1995 continue
1997 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1999 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2000 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2001 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2002 and not linkID):
2003 set_attrs_seen.add(str(attrname).lower())
2005 if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2006 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
2007 # it's some form of DN, do specialised checking on those
2008 error_count += self.check_dn(obj, attrname, syntax_oid)
2009 else:
2011 values = set()
2012 # check for incorrectly normalised attributes
2013 for val in obj[attrname]:
2014 values.add(str(val))
2016 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2017 if len(normalised) != 1 or normalised[0] != val:
2018 self.err_normalise_mismatch(dn, attrname, obj[attrname])
2019 error_count += 1
2020 break
2022 if len(obj[attrname]) != len(values):
2023 self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2024 error_count += 1
2025 break
2027 if str(attrname).lower() == "instancetype":
2028 calculated_instancetype = self.calculate_instancetype(dn)
2029 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
2030 error_count += 1
2031 self.err_wrong_instancetype(obj, calculated_instancetype)
2033 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
2034 error_count += 1
2035 self.err_missing_objectclass(dn)
2037 if ("*" in attrs or "name" in map(str.lower, attrs)):
2038 if name_val is None:
2039 error_count += 1
2040 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2041 if object_rdn_attr is None:
2042 error_count += 1
2043 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2045 if name_val is not None:
2046 parent_dn = None
2047 if isDeleted:
2048 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2049 parent_dn = deleted_objects_dn
2050 if parent_dn is None:
2051 parent_dn = obj.dn.parent()
2052 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2053 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2055 if obj.dn == deleted_objects_dn:
2056 expected_dn = obj.dn
2058 if expected_dn != obj.dn:
2059 error_count += 1
2060 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
2061 elif obj.dn.get_rdn_value() != object_rdn_val:
2062 error_count += 1
2063 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
2065 show_dn = True
2066 if got_repl_property_meta_data:
2067 if obj.dn == deleted_objects_dn:
2068 isDeletedAttId = 131120
2069 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2071 expectedTimeDo = 2650466015990000000
2072 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
2073 if originating != expectedTimeDo:
2074 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2075 nmsg = ldb.Message()
2076 nmsg.dn = dn
2077 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2078 error_count += 1
2079 self.samdb.modify(nmsg, controls=["provision:0"])
2081 else:
2082 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2084 for att in set_attrs_seen.difference(set_attrs_from_md):
2085 if show_dn:
2086 self.report("On object %s" % dn)
2087 show_dn = False
2088 error_count += 1
2089 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2090 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2091 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2092 continue
2093 self.fix_metadata(obj, att)
2095 if self.is_fsmo_role(dn):
2096 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
2097 self.err_no_fsmoRoleOwner(obj)
2098 error_count += 1
2100 try:
2101 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2102 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2103 controls=["show_recycled:1", "show_deleted:1"])
2104 except ldb.LdbError, (enum, estr):
2105 if enum == ldb.ERR_NO_SUCH_OBJECT:
2106 self.err_missing_parent(obj)
2107 error_count += 1
2108 else:
2109 raise
2111 if dn in self.deleted_objects_containers and '*' in attrs:
2112 if self.is_deleted_deleted_objects(obj):
2113 self.err_deleted_deleted_objects(obj)
2114 error_count += 1
2116 for (dns_part, msg) in self.dns_partitions:
2117 if dn == dns_part and 'repsFrom' in obj:
2118 location = "msDS-NC-Replica-Locations"
2119 if self.samdb.am_rodc():
2120 location = "msDS-NC-RO-Replica-Locations"
2122 if location not in msg:
2123 # There are no replica locations!
2124 self.err_replica_locations(obj, msg.dn, location)
2125 error_count += 1
2126 continue
2128 found = False
2129 for loc in msg[location]:
2130 if loc == self.samdb.get_dsServiceName():
2131 found = True
2132 if not found:
2133 # This DC is not in the replica locations
2134 self.err_replica_locations(obj, msg.dn, location)
2135 error_count += 1
2137 if dn == self.server_ref_dn:
2138 # Check we have a valid RID Set
2139 if "*" in attrs or "rIDSetReferences" in attrs:
2140 if "rIDSetReferences" not in obj:
2141 # NO RID SET reference
2142 # We are RID master, allocate it.
2143 error_count += 1
2145 if self.is_rid_master:
2146 # Allocate a RID Set
2147 if self.confirm_all('Allocate the missing RID set for RID master?',
2148 'fix_missing_rid_set_master'):
2150 # We don't have auto-transaction logic on
2151 # extended operations, so we have to do it
2152 # here.
2154 self.samdb.transaction_start()
2156 try:
2157 self.samdb.create_own_rid_set()
2159 except:
2160 self.samdb.transaction_cancel()
2161 raise
2163 self.samdb.transaction_commit()
2166 elif not self.samdb.am_rodc():
2167 self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2170 # Check some details of our own RID Set
2171 if dn == self.rid_set_dn:
2172 res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2173 attrs=["rIDAllocationPool",
2174 "rIDPreviousAllocationPool",
2175 "rIDUsedPool",
2176 "rIDNextRID"])
2177 if "rIDAllocationPool" not in res[0]:
2178 self.report("No rIDAllocationPool found in %s" % dn)
2179 error_count += 1
2180 else:
2181 next_pool = int(res[0]["rIDAllocationPool"][0])
2183 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2184 low = 0x00000000FFFFFFFF & next_pool
2186 if high <= low:
2187 self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2188 error_count += 1
2190 if "rIDNextRID" in res[0]:
2191 next_free_rid = int(res[0]["rIDNextRID"][0])
2192 else:
2193 next_free_rid = 0
2195 if next_free_rid == 0:
2196 next_free_rid = low
2197 else:
2198 next_free_rid += 1
2200 # Check the remainder of this pool for conflicts. If
2201 # ridalloc_allocate_rid() moves to a new pool, this
2202 # will be above high, so we will stop.
2203 while next_free_rid <= high:
2204 sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2205 try:
2206 res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2207 attrs=[])
2208 except ldb.LdbError, (enum, estr):
2209 if enum != ldb.ERR_NO_SUCH_OBJECT:
2210 raise
2211 res = None
2212 if res is not None:
2213 self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2214 error_count += 1
2216 if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2217 % (sid, dn),
2218 'fix_sid_rid_set_conflict'):
2219 self.samdb.transaction_start()
2221 # This will burn RIDs, which will move
2222 # past the conflict. We then check again
2223 # to see if the new RID conflicts, until
2224 # the end of the current pool. We don't
2225 # look at the next pool to avoid burning
2226 # all RIDs in one go in some strange
2227 # failure case.
2228 try:
2229 while True:
2230 allocated_rid = self.samdb.allocate_rid()
2231 if allocated_rid >= next_free_rid:
2232 next_free_rid = allocated_rid + 1
2233 break
2234 except:
2235 self.samdb.transaction_cancel()
2236 raise
2238 self.samdb.transaction_commit()
2239 else:
2240 break
2241 else:
2242 next_free_rid += 1
2245 return error_count
2247 ################################################################
2248 # check special @ROOTDSE attributes
2249 def check_rootdse(self):
2250 '''check the @ROOTDSE special object'''
2251 dn = ldb.Dn(self.samdb, '@ROOTDSE')
2252 if self.verbose:
2253 self.report("Checking object %s" % dn)
2254 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2255 if len(res) != 1:
2256 self.report("Object %s disappeared during check" % dn)
2257 return 1
2258 obj = res[0]
2259 error_count = 0
2261 # check that the dsServiceName is in GUID form
2262 if not 'dsServiceName' in obj:
2263 self.report('ERROR: dsServiceName missing in @ROOTDSE')
2264 return error_count+1
2266 if not obj['dsServiceName'][0].startswith('<GUID='):
2267 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2268 error_count += 1
2269 if not self.confirm('Change dsServiceName to GUID form?'):
2270 return error_count
2271 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
2272 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2273 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2274 m = ldb.Message()
2275 m.dn = dn
2276 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2277 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2278 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2279 self.report("Changed dsServiceName to GUID form")
2280 return error_count
2283 ###############################################
2284 # re-index the database
2285 def reindex_database(self):
2286 '''re-index the whole database'''
2287 m = ldb.Message()
2288 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2289 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2290 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2291 return self.do_modify(m, [], 're-indexed database', validate=False)
2293 ###############################################
2294 # reset @MODULES
2295 def reset_modules(self):
2296 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2297 m = ldb.Message()
2298 m.dn = ldb.Dn(self.samdb, "@MODULES")
2299 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2300 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)