samba-tool domain demote: Remove correct DNs and from the correct locations
[Samba.git] / python / samba / dbchecker.py
blob69b4c61e42ec1c87966affdb9bf669d491ba592f
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
36 class dbcheck(object):
37 """check a SAM database for errors"""
39 def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
40 yes=False, quiet=False, in_transaction=False,
41 reset_well_known_acls=False):
42 self.samdb = samdb
43 self.dict_oid_name = None
44 self.samdb_schema = (samdb_schema or samdb)
45 self.verbose = verbose
46 self.fix = fix
47 self.yes = yes
48 self.quiet = quiet
49 self.remove_all_unknown_attributes = False
50 self.remove_all_empty_attributes = False
51 self.fix_all_normalisation = False
52 self.fix_all_DN_GUIDs = False
53 self.fix_all_binary_dn = False
54 self.remove_all_deleted_DN_links = False
55 self.fix_all_target_mismatch = False
56 self.fix_all_metadata = False
57 self.fix_time_metadata = False
58 self.fix_all_missing_backlinks = False
59 self.fix_all_orphaned_backlinks = False
60 self.fix_rmd_flags = False
61 self.fix_ntsecuritydescriptor = False
62 self.fix_ntsecuritydescriptor_owner_group = False
63 self.seize_fsmo_role = False
64 self.move_to_lost_and_found = False
65 self.fix_instancetype = False
66 self.fix_replmetadata_zero_invocationid = False
67 self.fix_replmetadata_unsorted_attid = False
68 self.fix_deleted_deleted_objects = False
69 self.fix_dn = False
70 self.fix_base64_userparameters = False
71 self.fix_utf8_userparameters = False
72 self.fix_doubled_userparameters = False
73 self.reset_well_known_acls = reset_well_known_acls
74 self.reset_all_well_known_acls = False
75 self.in_transaction = in_transaction
76 self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
77 self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
78 self.schema_dn = samdb.get_schema_basedn()
79 self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
80 self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
81 self.class_schemaIDGUID = {}
82 self.wellknown_sds = get_wellknown_sds(self.samdb)
83 self.fix_all_missing_objectclass = False
85 self.dn_set = set()
87 self.name_map = {}
88 try:
89 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE,
90 attrs=["objectSid"])
91 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
92 self.name_map['DnsAdmins'] = str(dnsadmins_sid)
93 except ldb.LdbError, (enum, estr):
94 if enum != ldb.ERR_NO_SUCH_OBJECT:
95 raise
96 pass
98 self.system_session_info = system_session()
99 self.admin_session_info = admin_session(None, samdb.get_domain_sid())
101 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
102 if "msDS-hasMasterNCs" in res[0]:
103 self.write_ncs = res[0]["msDS-hasMasterNCs"]
104 else:
105 # If the Forest Level is less than 2003 then there is no
106 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
107 # no need to merge as all the NCs that are in hasMasterNCs must
108 # also be in msDS-hasMasterNCs (but not the opposite)
109 if "hasMasterNCs" in res[0]:
110 self.write_ncs = res[0]["hasMasterNCs"]
111 else:
112 self.write_ncs = None
114 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
115 try:
116 ncs = res[0]["namingContexts"]
117 self.deleted_objects_containers = []
118 for nc in ncs:
119 try:
120 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc),
121 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
122 self.deleted_objects_containers.append(dn)
123 except KeyError:
124 pass
125 except KeyError:
126 pass
127 except IndexError:
128 pass
130 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
131 '''perform a database check, returning the number of errors found'''
132 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
133 self.report('Checking %u objects' % len(res))
134 error_count = 0
136 for object in res:
137 self.dn_set.add(str(object.dn))
138 error_count += self.check_object(object.dn, attrs=attrs)
140 if DN is None:
141 error_count += self.check_rootdse()
143 if error_count != 0 and not self.fix:
144 self.report("Please use --fix to fix these errors")
146 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
147 return error_count
149 def report(self, msg):
150 '''print a message unless quiet is set'''
151 if not self.quiet:
152 print(msg)
154 def confirm(self, msg, allow_all=False, forced=False):
155 '''confirm a change'''
156 if not self.fix:
157 return False
158 if self.quiet:
159 return self.yes
160 if self.yes:
161 forced = True
162 return common.confirm(msg, forced=forced, allow_all=allow_all)
164 ################################################################
165 # a local confirm function with support for 'all'
166 def confirm_all(self, msg, all_attr):
167 '''confirm a change with support for "all" '''
168 if not self.fix:
169 return False
170 if self.quiet:
171 return self.yes
172 if getattr(self, all_attr) == 'NONE':
173 return False
174 if getattr(self, all_attr) == 'ALL':
175 forced = True
176 else:
177 forced = self.yes
178 c = common.confirm(msg, forced=forced, allow_all=True)
179 if c == 'ALL':
180 setattr(self, all_attr, 'ALL')
181 return True
182 if c == 'NONE':
183 setattr(self, all_attr, 'NONE')
184 return False
185 return c
187 def do_delete(self, dn, controls, msg):
188 '''delete dn with optional verbose output'''
189 if self.verbose:
190 self.report("delete DN %s" % dn)
191 try:
192 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
193 self.samdb.delete(dn, controls=controls)
194 except Exception, err:
195 self.report("%s : %s" % (msg, err))
196 return False
197 return True
199 def do_modify(self, m, controls, msg, validate=True):
200 '''perform a modify with optional verbose output'''
201 if self.verbose:
202 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
203 try:
204 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
205 self.samdb.modify(m, controls=controls, validate=validate)
206 except Exception, err:
207 self.report("%s : %s" % (msg, err))
208 return False
209 return True
211 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
212 '''perform a modify with optional verbose output'''
213 if self.verbose:
214 self.report("""dn: %s
215 changeType: modrdn
216 newrdn: %s
217 deleteOldRdn: 1
218 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
219 try:
220 to_dn = to_rdn + to_base
221 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
222 self.samdb.rename(from_dn, to_dn, controls=controls)
223 except Exception, err:
224 self.report("%s : %s" % (msg, err))
225 return False
226 return True
228 def err_empty_attribute(self, dn, attrname):
229 '''fix empty attributes'''
230 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
231 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
232 self.report("Not fixing empty attribute %s" % attrname)
233 return
235 m = ldb.Message()
236 m.dn = dn
237 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
238 if self.do_modify(m, ["relax:0", "show_recycled:1"],
239 "Failed to remove empty attribute %s" % attrname, validate=False):
240 self.report("Removed empty attribute %s" % attrname)
242 def err_normalise_mismatch(self, dn, attrname, values):
243 '''fix attribute normalisation errors'''
244 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
245 mod_list = []
246 for val in values:
247 normalised = self.samdb.dsdb_normalise_attributes(
248 self.samdb_schema, attrname, [val])
249 if len(normalised) != 1:
250 self.report("Unable to normalise value '%s'" % val)
251 mod_list.append((val, ''))
252 elif (normalised[0] != val):
253 self.report("value '%s' should be '%s'" % (val, normalised[0]))
254 mod_list.append((val, normalised[0]))
255 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
256 self.report("Not fixing attribute %s" % attrname)
257 return
259 m = ldb.Message()
260 m.dn = dn
261 for i in range(0, len(mod_list)):
262 (val, nval) = mod_list[i]
263 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
264 if nval != '':
265 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
266 attrname)
268 if self.do_modify(m, ["relax:0", "show_recycled:1"],
269 "Failed to normalise attribute %s" % attrname,
270 validate=False):
271 self.report("Normalised attribute %s" % attrname)
273 def err_normalise_mismatch_replace(self, dn, attrname, values):
274 '''fix attribute normalisation errors'''
275 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
276 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
277 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
278 if list(normalised) == values:
279 return
280 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
281 self.report("Not fixing attribute '%s'" % attrname)
282 return
284 m = ldb.Message()
285 m.dn = dn
286 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
288 if self.do_modify(m, ["relax:0", "show_recycled:1"],
289 "Failed to normalise attribute %s" % attrname,
290 validate=False):
291 self.report("Normalised attribute %s" % attrname)
293 def is_deleted_objects_dn(self, dsdb_dn):
294 '''see if a dsdb_Dn is the special Deleted Objects DN'''
295 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
297 def err_missing_objectclass(self, dn):
298 """handle object without objectclass"""
299 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)))
300 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'):
301 self.report("Not deleting object with missing objectclass '%s'" % dn)
302 return
303 if self.do_delete(dn, ["relax:0"],
304 "Failed to remove DN %s" % dn):
305 self.report("Removed DN %s" % dn)
307 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn):
308 """handle a DN pointing to a deleted object"""
309 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
310 self.report("Target GUID points at deleted DN %s" % correct_dn)
311 if not self.confirm_all('Remove DN link?', 'remove_all_deleted_DN_links'):
312 self.report("Not removing")
313 return
314 m = ldb.Message()
315 m.dn = dn
316 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
317 if self.do_modify(m, ["show_recycled:1", "local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK],
318 "Failed to remove deleted DN attribute %s" % attrname):
319 self.report("Removed deleted DN on attribute %s" % attrname)
321 def err_missing_dn_GUID(self, dn, attrname, val, dsdb_dn):
322 """handle a missing target DN (both GUID and DN string form are missing)"""
323 # check if its a backlink
324 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
325 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
326 self.report("Not removing dangling forward link")
327 return
328 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn)
330 def err_incorrect_dn_GUID(self, dn, attrname, val, dsdb_dn, errstr):
331 """handle a missing GUID extended DN component"""
332 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
333 controls=["extended_dn:1:1", "show_recycled:1"]
334 try:
335 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
336 attrs=[], controls=controls)
337 except ldb.LdbError, (enum, estr):
338 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
339 self.err_missing_dn_GUID(dn, attrname, val, dsdb_dn)
340 return
341 if len(res) == 0:
342 self.report("unable to find object for DN %s" % dsdb_dn.dn)
343 self.err_missing_dn_GUID(dn, attrname, val, dsdb_dn)
344 return
345 dsdb_dn.dn = res[0].dn
347 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
348 self.report("Not fixing %s" % errstr)
349 return
350 m = ldb.Message()
351 m.dn = dn
352 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
353 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
355 if self.do_modify(m, ["show_recycled:1"],
356 "Failed to fix %s on attribute %s" % (errstr, attrname)):
357 self.report("Fixed %s on attribute %s" % (errstr, attrname))
359 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
360 """handle an incorrect binary DN component"""
361 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
362 controls=["extended_dn:1:1", "show_recycled:1"]
364 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
365 self.report("Not fixing %s" % errstr)
366 return
367 m = ldb.Message()
368 m.dn = dn
369 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
370 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
372 if self.do_modify(m, ["show_recycled:1"],
373 "Failed to fix %s on attribute %s" % (errstr, attrname)):
374 self.report("Fixed %s on attribute %s" % (errstr, attrname))
376 def err_dn_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, errstr):
377 """handle a DN string being incorrect"""
378 self.report("ERROR: incorrect DN string component for %s in object %s - %s" % (attrname, dn, val))
379 dsdb_dn.dn = correct_dn
381 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_target_mismatch'):
382 self.report("Not fixing %s" % errstr)
383 return
384 m = ldb.Message()
385 m.dn = dn
386 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
387 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
388 if self.do_modify(m, ["show_recycled:1"],
389 "Failed to fix incorrect DN string on attribute %s" % attrname):
390 self.report("Fixed incorrect DN string on attribute %s" % (attrname))
392 def err_unknown_attribute(self, obj, attrname):
393 '''handle an unknown attribute error'''
394 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
395 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
396 self.report("Not removing %s" % attrname)
397 return
398 m = ldb.Message()
399 m.dn = obj.dn
400 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
401 if self.do_modify(m, ["relax:0", "show_recycled:1"],
402 "Failed to remove unknown attribute %s" % attrname):
403 self.report("Removed unknown attribute %s" % (attrname))
405 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
406 '''handle a missing backlink value'''
407 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
408 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
409 self.report("Not fixing missing backlink %s" % backlink_name)
410 return
411 m = ldb.Message()
412 m.dn = obj.dn
413 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
414 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, attrname)
415 if self.do_modify(m, ["show_recycled:1"],
416 "Failed to fix missing backlink %s" % backlink_name):
417 self.report("Fixed missing backlink %s" % (backlink_name))
419 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
420 '''handle a incorrect RMD_FLAGS value'''
421 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
422 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()))
423 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
424 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
425 return
426 m = ldb.Message()
427 m.dn = obj.dn
428 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
429 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
430 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
431 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
433 def err_orphaned_backlink(self, obj, attrname, val, link_name, target_dn):
434 '''handle a orphaned backlink value'''
435 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (attrname, obj.dn, link_name, target_dn))
436 if not self.confirm_all('Remove orphaned backlink %s' % link_name, 'fix_all_orphaned_backlinks'):
437 self.report("Not removing orphaned backlink %s" % link_name)
438 return
439 m = ldb.Message()
440 m.dn = obj.dn
441 m['value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
442 if self.do_modify(m, ["show_recycled:1", "relax:0"],
443 "Failed to fix orphaned backlink %s" % link_name):
444 self.report("Fixed orphaned backlink %s" % (link_name))
446 def err_no_fsmoRoleOwner(self, obj):
447 '''handle a missing fSMORoleOwner'''
448 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
449 res = self.samdb.search("",
450 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
451 assert len(res) == 1
452 serviceName = res[0]["dsServiceName"][0]
453 if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
454 self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
455 return
456 m = ldb.Message()
457 m.dn = obj.dn
458 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
459 if self.do_modify(m, [],
460 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
461 self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
463 def err_missing_parent(self, obj):
464 '''handle a missing parent'''
465 self.report("ERROR: parent object not found for %s" % (obj.dn))
466 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
467 self.report('Not moving object %s into LostAndFound' % (obj.dn))
468 return
470 keep_transaction = True
471 self.samdb.transaction_start()
472 try:
473 nc_root = self.samdb.get_nc_root(obj.dn);
474 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
475 new_dn = ldb.Dn(self.samdb, str(obj.dn))
476 new_dn.remove_base_components(len(new_dn) - 1)
477 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
478 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
479 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
481 m = ldb.Message()
482 m.dn = obj.dn
483 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
485 if self.do_modify(m, [],
486 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
487 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
488 keep_transaction = True
489 except:
490 self.samdb.transaction_cancel()
491 raise
493 if keep_transaction:
494 self.samdb.transaction_commit()
495 else:
496 self.samdb.transaction_cancel()
498 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
499 '''handle a wrong dn'''
501 new_rdn = ldb.Dn(self.samdb, str(new_dn))
502 new_rdn.remove_base_components(len(new_rdn) - 1)
503 new_parent = new_dn.parent()
505 attributes = ""
506 if rdn_val != name_val:
507 attributes += "%s=%r " % (rdn_attr, rdn_val)
508 attributes += "name=%r" % (name_val)
510 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
511 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
512 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
513 return
515 if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
516 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
517 self.report("Renamed %s into %s" % (obj.dn, new_dn))
519 def err_wrong_instancetype(self, obj, calculated_instancetype):
520 '''handle a wrong instanceType'''
521 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
522 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
523 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
524 return
526 m = ldb.Message()
527 m.dn = obj.dn
528 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
529 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
530 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
531 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
533 def err_short_userParameters(self, obj, attrname, value):
534 # This is a truncated userParameters due to a pre 4.1 replication bug
535 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)))
537 def err_base64_userParameters(self, obj, attrname, value):
538 '''handle a wrong userParameters'''
539 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
540 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
541 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
542 return
544 m = ldb.Message()
545 m.dn = obj.dn
546 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
547 if self.do_modify(m, [],
548 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
549 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
551 def err_utf8_userParameters(self, obj, attrname, value):
552 '''handle a wrong userParameters'''
553 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
554 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
555 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
556 return
558 m = ldb.Message()
559 m.dn = obj.dn
560 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
561 ldb.FLAG_MOD_REPLACE, 'userParameters')
562 if self.do_modify(m, [],
563 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
564 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
566 def err_doubled_userParameters(self, obj, attrname, value):
567 '''handle a wrong userParameters'''
568 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
569 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
570 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
571 return
573 m = ldb.Message()
574 m.dn = obj.dn
575 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
576 ldb.FLAG_MOD_REPLACE, 'userParameters')
577 if self.do_modify(m, [],
578 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
579 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
581 def err_odd_userParameters(self, obj, attrname):
582 # This is a truncated userParameters due to a pre 4.1 replication bug
583 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)))
585 def find_revealed_link(self, dn, attrname, guid):
586 '''return a revealed link in an object'''
587 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
588 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
589 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
590 for val in res[0][attrname]:
591 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
592 guid2 = dsdb_dn.dn.get_extended_component("GUID")
593 if guid == guid2:
594 return dsdb_dn
595 return None
597 def check_dn(self, obj, attrname, syntax_oid):
598 '''check a DN attribute for correctness'''
599 error_count = 0
600 for val in obj[attrname]:
601 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
603 # all DNs should have a GUID component
604 guid = dsdb_dn.dn.get_extended_component("GUID")
605 if guid is None:
606 error_count += 1
607 self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn,
608 "missing GUID")
609 continue
611 guidstr = str(misc.GUID(guid))
613 attrs = ['isDeleted']
615 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
616 fixing_msDS_HasInstantiatedNCs = True
617 attrs.append("instanceType")
618 else:
619 fixing_msDS_HasInstantiatedNCs = False
621 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
622 reverse_link_name = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
623 if reverse_link_name is not None:
624 attrs.append(reverse_link_name)
626 # check its the right GUID
627 try:
628 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
629 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1"])
630 except ldb.LdbError, (enum, estr):
631 error_count += 1
632 self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn, "incorrect GUID")
633 continue
635 if fixing_msDS_HasInstantiatedNCs:
636 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
637 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
639 if str(dsdb_dn) != val:
640 error_count +=1
641 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
642 continue
644 # now we have two cases - the source object might or might not be deleted
645 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
646 target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
648 # the target DN is not allowed to be deleted, unless the target DN is the
649 # special Deleted Objects container
650 if target_is_deleted and not is_deleted and not self.is_deleted_objects_dn(dsdb_dn):
651 error_count += 1
652 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn)
653 continue
655 # check the DN matches in string form
656 if res[0].dn.extended_str() != dsdb_dn.dn.extended_str():
657 error_count += 1
658 self.err_dn_target_mismatch(obj.dn, attrname, val, dsdb_dn,
659 res[0].dn, "incorrect string version of DN")
660 continue
662 if is_deleted and not target_is_deleted and reverse_link_name is not None:
663 revealed_dn = self.find_revealed_link(obj.dn, attrname, guid)
664 rmd_flags = revealed_dn.dn.get_extended_component("RMD_FLAGS")
665 if rmd_flags is not None and (int(rmd_flags) & 1) == 0:
666 # the RMD_FLAGS for this link should be 1, as the target is deleted
667 self.err_incorrect_rmd_flags(obj, attrname, revealed_dn)
668 continue
670 # check the reverse_link is correct if there should be one
671 if reverse_link_name is not None:
672 match_count = 0
673 if reverse_link_name in res[0]:
674 for v in res[0][reverse_link_name]:
675 if v == obj.dn.extended_str():
676 match_count += 1
677 if match_count != 1:
678 error_count += 1
679 if linkID & 1:
680 self.err_orphaned_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
681 else:
682 self.err_missing_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
683 continue
685 return error_count
688 def get_originating_time(self, val, attid):
689 '''Read metadata properties and return the originating time for
690 a given attributeId.
692 :return: the originating time or 0 if not found
695 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
696 obj = repl.ctr
698 for o in repl.ctr.array:
699 if o.attid == attid:
700 return o.originating_change_time
702 return 0
704 def process_metadata(self, val):
705 '''Read metadata properties and list attributes in it.
706 raises KeyError if the attid is unknown.'''
708 set_att = set()
709 list_attid = []
711 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
712 obj = repl.ctr
714 for o in repl.ctr.array:
715 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
716 set_att.add(att.lower())
717 list_attid.append(o.attid)
719 return (set_att, list_attid)
722 def fix_metadata(self, dn, attr):
723 '''re-write replPropertyMetaData elements for a single attribute for a
724 object. This is used to fix missing replPropertyMetaData elements'''
725 res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
726 controls = ["search_options:1:2", "show_recycled:1"])
727 msg = res[0]
728 nmsg = ldb.Message()
729 nmsg.dn = dn
730 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
731 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
732 "Failed to fix metadata for attribute %s" % attr):
733 self.report("Fixed metadata for attribute %s" % attr)
735 def ace_get_effective_inherited_type(self, ace):
736 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
737 return None
739 check = False
740 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
741 check = True
742 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
743 check = True
744 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
745 check = True
746 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
747 check = True
749 if not check:
750 return None
752 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
753 return None
755 return str(ace.object.inherited_type)
757 def lookup_class_schemaIDGUID(self, cls):
758 if cls in self.class_schemaIDGUID:
759 return self.class_schemaIDGUID[cls]
761 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
762 res = self.samdb.search(base=self.schema_dn,
763 expression=flt,
764 attrs=["schemaIDGUID"])
765 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
767 self.class_schemaIDGUID[cls] = t
768 return t
770 def process_sd(self, dn, obj):
771 sd_attr = "nTSecurityDescriptor"
772 sd_val = obj[sd_attr]
774 sd = ndr_unpack(security.descriptor, str(sd_val))
776 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
777 if is_deleted:
778 # we don't fix deleted objects
779 return (sd, None)
781 sd_clean = security.descriptor()
782 sd_clean.owner_sid = sd.owner_sid
783 sd_clean.group_sid = sd.group_sid
784 sd_clean.type = sd.type
785 sd_clean.revision = sd.revision
787 broken = False
788 last_inherited_type = None
790 aces = []
791 if sd.sacl is not None:
792 aces = sd.sacl.aces
793 for i in range(0, len(aces)):
794 ace = aces[i]
796 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
797 sd_clean.sacl_add(ace)
798 continue
800 t = self.ace_get_effective_inherited_type(ace)
801 if t is None:
802 continue
804 if last_inherited_type is not None:
805 if t != last_inherited_type:
806 # if it inherited from more than
807 # one type it's very likely to be broken
809 # If not the recalculation will calculate
810 # the same result.
811 broken = True
812 continue
814 last_inherited_type = t
816 aces = []
817 if sd.dacl is not None:
818 aces = sd.dacl.aces
819 for i in range(0, len(aces)):
820 ace = aces[i]
822 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
823 sd_clean.dacl_add(ace)
824 continue
826 t = self.ace_get_effective_inherited_type(ace)
827 if t is None:
828 continue
830 if last_inherited_type is not None:
831 if t != last_inherited_type:
832 # if it inherited from more than
833 # one type it's very likely to be broken
835 # If not the recalculation will calculate
836 # the same result.
837 broken = True
838 continue
840 last_inherited_type = t
842 if broken:
843 return (sd_clean, sd)
845 if last_inherited_type is None:
846 # ok
847 return (sd, None)
849 cls = None
850 try:
851 cls = obj["objectClass"][-1]
852 except KeyError, e:
853 pass
855 if cls is None:
856 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
857 attrs=["isDeleted", "objectClass"],
858 controls=["show_recycled:1"])
859 o = res[0]
860 is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
861 if is_deleted:
862 # we don't fix deleted objects
863 return (sd, None)
864 cls = o["objectClass"][-1]
866 t = self.lookup_class_schemaIDGUID(cls)
868 if t != last_inherited_type:
869 # broken
870 return (sd_clean, sd)
872 # ok
873 return (sd, None)
875 def err_wrong_sd(self, dn, sd, sd_broken):
876 '''re-write the SD due to incorrect inherited ACEs'''
877 sd_attr = "nTSecurityDescriptor"
878 sd_val = ndr_pack(sd)
879 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
881 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
882 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
883 return
885 nmsg = ldb.Message()
886 nmsg.dn = dn
887 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
888 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
889 "Failed to fix attribute %s" % sd_attr):
890 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
892 def err_wrong_default_sd(self, dn, sd, sd_old, diff):
893 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
894 sd_attr = "nTSecurityDescriptor"
895 sd_val = ndr_pack(sd)
896 sd_old_val = ndr_pack(sd_old)
897 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
898 if sd.owner_sid is not None:
899 sd_flags |= security.SECINFO_OWNER
900 if sd.group_sid is not None:
901 sd_flags |= security.SECINFO_GROUP
903 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
904 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
905 return
907 m = ldb.Message()
908 m.dn = dn
909 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
910 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
911 "Failed to reset attribute %s" % sd_attr):
912 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
914 def err_missing_sd_owner(self, dn, sd):
915 '''re-write the SD due to a missing owner or group'''
916 sd_attr = "nTSecurityDescriptor"
917 sd_val = ndr_pack(sd)
918 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
920 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
921 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
922 return
924 nmsg = ldb.Message()
925 nmsg.dn = dn
926 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
928 # By setting the session_info to admin_session_info and
929 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
930 # flags we cause the descriptor module to set the correct
931 # owner and group on the SD, replacing the None/NULL values
932 # for owner_sid and group_sid currently present.
934 # The admin_session_info matches that used in provision, and
935 # is the best guess we can make for an existing object that
936 # hasn't had something specifically set.
938 # This is important for the dns related naming contexts.
939 self.samdb.set_session_info(self.admin_session_info)
940 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
941 "Failed to fix metadata for attribute %s" % sd_attr):
942 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
943 self.samdb.set_session_info(self.system_session_info)
946 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
947 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
948 str(repl_meta_data))
949 ctr = repl.ctr
950 found = False
951 for o in ctr.array:
952 # Search for a zero invocationID
953 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
954 continue
956 found = True
957 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
958 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
959 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
960 % (dn, o.attid, o.version,
961 time.ctime(samba.nttime2unix(o.originating_change_time)),
962 self.samdb.get_invocation_id()))
964 return found
967 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
968 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
969 str(repl_meta_data))
970 ctr = repl.ctr
971 now = samba.unix2nttime(int(time.time()))
972 found = False
973 for o in ctr.array:
974 # Search for a zero invocationID
975 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
976 continue
978 found = True
979 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
980 o.version = o.version + 1
981 o.originating_change_time = now
982 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
983 o.originating_usn = seq
984 o.local_usn = seq
986 if found:
987 replBlob = ndr_pack(repl)
988 msg = ldb.Message()
989 msg.dn = dn
991 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
992 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
993 self.report('Not fixing %s on %s\n' % (attr, dn))
994 return
996 nmsg = ldb.Message()
997 nmsg.dn = dn
998 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
999 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1000 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1001 "Failed to fix attribute %s" % attr):
1002 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1005 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1006 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1007 str(repl_meta_data))
1008 ctr = repl.ctr
1009 for o in ctr.array:
1010 # Search for an invalid attid
1011 try:
1012 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1013 except KeyError:
1014 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1015 return
1018 def err_replmetadata_unsorted_attid(self, dn, attr, repl_meta_data):
1019 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1020 str(repl_meta_data))
1021 ctr = repl.ctr
1022 found = False
1024 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1025 if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1026 % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1027 self.report('Not fixing %s on %s\n' % (attr, dn))
1028 return
1030 # Sort the array, except for the last element
1031 ctr.array[:-1] = sorted(ctr.array[:-1], key=lambda o: o.attid)
1033 replBlob = ndr_pack(repl)
1035 nmsg = ldb.Message()
1036 nmsg.dn = dn
1037 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1038 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1039 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1040 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1041 "Failed to fix attribute %s" % attr):
1042 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1045 def is_deleted_deleted_objects(self, obj):
1046 faulty = False
1047 if "description" not in obj:
1048 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1049 faulty = True
1050 if "showInAdvancedViewOnly" not in obj:
1051 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1052 faulty = True
1053 if "objectCategory" not in obj:
1054 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1055 faulty = True
1056 if "isCriticalSystemObject" not in obj:
1057 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1058 faulty = True
1059 if "isRecycled" in obj:
1060 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1061 faulty = True
1062 return faulty
1065 def err_deleted_deleted_objects(self, obj):
1066 nmsg = ldb.Message()
1067 nmsg.dn = dn = obj.dn
1069 if "description" not in obj:
1070 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1071 if "showInAdvancedViewOnly" not in obj:
1072 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1073 if "objectCategory" not in obj:
1074 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1075 if "isCriticalSystemObject" not in obj:
1076 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1077 if "isRecycled" in obj:
1078 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1080 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1081 % (dn), 'fix_deleted_deleted_objects'):
1082 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1083 return
1085 if self.do_modify(nmsg, ["relax:0"],
1086 "Failed to fix Deleted Objects container %s" % dn):
1087 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1090 def is_fsmo_role(self, dn):
1091 if dn == self.samdb.domain_dn:
1092 return True
1093 if dn == self.infrastructure_dn:
1094 return True
1095 if dn == self.naming_dn:
1096 return True
1097 if dn == self.schema_dn:
1098 return True
1099 if dn == self.rid_dn:
1100 return True
1102 return False
1104 def calculate_instancetype(self, dn):
1105 instancetype = 0
1106 nc_root = self.samdb.get_nc_root(dn)
1107 if dn == nc_root:
1108 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1109 try:
1110 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1111 except ldb.LdbError, (enum, estr):
1112 if enum != ldb.ERR_NO_SUCH_OBJECT:
1113 raise
1114 else:
1115 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1117 if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1118 instancetype |= dsdb.INSTANCE_TYPE_WRITE
1120 return instancetype
1122 def get_wellknown_sd(self, dn):
1123 for [sd_dn, descriptor_fn] in self.wellknown_sds:
1124 if dn == sd_dn:
1125 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1126 return ndr_unpack(security.descriptor,
1127 descriptor_fn(domain_sid,
1128 name_map=self.name_map))
1130 raise KeyError
1132 def check_object(self, dn, attrs=['*']):
1133 '''check one object'''
1134 if self.verbose:
1135 self.report("Checking object %s" % dn)
1136 if "dn" in map(str.lower, attrs):
1137 attrs.append("name")
1138 if "distinguishedname" in map(str.lower, attrs):
1139 attrs.append("name")
1140 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1141 attrs.append("name")
1142 if 'name' in map(str.lower, attrs):
1143 attrs.append(dn.get_rdn_name())
1144 attrs.append("isDeleted")
1145 attrs.append("systemFlags")
1146 if '*' in attrs:
1147 attrs.append("replPropertyMetaData")
1149 try:
1150 sd_flags = 0
1151 sd_flags |= security.SECINFO_OWNER
1152 sd_flags |= security.SECINFO_GROUP
1153 sd_flags |= security.SECINFO_DACL
1154 sd_flags |= security.SECINFO_SACL
1156 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1157 controls=[
1158 "extended_dn:1:1",
1159 "show_recycled:1",
1160 "show_deleted:1",
1161 "sd_flags:1:%d" % sd_flags,
1163 attrs=attrs)
1164 except ldb.LdbError, (enum, estr):
1165 if enum == ldb.ERR_NO_SUCH_OBJECT:
1166 if self.in_transaction:
1167 self.report("ERROR: Object %s disappeared during check" % dn)
1168 return 1
1169 return 0
1170 raise
1171 if len(res) != 1:
1172 self.report("ERROR: Object %s failed to load during check" % dn)
1173 return 1
1174 obj = res[0]
1175 error_count = 0
1176 set_attrs_from_md = set()
1177 set_attrs_seen = set()
1178 got_repl_property_meta_data = False
1179 got_objectclass = False
1181 nc_dn = self.samdb.get_nc_root(obj.dn)
1182 try:
1183 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
1184 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
1185 except KeyError, e:
1186 deleted_objects_dn = ldb.Dn(self.samdb, "CN=Deleted Objects,%s" % nc_dn)
1188 object_rdn_attr = None
1189 object_rdn_val = None
1190 name_val = None
1191 isDeleted = False
1192 systemFlags = 0
1194 for attrname in obj:
1195 if attrname == 'dn':
1196 continue
1198 if str(attrname).lower() == 'objectclass':
1199 got_objectclass = True
1201 if str(attrname).lower() == "name":
1202 if len(obj[attrname]) != 1:
1203 error_count += 1
1204 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1205 (len(obj[attrname]), attrname, str(obj.dn)))
1206 else:
1207 name_val = obj[attrname][0]
1209 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
1210 object_rdn_attr = attrname
1211 if len(obj[attrname]) != 1:
1212 error_count += 1
1213 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1214 (len(obj[attrname]), attrname, str(obj.dn)))
1215 else:
1216 object_rdn_val = obj[attrname][0]
1218 if str(attrname).lower() == 'isdeleted':
1219 if obj[attrname][0] != "FALSE":
1220 isDeleted = True
1222 if str(attrname).lower() == 'systemflags':
1223 systemFlags = int(obj[attrname][0])
1225 if str(attrname).lower() == 'replpropertymetadata':
1226 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
1227 error_count += 1
1228 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
1229 # We don't continue, as we may also have other fixes for this attribute
1230 # based on what other attributes we see.
1232 try:
1233 (set_attrs_from_md, list_attid_from_md) = self.process_metadata(obj[attrname])
1234 except KeyError:
1235 error_count += 1
1236 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
1237 continue
1239 if sorted(list_attid_from_md[:-1]) != list_attid_from_md[:-1]:
1240 error_count += 1
1241 self.err_replmetadata_unsorted_attid(dn, attrname, obj[attrname])
1242 else:
1243 # Here we check that the first attid is 0
1244 # (objectClass) and that the last on is the RDN
1245 # from the DN.
1246 rdn_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(dn.get_rdn_name())
1247 if list_attid_from_md[-1] != rdn_attid:
1248 error_count += 1
1249 self.report("ERROR: Not fixing incorrect final attributeID in '%s' on '%s', it should match the RDN %s" %
1250 (attrname, str(dn), dn.get_rdn_name()))
1252 if list_attid_from_md[0] != 0:
1253 error_count += 1
1254 self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
1255 (attrname, str(dn)))
1257 got_repl_property_meta_data = True
1258 continue
1260 if str(attrname).lower() == 'ntsecuritydescriptor':
1261 (sd, sd_broken) = self.process_sd(dn, obj)
1262 if sd_broken is not None:
1263 self.err_wrong_sd(dn, sd, sd_broken)
1264 error_count += 1
1265 continue
1267 if sd.owner_sid is None or sd.group_sid is None:
1268 self.err_missing_sd_owner(dn, sd)
1269 error_count += 1
1270 continue
1272 if self.reset_well_known_acls:
1273 try:
1274 well_known_sd = self.get_wellknown_sd(dn)
1275 except KeyError:
1276 continue
1278 current_sd = ndr_unpack(security.descriptor,
1279 str(obj[attrname][0]))
1281 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
1282 if diff != "":
1283 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
1284 error_count += 1
1285 continue
1286 continue
1288 if str(attrname).lower() == 'objectclass':
1289 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
1290 if normalised != obj[attrname]:
1291 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
1292 error_count += 1
1293 continue
1295 if str(attrname).lower() == 'userparameters':
1296 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
1297 error_count += 1
1298 self.err_short_userParameters(obj, attrname, obj[attrname])
1299 continue
1301 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1302 # This is the correct, normal prefix
1303 continue
1305 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1306 # this is the typical prefix from a windows migration
1307 error_count += 1
1308 self.err_base64_userParameters(obj, attrname, obj[attrname])
1309 continue
1311 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':
1312 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1313 error_count += 1
1314 self.err_utf8_userParameters(obj, attrname, obj[attrname])
1315 continue
1317 elif len(obj[attrname][0]) % 2 != 0:
1318 # This is a value that isn't even in length
1319 error_count += 1
1320 self.err_odd_userParameters(obj, attrname, obj[attrname])
1321 continue
1323 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':
1324 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
1325 error_count += 1
1326 self.err_doubled_userParameters(obj, attrname, obj[attrname])
1327 continue
1329 # check for empty attributes
1330 for val in obj[attrname]:
1331 if val == '':
1332 self.err_empty_attribute(dn, attrname)
1333 error_count += 1
1334 continue
1336 # get the syntax oid for the attribute, so we can can have
1337 # special handling for some specific attribute types
1338 try:
1339 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1340 except Exception, msg:
1341 self.err_unknown_attribute(obj, attrname)
1342 error_count += 1
1343 continue
1345 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
1346 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
1347 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
1348 and not self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)):
1349 set_attrs_seen.add(str(attrname).lower())
1351 if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
1352 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
1353 # it's some form of DN, do specialised checking on those
1354 error_count += self.check_dn(obj, attrname, syntax_oid)
1356 # check for incorrectly normalised attributes
1357 for val in obj[attrname]:
1358 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
1359 if len(normalised) != 1 or normalised[0] != val:
1360 self.err_normalise_mismatch(dn, attrname, obj[attrname])
1361 error_count += 1
1362 break
1364 if str(attrname).lower() == "instancetype":
1365 calculated_instancetype = self.calculate_instancetype(dn)
1366 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
1367 error_count += 1
1368 self.err_wrong_instancetype(obj, calculated_instancetype)
1370 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
1371 error_count += 1
1372 self.err_missing_objectclass(dn)
1374 if ("*" in attrs or "name" in map(str.lower, attrs)):
1375 if name_val is None:
1376 error_count += 1
1377 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
1378 if object_rdn_attr is None:
1379 error_count += 1
1380 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
1382 if name_val is not None:
1383 parent_dn = None
1384 if isDeleted:
1385 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
1386 parent_dn = deleted_objects_dn
1387 if parent_dn is None:
1388 parent_dn = obj.dn.parent()
1389 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
1390 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
1392 if obj.dn == deleted_objects_dn:
1393 expected_dn = obj.dn
1395 if expected_dn != obj.dn:
1396 error_count += 1
1397 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
1398 elif obj.dn.get_rdn_value() != object_rdn_val:
1399 error_count += 1
1400 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
1402 show_dn = True
1403 if got_repl_property_meta_data:
1404 if obj.dn == deleted_objects_dn:
1405 isDeletedAttId = 131120
1406 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
1408 expectedTimeDo = 2650466015990000000
1409 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
1410 if originating != expectedTimeDo:
1411 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
1412 nmsg = ldb.Message()
1413 nmsg.dn = dn
1414 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1415 error_count += 1
1416 self.samdb.modify(nmsg, controls=["provision:0"])
1418 else:
1419 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
1421 for att in set_attrs_seen.difference(set_attrs_from_md):
1422 if show_dn:
1423 self.report("On object %s" % dn)
1424 show_dn = False
1425 error_count += 1
1426 self.report("ERROR: Attribute %s not present in replication metadata" % att)
1427 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
1428 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
1429 continue
1430 self.fix_metadata(dn, att)
1432 if self.is_fsmo_role(dn):
1433 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
1434 self.err_no_fsmoRoleOwner(obj)
1435 error_count += 1
1437 try:
1438 if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
1439 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
1440 controls=["show_recycled:1", "show_deleted:1"])
1441 except ldb.LdbError, (enum, estr):
1442 if enum == ldb.ERR_NO_SUCH_OBJECT:
1443 self.err_missing_parent(obj)
1444 error_count += 1
1445 else:
1446 raise
1448 if dn in self.deleted_objects_containers and '*' in attrs:
1449 if self.is_deleted_deleted_objects(obj):
1450 self.err_deleted_deleted_objects(obj)
1451 error_count += 1
1453 return error_count
1455 ################################################################
1456 # check special @ROOTDSE attributes
1457 def check_rootdse(self):
1458 '''check the @ROOTDSE special object'''
1459 dn = ldb.Dn(self.samdb, '@ROOTDSE')
1460 if self.verbose:
1461 self.report("Checking object %s" % dn)
1462 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
1463 if len(res) != 1:
1464 self.report("Object %s disappeared during check" % dn)
1465 return 1
1466 obj = res[0]
1467 error_count = 0
1469 # check that the dsServiceName is in GUID form
1470 if not 'dsServiceName' in obj:
1471 self.report('ERROR: dsServiceName missing in @ROOTDSE')
1472 return error_count+1
1474 if not obj['dsServiceName'][0].startswith('<GUID='):
1475 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
1476 error_count += 1
1477 if not self.confirm('Change dsServiceName to GUID form?'):
1478 return error_count
1479 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
1480 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
1481 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
1482 m = ldb.Message()
1483 m.dn = dn
1484 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
1485 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
1486 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
1487 self.report("Changed dsServiceName to GUID form")
1488 return error_count
1491 ###############################################
1492 # re-index the database
1493 def reindex_database(self):
1494 '''re-index the whole database'''
1495 m = ldb.Message()
1496 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
1497 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
1498 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
1499 return self.do_modify(m, [], 're-indexed database', validate=False)
1501 ###############################################
1502 # reset @MODULES
1503 def reset_modules(self):
1504 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
1505 m = ldb.Message()
1506 m.dn = ldb.Dn(self.samdb, "@MODULES")
1507 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
1508 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)