dbcheck: Add explict tests for unknown and unsorted attributeID values
[Samba.git] / python / samba / dbchecker.py
bloba97b58a0e041bf1a937b120e1155924afb7277f4
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.name_map = {}
86 try:
87 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE,
88 attrs=["objectSid"])
89 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
90 self.name_map['DnsAdmins'] = str(dnsadmins_sid)
91 except ldb.LdbError, (enum, estr):
92 if enum != ldb.ERR_NO_SUCH_OBJECT:
93 raise
94 pass
96 self.system_session_info = system_session()
97 self.admin_session_info = admin_session(None, samdb.get_domain_sid())
99 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
100 if "msDS-hasMasterNCs" in res[0]:
101 self.write_ncs = res[0]["msDS-hasMasterNCs"]
102 else:
103 # If the Forest Level is less than 2003 then there is no
104 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
105 # no need to merge as all the NCs that are in hasMasterNCs must
106 # also be in msDS-hasMasterNCs (but not the opposite)
107 if "hasMasterNCs" in res[0]:
108 self.write_ncs = res[0]["hasMasterNCs"]
109 else:
110 self.write_ncs = None
112 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
113 try:
114 ncs = res[0]["namingContexts"]
115 self.deleted_objects_containers = []
116 for nc in ncs:
117 try:
118 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc),
119 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
120 self.deleted_objects_containers.append(dn)
121 except KeyError:
122 pass
123 except KeyError:
124 pass
125 except IndexError:
126 pass
128 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
129 '''perform a database check, returning the number of errors found'''
131 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
132 self.report('Checking %u objects' % len(res))
133 error_count = 0
135 for object in res:
136 error_count += self.check_object(object.dn, attrs=attrs)
138 if DN is None:
139 error_count += self.check_rootdse()
141 if error_count != 0 and not self.fix:
142 self.report("Please use --fix to fix these errors")
144 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
145 return error_count
147 def report(self, msg):
148 '''print a message unless quiet is set'''
149 if not self.quiet:
150 print(msg)
152 def confirm(self, msg, allow_all=False, forced=False):
153 '''confirm a change'''
154 if not self.fix:
155 return False
156 if self.quiet:
157 return self.yes
158 if self.yes:
159 forced = True
160 return common.confirm(msg, forced=forced, allow_all=allow_all)
162 ################################################################
163 # a local confirm function with support for 'all'
164 def confirm_all(self, msg, all_attr):
165 '''confirm a change with support for "all" '''
166 if not self.fix:
167 return False
168 if self.quiet:
169 return self.yes
170 if getattr(self, all_attr) == 'NONE':
171 return False
172 if getattr(self, all_attr) == 'ALL':
173 forced = True
174 else:
175 forced = self.yes
176 c = common.confirm(msg, forced=forced, allow_all=True)
177 if c == 'ALL':
178 setattr(self, all_attr, 'ALL')
179 return True
180 if c == 'NONE':
181 setattr(self, all_attr, 'NONE')
182 return False
183 return c
185 def do_delete(self, dn, controls, msg):
186 '''delete dn with optional verbose output'''
187 if self.verbose:
188 self.report("delete DN %s" % dn)
189 try:
190 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
191 self.samdb.delete(dn, controls=controls)
192 except Exception, err:
193 self.report("%s : %s" % (msg, err))
194 return False
195 return True
197 def do_modify(self, m, controls, msg, validate=True):
198 '''perform a modify with optional verbose output'''
199 if self.verbose:
200 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
201 try:
202 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
203 self.samdb.modify(m, controls=controls, validate=validate)
204 except Exception, err:
205 self.report("%s : %s" % (msg, err))
206 return False
207 return True
209 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
210 '''perform a modify with optional verbose output'''
211 if self.verbose:
212 self.report("""dn: %s
213 changeType: modrdn
214 newrdn: %s
215 deleteOldRdn: 1
216 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
217 try:
218 to_dn = to_rdn + to_base
219 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
220 self.samdb.rename(from_dn, to_dn, controls=controls)
221 except Exception, err:
222 self.report("%s : %s" % (msg, err))
223 return False
224 return True
226 def err_empty_attribute(self, dn, attrname):
227 '''fix empty attributes'''
228 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
229 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
230 self.report("Not fixing empty attribute %s" % attrname)
231 return
233 m = ldb.Message()
234 m.dn = dn
235 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
236 if self.do_modify(m, ["relax:0", "show_recycled:1"],
237 "Failed to remove empty attribute %s" % attrname, validate=False):
238 self.report("Removed empty attribute %s" % attrname)
240 def err_normalise_mismatch(self, dn, attrname, values):
241 '''fix attribute normalisation errors'''
242 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
243 mod_list = []
244 for val in values:
245 normalised = self.samdb.dsdb_normalise_attributes(
246 self.samdb_schema, attrname, [val])
247 if len(normalised) != 1:
248 self.report("Unable to normalise value '%s'" % val)
249 mod_list.append((val, ''))
250 elif (normalised[0] != val):
251 self.report("value '%s' should be '%s'" % (val, normalised[0]))
252 mod_list.append((val, normalised[0]))
253 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
254 self.report("Not fixing attribute %s" % attrname)
255 return
257 m = ldb.Message()
258 m.dn = dn
259 for i in range(0, len(mod_list)):
260 (val, nval) = mod_list[i]
261 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
262 if nval != '':
263 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
264 attrname)
266 if self.do_modify(m, ["relax:0", "show_recycled:1"],
267 "Failed to normalise attribute %s" % attrname,
268 validate=False):
269 self.report("Normalised attribute %s" % attrname)
271 def err_normalise_mismatch_replace(self, dn, attrname, values):
272 '''fix attribute normalisation errors'''
273 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
274 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
275 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
276 if list(normalised) == values:
277 return
278 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
279 self.report("Not fixing attribute '%s'" % attrname)
280 return
282 m = ldb.Message()
283 m.dn = dn
284 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
286 if self.do_modify(m, ["relax:0", "show_recycled:1"],
287 "Failed to normalise attribute %s" % attrname,
288 validate=False):
289 self.report("Normalised attribute %s" % attrname)
291 def is_deleted_objects_dn(self, dsdb_dn):
292 '''see if a dsdb_Dn is the special Deleted Objects DN'''
293 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
295 def err_missing_objectclass(self, dn):
296 """handle object without objectclass"""
297 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)))
298 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'):
299 self.report("Not deleting object with missing objectclass '%s'" % dn)
300 return
301 if self.do_delete(dn, ["relax:0"],
302 "Failed to remove DN %s" % dn):
303 self.report("Removed DN %s" % dn)
305 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn):
306 """handle a DN pointing to a deleted object"""
307 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
308 self.report("Target GUID points at deleted DN %s" % correct_dn)
309 if not self.confirm_all('Remove DN link?', 'remove_all_deleted_DN_links'):
310 self.report("Not removing")
311 return
312 m = ldb.Message()
313 m.dn = dn
314 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
315 if self.do_modify(m, ["show_recycled:1", "local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK],
316 "Failed to remove deleted DN attribute %s" % attrname):
317 self.report("Removed deleted DN on attribute %s" % attrname)
319 def err_missing_dn_GUID(self, dn, attrname, val, dsdb_dn):
320 """handle a missing target DN (both GUID and DN string form are missing)"""
321 # check if its a backlink
322 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
323 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
324 self.report("Not removing dangling forward link")
325 return
326 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn)
328 def err_incorrect_dn_GUID(self, dn, attrname, val, dsdb_dn, errstr):
329 """handle a missing GUID extended DN component"""
330 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
331 controls=["extended_dn:1:1", "show_recycled:1"]
332 try:
333 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
334 attrs=[], controls=controls)
335 except ldb.LdbError, (enum, estr):
336 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
337 self.err_missing_dn_GUID(dn, attrname, val, dsdb_dn)
338 return
339 if len(res) == 0:
340 self.report("unable to find object for DN %s" % dsdb_dn.dn)
341 self.err_missing_dn_GUID(dn, attrname, val, dsdb_dn)
342 return
343 dsdb_dn.dn = res[0].dn
345 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
346 self.report("Not fixing %s" % errstr)
347 return
348 m = ldb.Message()
349 m.dn = dn
350 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
351 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
353 if self.do_modify(m, ["show_recycled:1"],
354 "Failed to fix %s on attribute %s" % (errstr, attrname)):
355 self.report("Fixed %s on attribute %s" % (errstr, attrname))
357 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
358 """handle an incorrect binary DN component"""
359 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
360 controls=["extended_dn:1:1", "show_recycled:1"]
362 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
363 self.report("Not fixing %s" % errstr)
364 return
365 m = ldb.Message()
366 m.dn = dn
367 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
368 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
370 if self.do_modify(m, ["show_recycled:1"],
371 "Failed to fix %s on attribute %s" % (errstr, attrname)):
372 self.report("Fixed %s on attribute %s" % (errstr, attrname))
374 def err_dn_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, errstr):
375 """handle a DN string being incorrect"""
376 self.report("ERROR: incorrect DN string component for %s in object %s - %s" % (attrname, dn, val))
377 dsdb_dn.dn = correct_dn
379 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_target_mismatch'):
380 self.report("Not fixing %s" % errstr)
381 return
382 m = ldb.Message()
383 m.dn = dn
384 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
385 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
386 if self.do_modify(m, ["show_recycled:1"],
387 "Failed to fix incorrect DN string on attribute %s" % attrname):
388 self.report("Fixed incorrect DN string on attribute %s" % (attrname))
390 def err_unknown_attribute(self, obj, attrname):
391 '''handle an unknown attribute error'''
392 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
393 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
394 self.report("Not removing %s" % attrname)
395 return
396 m = ldb.Message()
397 m.dn = obj.dn
398 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
399 if self.do_modify(m, ["relax:0", "show_recycled:1"],
400 "Failed to remove unknown attribute %s" % attrname):
401 self.report("Removed unknown attribute %s" % (attrname))
403 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
404 '''handle a missing backlink value'''
405 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
406 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
407 self.report("Not fixing missing backlink %s" % backlink_name)
408 return
409 m = ldb.Message()
410 m.dn = obj.dn
411 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
412 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, attrname)
413 if self.do_modify(m, ["show_recycled:1"],
414 "Failed to fix missing backlink %s" % backlink_name):
415 self.report("Fixed missing backlink %s" % (backlink_name))
417 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
418 '''handle a incorrect RMD_FLAGS value'''
419 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
420 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()))
421 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
422 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
423 return
424 m = ldb.Message()
425 m.dn = obj.dn
426 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
427 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
428 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
429 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
431 def err_orphaned_backlink(self, obj, attrname, val, link_name, target_dn):
432 '''handle a orphaned backlink value'''
433 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (attrname, obj.dn, link_name, target_dn))
434 if not self.confirm_all('Remove orphaned backlink %s' % link_name, 'fix_all_orphaned_backlinks'):
435 self.report("Not removing orphaned backlink %s" % link_name)
436 return
437 m = ldb.Message()
438 m.dn = obj.dn
439 m['value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
440 if self.do_modify(m, ["show_recycled:1", "relax:0"],
441 "Failed to fix orphaned backlink %s" % link_name):
442 self.report("Fixed orphaned backlink %s" % (link_name))
444 def err_no_fsmoRoleOwner(self, obj):
445 '''handle a missing fSMORoleOwner'''
446 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
447 res = self.samdb.search("",
448 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
449 assert len(res) == 1
450 serviceName = res[0]["dsServiceName"][0]
451 if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
452 self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
453 return
454 m = ldb.Message()
455 m.dn = obj.dn
456 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
457 if self.do_modify(m, [],
458 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
459 self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
461 def err_missing_parent(self, obj):
462 '''handle a missing parent'''
463 self.report("ERROR: parent object not found for %s" % (obj.dn))
464 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
465 self.report('Not moving object %s into LostAndFound' % (obj.dn))
466 return
468 keep_transaction = True
469 self.samdb.transaction_start()
470 try:
471 nc_root = self.samdb.get_nc_root(obj.dn);
472 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
473 new_dn = ldb.Dn(self.samdb, str(obj.dn))
474 new_dn.remove_base_components(len(new_dn) - 1)
475 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
476 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
477 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
479 m = ldb.Message()
480 m.dn = obj.dn
481 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
483 if self.do_modify(m, [],
484 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
485 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
486 keep_transaction = True
487 except:
488 self.samdb.transaction_cancel()
489 raise
491 if keep_transaction:
492 self.samdb.transaction_commit()
493 else:
494 self.samdb.transaction_cancel()
496 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
497 '''handle a wrong dn'''
499 new_rdn = ldb.Dn(self.samdb, str(new_dn))
500 new_rdn.remove_base_components(len(new_rdn) - 1)
501 new_parent = new_dn.parent()
503 attributes = ""
504 if rdn_val != name_val:
505 attributes += "%s=%r " % (rdn_attr, rdn_val)
506 attributes += "name=%r" % (name_val)
508 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
509 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
510 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
511 return
513 if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
514 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
515 self.report("Renamed %s into %s" % (obj.dn, new_dn))
517 def err_wrong_instancetype(self, obj, calculated_instancetype):
518 '''handle a wrong instanceType'''
519 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
520 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
521 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
522 return
524 m = ldb.Message()
525 m.dn = obj.dn
526 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
527 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
528 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
529 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
531 def err_short_userParameters(self, obj, attrname, value):
532 # This is a truncated userParameters due to a pre 4.1 replication bug
533 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)))
535 def err_base64_userParameters(self, obj, attrname, value):
536 '''handle a wrong userParameters'''
537 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
538 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
539 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
540 return
542 m = ldb.Message()
543 m.dn = obj.dn
544 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
545 if self.do_modify(m, [],
546 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
547 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
549 def err_utf8_userParameters(self, obj, attrname, value):
550 '''handle a wrong userParameters'''
551 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
552 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
553 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
554 return
556 m = ldb.Message()
557 m.dn = obj.dn
558 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
559 ldb.FLAG_MOD_REPLACE, 'userParameters')
560 if self.do_modify(m, [],
561 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
562 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
564 def err_doubled_userParameters(self, obj, attrname, value):
565 '''handle a wrong userParameters'''
566 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
567 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
568 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
569 return
571 m = ldb.Message()
572 m.dn = obj.dn
573 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
574 ldb.FLAG_MOD_REPLACE, 'userParameters')
575 if self.do_modify(m, [],
576 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
577 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
579 def err_odd_userParameters(self, obj, attrname):
580 # This is a truncated userParameters due to a pre 4.1 replication bug
581 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)))
583 def find_revealed_link(self, dn, attrname, guid):
584 '''return a revealed link in an object'''
585 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
586 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
587 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
588 for val in res[0][attrname]:
589 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
590 guid2 = dsdb_dn.dn.get_extended_component("GUID")
591 if guid == guid2:
592 return dsdb_dn
593 return None
595 def check_dn(self, obj, attrname, syntax_oid):
596 '''check a DN attribute for correctness'''
597 error_count = 0
598 for val in obj[attrname]:
599 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
601 # all DNs should have a GUID component
602 guid = dsdb_dn.dn.get_extended_component("GUID")
603 if guid is None:
604 error_count += 1
605 self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn,
606 "missing GUID")
607 continue
609 guidstr = str(misc.GUID(guid))
611 attrs = ['isDeleted']
613 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
614 fixing_msDS_HasInstantiatedNCs = True
615 attrs.append("instanceType")
616 else:
617 fixing_msDS_HasInstantiatedNCs = False
619 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
620 reverse_link_name = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
621 if reverse_link_name is not None:
622 attrs.append(reverse_link_name)
624 # check its the right GUID
625 try:
626 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
627 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1"])
628 except ldb.LdbError, (enum, estr):
629 error_count += 1
630 self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn, "incorrect GUID")
631 continue
633 if fixing_msDS_HasInstantiatedNCs:
634 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
635 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
637 if str(dsdb_dn) != val:
638 error_count +=1
639 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
640 continue
642 # now we have two cases - the source object might or might not be deleted
643 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
644 target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
646 # the target DN is not allowed to be deleted, unless the target DN is the
647 # special Deleted Objects container
648 if target_is_deleted and not is_deleted and not self.is_deleted_objects_dn(dsdb_dn):
649 error_count += 1
650 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn)
651 continue
653 # check the DN matches in string form
654 if res[0].dn.extended_str() != dsdb_dn.dn.extended_str():
655 error_count += 1
656 self.err_dn_target_mismatch(obj.dn, attrname, val, dsdb_dn,
657 res[0].dn, "incorrect string version of DN")
658 continue
660 if is_deleted and not target_is_deleted and reverse_link_name is not None:
661 revealed_dn = self.find_revealed_link(obj.dn, attrname, guid)
662 rmd_flags = revealed_dn.dn.get_extended_component("RMD_FLAGS")
663 if rmd_flags is not None and (int(rmd_flags) & 1) == 0:
664 # the RMD_FLAGS for this link should be 1, as the target is deleted
665 self.err_incorrect_rmd_flags(obj, attrname, revealed_dn)
666 continue
668 # check the reverse_link is correct if there should be one
669 if reverse_link_name is not None:
670 match_count = 0
671 if reverse_link_name in res[0]:
672 for v in res[0][reverse_link_name]:
673 if v == obj.dn.extended_str():
674 match_count += 1
675 if match_count != 1:
676 error_count += 1
677 if linkID & 1:
678 self.err_orphaned_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
679 else:
680 self.err_missing_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
681 continue
683 return error_count
686 def get_originating_time(self, val, attid):
687 '''Read metadata properties and return the originating time for
688 a given attributeId.
690 :return: the originating time or 0 if not found
693 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
694 obj = repl.ctr
696 for o in repl.ctr.array:
697 if o.attid == attid:
698 return o.originating_change_time
700 return 0
702 def process_metadata(self, val):
703 '''Read metadata properties and list attributes in it.
704 raises KeyError if the attid is unknown.'''
706 list_att = []
707 list_attid = []
709 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
710 obj = repl.ctr
712 for o in repl.ctr.array:
713 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
714 list_att.append(att.lower())
715 list_attid.append(o.attid)
717 return (list_att, list_attid)
720 def fix_metadata(self, dn, attr):
721 '''re-write replPropertyMetaData elements for a single attribute for a
722 object. This is used to fix missing replPropertyMetaData elements'''
723 res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
724 controls = ["search_options:1:2", "show_recycled:1"])
725 msg = res[0]
726 nmsg = ldb.Message()
727 nmsg.dn = dn
728 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
729 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
730 "Failed to fix metadata for attribute %s" % attr):
731 self.report("Fixed metadata for attribute %s" % attr)
733 def ace_get_effective_inherited_type(self, ace):
734 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
735 return None
737 check = False
738 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
739 check = True
740 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
741 check = True
742 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
743 check = True
744 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
745 check = True
747 if not check:
748 return None
750 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
751 return None
753 return str(ace.object.inherited_type)
755 def lookup_class_schemaIDGUID(self, cls):
756 if cls in self.class_schemaIDGUID:
757 return self.class_schemaIDGUID[cls]
759 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
760 res = self.samdb.search(base=self.schema_dn,
761 expression=flt,
762 attrs=["schemaIDGUID"])
763 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
765 self.class_schemaIDGUID[cls] = t
766 return t
768 def process_sd(self, dn, obj):
769 sd_attr = "nTSecurityDescriptor"
770 sd_val = obj[sd_attr]
772 sd = ndr_unpack(security.descriptor, str(sd_val))
774 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
775 if is_deleted:
776 # we don't fix deleted objects
777 return (sd, None)
779 sd_clean = security.descriptor()
780 sd_clean.owner_sid = sd.owner_sid
781 sd_clean.group_sid = sd.group_sid
782 sd_clean.type = sd.type
783 sd_clean.revision = sd.revision
785 broken = False
786 last_inherited_type = None
788 aces = []
789 if sd.sacl is not None:
790 aces = sd.sacl.aces
791 for i in range(0, len(aces)):
792 ace = aces[i]
794 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
795 sd_clean.sacl_add(ace)
796 continue
798 t = self.ace_get_effective_inherited_type(ace)
799 if t is None:
800 continue
802 if last_inherited_type is not None:
803 if t != last_inherited_type:
804 # if it inherited from more than
805 # one type it's very likely to be broken
807 # If not the recalculation will calculate
808 # the same result.
809 broken = True
810 continue
812 last_inherited_type = t
814 aces = []
815 if sd.dacl is not None:
816 aces = sd.dacl.aces
817 for i in range(0, len(aces)):
818 ace = aces[i]
820 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
821 sd_clean.dacl_add(ace)
822 continue
824 t = self.ace_get_effective_inherited_type(ace)
825 if t is None:
826 continue
828 if last_inherited_type is not None:
829 if t != last_inherited_type:
830 # if it inherited from more than
831 # one type it's very likely to be broken
833 # If not the recalculation will calculate
834 # the same result.
835 broken = True
836 continue
838 last_inherited_type = t
840 if broken:
841 return (sd_clean, sd)
843 if last_inherited_type is None:
844 # ok
845 return (sd, None)
847 cls = None
848 try:
849 cls = obj["objectClass"][-1]
850 except KeyError, e:
851 pass
853 if cls is None:
854 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
855 attrs=["isDeleted", "objectClass"],
856 controls=["show_recycled:1"])
857 o = res[0]
858 is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
859 if is_deleted:
860 # we don't fix deleted objects
861 return (sd, None)
862 cls = o["objectClass"][-1]
864 t = self.lookup_class_schemaIDGUID(cls)
866 if t != last_inherited_type:
867 # broken
868 return (sd_clean, sd)
870 # ok
871 return (sd, None)
873 def err_wrong_sd(self, dn, sd, sd_broken):
874 '''re-write the SD due to incorrect inherited ACEs'''
875 sd_attr = "nTSecurityDescriptor"
876 sd_val = ndr_pack(sd)
877 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
879 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
880 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
881 return
883 nmsg = ldb.Message()
884 nmsg.dn = dn
885 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
886 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
887 "Failed to fix attribute %s" % sd_attr):
888 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
890 def err_wrong_default_sd(self, dn, sd, sd_old, diff):
891 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
892 sd_attr = "nTSecurityDescriptor"
893 sd_val = ndr_pack(sd)
894 sd_old_val = ndr_pack(sd_old)
895 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
896 if sd.owner_sid is not None:
897 sd_flags |= security.SECINFO_OWNER
898 if sd.group_sid is not None:
899 sd_flags |= security.SECINFO_GROUP
901 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
902 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
903 return
905 m = ldb.Message()
906 m.dn = dn
907 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
908 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
909 "Failed to reset attribute %s" % sd_attr):
910 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
912 def err_missing_sd_owner(self, dn, sd):
913 '''re-write the SD due to a missing owner or group'''
914 sd_attr = "nTSecurityDescriptor"
915 sd_val = ndr_pack(sd)
916 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
918 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
919 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
920 return
922 nmsg = ldb.Message()
923 nmsg.dn = dn
924 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
926 # By setting the session_info to admin_session_info and
927 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
928 # flags we cause the descriptor module to set the correct
929 # owner and group on the SD, replacing the None/NULL values
930 # for owner_sid and group_sid currently present.
932 # The admin_session_info matches that used in provision, and
933 # is the best guess we can make for an existing object that
934 # hasn't had something specifically set.
936 # This is important for the dns related naming contexts.
937 self.samdb.set_session_info(self.admin_session_info)
938 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
939 "Failed to fix metadata for attribute %s" % sd_attr):
940 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
941 self.samdb.set_session_info(self.system_session_info)
944 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
945 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
946 str(repl_meta_data))
947 ctr = repl.ctr
948 found = False
949 for o in ctr.array:
950 # Search for a zero invocationID
951 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
952 continue
954 found = True
955 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
956 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
957 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
958 % (dn, o.attid, o.version,
959 time.ctime(samba.nttime2unix(o.originating_change_time)),
960 self.samdb.get_invocation_id()))
962 return found
965 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
966 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
967 str(repl_meta_data))
968 ctr = repl.ctr
969 now = samba.unix2nttime(int(time.time()))
970 found = False
971 for o in ctr.array:
972 # Search for a zero invocationID
973 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
974 continue
976 found = True
977 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
978 o.version = o.version + 1
979 o.originating_change_time = now
980 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
981 o.originating_usn = seq
982 o.local_usn = seq
984 if found:
985 replBlob = ndr_pack(repl)
986 msg = ldb.Message()
987 msg.dn = dn
989 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
990 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
991 self.report('Not fixing %s on %s\n' % (attr, dn))
992 return
994 nmsg = ldb.Message()
995 nmsg.dn = dn
996 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
997 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
998 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
999 "Failed to fix attribute %s" % attr):
1000 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1003 def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1004 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1005 str(repl_meta_data))
1006 ctr = repl.ctr
1007 for o in ctr.array:
1008 # Search for an invalid attid
1009 try:
1010 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1011 except KeyError:
1012 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1013 return
1016 def err_replmetadata_unsorted_attid(self, dn, attr, repl_meta_data):
1017 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1018 str(repl_meta_data))
1019 ctr = repl.ctr
1020 found = False
1022 self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1023 if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1024 % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1025 self.report('Not fixing %s on %s\n' % (attr, dn))
1026 return
1028 # Sort the array, except for the last element
1029 ctr.array[:-1] = sorted(ctr.array[:-1], key=lambda o: o.attid)
1031 replBlob = ndr_pack(repl)
1033 nmsg = ldb.Message()
1034 nmsg.dn = dn
1035 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1036 if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1037 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1038 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1039 "Failed to fix attribute %s" % attr):
1040 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1043 def is_deleted_deleted_objects(self, obj):
1044 faulty = False
1045 if "description" not in obj:
1046 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1047 faulty = True
1048 if "showInAdvancedViewOnly" not in obj:
1049 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1050 faulty = True
1051 if "objectCategory" not in obj:
1052 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1053 faulty = True
1054 if "isCriticalSystemObject" not in obj:
1055 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1056 faulty = True
1057 if "isRecycled" in obj:
1058 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1059 faulty = True
1060 return faulty
1063 def err_deleted_deleted_objects(self, obj):
1064 nmsg = ldb.Message()
1065 nmsg.dn = dn = obj.dn
1067 if "description" not in obj:
1068 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1069 if "showInAdvancedViewOnly" not in obj:
1070 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1071 if "objectCategory" not in obj:
1072 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1073 if "isCriticalSystemObject" not in obj:
1074 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1075 if "isRecycled" in obj:
1076 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1078 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1079 % (dn), 'fix_deleted_deleted_objects'):
1080 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1081 return
1083 if self.do_modify(nmsg, ["relax:0"],
1084 "Failed to fix Deleted Objects container %s" % dn):
1085 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1088 def is_fsmo_role(self, dn):
1089 if dn == self.samdb.domain_dn:
1090 return True
1091 if dn == self.infrastructure_dn:
1092 return True
1093 if dn == self.naming_dn:
1094 return True
1095 if dn == self.schema_dn:
1096 return True
1097 if dn == self.rid_dn:
1098 return True
1100 return False
1102 def calculate_instancetype(self, dn):
1103 instancetype = 0
1104 nc_root = self.samdb.get_nc_root(dn)
1105 if dn == nc_root:
1106 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1107 try:
1108 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1109 except ldb.LdbError, (enum, estr):
1110 if enum != ldb.ERR_NO_SUCH_OBJECT:
1111 raise
1112 else:
1113 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1115 if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1116 instancetype |= dsdb.INSTANCE_TYPE_WRITE
1118 return instancetype
1120 def get_wellknown_sd(self, dn):
1121 for [sd_dn, descriptor_fn] in self.wellknown_sds:
1122 if dn == sd_dn:
1123 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1124 return ndr_unpack(security.descriptor,
1125 descriptor_fn(domain_sid,
1126 name_map=self.name_map))
1128 raise KeyError
1130 def check_object(self, dn, attrs=['*']):
1131 '''check one object'''
1132 if self.verbose:
1133 self.report("Checking object %s" % dn)
1134 if "dn" in map(str.lower, attrs):
1135 attrs.append("name")
1136 if "distinguishedname" in map(str.lower, attrs):
1137 attrs.append("name")
1138 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1139 attrs.append("name")
1140 if 'name' in map(str.lower, attrs):
1141 attrs.append(dn.get_rdn_name())
1142 attrs.append("isDeleted")
1143 attrs.append("systemFlags")
1144 if '*' in attrs:
1145 attrs.append("replPropertyMetaData")
1147 try:
1148 sd_flags = 0
1149 sd_flags |= security.SECINFO_OWNER
1150 sd_flags |= security.SECINFO_GROUP
1151 sd_flags |= security.SECINFO_DACL
1152 sd_flags |= security.SECINFO_SACL
1154 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1155 controls=[
1156 "extended_dn:1:1",
1157 "show_recycled:1",
1158 "show_deleted:1",
1159 "sd_flags:1:%d" % sd_flags,
1161 attrs=attrs)
1162 except ldb.LdbError, (enum, estr):
1163 if enum == ldb.ERR_NO_SUCH_OBJECT:
1164 if self.in_transaction:
1165 self.report("ERROR: Object %s disappeared during check" % dn)
1166 return 1
1167 return 0
1168 raise
1169 if len(res) != 1:
1170 self.report("ERROR: Object %s failed to load during check" % dn)
1171 return 1
1172 obj = res[0]
1173 error_count = 0
1174 list_attrs_from_md = []
1175 list_attrs_seen = []
1176 got_repl_property_meta_data = False
1177 got_objectclass = False
1179 nc_dn = self.samdb.get_nc_root(obj.dn)
1180 try:
1181 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
1182 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
1183 except KeyError, e:
1184 deleted_objects_dn = ldb.Dn(self.samdb, "CN=Deleted Objects,%s" % nc_dn)
1186 object_rdn_attr = None
1187 object_rdn_val = None
1188 name_val = None
1189 isDeleted = False
1190 systemFlags = 0
1192 for attrname in obj:
1193 if attrname == 'dn':
1194 continue
1196 if str(attrname).lower() == 'objectclass':
1197 got_objectclass = True
1199 if str(attrname).lower() == "name":
1200 if len(obj[attrname]) != 1:
1201 error_count += 1
1202 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1203 (len(obj[attrname]), attrname, str(obj.dn)))
1204 else:
1205 name_val = obj[attrname][0]
1207 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
1208 object_rdn_attr = attrname
1209 if len(obj[attrname]) != 1:
1210 error_count += 1
1211 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1212 (len(obj[attrname]), attrname, str(obj.dn)))
1213 else:
1214 object_rdn_val = obj[attrname][0]
1216 if str(attrname).lower() == 'isdeleted':
1217 if obj[attrname][0] != "FALSE":
1218 isDeleted = True
1220 if str(attrname).lower() == 'systemflags':
1221 systemFlags = int(obj[attrname][0])
1223 if str(attrname).lower() == 'replpropertymetadata':
1224 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
1225 error_count += 1
1226 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
1227 # We don't continue, as we may also have other fixes for this attribute
1228 # based on what other attributes we see.
1230 try:
1231 (list_attrs_from_md, list_attid_from_md) = self.process_metadata(obj[attrname])
1232 except KeyError:
1233 error_count += 1
1234 self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
1235 continue
1237 if sorted(list_attid_from_md[:-1]) != list_attid_from_md[:-1]:
1238 error_count += 1
1239 self.err_replmetadata_unsorted_attid(dn, attrname, obj[attrname])
1241 got_repl_property_meta_data = True
1242 continue
1244 if str(attrname).lower() == 'ntsecuritydescriptor':
1245 (sd, sd_broken) = self.process_sd(dn, obj)
1246 if sd_broken is not None:
1247 self.err_wrong_sd(dn, sd, sd_broken)
1248 error_count += 1
1249 continue
1251 if sd.owner_sid is None or sd.group_sid is None:
1252 self.err_missing_sd_owner(dn, sd)
1253 error_count += 1
1254 continue
1256 if self.reset_well_known_acls:
1257 try:
1258 well_known_sd = self.get_wellknown_sd(dn)
1259 except KeyError:
1260 continue
1262 current_sd = ndr_unpack(security.descriptor,
1263 str(obj[attrname][0]))
1265 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
1266 if diff != "":
1267 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
1268 error_count += 1
1269 continue
1270 continue
1272 if str(attrname).lower() == 'objectclass':
1273 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, list(obj[attrname]))
1274 if list(normalised) != list(obj[attrname]):
1275 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
1276 error_count += 1
1277 continue
1279 if str(attrname).lower() == 'userparameters':
1280 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
1281 error_count += 1
1282 self.err_short_userParameters(obj, attrname, obj[attrname])
1283 continue
1285 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1286 # This is the correct, normal prefix
1287 continue
1289 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1290 # this is the typical prefix from a windows migration
1291 error_count += 1
1292 self.err_base64_userParameters(obj, attrname, obj[attrname])
1293 continue
1295 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':
1296 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1297 error_count += 1
1298 self.err_utf8_userParameters(obj, attrname, obj[attrname])
1299 continue
1301 elif len(obj[attrname][0]) % 2 != 0:
1302 # This is a value that isn't even in length
1303 error_count += 1
1304 self.err_odd_userParameters(obj, attrname, obj[attrname])
1305 continue
1307 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':
1308 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
1309 error_count += 1
1310 self.err_doubled_userParameters(obj, attrname, obj[attrname])
1311 continue
1313 # check for empty attributes
1314 for val in obj[attrname]:
1315 if val == '':
1316 self.err_empty_attribute(dn, attrname)
1317 error_count += 1
1318 continue
1320 # get the syntax oid for the attribute, so we can can have
1321 # special handling for some specific attribute types
1322 try:
1323 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1324 except Exception, msg:
1325 self.err_unknown_attribute(obj, attrname)
1326 error_count += 1
1327 continue
1329 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
1330 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
1331 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
1332 and not self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)):
1333 list_attrs_seen.append(str(attrname).lower())
1335 if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
1336 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
1337 # it's some form of DN, do specialised checking on those
1338 error_count += self.check_dn(obj, attrname, syntax_oid)
1340 # check for incorrectly normalised attributes
1341 for val in obj[attrname]:
1342 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
1343 if len(normalised) != 1 or normalised[0] != val:
1344 self.err_normalise_mismatch(dn, attrname, obj[attrname])
1345 error_count += 1
1346 break
1348 if str(attrname).lower() == "instancetype":
1349 calculated_instancetype = self.calculate_instancetype(dn)
1350 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
1351 error_count += 1
1352 self.err_wrong_instancetype(obj, calculated_instancetype)
1354 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
1355 error_count += 1
1356 self.err_missing_objectclass(dn)
1358 if ("*" in attrs or "name" in map(str.lower, attrs)):
1359 if name_val is None:
1360 error_count += 1
1361 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
1362 if object_rdn_attr is None:
1363 error_count += 1
1364 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
1366 if name_val is not None:
1367 parent_dn = None
1368 if isDeleted:
1369 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
1370 parent_dn = deleted_objects_dn
1371 if parent_dn is None:
1372 parent_dn = obj.dn.parent()
1373 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
1374 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
1376 if obj.dn == deleted_objects_dn:
1377 expected_dn = obj.dn
1379 if expected_dn != obj.dn:
1380 error_count += 1
1381 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
1382 elif obj.dn.get_rdn_value() != object_rdn_val:
1383 error_count += 1
1384 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
1386 show_dn = True
1387 if got_repl_property_meta_data:
1388 if obj.dn == deleted_objects_dn:
1389 isDeletedAttId = 131120
1390 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
1392 expectedTimeDo = 2650466015990000000
1393 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
1394 if originating != expectedTimeDo:
1395 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
1396 nmsg = ldb.Message()
1397 nmsg.dn = dn
1398 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1399 error_count += 1
1400 self.samdb.modify(nmsg, controls=["provision:0"])
1402 else:
1403 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
1404 for att in list_attrs_seen:
1405 if not att in list_attrs_from_md:
1406 if show_dn:
1407 self.report("On object %s" % dn)
1408 show_dn = False
1409 error_count += 1
1410 self.report("ERROR: Attribute %s not present in replication metadata" % att)
1411 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
1412 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
1413 continue
1414 self.fix_metadata(dn, att)
1416 if self.is_fsmo_role(dn):
1417 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
1418 self.err_no_fsmoRoleOwner(obj)
1419 error_count += 1
1421 try:
1422 if dn != self.samdb.get_root_basedn():
1423 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
1424 controls=["show_recycled:1", "show_deleted:1"])
1425 except ldb.LdbError, (enum, estr):
1426 if enum == ldb.ERR_NO_SUCH_OBJECT:
1427 self.err_missing_parent(obj)
1428 error_count += 1
1429 else:
1430 raise
1432 if dn in self.deleted_objects_containers and '*' in attrs:
1433 if self.is_deleted_deleted_objects(obj):
1434 self.err_deleted_deleted_objects(obj)
1435 error_count += 1
1437 return error_count
1439 ################################################################
1440 # check special @ROOTDSE attributes
1441 def check_rootdse(self):
1442 '''check the @ROOTDSE special object'''
1443 dn = ldb.Dn(self.samdb, '@ROOTDSE')
1444 if self.verbose:
1445 self.report("Checking object %s" % dn)
1446 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
1447 if len(res) != 1:
1448 self.report("Object %s disappeared during check" % dn)
1449 return 1
1450 obj = res[0]
1451 error_count = 0
1453 # check that the dsServiceName is in GUID form
1454 if not 'dsServiceName' in obj:
1455 self.report('ERROR: dsServiceName missing in @ROOTDSE')
1456 return error_count+1
1458 if not obj['dsServiceName'][0].startswith('<GUID='):
1459 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
1460 error_count += 1
1461 if not self.confirm('Change dsServiceName to GUID form?'):
1462 return error_count
1463 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
1464 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
1465 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
1466 m = ldb.Message()
1467 m.dn = dn
1468 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
1469 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
1470 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
1471 self.report("Changed dsServiceName to GUID form")
1472 return error_count
1475 ###############################################
1476 # re-index the database
1477 def reindex_database(self):
1478 '''re-index the whole database'''
1479 m = ldb.Message()
1480 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
1481 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
1482 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
1483 return self.do_modify(m, [], 're-indexed database', validate=False)
1485 ###############################################
1486 # reset @MODULES
1487 def reset_modules(self):
1488 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
1489 m = ldb.Message()
1490 m.dn = ldb.Dn(self.samdb, "@MODULES")
1491 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
1492 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)