auth/spnego: rename 'nt_status' to 'status' in gensec_spnego_create_negTokenInit()
[Samba.git] / python / samba / dbchecker.py
blob1a73fe0e5644b49bf30eb6e59c9064d912f21cfc
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_rmd_flags = False
69 self.fix_ntsecuritydescriptor = False
70 self.fix_ntsecuritydescriptor_owner_group = False
71 self.seize_fsmo_role = False
72 self.move_to_lost_and_found = False
73 self.fix_instancetype = False
74 self.fix_replmetadata_zero_invocationid = False
75 self.fix_replmetadata_duplicate_attid = False
76 self.fix_replmetadata_wrong_attid = False
77 self.fix_replmetadata_unsorted_attid = False
78 self.fix_deleted_deleted_objects = False
79 self.fix_incorrect_deleted_objects = False
80 self.fix_dn = False
81 self.fix_base64_userparameters = False
82 self.fix_utf8_userparameters = False
83 self.fix_doubled_userparameters = False
84 self.fix_sid_rid_set_conflict = False
85 self.reset_well_known_acls = reset_well_known_acls
86 self.reset_all_well_known_acls = False
87 self.in_transaction = in_transaction
88 self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
89 self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
90 self.schema_dn = samdb.get_schema_basedn()
91 self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
92 self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
93 self.class_schemaIDGUID = {}
94 self.wellknown_sds = get_wellknown_sds(self.samdb)
95 self.fix_all_missing_objectclass = False
96 self.fix_missing_deleted_objects = False
97 self.fix_replica_locations = False
98 self.fix_missing_rid_set_master = False
100 self.dn_set = set()
101 self.link_id_cache = {}
102 self.name_map = {}
103 try:
104 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE,
105 attrs=["objectSid"])
106 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
107 self.name_map['DnsAdmins'] = str(dnsadmins_sid)
108 except ldb.LdbError, (enum, estr):
109 if enum != ldb.ERR_NO_SUCH_OBJECT:
110 raise
111 pass
113 self.system_session_info = system_session()
114 self.admin_session_info = admin_session(None, samdb.get_domain_sid())
116 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
117 if "msDS-hasMasterNCs" in res[0]:
118 self.write_ncs = res[0]["msDS-hasMasterNCs"]
119 else:
120 # If the Forest Level is less than 2003 then there is no
121 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
122 # no need to merge as all the NCs that are in hasMasterNCs must
123 # also be in msDS-hasMasterNCs (but not the opposite)
124 if "hasMasterNCs" in res[0]:
125 self.write_ncs = res[0]["hasMasterNCs"]
126 else:
127 self.write_ncs = None
129 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
130 self.deleted_objects_containers = []
131 self.ncs_lacking_deleted_containers = []
132 self.dns_partitions = []
133 try:
134 self.ncs = res[0]["namingContexts"]
135 except KeyError:
136 pass
137 except IndexError:
138 pass
140 for nc in self.ncs:
141 try:
142 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc),
143 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
144 self.deleted_objects_containers.append(dn)
145 except KeyError:
146 self.ncs_lacking_deleted_containers.append(ldb.Dn(self.samdb, nc))
148 domaindns_zone = 'DC=DomainDnsZones,%s' % self.samdb.get_default_basedn()
149 forestdns_zone = 'DC=ForestDnsZones,%s' % self.samdb.get_root_basedn()
150 domain = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
151 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
152 base=self.samdb.get_partitions_dn(),
153 expression="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone)
154 if len(domain) == 1:
155 self.dns_partitions.append((ldb.Dn(self.samdb, forestdns_zone), domain[0]))
157 forest = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
158 attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
159 base=self.samdb.get_partitions_dn(),
160 expression="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone)
161 if len(forest) == 1:
162 self.dns_partitions.append((ldb.Dn(self.samdb, domaindns_zone), forest[0]))
164 fsmo_dn = ldb.Dn(self.samdb, "CN=RID Manager$,CN=System," + self.samdb.domain_dn())
165 rid_master = get_fsmo_roleowner(self.samdb, fsmo_dn, "rid")
166 if ldb.Dn(self.samdb, self.samdb.get_dsServiceName()) == rid_master:
167 self.is_rid_master = True
168 else:
169 self.is_rid_master = False
171 # To get your rid set
172 # 1. Get server name
173 res = self.samdb.search(base=ldb.Dn(self.samdb, self.samdb.get_serverName()),
174 scope=ldb.SCOPE_BASE, attrs=["serverReference"])
175 # 2. Get server reference
176 self.server_ref_dn = ldb.Dn(self.samdb, res[0]['serverReference'][0])
178 # 3. Get RID Set
179 res = self.samdb.search(base=self.server_ref_dn,
180 scope=ldb.SCOPE_BASE, attrs=['rIDSetReferences'])
181 if "rIDSetReferences" in res[0]:
182 self.rid_set_dn = ldb.Dn(self.samdb, res[0]['rIDSetReferences'][0])
183 else:
184 self.rid_set_dn = None
186 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
187 '''perform a database check, returning the number of errors found'''
188 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
189 self.report('Checking %u objects' % len(res))
190 error_count = 0
192 error_count += self.check_deleted_objects_containers()
194 self.attribute_or_class_ids = set()
196 for object in res:
197 self.dn_set.add(str(object.dn))
198 error_count += self.check_object(object.dn, attrs=attrs)
200 if DN is None:
201 error_count += self.check_rootdse()
203 if error_count != 0 and not self.fix:
204 self.report("Please use --fix to fix these errors")
206 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
207 return error_count
209 def check_deleted_objects_containers(self):
210 """This function only fixes conflicts on the Deleted Objects
211 containers, not the attributes"""
212 error_count = 0
213 for nc in self.ncs_lacking_deleted_containers:
214 if nc == self.schema_dn:
215 continue
216 error_count += 1
217 self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc)
218 if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'):
219 continue
221 dn = ldb.Dn(self.samdb, "CN=Deleted Objects")
222 dn.add_base(nc)
224 conflict_dn = None
225 try:
226 # If something already exists here, add a conflict
227 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[],
228 controls=["show_deleted:1", "extended_dn:1:1",
229 "show_recycled:1", "reveal_internals:0"])
230 if len(res) != 0:
231 guid = res[0].dn.get_extended_component("GUID")
232 conflict_dn = ldb.Dn(self.samdb,
233 "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid)))
234 conflict_dn.add_base(nc)
236 except ldb.LdbError, (enum, estr):
237 if enum == ldb.ERR_NO_SUCH_OBJECT:
238 pass
239 else:
240 self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr)
241 return 1
243 if conflict_dn is not None:
244 try:
245 self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"])
246 except ldb.LdbError, (enum, estr):
247 self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr))
248 return 1
250 # Refresh wellKnownObjects links
251 res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE,
252 attrs=['wellKnownObjects'],
253 controls=["show_deleted:1", "extended_dn:0",
254 "show_recycled:1", "reveal_internals:0"])
255 if len(res) != 1:
256 self.report("wellKnownObjects was not found for NC %s" % nc)
257 return 1
259 # Prevent duplicate deleted objects containers just in case
260 wko = res[0]["wellKnownObjects"]
261 listwko = []
262 proposed_objectguid = None
263 for o in wko:
264 dsdb_dn = dsdb_Dn(self.samdb, o, dsdb.DSDB_SYNTAX_BINARY_DN)
265 if self.is_deleted_objects_dn(dsdb_dn):
266 self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o)
267 # We really want to put this back in the same spot
268 # as the original one, so that on replication we
269 # merge, rather than conflict.
270 proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID")
271 listwko.append(o)
273 if proposed_objectguid is not None:
274 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid))
275 else:
276 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
277 listwko.append('%s:%s' % (wko_prefix, dn))
278 guid_suffix = ""
280 # Insert a brand new Deleted Objects container
281 self.samdb.add_ldif("""dn: %s
282 objectClass: top
283 objectClass: container
284 description: Container for deleted objects
285 isDeleted: TRUE
286 isCriticalSystemObject: TRUE
287 showInAdvancedViewOnly: TRUE
288 systemFlags: -1946157056%s""" % (dn, guid_suffix),
289 controls=["relax:0", "provision:0"])
291 delta = ldb.Message()
292 delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"]))
293 delta["wellKnownObjects"] = ldb.MessageElement(listwko,
294 ldb.FLAG_MOD_REPLACE,
295 "wellKnownObjects")
297 # Insert the link to the brand new container
298 if self.do_modify(delta, ["relax:0"],
299 "NC %s lacks Deleted Objects WKGUID" % nc,
300 validate=False):
301 self.report("Added %s well known guid link" % dn)
303 self.deleted_objects_containers.append(dn)
305 return error_count
307 def report(self, msg):
308 '''print a message unless quiet is set'''
309 if not self.quiet:
310 print(msg)
312 def confirm(self, msg, allow_all=False, forced=False):
313 '''confirm a change'''
314 if not self.fix:
315 return False
316 if self.quiet:
317 return self.yes
318 if self.yes:
319 forced = True
320 return common.confirm(msg, forced=forced, allow_all=allow_all)
322 ################################################################
323 # a local confirm function with support for 'all'
324 def confirm_all(self, msg, all_attr):
325 '''confirm a change with support for "all" '''
326 if not self.fix:
327 return False
328 if getattr(self, all_attr) == 'NONE':
329 return False
330 if getattr(self, all_attr) == 'ALL':
331 forced = True
332 else:
333 forced = self.yes
334 if self.quiet:
335 return forced
336 c = common.confirm(msg, forced=forced, allow_all=True)
337 if c == 'ALL':
338 setattr(self, all_attr, 'ALL')
339 return True
340 if c == 'NONE':
341 setattr(self, all_attr, 'NONE')
342 return False
343 return c
345 def do_delete(self, dn, controls, msg):
346 '''delete dn with optional verbose output'''
347 if self.verbose:
348 self.report("delete DN %s" % dn)
349 try:
350 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
351 self.samdb.delete(dn, controls=controls)
352 except Exception, err:
353 if self.in_transaction:
354 raise CommandError("%s : %s" % (msg, err))
355 self.report("%s : %s" % (msg, err))
356 return False
357 return True
359 def do_modify(self, m, controls, msg, validate=True):
360 '''perform a modify with optional verbose output'''
361 if self.verbose:
362 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
363 try:
364 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
365 self.samdb.modify(m, controls=controls, validate=validate)
366 except Exception, err:
367 if self.in_transaction:
368 raise CommandError("%s : %s" % (msg, err))
369 self.report("%s : %s" % (msg, err))
370 return False
371 return True
373 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
374 '''perform a modify with optional verbose output'''
375 if self.verbose:
376 self.report("""dn: %s
377 changeType: modrdn
378 newrdn: %s
379 deleteOldRdn: 1
380 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
381 try:
382 to_dn = to_rdn + to_base
383 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
384 self.samdb.rename(from_dn, to_dn, controls=controls)
385 except Exception, err:
386 if self.in_transaction:
387 raise CommandError("%s : %s" % (msg, err))
388 self.report("%s : %s" % (msg, err))
389 return False
390 return True
392 def get_attr_linkID_and_reverse_name(self, attrname):
393 if attrname in self.link_id_cache:
394 return self.link_id_cache[attrname]
395 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
396 if linkID:
397 revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
398 else:
399 revname = None
400 self.link_id_cache[attrname] = (linkID, revname)
401 return linkID, revname
403 def err_empty_attribute(self, dn, attrname):
404 '''fix empty attributes'''
405 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
406 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
407 self.report("Not fixing empty attribute %s" % attrname)
408 return
410 m = ldb.Message()
411 m.dn = dn
412 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
413 if self.do_modify(m, ["relax:0", "show_recycled:1"],
414 "Failed to remove empty attribute %s" % attrname, validate=False):
415 self.report("Removed empty attribute %s" % attrname)
417 def err_normalise_mismatch(self, dn, attrname, values):
418 '''fix attribute normalisation errors'''
419 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
420 mod_list = []
421 for val in values:
422 normalised = self.samdb.dsdb_normalise_attributes(
423 self.samdb_schema, attrname, [val])
424 if len(normalised) != 1:
425 self.report("Unable to normalise value '%s'" % val)
426 mod_list.append((val, ''))
427 elif (normalised[0] != val):
428 self.report("value '%s' should be '%s'" % (val, normalised[0]))
429 mod_list.append((val, normalised[0]))
430 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
431 self.report("Not fixing attribute %s" % attrname)
432 return
434 m = ldb.Message()
435 m.dn = dn
436 for i in range(0, len(mod_list)):
437 (val, nval) = mod_list[i]
438 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
439 if nval != '':
440 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
441 attrname)
443 if self.do_modify(m, ["relax:0", "show_recycled:1"],
444 "Failed to normalise attribute %s" % attrname,
445 validate=False):
446 self.report("Normalised attribute %s" % attrname)
448 def err_normalise_mismatch_replace(self, dn, attrname, values):
449 '''fix attribute normalisation errors'''
450 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
451 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
452 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
453 if list(normalised) == values:
454 return
455 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
456 self.report("Not fixing attribute '%s'" % attrname)
457 return
459 m = ldb.Message()
460 m.dn = dn
461 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
463 if self.do_modify(m, ["relax:0", "show_recycled:1"],
464 "Failed to normalise attribute %s" % attrname,
465 validate=False):
466 self.report("Normalised attribute %s" % attrname)
468 def err_duplicate_values(self, dn, attrname, dup_values, values):
469 '''fix attribute normalisation errors'''
470 self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
471 self.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values), ','.join(values)))
472 if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
473 self.report("Not fixing attribute '%s'" % attrname)
474 return
476 m = ldb.Message()
477 m.dn = dn
478 m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
480 if self.do_modify(m, ["relax:0", "show_recycled:1"],
481 "Failed to remove duplicate value on attribute %s" % attrname,
482 validate=False):
483 self.report("Removed duplicate value on attribute %s" % attrname)
485 def is_deleted_objects_dn(self, dsdb_dn):
486 '''see if a dsdb_Dn is the special Deleted Objects DN'''
487 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
489 def err_missing_objectclass(self, dn):
490 """handle object without objectclass"""
491 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)))
492 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'):
493 self.report("Not deleting object with missing objectclass '%s'" % dn)
494 return
495 if self.do_delete(dn, ["relax:0"],
496 "Failed to remove DN %s" % dn):
497 self.report("Removed DN %s" % dn)
499 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False):
500 """handle a DN pointing to a deleted object"""
501 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
502 self.report("Target GUID points at deleted DN %r" % str(correct_dn))
503 if not remove_plausible:
504 if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
505 self.report("Not removing")
506 return
507 else:
508 if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
509 self.report("Not removing")
510 return
512 m = ldb.Message()
513 m.dn = dn
514 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
515 if self.do_modify(m, ["show_recycled:1",
516 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
517 "Failed to remove deleted DN attribute %s" % attrname):
518 self.report("Removed deleted DN on attribute %s" % attrname)
520 def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
521 """handle a missing target DN (if specified, GUID form can't be found,
522 and otherwise DN string form can't be found)"""
523 # check if its a backlink
524 linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
525 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
526 self.report("Not removing dangling forward link")
527 return
528 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
530 def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
531 """handle a missing GUID extended DN component"""
532 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
533 controls=["extended_dn:1:1", "show_recycled:1"]
534 try:
535 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
536 attrs=[], controls=controls)
537 except ldb.LdbError, (enum, estr):
538 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
539 if enum != ldb.ERR_NO_SUCH_OBJECT:
540 raise
541 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
542 return
543 if len(res) == 0:
544 self.report("unable to find object for DN %s" % dsdb_dn.dn)
545 self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
546 return
547 dsdb_dn.dn = res[0].dn
549 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
550 self.report("Not fixing %s" % errstr)
551 return
552 m = ldb.Message()
553 m.dn = dn
554 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
555 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
557 if self.do_modify(m, ["show_recycled:1"],
558 "Failed to fix %s on attribute %s" % (errstr, attrname)):
559 self.report("Fixed %s on attribute %s" % (errstr, attrname))
561 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
562 """handle an incorrect binary DN component"""
563 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
564 controls=["extended_dn:1:1", "show_recycled:1"]
566 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
567 self.report("Not fixing %s" % errstr)
568 return
569 m = ldb.Message()
570 m.dn = dn
571 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
572 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
574 if self.do_modify(m, ["show_recycled:1"],
575 "Failed to fix %s on attribute %s" % (errstr, attrname)):
576 self.report("Fixed %s on attribute %s" % (errstr, attrname))
578 def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn):
579 """handle a DN string being incorrect"""
580 self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val))
581 dsdb_dn.dn = correct_dn
583 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
584 'fix_all_old_dn_string_component_mismatch'):
585 self.report("Not fixing old string component")
586 return
587 m = ldb.Message()
588 m.dn = dn
589 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
590 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
591 if self.do_modify(m, ["show_recycled:1"],
592 "Failed to fix old DN string on attribute %s" % (attrname)):
593 self.report("Fixed old DN string on attribute %s" % (attrname))
595 def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
596 """handle a DN string being incorrect"""
597 self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
598 dsdb_dn.dn = correct_dn
600 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
601 'fix_all_%s_dn_component_mismatch' % mismatch_type):
602 self.report("Not fixing %s component mismatch" % mismatch_type)
603 return
604 m = ldb.Message()
605 m.dn = dn
606 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
607 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
608 if self.do_modify(m, ["show_recycled:1"],
609 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
610 self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
612 def err_unknown_attribute(self, obj, attrname):
613 '''handle an unknown attribute error'''
614 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
615 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
616 self.report("Not removing %s" % attrname)
617 return
618 m = ldb.Message()
619 m.dn = obj.dn
620 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
621 if self.do_modify(m, ["relax:0", "show_recycled:1"],
622 "Failed to remove unknown attribute %s" % attrname):
623 self.report("Removed unknown attribute %s" % (attrname))
625 def err_undead_linked_attribute(self, obj, attrname, val):
626 '''handle a link that should not be there on a deleted object'''
627 self.report("ERROR: linked attribute '%s' to '%s' is present on "
628 "deleted object %s" % (attrname, val, obj.dn))
629 if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
630 self.report("Not removing linked attribute %s" % attrname)
631 return
632 m = ldb.Message()
633 m.dn = obj.dn
634 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
636 if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
637 "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
638 "Failed to delete forward link %s" % attrname):
639 self.report("Fixed undead forward link %s" % (attrname))
641 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
642 '''handle a missing backlink value'''
643 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
644 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
645 self.report("Not fixing missing backlink %s" % backlink_name)
646 return
647 m = ldb.Message()
648 m.dn = target_dn
649 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
650 if self.do_modify(m, ["show_recycled:1", "relax:0"],
651 "Failed to fix missing backlink %s" % backlink_name):
652 self.report("Fixed missing backlink %s" % (backlink_name))
654 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
655 '''handle a incorrect RMD_FLAGS value'''
656 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
657 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()))
658 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
659 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
660 return
661 m = ldb.Message()
662 m.dn = obj.dn
663 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
664 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
665 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
666 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
668 def err_orphaned_backlink(self, obj, attrname, val, link_name, target_dn):
669 '''handle a orphaned backlink value'''
670 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (attrname, obj.dn, link_name, target_dn))
671 if not self.confirm_all('Remove orphaned backlink %s' % attrname, 'fix_all_orphaned_backlinks'):
672 self.report("Not removing orphaned backlink %s" % attrname)
673 return
674 m = ldb.Message()
675 m.dn = obj.dn
676 m['value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
677 if self.do_modify(m, ["show_recycled:1", "relax:0"],
678 "Failed to fix orphaned backlink %s" % attrname):
679 self.report("Fixed orphaned backlink %s" % (attrname))
681 def err_no_fsmoRoleOwner(self, obj):
682 '''handle a missing fSMORoleOwner'''
683 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
684 res = self.samdb.search("",
685 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
686 assert len(res) == 1
687 serviceName = res[0]["dsServiceName"][0]
688 if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
689 self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
690 return
691 m = ldb.Message()
692 m.dn = obj.dn
693 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
694 if self.do_modify(m, [],
695 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
696 self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
698 def err_missing_parent(self, obj):
699 '''handle a missing parent'''
700 self.report("ERROR: parent object not found for %s" % (obj.dn))
701 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
702 self.report('Not moving object %s into LostAndFound' % (obj.dn))
703 return
705 keep_transaction = False
706 self.samdb.transaction_start()
707 try:
708 nc_root = self.samdb.get_nc_root(obj.dn);
709 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
710 new_dn = ldb.Dn(self.samdb, str(obj.dn))
711 new_dn.remove_base_components(len(new_dn) - 1)
712 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
713 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
714 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
716 m = ldb.Message()
717 m.dn = obj.dn
718 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
720 if self.do_modify(m, [],
721 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
722 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
723 keep_transaction = True
724 except:
725 self.samdb.transaction_cancel()
726 raise
728 if keep_transaction:
729 self.samdb.transaction_commit()
730 else:
731 self.samdb.transaction_cancel()
733 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
734 '''handle a wrong dn'''
736 new_rdn = ldb.Dn(self.samdb, str(new_dn))
737 new_rdn.remove_base_components(len(new_rdn) - 1)
738 new_parent = new_dn.parent()
740 attributes = ""
741 if rdn_val != name_val:
742 attributes += "%s=%r " % (rdn_attr, rdn_val)
743 attributes += "name=%r" % (name_val)
745 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
746 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
747 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
748 return
750 if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
751 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
752 self.report("Renamed %s into %s" % (obj.dn, new_dn))
754 def err_wrong_instancetype(self, obj, calculated_instancetype):
755 '''handle a wrong instanceType'''
756 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
757 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
758 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
759 return
761 m = ldb.Message()
762 m.dn = obj.dn
763 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
764 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
765 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
766 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
768 def err_short_userParameters(self, obj, attrname, value):
769 # This is a truncated userParameters due to a pre 4.1 replication bug
770 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)))
772 def err_base64_userParameters(self, obj, attrname, value):
773 '''handle a wrong userParameters'''
774 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
775 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
776 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
777 return
779 m = ldb.Message()
780 m.dn = obj.dn
781 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
782 if self.do_modify(m, [],
783 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
784 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
786 def err_utf8_userParameters(self, obj, attrname, value):
787 '''handle a wrong userParameters'''
788 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
789 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
790 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
791 return
793 m = ldb.Message()
794 m.dn = obj.dn
795 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
796 ldb.FLAG_MOD_REPLACE, 'userParameters')
797 if self.do_modify(m, [],
798 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
799 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
801 def err_doubled_userParameters(self, obj, attrname, value):
802 '''handle a wrong userParameters'''
803 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
804 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
805 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
806 return
808 m = ldb.Message()
809 m.dn = obj.dn
810 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
811 ldb.FLAG_MOD_REPLACE, 'userParameters')
812 if self.do_modify(m, [],
813 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
814 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
816 def err_odd_userParameters(self, obj, attrname):
817 # This is a truncated userParameters due to a pre 4.1 replication bug
818 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)))
820 def find_revealed_link(self, dn, attrname, guid):
821 '''return a revealed link in an object'''
822 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
823 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
824 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
825 for val in res[0][attrname]:
826 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
827 guid2 = dsdb_dn.dn.get_extended_component("GUID")
828 if guid == guid2:
829 return dsdb_dn
830 return None
832 def check_dn(self, obj, attrname, syntax_oid):
833 '''check a DN attribute for correctness'''
834 error_count = 0
835 obj_guid = obj['objectGUID'][0]
837 for val in obj[attrname]:
838 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
840 # all DNs should have a GUID component
841 guid = dsdb_dn.dn.get_extended_component("GUID")
842 if guid is None:
843 error_count += 1
844 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
845 "missing GUID")
846 continue
848 guidstr = str(misc.GUID(guid))
849 attrs = ['isDeleted', 'replPropertyMetaData']
851 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
852 fixing_msDS_HasInstantiatedNCs = True
853 attrs.append("instanceType")
854 else:
855 fixing_msDS_HasInstantiatedNCs = False
857 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
858 if reverse_link_name is not None:
859 attrs.append(reverse_link_name)
861 # check its the right GUID
862 try:
863 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
864 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
865 "reveal_internals:0"
867 except ldb.LdbError, (enum, estr):
868 error_count += 1
869 self.report("ERROR: no target object found for GUID component for %s in object %s - %s" % (attrname, obj.dn, val))
870 if enum != ldb.ERR_NO_SUCH_OBJECT:
871 raise
873 self.err_missing_target_dn_or_GUID(obj.dn, attrname, val, dsdb_dn)
874 continue
876 if fixing_msDS_HasInstantiatedNCs:
877 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
878 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
880 if str(dsdb_dn) != val:
881 error_count +=1
882 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
883 continue
885 # now we have two cases - the source object might or might not be deleted
886 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
887 target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
890 if is_deleted and not obj.dn in self.deleted_objects_containers and linkID:
891 # A fully deleted object should not have any linked
892 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
893 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
894 # Requirements)
895 self.err_undead_linked_attribute(obj, attrname, val)
896 error_count += 1
897 continue
898 elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
899 # the target DN is not allowed to be deleted, unless the target DN is the
900 # special Deleted Objects container
901 error_count += 1
902 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
903 if local_usn:
904 if 'replPropertyMetaData' in res[0]:
905 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
906 str(res[0]['replPropertyMetadata']))
907 found_data = False
908 for o in repl.ctr.array:
909 if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
910 deleted_usn = o.local_usn
911 if deleted_usn >= int(local_usn):
912 # If the object was deleted after the link
913 # was last modified then, clean it up here
914 found_data = True
915 break
917 if found_data:
918 self.err_deleted_dn(obj.dn, attrname,
919 val, dsdb_dn, res[0].dn, True)
920 continue
922 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
923 continue
925 # We should not check for incorrect
926 # components on deleted links, as these are allowed to
927 # go stale (we just need the GUID, not the name)
928 rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
929 if rmd_blob is not None:
930 rmd_flags = int(rmd_blob)
931 if rmd_flags & 1:
932 continue
934 # assert the DN matches in string form, where a reverse
935 # link exists, otherwise (below) offer to fix it as a non-error.
936 # The string form is essentially only kept for forensics,
937 # as we always re-resolve by GUID in normal operations.
938 if reverse_link_name is not None:
939 if str(res[0].dn) != str(dsdb_dn.dn):
940 error_count += 1
941 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
942 res[0].dn, "string")
943 continue
945 if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
946 error_count += 1
947 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
948 res[0].dn, "GUID")
949 continue
951 if res[0].dn.get_extended_component("SID") != dsdb_dn.dn.get_extended_component("SID"):
952 error_count += 1
953 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
954 res[0].dn, "SID")
955 continue
957 # Now we have checked the GUID and SID, offer to fix old
958 # DN strings as a non-error (for forward links with no
959 # backlink). Samba does not maintain this string
960 # otherwise, so we don't increment error_count.
961 if reverse_link_name is None:
962 if str(res[0].dn) != str(dsdb_dn.dn):
963 self.err_dn_string_component_old(obj.dn, attrname, val, dsdb_dn,
964 res[0].dn)
965 continue
967 else:
968 # check the reverse_link is correct if there should be one
969 match_count = 0
970 if reverse_link_name in res[0]:
971 for v in res[0][reverse_link_name]:
972 v_guid = dsdb_Dn(self.samdb, v).dn.get_extended_component("GUID")
973 if v_guid == obj_guid:
974 match_count += 1
975 if match_count != 1:
976 reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
977 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
978 if not linkID & 1:
979 # Forward binary multi-valued linked attribute
980 forward_count = 0
981 for w in obj[attrname]:
982 w_guid = dsdb_Dn(self.samdb, w).dn.get_extended_component("GUID")
983 if w_guid == guid:
984 forward_count += 1
986 if match_count == forward_count:
987 continue
989 error_count += 1
991 # Add or remove the missing number of backlinks
992 diff_count = forward_count - match_count
994 # Loop until the difference between the forward and
995 # the backward links is resolved.
996 while diff_count != 0:
997 if diff_count > 0:
998 # self.err_missing_backlink(obj, attrname,
999 # obj.dn.extended_str(),
1000 # reverse_link_name,
1001 # dsdb_dn.dn)
1002 # diff_count -= 1
1003 # TODO no method to fix these right now
1004 self.report("ERROR: Can't fix missing "
1005 "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1006 break
1007 else:
1008 self.err_orphaned_backlink(res[0], reverse_link_name,
1009 obj.dn.extended_str(), attrname,
1010 dsdb_dn.dn)
1011 diff_count += 1
1013 else:
1014 # If there's a backward link on binary multi-valued linked attribute,
1015 # let the check on the forward link remedy the value.
1016 # UNLESS, there is no forward link detected.
1017 if match_count == 0:
1018 self.err_orphaned_backlink(obj, attrname,
1019 val, reverse_link_name,
1020 dsdb_dn.dn)
1022 continue
1024 error_count += 1
1025 if linkID & 1:
1026 # Backlink exists, but forward link does not
1027 # Delete the hanging backlink
1028 self.err_orphaned_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
1029 else:
1030 # Forward link exists, but backlink does not
1031 # Add the missing backlink (if the target object is not Deleted Objects?)
1032 if not target_is_deleted:
1033 self.err_missing_backlink(obj, attrname, obj.dn.extended_str(), reverse_link_name, dsdb_dn.dn)
1034 continue
1039 return error_count
1042 def get_originating_time(self, val, attid):
1043 '''Read metadata properties and return the originating time for
1044 a given attributeId.
1046 :return: the originating time or 0 if not found
1049 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1050 obj = repl.ctr
1052 for o in repl.ctr.array:
1053 if o.attid == attid:
1054 return o.originating_change_time
1056 return 0
1058 def process_metadata(self, dn, val):
1059 '''Read metadata properties and list attributes in it.
1060 raises KeyError if the attid is unknown.'''
1062 set_att = set()
1063 wrong_attids = set()
1064 list_attid = []
1065 in_schema_nc = dn.is_child_of(self.schema_dn)
1067 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1068 obj = repl.ctr
1070 for o in repl.ctr.array:
1071 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1072 set_att.add(att.lower())
1073 list_attid.append(o.attid)
1074 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1075 is_schema_nc=in_schema_nc)
1076 if correct_attid != o.attid:
1077 wrong_attids.add(o.attid)
1079 return (set_att, list_attid, wrong_attids)
1082 def fix_metadata(self, dn, attr):
1083 '''re-write replPropertyMetaData elements for a single attribute for a
1084 object. This is used to fix missing replPropertyMetaData elements'''
1085 res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
1086 controls = ["search_options:1:2", "show_recycled:1"])
1087 msg = res[0]
1088 nmsg = ldb.Message()
1089 nmsg.dn = dn
1090 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1091 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1092 "Failed to fix metadata for attribute %s" % attr):
1093 self.report("Fixed metadata for attribute %s" % attr)
1095 def ace_get_effective_inherited_type(self, ace):
1096 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1097 return None
1099 check = False
1100 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1101 check = True
1102 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1103 check = True
1104 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1105 check = True
1106 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1107 check = True
1109 if not check:
1110 return None
1112 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1113 return None
1115 return str(ace.object.inherited_type)
1117 def lookup_class_schemaIDGUID(self, cls):
1118 if cls in self.class_schemaIDGUID:
1119 return self.class_schemaIDGUID[cls]
1121 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1122 res = self.samdb.search(base=self.schema_dn,
1123 expression=flt,
1124 attrs=["schemaIDGUID"])
1125 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1127 self.class_schemaIDGUID[cls] = t
1128 return t
1130 def process_sd(self, dn, obj):
1131 sd_attr = "nTSecurityDescriptor"
1132 sd_val = obj[sd_attr]
1134 sd = ndr_unpack(security.descriptor, str(sd_val))
1136 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1137 if is_deleted:
1138 # we don't fix deleted objects
1139 return (sd, None)
1141 sd_clean = security.descriptor()
1142 sd_clean.owner_sid = sd.owner_sid
1143 sd_clean.group_sid = sd.group_sid
1144 sd_clean.type = sd.type
1145 sd_clean.revision = sd.revision
1147 broken = False
1148 last_inherited_type = None
1150 aces = []
1151 if sd.sacl is not None:
1152 aces = sd.sacl.aces
1153 for i in range(0, len(aces)):
1154 ace = aces[i]
1156 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1157 sd_clean.sacl_add(ace)
1158 continue
1160 t = self.ace_get_effective_inherited_type(ace)
1161 if t is None:
1162 continue
1164 if last_inherited_type is not None:
1165 if t != last_inherited_type:
1166 # if it inherited from more than
1167 # one type it's very likely to be broken
1169 # If not the recalculation will calculate
1170 # the same result.
1171 broken = True
1172 continue
1174 last_inherited_type = t
1176 aces = []
1177 if sd.dacl is not None:
1178 aces = sd.dacl.aces
1179 for i in range(0, len(aces)):
1180 ace = aces[i]
1182 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1183 sd_clean.dacl_add(ace)
1184 continue
1186 t = self.ace_get_effective_inherited_type(ace)
1187 if t is None:
1188 continue
1190 if last_inherited_type is not None:
1191 if t != last_inherited_type:
1192 # if it inherited from more than
1193 # one type it's very likely to be broken
1195 # If not the recalculation will calculate
1196 # the same result.
1197 broken = True
1198 continue
1200 last_inherited_type = t
1202 if broken:
1203 return (sd_clean, sd)
1205 if last_inherited_type is None:
1206 # ok
1207 return (sd, None)
1209 cls = None
1210 try:
1211 cls = obj["objectClass"][-1]
1212 except KeyError, e:
1213 pass
1215 if cls is None:
1216 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1217 attrs=["isDeleted", "objectClass"],
1218 controls=["show_recycled:1"])
1219 o = res[0]
1220 is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
1221 if is_deleted:
1222 # we don't fix deleted objects
1223 return (sd, None)
1224 cls = o["objectClass"][-1]
1226 t = self.lookup_class_schemaIDGUID(cls)
1228 if t != last_inherited_type:
1229 # broken
1230 return (sd_clean, sd)
1232 # ok
1233 return (sd, None)
1235 def err_wrong_sd(self, dn, sd, sd_broken):
1236 '''re-write the SD due to incorrect inherited ACEs'''
1237 sd_attr = "nTSecurityDescriptor"
1238 sd_val = ndr_pack(sd)
1239 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1241 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1242 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1243 return
1245 nmsg = ldb.Message()
1246 nmsg.dn = dn
1247 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1248 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1249 "Failed to fix attribute %s" % sd_attr):
1250 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1252 def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1253 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1254 sd_attr = "nTSecurityDescriptor"
1255 sd_val = ndr_pack(sd)
1256 sd_old_val = ndr_pack(sd_old)
1257 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1258 if sd.owner_sid is not None:
1259 sd_flags |= security.SECINFO_OWNER
1260 if sd.group_sid is not None:
1261 sd_flags |= security.SECINFO_GROUP
1263 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1264 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1265 return
1267 m = ldb.Message()
1268 m.dn = dn
1269 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1270 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1271 "Failed to reset attribute %s" % sd_attr):
1272 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1274 def err_missing_sd_owner(self, dn, sd):
1275 '''re-write the SD due to a missing owner or group'''
1276 sd_attr = "nTSecurityDescriptor"
1277 sd_val = ndr_pack(sd)
1278 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1280 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1281 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1282 return
1284 nmsg = ldb.Message()
1285 nmsg.dn = dn
1286 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1288 # By setting the session_info to admin_session_info and
1289 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1290 # flags we cause the descriptor module to set the correct
1291 # owner and group on the SD, replacing the None/NULL values
1292 # for owner_sid and group_sid currently present.
1294 # The admin_session_info matches that used in provision, and
1295 # is the best guess we can make for an existing object that
1296 # hasn't had something specifically set.
1298 # This is important for the dns related naming contexts.
1299 self.samdb.set_session_info(self.admin_session_info)
1300 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1301 "Failed to fix metadata for attribute %s" % sd_attr):
1302 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1303 self.samdb.set_session_info(self.system_session_info)
1306 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1307 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1308 str(repl_meta_data))
1309 ctr = repl.ctr
1310 found = False
1311 for o in ctr.array:
1312 # Search for a zero invocationID
1313 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1314 continue
1316 found = True
1317 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1318 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1319 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1320 % (dn, o.attid, o.version,
1321 time.ctime(samba.nttime2unix(o.originating_change_time)),
1322 self.samdb.get_invocation_id()))
1324 return found
1327 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1328 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1329 str(repl_meta_data))
1330 ctr = repl.ctr
1331 now = samba.unix2nttime(int(time.time()))
1332 found = False
1333 for o in ctr.array:
1334 # Search for a zero invocationID
1335 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1336 continue
1338 found = True
1339 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1340 o.version = o.version + 1
1341 o.originating_change_time = now
1342 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1343 o.originating_usn = seq
1344 o.local_usn = seq
1346 if found:
1347 replBlob = ndr_pack(repl)
1348 msg = ldb.Message()
1349 msg.dn = dn
1351 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1352 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1353 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1354 return
1356 nmsg = ldb.Message()
1357 nmsg.dn = dn
1358 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1359 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1360 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1361 "Failed to fix attribute %s" % attr):
1362 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1365 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1366 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1367 str(repl_meta_data))
1368 ctr = repl.ctr
1369 for o in ctr.array:
1370 # Search for an invalid attid
1371 try:
1372 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1373 except KeyError:
1374 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1375 return
1378 def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1379 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1380 str(repl_meta_data))
1381 fix = False
1383 set_att = set()
1384 remove_attid = set()
1385 hash_att = {}
1387 in_schema_nc = dn.is_child_of(self.schema_dn)
1389 ctr = repl.ctr
1390 # Sort the array, except for the last element. This strange
1391 # construction, creating a new list, due to bugs in samba's
1392 # array handling in IDL generated objects.
1393 ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1394 # Now walk it in reverse, so we see the low (and so incorrect,
1395 # the correct values are above 0x80000000) values first and
1396 # remove the 'second' value we see.
1397 for o in reversed(ctr.array):
1398 print "%s: 0x%08x" % (dn, o.attid)
1399 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1400 if att.lower() in set_att:
1401 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1402 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1403 % (attr, dn, o.attid, att, hash_att[att].attid),
1404 'fix_replmetadata_duplicate_attid'):
1405 self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1406 % (o.attid, att, attr, dn))
1407 return
1408 fix = True
1409 remove_attid.add(o.attid)
1410 # We want to set the metadata for the most recent
1411 # update to have been applied locally, that is the metadata
1412 # matching the (eg string) value in the attribute
1413 if o.local_usn > hash_att[att].local_usn:
1414 # This is always what we would have sent over DRS,
1415 # because the DRS server will have sent the
1416 # msDS-IntID, but with the values from both
1417 # attribute entries.
1418 hash_att[att].version = o.version
1419 hash_att[att].originating_change_time = o.originating_change_time
1420 hash_att[att].originating_invocation_id = o.originating_invocation_id
1421 hash_att[att].originating_usn = o.originating_usn
1422 hash_att[att].local_usn = o.local_usn
1424 # Do not re-add the value to the set or overwrite the hash value
1425 continue
1427 hash_att[att] = o
1428 set_att.add(att.lower())
1430 # Generate a real list we can sort on properly
1431 new_list = [o for o in ctr.array if o.attid not in remove_attid]
1433 if (len(wrong_attids) > 0):
1434 for o in new_list:
1435 if o.attid in wrong_attids:
1436 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1437 correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1438 self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1439 if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1440 % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1441 self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1442 % (o.attid, correct_attid, att, attr, dn))
1443 return
1444 fix = True
1445 o.attid = correct_attid
1446 if fix:
1447 # Sort the array, (we changed the value so must re-sort)
1448 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1450 # If we did not already need to fix it, then ask about sorting
1451 if not fix:
1452 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1453 if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1454 % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1455 self.report('Not fixing %s on %s\n' % (attr, dn))
1456 return
1458 # The actual sort done is done at the top of the function
1460 ctr.count = len(new_list)
1461 ctr.array = new_list
1462 replBlob = ndr_pack(repl)
1464 nmsg = ldb.Message()
1465 nmsg.dn = dn
1466 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1467 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1468 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1469 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1470 "Failed to fix attribute %s" % attr):
1471 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1474 def is_deleted_deleted_objects(self, obj):
1475 faulty = False
1476 if "description" not in obj:
1477 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1478 faulty = True
1479 if "showInAdvancedViewOnly" not in obj or obj['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1480 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1481 faulty = True
1482 if "objectCategory" not in obj:
1483 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1484 faulty = True
1485 if "isCriticalSystemObject" not in obj or obj['isCriticalSystemObject'][0].upper() == 'FALSE':
1486 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1487 faulty = True
1488 if "isRecycled" in obj:
1489 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1490 faulty = True
1491 if "isDeleted" in obj and obj['isDeleted'][0].upper() == 'FALSE':
1492 self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1493 faulty = True
1494 if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1495 obj['objectClass'][0] != 'top' or
1496 obj['objectClass'][1] != 'container'):
1497 self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1498 faulty = True
1499 if "systemFlags" not in obj or obj['systemFlags'][0] != '-1946157056':
1500 self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1501 faulty = True
1502 return faulty
1504 def err_deleted_deleted_objects(self, obj):
1505 nmsg = ldb.Message()
1506 nmsg.dn = dn = obj.dn
1508 if "description" not in obj:
1509 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1510 if "showInAdvancedViewOnly" not in obj:
1511 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1512 if "objectCategory" not in obj:
1513 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1514 if "isCriticalSystemObject" not in obj:
1515 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1516 if "isRecycled" in obj:
1517 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1519 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1520 nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1521 nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1523 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1524 % (dn), 'fix_deleted_deleted_objects'):
1525 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1526 return
1528 if self.do_modify(nmsg, ["relax:0"],
1529 "Failed to fix Deleted Objects container %s" % dn):
1530 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1532 def err_replica_locations(self, obj, cross_ref, attr):
1533 nmsg = ldb.Message()
1534 nmsg.dn = cross_ref
1535 target = self.samdb.get_dsServiceName()
1537 if self.samdb.am_rodc():
1538 self.report('Not fixing %s for the RODC' % (attr, obj.dn))
1539 return
1541 if not self.confirm_all('Add yourself to the replica locations for %s?'
1542 % (obj.dn), 'fix_replica_locations'):
1543 self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1544 return
1546 nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1547 if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1548 self.report("Fixed %s for %s" % (attr, obj.dn))
1550 def is_fsmo_role(self, dn):
1551 if dn == self.samdb.domain_dn:
1552 return True
1553 if dn == self.infrastructure_dn:
1554 return True
1555 if dn == self.naming_dn:
1556 return True
1557 if dn == self.schema_dn:
1558 return True
1559 if dn == self.rid_dn:
1560 return True
1562 return False
1564 def calculate_instancetype(self, dn):
1565 instancetype = 0
1566 nc_root = self.samdb.get_nc_root(dn)
1567 if dn == nc_root:
1568 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1569 try:
1570 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1571 except ldb.LdbError, (enum, estr):
1572 if enum != ldb.ERR_NO_SUCH_OBJECT:
1573 raise
1574 else:
1575 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1577 if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1578 instancetype |= dsdb.INSTANCE_TYPE_WRITE
1580 return instancetype
1582 def get_wellknown_sd(self, dn):
1583 for [sd_dn, descriptor_fn] in self.wellknown_sds:
1584 if dn == sd_dn:
1585 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1586 return ndr_unpack(security.descriptor,
1587 descriptor_fn(domain_sid,
1588 name_map=self.name_map))
1590 raise KeyError
1592 def check_object(self, dn, attrs=['*']):
1593 '''check one object'''
1594 if self.verbose:
1595 self.report("Checking object %s" % dn)
1597 # If we modify the pass-by-reference attrs variable, then we get a
1598 # replPropertyMetadata for every object that we check.
1599 attrs = list(attrs)
1600 if "dn" in map(str.lower, attrs):
1601 attrs.append("name")
1602 if "distinguishedname" in map(str.lower, attrs):
1603 attrs.append("name")
1604 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1605 attrs.append("name")
1606 if 'name' in map(str.lower, attrs):
1607 attrs.append(dn.get_rdn_name())
1608 attrs.append("isDeleted")
1609 attrs.append("systemFlags")
1610 if '*' in attrs:
1611 attrs.append("replPropertyMetaData")
1612 else:
1613 attrs.append("objectGUID")
1615 try:
1616 sd_flags = 0
1617 sd_flags |= security.SECINFO_OWNER
1618 sd_flags |= security.SECINFO_GROUP
1619 sd_flags |= security.SECINFO_DACL
1620 sd_flags |= security.SECINFO_SACL
1622 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1623 controls=[
1624 "extended_dn:1:1",
1625 "show_recycled:1",
1626 "show_deleted:1",
1627 "sd_flags:1:%d" % sd_flags,
1628 "reveal_internals:0",
1630 attrs=attrs)
1631 except ldb.LdbError, (enum, estr):
1632 if enum == ldb.ERR_NO_SUCH_OBJECT:
1633 if self.in_transaction:
1634 self.report("ERROR: Object %s disappeared during check" % dn)
1635 return 1
1636 return 0
1637 raise
1638 if len(res) != 1:
1639 self.report("ERROR: Object %s failed to load during check" % dn)
1640 return 1
1641 obj = res[0]
1642 error_count = 0
1643 set_attrs_from_md = set()
1644 set_attrs_seen = set()
1645 got_repl_property_meta_data = False
1646 got_objectclass = False
1648 nc_dn = self.samdb.get_nc_root(obj.dn)
1649 try:
1650 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
1651 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
1652 except KeyError:
1653 # We have no deleted objects DN for schema, and we check for this above for the other
1654 # NCs
1655 deleted_objects_dn = None
1658 object_rdn_attr = None
1659 object_rdn_val = None
1660 name_val = None
1661 isDeleted = False
1662 systemFlags = 0
1664 for attrname in obj:
1665 if attrname == 'dn' or attrname == "distinguishedName":
1666 continue
1668 if str(attrname).lower() == 'objectclass':
1669 got_objectclass = True
1671 if str(attrname).lower() == "name":
1672 if len(obj[attrname]) != 1:
1673 error_count += 1
1674 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1675 (len(obj[attrname]), attrname, str(obj.dn)))
1676 else:
1677 name_val = obj[attrname][0]
1679 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
1680 object_rdn_attr = attrname
1681 if len(obj[attrname]) != 1:
1682 error_count += 1
1683 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1684 (len(obj[attrname]), attrname, str(obj.dn)))
1685 else:
1686 object_rdn_val = obj[attrname][0]
1688 if str(attrname).lower() == 'isdeleted':
1689 if obj[attrname][0] != "FALSE":
1690 isDeleted = True
1692 if str(attrname).lower() == 'systemflags':
1693 systemFlags = int(obj[attrname][0])
1695 if str(attrname).lower() == 'replpropertymetadata':
1696 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
1697 error_count += 1
1698 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
1699 # We don't continue, as we may also have other fixes for this attribute
1700 # based on what other attributes we see.
1702 try:
1703 (set_attrs_from_md, list_attid_from_md, wrong_attids) \
1704 = self.process_metadata(dn, obj[attrname])
1705 except KeyError:
1706 error_count += 1
1707 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
1708 continue
1710 if len(set_attrs_from_md) < len(list_attid_from_md) \
1711 or len(wrong_attids) > 0 \
1712 or sorted(list_attid_from_md) != list_attid_from_md:
1713 error_count +=1
1714 self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname], wrong_attids)
1716 else:
1717 # Here we check that the first attid is 0
1718 # (objectClass).
1719 if list_attid_from_md[0] != 0:
1720 error_count += 1
1721 self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
1722 (attrname, str(dn)))
1724 got_repl_property_meta_data = True
1725 continue
1727 if str(attrname).lower() == 'ntsecuritydescriptor':
1728 (sd, sd_broken) = self.process_sd(dn, obj)
1729 if sd_broken is not None:
1730 self.err_wrong_sd(dn, sd, sd_broken)
1731 error_count += 1
1732 continue
1734 if sd.owner_sid is None or sd.group_sid is None:
1735 self.err_missing_sd_owner(dn, sd)
1736 error_count += 1
1737 continue
1739 if self.reset_well_known_acls:
1740 try:
1741 well_known_sd = self.get_wellknown_sd(dn)
1742 except KeyError:
1743 continue
1745 current_sd = ndr_unpack(security.descriptor,
1746 str(obj[attrname][0]))
1748 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
1749 if diff != "":
1750 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
1751 error_count += 1
1752 continue
1753 continue
1755 if str(attrname).lower() == 'objectclass':
1756 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
1757 # Do not consider the attribute incorrect if:
1758 # - The sorted (alphabetically) list is the same, inclding case
1759 # - The first and last elements are the same
1761 # This avoids triggering an error due to
1762 # non-determinism in the sort routine in (at least)
1763 # 4.3 and earlier, and the fact that any AUX classes
1764 # in these attributes are also not sorted when
1765 # imported from Windows (they are just in the reverse
1766 # order of last set)
1767 if sorted(normalised) != sorted(obj[attrname]) \
1768 or normalised[0] != obj[attrname][0] \
1769 or normalised[-1] != obj[attrname][-1]:
1770 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
1771 error_count += 1
1772 continue
1774 if str(attrname).lower() == 'userparameters':
1775 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
1776 error_count += 1
1777 self.err_short_userParameters(obj, attrname, obj[attrname])
1778 continue
1780 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1781 # This is the correct, normal prefix
1782 continue
1784 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1785 # this is the typical prefix from a windows migration
1786 error_count += 1
1787 self.err_base64_userParameters(obj, attrname, obj[attrname])
1788 continue
1790 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':
1791 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1792 error_count += 1
1793 self.err_utf8_userParameters(obj, attrname, obj[attrname])
1794 continue
1796 elif len(obj[attrname][0]) % 2 != 0:
1797 # This is a value that isn't even in length
1798 error_count += 1
1799 self.err_odd_userParameters(obj, attrname, obj[attrname])
1800 continue
1802 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':
1803 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
1804 error_count += 1
1805 self.err_doubled_userParameters(obj, attrname, obj[attrname])
1806 continue
1808 if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
1809 if obj[attrname][0] in self.attribute_or_class_ids:
1810 error_count += 1
1811 self.report('Error: %s %s on %s already exists as an attributeId or governsId'
1812 % (attrname, obj.dn, obj[attrname][0]))
1813 else:
1814 self.attribute_or_class_ids.add(obj[attrname][0])
1816 # check for empty attributes
1817 for val in obj[attrname]:
1818 if val == '':
1819 self.err_empty_attribute(dn, attrname)
1820 error_count += 1
1821 continue
1823 # get the syntax oid for the attribute, so we can can have
1824 # special handling for some specific attribute types
1825 try:
1826 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1827 except Exception, msg:
1828 self.err_unknown_attribute(obj, attrname)
1829 error_count += 1
1830 continue
1832 linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1834 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
1835 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
1836 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
1837 and not linkID):
1838 set_attrs_seen.add(str(attrname).lower())
1840 if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
1841 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
1842 # it's some form of DN, do specialised checking on those
1843 error_count += self.check_dn(obj, attrname, syntax_oid)
1844 else:
1846 values = set()
1847 # check for incorrectly normalised attributes
1848 for val in obj[attrname]:
1849 values.add(str(val))
1851 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
1852 if len(normalised) != 1 or normalised[0] != val:
1853 self.err_normalise_mismatch(dn, attrname, obj[attrname])
1854 error_count += 1
1855 break
1857 if len(obj[attrname]) != len(values):
1858 self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
1859 error_count += 1
1860 break
1862 if str(attrname).lower() == "instancetype":
1863 calculated_instancetype = self.calculate_instancetype(dn)
1864 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
1865 error_count += 1
1866 self.err_wrong_instancetype(obj, calculated_instancetype)
1868 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
1869 error_count += 1
1870 self.err_missing_objectclass(dn)
1872 if ("*" in attrs or "name" in map(str.lower, attrs)):
1873 if name_val is None:
1874 error_count += 1
1875 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
1876 if object_rdn_attr is None:
1877 error_count += 1
1878 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
1880 if name_val is not None:
1881 parent_dn = None
1882 if isDeleted:
1883 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
1884 parent_dn = deleted_objects_dn
1885 if parent_dn is None:
1886 parent_dn = obj.dn.parent()
1887 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
1888 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
1890 if obj.dn == deleted_objects_dn:
1891 expected_dn = obj.dn
1893 if expected_dn != obj.dn:
1894 error_count += 1
1895 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
1896 elif obj.dn.get_rdn_value() != object_rdn_val:
1897 error_count += 1
1898 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
1900 show_dn = True
1901 if got_repl_property_meta_data:
1902 if obj.dn == deleted_objects_dn:
1903 isDeletedAttId = 131120
1904 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
1906 expectedTimeDo = 2650466015990000000
1907 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
1908 if originating != expectedTimeDo:
1909 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
1910 nmsg = ldb.Message()
1911 nmsg.dn = dn
1912 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1913 error_count += 1
1914 self.samdb.modify(nmsg, controls=["provision:0"])
1916 else:
1917 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
1919 for att in set_attrs_seen.difference(set_attrs_from_md):
1920 if show_dn:
1921 self.report("On object %s" % dn)
1922 show_dn = False
1923 error_count += 1
1924 self.report("ERROR: Attribute %s not present in replication metadata" % att)
1925 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
1926 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
1927 continue
1928 self.fix_metadata(dn, att)
1930 if self.is_fsmo_role(dn):
1931 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
1932 self.err_no_fsmoRoleOwner(obj)
1933 error_count += 1
1935 try:
1936 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
1937 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
1938 controls=["show_recycled:1", "show_deleted:1"])
1939 except ldb.LdbError, (enum, estr):
1940 if enum == ldb.ERR_NO_SUCH_OBJECT:
1941 self.err_missing_parent(obj)
1942 error_count += 1
1943 else:
1944 raise
1946 if dn in self.deleted_objects_containers and '*' in attrs:
1947 if self.is_deleted_deleted_objects(obj):
1948 self.err_deleted_deleted_objects(obj)
1949 error_count += 1
1951 for (dns_part, msg) in self.dns_partitions:
1952 if dn == dns_part and 'repsFrom' in obj:
1953 location = "msDS-NC-Replica-Locations"
1954 if self.samdb.am_rodc():
1955 location = "msDS-NC-RO-Replica-Locations"
1957 if location not in msg:
1958 # There are no replica locations!
1959 self.err_replica_locations(obj, msg.dn, location)
1960 error_count += 1
1961 continue
1963 found = False
1964 for loc in msg[location]:
1965 if loc == self.samdb.get_dsServiceName():
1966 found = True
1967 if not found:
1968 # This DC is not in the replica locations
1969 self.err_replica_locations(obj, msg.dn, location)
1970 error_count += 1
1972 if dn == self.server_ref_dn:
1973 # Check we have a valid RID Set
1974 if "*" in attrs or "rIDSetReferences" in attrs:
1975 if "rIDSetReferences" not in obj:
1976 # NO RID SET reference
1977 # We are RID master, allocate it.
1978 error_count += 1
1980 if self.is_rid_master:
1981 # Allocate a RID Set
1982 if self.confirm_all('Allocate the missing RID set for RID master?',
1983 'fix_missing_rid_set_master'):
1985 # We don't have auto-transaction logic on
1986 # extended operations, so we have to do it
1987 # here.
1989 self.samdb.transaction_start()
1991 try:
1992 self.samdb.create_own_rid_set()
1994 except:
1995 self.samdb.transaction_cancel()
1996 raise
1998 self.samdb.transaction_commit()
2001 elif not self.samdb.am_rodc():
2002 self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2005 # Check some details of our own RID Set
2006 if dn == self.rid_set_dn:
2007 res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2008 attrs=["rIDAllocationPool",
2009 "rIDPreviousAllocationPool",
2010 "rIDUsedPool",
2011 "rIDNextRID"])
2012 if "rIDAllocationPool" not in res[0]:
2013 self.report("No rIDAllocationPool found in %s" % dn)
2014 error_count += 1
2015 else:
2016 next_pool = int(res[0]["rIDAllocationPool"][0])
2018 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2019 low = 0x00000000FFFFFFFF & next_pool
2021 if high <= low:
2022 self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2023 error_count += 1
2025 if "rIDNextRID" in res[0]:
2026 next_free_rid = int(res[0]["rIDNextRID"][0])
2027 else:
2028 next_free_rid = 0
2030 if next_free_rid == 0:
2031 next_free_rid = low
2032 else:
2033 next_free_rid += 1
2035 # Check the remainder of this pool for conflicts. If
2036 # ridalloc_allocate_rid() moves to a new pool, this
2037 # will be above high, so we will stop.
2038 while next_free_rid <= high:
2039 sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2040 try:
2041 res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2042 attrs=[])
2043 except ldb.LdbError, (enum, estr):
2044 if enum != ldb.ERR_NO_SUCH_OBJECT:
2045 raise
2046 res = None
2047 if res is not None:
2048 self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2049 error_count += 1
2051 if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2052 % (sid, dn),
2053 'fix_sid_rid_set_conflict'):
2054 self.samdb.transaction_start()
2056 # This will burn RIDs, which will move
2057 # past the conflict. We then check again
2058 # to see if the new RID conflicts, until
2059 # the end of the current pool. We don't
2060 # look at the next pool to avoid burning
2061 # all RIDs in one go in some strange
2062 # failure case.
2063 try:
2064 while True:
2065 allocated_rid = self.samdb.allocate_rid()
2066 if allocated_rid >= next_free_rid:
2067 next_free_rid = allocated_rid + 1
2068 break
2069 except:
2070 self.samdb.transaction_cancel()
2071 raise
2073 self.samdb.transaction_commit()
2074 else:
2075 break
2076 else:
2077 next_free_rid += 1
2080 return error_count
2082 ################################################################
2083 # check special @ROOTDSE attributes
2084 def check_rootdse(self):
2085 '''check the @ROOTDSE special object'''
2086 dn = ldb.Dn(self.samdb, '@ROOTDSE')
2087 if self.verbose:
2088 self.report("Checking object %s" % dn)
2089 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2090 if len(res) != 1:
2091 self.report("Object %s disappeared during check" % dn)
2092 return 1
2093 obj = res[0]
2094 error_count = 0
2096 # check that the dsServiceName is in GUID form
2097 if not 'dsServiceName' in obj:
2098 self.report('ERROR: dsServiceName missing in @ROOTDSE')
2099 return error_count+1
2101 if not obj['dsServiceName'][0].startswith('<GUID='):
2102 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2103 error_count += 1
2104 if not self.confirm('Change dsServiceName to GUID form?'):
2105 return error_count
2106 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
2107 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2108 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2109 m = ldb.Message()
2110 m.dn = dn
2111 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2112 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2113 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2114 self.report("Changed dsServiceName to GUID form")
2115 return error_count
2118 ###############################################
2119 # re-index the database
2120 def reindex_database(self):
2121 '''re-index the whole database'''
2122 m = ldb.Message()
2123 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2124 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2125 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2126 return self.do_modify(m, [], 're-indexed database', validate=False)
2128 ###############################################
2129 # reset @MODULES
2130 def reset_modules(self):
2131 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2132 m = ldb.Message()
2133 m.dn = ldb.Dn(self.samdb, "@MODULES")
2134 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2135 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)