param: use the generated parameter table.
[Samba.git] / python / samba / dbchecker.py
blob74e9678367f98276268a1238809811f20e192f0b
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.ndr import ndr_unpack, ndr_pack
28 from samba.dcerpc import drsblobs
29 from samba.common import dsdb_Dn
30 from samba.dcerpc import security
31 from samba.descriptor import get_wellknown_sds, get_diff_sds
32 from samba.auth import system_session, admin_session
35 class dbcheck(object):
36 """check a SAM database for errors"""
38 def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
39 yes=False, quiet=False, in_transaction=False,
40 reset_well_known_acls=False):
41 self.samdb = samdb
42 self.dict_oid_name = None
43 self.samdb_schema = (samdb_schema or samdb)
44 self.verbose = verbose
45 self.fix = fix
46 self.yes = yes
47 self.quiet = quiet
48 self.remove_all_unknown_attributes = False
49 self.remove_all_empty_attributes = False
50 self.fix_all_normalisation = False
51 self.fix_all_DN_GUIDs = False
52 self.fix_all_binary_dn = False
53 self.remove_all_deleted_DN_links = False
54 self.fix_all_target_mismatch = False
55 self.fix_all_metadata = False
56 self.fix_time_metadata = False
57 self.fix_all_missing_backlinks = False
58 self.fix_all_orphaned_backlinks = False
59 self.fix_rmd_flags = False
60 self.fix_ntsecuritydescriptor = False
61 self.fix_ntsecuritydescriptor_owner_group = False
62 self.seize_fsmo_role = False
63 self.move_to_lost_and_found = False
64 self.fix_instancetype = False
65 self.fix_replmetadata_zero_invocationid = False
66 self.fix_deleted_deleted_objects = False
67 self.fix_dn = False
68 self.fix_base64_userparameters = False
69 self.fix_utf8_userparameters = False
70 self.fix_doubled_userparameters = False
71 self.reset_well_known_acls = reset_well_known_acls
72 self.reset_all_well_known_acls = False
73 self.in_transaction = in_transaction
74 self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
75 self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
76 self.schema_dn = samdb.get_schema_basedn()
77 self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
78 self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
79 self.class_schemaIDGUID = {}
80 self.wellknown_sds = get_wellknown_sds(self.samdb)
81 self.fix_all_missing_objectclass = False
83 self.name_map = {}
84 try:
85 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE,
86 attrs=["objectSid"])
87 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
88 self.name_map['DnsAdmins'] = str(dnsadmins_sid)
89 except ldb.LdbError, (enum, estr):
90 if enum != ldb.ERR_NO_SUCH_OBJECT:
91 raise
92 pass
94 self.system_session_info = system_session()
95 self.admin_session_info = admin_session(None, samdb.get_domain_sid())
97 res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
98 if "msDS-hasMasterNCs" in res[0]:
99 self.write_ncs = res[0]["msDS-hasMasterNCs"]
100 else:
101 # If the Forest Level is less than 2003 then there is no
102 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
103 # no need to merge as all the NCs that are in hasMasterNCs must
104 # also be in msDS-hasMasterNCs (but not the opposite)
105 if "hasMasterNCs" in res[0]:
106 self.write_ncs = res[0]["hasMasterNCs"]
107 else:
108 self.write_ncs = None
110 res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
111 try:
112 ncs = res[0]["namingContexts"]
113 self.deleted_objects_containers = []
114 for nc in ncs:
115 try:
116 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc),
117 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
118 self.deleted_objects_containers.append(dn)
119 except KeyError:
120 pass
121 except KeyError:
122 pass
123 except IndexError:
124 pass
126 def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
127 '''perform a database check, returning the number of errors found'''
129 res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
130 self.report('Checking %u objects' % len(res))
131 error_count = 0
133 for object in res:
134 error_count += self.check_object(object.dn, attrs=attrs)
136 if DN is None:
137 error_count += self.check_rootdse()
139 if error_count != 0 and not self.fix:
140 self.report("Please use --fix to fix these errors")
142 self.report('Checked %u objects (%u errors)' % (len(res), error_count))
143 return error_count
145 def report(self, msg):
146 '''print a message unless quiet is set'''
147 if not self.quiet:
148 print(msg)
150 def confirm(self, msg, allow_all=False, forced=False):
151 '''confirm a change'''
152 if not self.fix:
153 return False
154 if self.quiet:
155 return self.yes
156 if self.yes:
157 forced = True
158 return common.confirm(msg, forced=forced, allow_all=allow_all)
160 ################################################################
161 # a local confirm function with support for 'all'
162 def confirm_all(self, msg, all_attr):
163 '''confirm a change with support for "all" '''
164 if not self.fix:
165 return False
166 if self.quiet:
167 return self.yes
168 if getattr(self, all_attr) == 'NONE':
169 return False
170 if getattr(self, all_attr) == 'ALL':
171 forced = True
172 else:
173 forced = self.yes
174 c = common.confirm(msg, forced=forced, allow_all=True)
175 if c == 'ALL':
176 setattr(self, all_attr, 'ALL')
177 return True
178 if c == 'NONE':
179 setattr(self, all_attr, 'NONE')
180 return False
181 return c
183 def do_delete(self, dn, controls, msg):
184 '''delete dn with optional verbose output'''
185 if self.verbose:
186 self.report("delete DN %s" % dn)
187 try:
188 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
189 self.samdb.delete(dn, controls=controls)
190 except Exception, err:
191 self.report("%s : %s" % (msg, err))
192 return False
193 return True
195 def do_modify(self, m, controls, msg, validate=True):
196 '''perform a modify with optional verbose output'''
197 if self.verbose:
198 self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
199 try:
200 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
201 self.samdb.modify(m, controls=controls, validate=validate)
202 except Exception, err:
203 self.report("%s : %s" % (msg, err))
204 return False
205 return True
207 def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
208 '''perform a modify with optional verbose output'''
209 if self.verbose:
210 self.report("""dn: %s
211 changeType: modrdn
212 newrdn: %s
213 deleteOldRdn: 1
214 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
215 try:
216 to_dn = to_rdn + to_base
217 controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
218 self.samdb.rename(from_dn, to_dn, controls=controls)
219 except Exception, err:
220 self.report("%s : %s" % (msg, err))
221 return False
222 return True
224 def err_empty_attribute(self, dn, attrname):
225 '''fix empty attributes'''
226 self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
227 if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
228 self.report("Not fixing empty attribute %s" % attrname)
229 return
231 m = ldb.Message()
232 m.dn = dn
233 m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
234 if self.do_modify(m, ["relax:0", "show_recycled:1"],
235 "Failed to remove empty attribute %s" % attrname, validate=False):
236 self.report("Removed empty attribute %s" % attrname)
238 def err_normalise_mismatch(self, dn, attrname, values):
239 '''fix attribute normalisation errors'''
240 self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
241 mod_list = []
242 for val in values:
243 normalised = self.samdb.dsdb_normalise_attributes(
244 self.samdb_schema, attrname, [val])
245 if len(normalised) != 1:
246 self.report("Unable to normalise value '%s'" % val)
247 mod_list.append((val, ''))
248 elif (normalised[0] != val):
249 self.report("value '%s' should be '%s'" % (val, normalised[0]))
250 mod_list.append((val, normalised[0]))
251 if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
252 self.report("Not fixing attribute %s" % attrname)
253 return
255 m = ldb.Message()
256 m.dn = dn
257 for i in range(0, len(mod_list)):
258 (val, nval) = mod_list[i]
259 m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
260 if nval != '':
261 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
262 attrname)
264 if self.do_modify(m, ["relax:0", "show_recycled:1"],
265 "Failed to normalise attribute %s" % attrname,
266 validate=False):
267 self.report("Normalised attribute %s" % attrname)
269 def err_normalise_mismatch_replace(self, dn, attrname, values):
270 '''fix attribute normalisation errors'''
271 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
272 self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
273 self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
274 if list(normalised) == values:
275 return
276 if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
277 self.report("Not fixing attribute '%s'" % attrname)
278 return
280 m = ldb.Message()
281 m.dn = dn
282 m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
284 if self.do_modify(m, ["relax:0", "show_recycled:1"],
285 "Failed to normalise attribute %s" % attrname,
286 validate=False):
287 self.report("Normalised attribute %s" % attrname)
289 def is_deleted_objects_dn(self, dsdb_dn):
290 '''see if a dsdb_Dn is the special Deleted Objects DN'''
291 return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
293 def err_missing_objectclass(self, dn):
294 """handle object without objectclass"""
295 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)))
296 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'):
297 self.report("Not deleting object with missing objectclass '%s'" % dn)
298 return
299 if self.do_delete(dn, ["relax:0"],
300 "Failed to remove DN %s" % dn):
301 self.report("Removed DN %s" % dn)
303 def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn):
304 """handle a DN pointing to a deleted object"""
305 self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
306 self.report("Target GUID points at deleted DN %s" % correct_dn)
307 if not self.confirm_all('Remove DN link?', 'remove_all_deleted_DN_links'):
308 self.report("Not removing")
309 return
310 m = ldb.Message()
311 m.dn = dn
312 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
313 if self.do_modify(m, ["show_recycled:1", "local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK],
314 "Failed to remove deleted DN attribute %s" % attrname):
315 self.report("Removed deleted DN on attribute %s" % attrname)
317 def err_missing_dn_GUID(self, dn, attrname, val, dsdb_dn):
318 """handle a missing target DN (both GUID and DN string form are missing)"""
319 # check if its a backlink
320 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
321 if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
322 self.report("Not removing dangling forward link")
323 return
324 self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn)
326 def err_incorrect_dn_GUID(self, dn, attrname, val, dsdb_dn, errstr):
327 """handle a missing GUID extended DN component"""
328 self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
329 controls=["extended_dn:1:1", "show_recycled:1"]
330 try:
331 res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
332 attrs=[], controls=controls)
333 except ldb.LdbError, (enum, estr):
334 self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
335 self.err_missing_dn_GUID(dn, attrname, val, dsdb_dn)
336 return
337 if len(res) == 0:
338 self.report("unable to find object for DN %s" % dsdb_dn.dn)
339 self.err_missing_dn_GUID(dn, attrname, val, dsdb_dn)
340 return
341 dsdb_dn.dn = res[0].dn
343 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
344 self.report("Not fixing %s" % errstr)
345 return
346 m = ldb.Message()
347 m.dn = dn
348 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
349 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
351 if self.do_modify(m, ["show_recycled:1"],
352 "Failed to fix %s on attribute %s" % (errstr, attrname)):
353 self.report("Fixed %s on attribute %s" % (errstr, attrname))
355 def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
356 """handle an incorrect binary DN component"""
357 self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
358 controls=["extended_dn:1:1", "show_recycled:1"]
360 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
361 self.report("Not fixing %s" % errstr)
362 return
363 m = ldb.Message()
364 m.dn = dn
365 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
366 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
368 if self.do_modify(m, ["show_recycled:1"],
369 "Failed to fix %s on attribute %s" % (errstr, attrname)):
370 self.report("Fixed %s on attribute %s" % (errstr, attrname))
372 def err_dn_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, errstr):
373 """handle a DN string being incorrect"""
374 self.report("ERROR: incorrect DN string component for %s in object %s - %s" % (attrname, dn, val))
375 dsdb_dn.dn = correct_dn
377 if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_target_mismatch'):
378 self.report("Not fixing %s" % errstr)
379 return
380 m = ldb.Message()
381 m.dn = dn
382 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
383 m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
384 if self.do_modify(m, ["show_recycled:1"],
385 "Failed to fix incorrect DN string on attribute %s" % attrname):
386 self.report("Fixed incorrect DN string on attribute %s" % (attrname))
388 def err_unknown_attribute(self, obj, attrname):
389 '''handle an unknown attribute error'''
390 self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
391 if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
392 self.report("Not removing %s" % attrname)
393 return
394 m = ldb.Message()
395 m.dn = obj.dn
396 m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
397 if self.do_modify(m, ["relax:0", "show_recycled:1"],
398 "Failed to remove unknown attribute %s" % attrname):
399 self.report("Removed unknown attribute %s" % (attrname))
401 def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
402 '''handle a missing backlink value'''
403 self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
404 if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
405 self.report("Not fixing missing backlink %s" % backlink_name)
406 return
407 m = ldb.Message()
408 m.dn = obj.dn
409 m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
410 m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, attrname)
411 if self.do_modify(m, ["show_recycled:1"],
412 "Failed to fix missing backlink %s" % backlink_name):
413 self.report("Fixed missing backlink %s" % (backlink_name))
415 def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
416 '''handle a incorrect RMD_FLAGS value'''
417 rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
418 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()))
419 if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
420 self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
421 return
422 m = ldb.Message()
423 m.dn = obj.dn
424 m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
425 if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
426 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
427 self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
429 def err_orphaned_backlink(self, obj, attrname, val, link_name, target_dn):
430 '''handle a orphaned backlink value'''
431 self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (attrname, obj.dn, link_name, target_dn))
432 if not self.confirm_all('Remove orphaned backlink %s' % link_name, 'fix_all_orphaned_backlinks'):
433 self.report("Not removing orphaned backlink %s" % link_name)
434 return
435 m = ldb.Message()
436 m.dn = obj.dn
437 m['value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
438 if self.do_modify(m, ["show_recycled:1", "relax:0"],
439 "Failed to fix orphaned backlink %s" % link_name):
440 self.report("Fixed orphaned backlink %s" % (link_name))
442 def err_no_fsmoRoleOwner(self, obj):
443 '''handle a missing fSMORoleOwner'''
444 self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
445 res = self.samdb.search("",
446 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
447 assert len(res) == 1
448 serviceName = res[0]["dsServiceName"][0]
449 if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
450 self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
451 return
452 m = ldb.Message()
453 m.dn = obj.dn
454 m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
455 if self.do_modify(m, [],
456 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
457 self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
459 def err_missing_parent(self, obj):
460 '''handle a missing parent'''
461 self.report("ERROR: parent object not found for %s" % (obj.dn))
462 if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
463 self.report('Not moving object %s into LostAndFound' % (obj.dn))
464 return
466 keep_transaction = True
467 self.samdb.transaction_start()
468 try:
469 nc_root = self.samdb.get_nc_root(obj.dn);
470 lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
471 new_dn = ldb.Dn(self.samdb, str(obj.dn))
472 new_dn.remove_base_components(len(new_dn) - 1)
473 if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
474 "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
475 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
477 m = ldb.Message()
478 m.dn = obj.dn
479 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
481 if self.do_modify(m, [],
482 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
483 self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
484 keep_transaction = True
485 except:
486 self.samdb.transaction_cancel()
487 raise
489 if keep_transaction:
490 self.samdb.transaction_commit()
491 else:
492 self.samdb.transaction_cancel()
494 def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
495 '''handle a wrong dn'''
497 new_rdn = ldb.Dn(self.samdb, str(new_dn))
498 new_rdn.remove_base_components(len(new_rdn) - 1)
499 new_parent = new_dn.parent()
501 attributes = ""
502 if rdn_val != name_val:
503 attributes += "%s=%r " % (rdn_attr, rdn_val)
504 attributes += "name=%r" % (name_val)
506 self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
507 if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
508 self.report("Not renaming %s to %s" % (obj.dn, new_dn))
509 return
511 if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
512 "Failed to rename object %s into %s" % (obj.dn, new_dn)):
513 self.report("Renamed %s into %s" % (obj.dn, new_dn))
515 def err_wrong_instancetype(self, obj, calculated_instancetype):
516 '''handle a wrong instanceType'''
517 self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
518 if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
519 self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
520 return
522 m = ldb.Message()
523 m.dn = obj.dn
524 m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
525 if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
526 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
527 self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
529 def err_short_userParameters(self, obj, attrname, value):
530 # This is a truncated userParameters due to a pre 4.1 replication bug
531 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)))
533 def err_base64_userParameters(self, obj, attrname, value):
534 '''handle a wrong userParameters'''
535 self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
536 if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
537 self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
538 return
540 m = ldb.Message()
541 m.dn = obj.dn
542 m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
543 if self.do_modify(m, [],
544 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
545 self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
547 def err_utf8_userParameters(self, obj, attrname, value):
548 '''handle a wrong userParameters'''
549 self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
550 if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
551 self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
552 return
554 m = ldb.Message()
555 m.dn = obj.dn
556 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
557 ldb.FLAG_MOD_REPLACE, 'userParameters')
558 if self.do_modify(m, [],
559 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
560 self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
562 def err_doubled_userParameters(self, obj, attrname, value):
563 '''handle a wrong userParameters'''
564 self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
565 if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
566 self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
567 return
569 m = ldb.Message()
570 m.dn = obj.dn
571 m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
572 ldb.FLAG_MOD_REPLACE, 'userParameters')
573 if self.do_modify(m, [],
574 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
575 self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
577 def err_odd_userParameters(self, obj, attrname):
578 # This is a truncated userParameters due to a pre 4.1 replication bug
579 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)))
581 def find_revealed_link(self, dn, attrname, guid):
582 '''return a revealed link in an object'''
583 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
584 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
585 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
586 for val in res[0][attrname]:
587 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
588 guid2 = dsdb_dn.dn.get_extended_component("GUID")
589 if guid == guid2:
590 return dsdb_dn
591 return None
593 def check_dn(self, obj, attrname, syntax_oid):
594 '''check a DN attribute for correctness'''
595 error_count = 0
596 for val in obj[attrname]:
597 dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
599 # all DNs should have a GUID component
600 guid = dsdb_dn.dn.get_extended_component("GUID")
601 if guid is None:
602 error_count += 1
603 self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn,
604 "missing GUID")
605 continue
607 guidstr = str(misc.GUID(guid))
609 attrs = ['isDeleted']
611 if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
612 fixing_msDS_HasInstantiatedNCs = True
613 attrs.append("instanceType")
614 else:
615 fixing_msDS_HasInstantiatedNCs = False
617 linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
618 reverse_link_name = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
619 if reverse_link_name is not None:
620 attrs.append(reverse_link_name)
622 # check its the right GUID
623 try:
624 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
625 attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1"])
626 except ldb.LdbError, (enum, estr):
627 error_count += 1
628 self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn, "incorrect GUID")
629 continue
631 if fixing_msDS_HasInstantiatedNCs:
632 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
633 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
635 if str(dsdb_dn) != val:
636 error_count +=1
637 self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
638 continue
640 # now we have two cases - the source object might or might not be deleted
641 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
642 target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
644 # the target DN is not allowed to be deleted, unless the target DN is the
645 # special Deleted Objects container
646 if target_is_deleted and not is_deleted and not self.is_deleted_objects_dn(dsdb_dn):
647 error_count += 1
648 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn)
649 continue
651 # check the DN matches in string form
652 if res[0].dn.extended_str() != dsdb_dn.dn.extended_str():
653 error_count += 1
654 self.err_dn_target_mismatch(obj.dn, attrname, val, dsdb_dn,
655 res[0].dn, "incorrect string version of DN")
656 continue
658 if is_deleted and not target_is_deleted and reverse_link_name is not None:
659 revealed_dn = self.find_revealed_link(obj.dn, attrname, guid)
660 rmd_flags = revealed_dn.dn.get_extended_component("RMD_FLAGS")
661 if rmd_flags is not None and (int(rmd_flags) & 1) == 0:
662 # the RMD_FLAGS for this link should be 1, as the target is deleted
663 self.err_incorrect_rmd_flags(obj, attrname, revealed_dn)
664 continue
666 # check the reverse_link is correct if there should be one
667 if reverse_link_name is not None:
668 match_count = 0
669 if reverse_link_name in res[0]:
670 for v in res[0][reverse_link_name]:
671 if v == obj.dn.extended_str():
672 match_count += 1
673 if match_count != 1:
674 error_count += 1
675 if linkID & 1:
676 self.err_orphaned_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
677 else:
678 self.err_missing_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
679 continue
681 return error_count
684 def get_originating_time(self, val, attid):
685 '''Read metadata properties and return the originating time for
686 a given attributeId.
688 :return: the originating time or 0 if not found
691 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
692 obj = repl.ctr
694 for o in repl.ctr.array:
695 if o.attid == attid:
696 return o.originating_change_time
698 return 0
700 def process_metadata(self, val):
701 '''Read metadata properties and list attributes in it'''
703 list_att = []
705 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
706 obj = repl.ctr
708 for o in repl.ctr.array:
709 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
710 list_att.append(att.lower())
712 return list_att
715 def fix_metadata(self, dn, attr):
716 '''re-write replPropertyMetaData elements for a single attribute for a
717 object. This is used to fix missing replPropertyMetaData elements'''
718 res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
719 controls = ["search_options:1:2", "show_recycled:1"])
720 msg = res[0]
721 nmsg = ldb.Message()
722 nmsg.dn = dn
723 nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
724 if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
725 "Failed to fix metadata for attribute %s" % attr):
726 self.report("Fixed metadata for attribute %s" % attr)
728 def ace_get_effective_inherited_type(self, ace):
729 if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
730 return None
732 check = False
733 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
734 check = True
735 elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
736 check = True
737 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
738 check = True
739 elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
740 check = True
742 if not check:
743 return None
745 if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
746 return None
748 return str(ace.object.inherited_type)
750 def lookup_class_schemaIDGUID(self, cls):
751 if cls in self.class_schemaIDGUID:
752 return self.class_schemaIDGUID[cls]
754 flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
755 res = self.samdb.search(base=self.schema_dn,
756 expression=flt,
757 attrs=["schemaIDGUID"])
758 t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
760 self.class_schemaIDGUID[cls] = t
761 return t
763 def process_sd(self, dn, obj):
764 sd_attr = "nTSecurityDescriptor"
765 sd_val = obj[sd_attr]
767 sd = ndr_unpack(security.descriptor, str(sd_val))
769 is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
770 if is_deleted:
771 # we don't fix deleted objects
772 return (sd, None)
774 sd_clean = security.descriptor()
775 sd_clean.owner_sid = sd.owner_sid
776 sd_clean.group_sid = sd.group_sid
777 sd_clean.type = sd.type
778 sd_clean.revision = sd.revision
780 broken = False
781 last_inherited_type = None
783 aces = []
784 if sd.sacl is not None:
785 aces = sd.sacl.aces
786 for i in range(0, len(aces)):
787 ace = aces[i]
789 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
790 sd_clean.sacl_add(ace)
791 continue
793 t = self.ace_get_effective_inherited_type(ace)
794 if t is None:
795 continue
797 if last_inherited_type is not None:
798 if t != last_inherited_type:
799 # if it inherited from more than
800 # one type it's very likely to be broken
802 # If not the recalculation will calculate
803 # the same result.
804 broken = True
805 continue
807 last_inherited_type = t
809 aces = []
810 if sd.dacl is not None:
811 aces = sd.dacl.aces
812 for i in range(0, len(aces)):
813 ace = aces[i]
815 if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
816 sd_clean.dacl_add(ace)
817 continue
819 t = self.ace_get_effective_inherited_type(ace)
820 if t is None:
821 continue
823 if last_inherited_type is not None:
824 if t != last_inherited_type:
825 # if it inherited from more than
826 # one type it's very likely to be broken
828 # If not the recalculation will calculate
829 # the same result.
830 broken = True
831 continue
833 last_inherited_type = t
835 if broken:
836 return (sd_clean, sd)
838 if last_inherited_type is None:
839 # ok
840 return (sd, None)
842 cls = None
843 try:
844 cls = obj["objectClass"][-1]
845 except KeyError, e:
846 pass
848 if cls is None:
849 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
850 attrs=["isDeleted", "objectClass"],
851 controls=["show_recycled:1"])
852 o = res[0]
853 is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
854 if is_deleted:
855 # we don't fix deleted objects
856 return (sd, None)
857 cls = o["objectClass"][-1]
859 t = self.lookup_class_schemaIDGUID(cls)
861 if t != last_inherited_type:
862 # broken
863 return (sd_clean, sd)
865 # ok
866 return (sd, None)
868 def err_wrong_sd(self, dn, sd, sd_broken):
869 '''re-write the SD due to incorrect inherited ACEs'''
870 sd_attr = "nTSecurityDescriptor"
871 sd_val = ndr_pack(sd)
872 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
874 if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
875 self.report('Not fixing %s on %s\n' % (sd_attr, dn))
876 return
878 nmsg = ldb.Message()
879 nmsg.dn = dn
880 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
881 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
882 "Failed to fix attribute %s" % sd_attr):
883 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
885 def err_wrong_default_sd(self, dn, sd, sd_old, diff):
886 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
887 sd_attr = "nTSecurityDescriptor"
888 sd_val = ndr_pack(sd)
889 sd_old_val = ndr_pack(sd_old)
890 sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
891 if sd.owner_sid is not None:
892 sd_flags |= security.SECINFO_OWNER
893 if sd.group_sid is not None:
894 sd_flags |= security.SECINFO_GROUP
896 if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
897 self.report('Not resetting %s on %s\n' % (sd_attr, dn))
898 return
900 m = ldb.Message()
901 m.dn = dn
902 m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
903 if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
904 "Failed to reset attribute %s" % sd_attr):
905 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
907 def err_missing_sd_owner(self, dn, sd):
908 '''re-write the SD due to a missing owner or group'''
909 sd_attr = "nTSecurityDescriptor"
910 sd_val = ndr_pack(sd)
911 sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
913 if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
914 self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
915 return
917 nmsg = ldb.Message()
918 nmsg.dn = dn
919 nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
921 # By setting the session_info to admin_session_info and
922 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
923 # flags we cause the descriptor module to set the correct
924 # owner and group on the SD, replacing the None/NULL values
925 # for owner_sid and group_sid currently present.
927 # The admin_session_info matches that used in provision, and
928 # is the best guess we can make for an existing object that
929 # hasn't had something specifically set.
931 # This is important for the dns related naming contexts.
932 self.samdb.set_session_info(self.admin_session_info)
933 if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
934 "Failed to fix metadata for attribute %s" % sd_attr):
935 self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
936 self.samdb.set_session_info(self.system_session_info)
939 def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
940 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
941 str(repl_meta_data))
942 ctr = repl.ctr
943 found = False
944 for o in ctr.array:
945 # Search for a zero invocationID
946 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
947 continue
949 found = True
950 self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
951 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
952 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
953 % (dn, o.attid, o.version,
954 time.ctime(samba.nttime2unix(o.originating_change_time)),
955 self.samdb.get_invocation_id()))
957 return found
960 def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
961 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
962 str(repl_meta_data))
963 ctr = repl.ctr
964 now = samba.unix2nttime(int(time.time()))
965 found = False
966 for o in ctr.array:
967 # Search for a zero invocationID
968 if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
969 continue
971 found = True
972 seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
973 o.version = o.version + 1
974 o.originating_change_time = now
975 o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
976 o.originating_usn = seq
977 o.local_usn = seq
979 if found:
980 replBlob = ndr_pack(repl)
981 msg = ldb.Message()
982 msg.dn = dn
984 if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
985 % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
986 self.report('Not fixing %s on %s\n' % (attr, dn))
987 return
989 nmsg = ldb.Message()
990 nmsg.dn = dn
991 nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
992 if self.do_modify(nmsg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
993 "Failed to fix attribute %s" % attr):
994 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
997 def is_deleted_deleted_objects(self, obj):
998 faulty = False
999 if "description" not in obj:
1000 self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1001 faulty = True
1002 if "showInAdvancedViewOnly" not in obj:
1003 self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1004 faulty = True
1005 if "objectCategory" not in obj:
1006 self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1007 faulty = True
1008 if "isCriticalSystemObject" not in obj:
1009 self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1010 faulty = True
1011 if "isRecycled" in obj:
1012 self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1013 faulty = True
1014 return faulty
1017 def err_deleted_deleted_objects(self, obj):
1018 nmsg = ldb.Message()
1019 nmsg.dn = dn = obj.dn
1021 if "description" not in obj:
1022 nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1023 if "showInAdvancedViewOnly" not in obj:
1024 nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1025 if "objectCategory" not in obj:
1026 nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1027 if "isCriticalSystemObject" not in obj:
1028 nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1029 if "isRecycled" in obj:
1030 nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1032 if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1033 % (dn), 'fix_deleted_deleted_objects'):
1034 self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1035 return
1037 if self.do_modify(nmsg, ["relax:0"],
1038 "Failed to fix Deleted Objects container %s" % dn):
1039 self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1042 def is_fsmo_role(self, dn):
1043 if dn == self.samdb.domain_dn:
1044 return True
1045 if dn == self.infrastructure_dn:
1046 return True
1047 if dn == self.naming_dn:
1048 return True
1049 if dn == self.schema_dn:
1050 return True
1051 if dn == self.rid_dn:
1052 return True
1054 return False
1056 def calculate_instancetype(self, dn):
1057 instancetype = 0
1058 nc_root = self.samdb.get_nc_root(dn)
1059 if dn == nc_root:
1060 instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1061 try:
1062 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1063 except ldb.LdbError, (enum, estr):
1064 if enum != ldb.ERR_NO_SUCH_OBJECT:
1065 raise
1066 else:
1067 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1069 if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1070 instancetype |= dsdb.INSTANCE_TYPE_WRITE
1072 return instancetype
1074 def get_wellknown_sd(self, dn):
1075 for [sd_dn, descriptor_fn] in self.wellknown_sds:
1076 if dn == sd_dn:
1077 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1078 return ndr_unpack(security.descriptor,
1079 descriptor_fn(domain_sid,
1080 name_map=self.name_map))
1082 raise KeyError
1084 def check_object(self, dn, attrs=['*']):
1085 '''check one object'''
1086 if self.verbose:
1087 self.report("Checking object %s" % dn)
1088 if "dn" in map(str.lower, attrs):
1089 attrs.append("name")
1090 if "distinguishedname" in map(str.lower, attrs):
1091 attrs.append("name")
1092 if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1093 attrs.append("name")
1094 if 'name' in map(str.lower, attrs):
1095 attrs.append(dn.get_rdn_name())
1096 attrs.append("isDeleted")
1097 attrs.append("systemFlags")
1098 if '*' in attrs:
1099 attrs.append("replPropertyMetaData")
1101 try:
1102 sd_flags = 0
1103 sd_flags |= security.SECINFO_OWNER
1104 sd_flags |= security.SECINFO_GROUP
1105 sd_flags |= security.SECINFO_DACL
1106 sd_flags |= security.SECINFO_SACL
1108 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1109 controls=[
1110 "extended_dn:1:1",
1111 "show_recycled:1",
1112 "show_deleted:1",
1113 "sd_flags:1:%d" % sd_flags,
1115 attrs=attrs)
1116 except ldb.LdbError, (enum, estr):
1117 if enum == ldb.ERR_NO_SUCH_OBJECT:
1118 if self.in_transaction:
1119 self.report("ERROR: Object %s disappeared during check" % dn)
1120 return 1
1121 return 0
1122 raise
1123 if len(res) != 1:
1124 self.report("ERROR: Object %s failed to load during check" % dn)
1125 return 1
1126 obj = res[0]
1127 error_count = 0
1128 list_attrs_from_md = []
1129 list_attrs_seen = []
1130 got_repl_property_meta_data = False
1131 got_objectclass = False
1133 nc_dn = self.samdb.get_nc_root(obj.dn)
1134 try:
1135 deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
1136 samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
1137 except KeyError, e:
1138 deleted_objects_dn = ldb.Dn(self.samdb, "CN=Deleted Objects,%s" % nc_dn)
1140 object_rdn_attr = None
1141 object_rdn_val = None
1142 name_val = None
1143 isDeleted = False
1144 systemFlags = 0
1146 for attrname in obj:
1147 if attrname == 'dn':
1148 continue
1150 if str(attrname).lower() == 'objectclass':
1151 got_objectclass = True
1153 if str(attrname).lower() == "name":
1154 if len(obj[attrname]) != 1:
1155 error_count += 1
1156 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1157 (len(obj[attrname]), attrname, str(obj.dn)))
1158 else:
1159 name_val = obj[attrname][0]
1161 if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
1162 object_rdn_attr = attrname
1163 if len(obj[attrname]) != 1:
1164 error_count += 1
1165 self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1166 (len(obj[attrname]), attrname, str(obj.dn)))
1167 else:
1168 object_rdn_val = obj[attrname][0]
1170 if str(attrname).lower() == 'isdeleted':
1171 if obj[attrname][0] != "FALSE":
1172 isDeleted = True
1174 if str(attrname).lower() == 'systemflags':
1175 systemFlags = int(obj[attrname][0])
1177 if str(attrname).lower() == 'replpropertymetadata':
1178 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
1179 error_count += 1
1180 self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
1181 # We don't continue, as we may also have other fixes for this attribute
1182 # based on what other attributes we see.
1184 list_attrs_from_md = self.process_metadata(obj[attrname])
1185 got_repl_property_meta_data = True
1186 continue
1188 if str(attrname).lower() == 'ntsecuritydescriptor':
1189 (sd, sd_broken) = self.process_sd(dn, obj)
1190 if sd_broken is not None:
1191 self.err_wrong_sd(dn, sd, sd_broken)
1192 error_count += 1
1193 continue
1195 if sd.owner_sid is None or sd.group_sid is None:
1196 self.err_missing_sd_owner(dn, sd)
1197 error_count += 1
1198 continue
1200 if self.reset_well_known_acls:
1201 try:
1202 well_known_sd = self.get_wellknown_sd(dn)
1203 except KeyError:
1204 continue
1206 current_sd = ndr_unpack(security.descriptor,
1207 str(obj[attrname][0]))
1209 diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
1210 if diff != "":
1211 self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
1212 error_count += 1
1213 continue
1214 continue
1216 if str(attrname).lower() == 'objectclass':
1217 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, list(obj[attrname]))
1218 if list(normalised) != list(obj[attrname]):
1219 self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
1220 error_count += 1
1221 continue
1223 if str(attrname).lower() == 'userparameters':
1224 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
1225 error_count += 1
1226 self.err_short_userParameters(obj, attrname, obj[attrname])
1227 continue
1229 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1230 # This is the correct, normal prefix
1231 continue
1233 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1234 # this is the typical prefix from a windows migration
1235 error_count += 1
1236 self.err_base64_userParameters(obj, attrname, obj[attrname])
1237 continue
1239 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':
1240 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1241 error_count += 1
1242 self.err_utf8_userParameters(obj, attrname, obj[attrname])
1243 continue
1245 elif len(obj[attrname][0]) % 2 != 0:
1246 # This is a value that isn't even in length
1247 error_count += 1
1248 self.err_odd_userParameters(obj, attrname, obj[attrname])
1249 continue
1251 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':
1252 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
1253 error_count += 1
1254 self.err_doubled_userParameters(obj, attrname, obj[attrname])
1255 continue
1257 # check for empty attributes
1258 for val in obj[attrname]:
1259 if val == '':
1260 self.err_empty_attribute(dn, attrname)
1261 error_count += 1
1262 continue
1264 # get the syntax oid for the attribute, so we can can have
1265 # special handling for some specific attribute types
1266 try:
1267 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1268 except Exception, msg:
1269 self.err_unknown_attribute(obj, attrname)
1270 error_count += 1
1271 continue
1273 flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
1274 if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
1275 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
1276 and not self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)):
1277 list_attrs_seen.append(str(attrname).lower())
1279 if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
1280 dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
1281 # it's some form of DN, do specialised checking on those
1282 error_count += self.check_dn(obj, attrname, syntax_oid)
1284 # check for incorrectly normalised attributes
1285 for val in obj[attrname]:
1286 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
1287 if len(normalised) != 1 or normalised[0] != val:
1288 self.err_normalise_mismatch(dn, attrname, obj[attrname])
1289 error_count += 1
1290 break
1292 if str(attrname).lower() == "instancetype":
1293 calculated_instancetype = self.calculate_instancetype(dn)
1294 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
1295 error_count += 1
1296 self.err_wrong_instancetype(obj, calculated_instancetype)
1298 if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
1299 error_count += 1
1300 self.err_missing_objectclass(dn)
1302 if ("*" in attrs or "name" in map(str.lower, attrs)):
1303 if name_val is None:
1304 error_count += 1
1305 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
1306 if object_rdn_attr is None:
1307 error_count += 1
1308 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
1310 if name_val is not None:
1311 parent_dn = None
1312 if isDeleted:
1313 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
1314 parent_dn = deleted_objects_dn
1315 if parent_dn is None:
1316 parent_dn = obj.dn.parent()
1317 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
1318 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
1320 if obj.dn == deleted_objects_dn:
1321 expected_dn = obj.dn
1323 if expected_dn != obj.dn:
1324 error_count += 1
1325 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
1326 elif obj.dn.get_rdn_value() != object_rdn_val:
1327 error_count += 1
1328 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
1330 show_dn = True
1331 if got_repl_property_meta_data:
1332 if obj.dn == deleted_objects_dn:
1333 isDeletedAttId = 131120
1334 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
1336 expectedTimeDo = 2650466015990000000
1337 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
1338 if originating != expectedTimeDo:
1339 if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
1340 nmsg = ldb.Message()
1341 nmsg.dn = dn
1342 nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1343 error_count += 1
1344 self.samdb.modify(nmsg, controls=["provision:0"])
1346 else:
1347 self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
1348 for att in list_attrs_seen:
1349 if not att in list_attrs_from_md:
1350 if show_dn:
1351 self.report("On object %s" % dn)
1352 show_dn = False
1353 error_count += 1
1354 self.report("ERROR: Attribute %s not present in replication metadata" % att)
1355 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
1356 self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
1357 continue
1358 self.fix_metadata(dn, att)
1360 if self.is_fsmo_role(dn):
1361 if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
1362 self.err_no_fsmoRoleOwner(obj)
1363 error_count += 1
1365 try:
1366 if dn != self.samdb.get_root_basedn():
1367 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
1368 controls=["show_recycled:1", "show_deleted:1"])
1369 except ldb.LdbError, (enum, estr):
1370 if enum == ldb.ERR_NO_SUCH_OBJECT:
1371 self.err_missing_parent(obj)
1372 error_count += 1
1373 else:
1374 raise
1376 if dn in self.deleted_objects_containers and '*' in attrs:
1377 if self.is_deleted_deleted_objects(obj):
1378 self.err_deleted_deleted_objects(obj)
1379 error_count += 1
1381 return error_count
1383 ################################################################
1384 # check special @ROOTDSE attributes
1385 def check_rootdse(self):
1386 '''check the @ROOTDSE special object'''
1387 dn = ldb.Dn(self.samdb, '@ROOTDSE')
1388 if self.verbose:
1389 self.report("Checking object %s" % dn)
1390 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
1391 if len(res) != 1:
1392 self.report("Object %s disappeared during check" % dn)
1393 return 1
1394 obj = res[0]
1395 error_count = 0
1397 # check that the dsServiceName is in GUID form
1398 if not 'dsServiceName' in obj:
1399 self.report('ERROR: dsServiceName missing in @ROOTDSE')
1400 return error_count+1
1402 if not obj['dsServiceName'][0].startswith('<GUID='):
1403 self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
1404 error_count += 1
1405 if not self.confirm('Change dsServiceName to GUID form?'):
1406 return error_count
1407 res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
1408 scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
1409 guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
1410 m = ldb.Message()
1411 m.dn = dn
1412 m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
1413 ldb.FLAG_MOD_REPLACE, 'dsServiceName')
1414 if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
1415 self.report("Changed dsServiceName to GUID form")
1416 return error_count
1419 ###############################################
1420 # re-index the database
1421 def reindex_database(self):
1422 '''re-index the whole database'''
1423 m = ldb.Message()
1424 m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
1425 m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
1426 m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
1427 return self.do_modify(m, [], 're-indexed database', validate=False)
1429 ###############################################
1430 # reset @MODULES
1431 def reset_modules(self):
1432 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
1433 m = ldb.Message()
1434 m.dn = ldb.Dn(self.samdb, "@MODULES")
1435 m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
1436 return self.do_modify(m, [], 'reset @MODULES on database', validate=False)