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/>.
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):
43 self
.dict_oid_name
= None
44 self
.samdb_schema
= (samdb_schema
or samdb
)
45 self
.verbose
= verbose
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
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
87 res
= samdb
.search(base
="CN=DnsAdmins,CN=Users,%s" % samdb
.domain_dn(), scope
=ldb
.SCOPE_BASE
,
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
:
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"]
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"]
110 self
.write_ncs
= None
112 res
= self
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['namingContexts'])
114 ncs
= res
[0]["namingContexts"]
115 self
.deleted_objects_containers
= []
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
)
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
))
136 error_count
+= self
.check_object(object.dn
, attrs
=attrs
)
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
))
147 def report(self
, msg
):
148 '''print a message unless quiet is set'''
152 def confirm(self
, msg
, allow_all
=False, forced
=False):
153 '''confirm a change'''
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" '''
170 if getattr(self
, all_attr
) == 'NONE':
172 if getattr(self
, all_attr
) == 'ALL':
176 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
178 setattr(self
, all_attr
, 'ALL')
181 setattr(self
, all_attr
, 'NONE')
185 def do_delete(self
, dn
, controls
, msg
):
186 '''delete dn with optional verbose output'''
188 self
.report("delete DN %s" % dn
)
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
))
197 def do_modify(self
, m
, controls
, msg
, validate
=True):
198 '''perform a modify with optional verbose output'''
200 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
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
))
209 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
210 '''perform a modify with optional verbose output'''
212 self
.report("""dn: %s
216 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
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
))
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
)
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
))
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
)
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
)
263 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
266 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
267 "Failed to normalise attribute %s" % attrname
,
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
:
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
)
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
,
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
)
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")
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")
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"]
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
)
340 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
341 self
.err_missing_dn_GUID(dn
, attrname
, val
, dsdb_dn
)
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
)
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
)
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
)
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
)
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
)
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
)
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
)
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"])
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
))
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
))
468 keep_transaction
= True
469 self
.samdb
.transaction_start()
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
))
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
488 self
.samdb
.transaction_cancel()
492 self
.samdb
.transaction_commit()
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()
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
))
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
))
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
))
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
))
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
))
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")
595 def check_dn(self
, obj
, attrname
, syntax_oid
):
596 '''check a DN attribute for correctness'''
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")
605 self
.err_incorrect_dn_GUID(obj
.dn
, attrname
, val
, dsdb_dn
,
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")
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
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
):
630 self
.err_incorrect_dn_GUID(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect GUID")
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
:
639 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
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
):
650 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
)
653 # check the DN matches in string form
654 if res
[0].dn
.extended_str() != dsdb_dn
.dn
.extended_str():
656 self
.err_dn_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
657 res
[0].dn
, "incorrect string version of DN")
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
)
668 # check the reverse_link is correct if there should be one
669 if reverse_link_name
is not None:
671 if reverse_link_name
in res
[0]:
672 for v
in res
[0][reverse_link_name
]:
673 if v
== obj
.dn
.extended_str():
678 self
.err_orphaned_backlink(obj
, attrname
, val
, reverse_link_name
, dsdb_dn
.dn
)
680 self
.err_missing_backlink(obj
, attrname
, val
, reverse_link_name
, dsdb_dn
.dn
)
686 def get_originating_time(self
, val
, attid
):
687 '''Read metadata properties and return the originating time for
690 :return: the originating time or 0 if not found
693 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
696 for o
in repl
.ctr
.array
:
698 return o
.originating_change_time
702 def process_metadata(self
, val
):
703 '''Read metadata properties and list attributes in it.
704 raises KeyError if the attid is unknown.'''
709 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
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"])
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
:
738 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
740 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
742 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
744 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
750 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
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
,
762 attrs
=["schemaIDGUID"])
763 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
765 self
.class_schemaIDGUID
[cls
] = 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'
776 # we don't fix deleted objects
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
786 last_inherited_type
= None
789 if sd
.sacl
is not None:
791 for i
in range(0, len(aces
)):
794 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
795 sd_clean
.sacl_add(ace
)
798 t
= self
.ace_get_effective_inherited_type(ace
)
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
812 last_inherited_type
= t
815 if sd
.dacl
is not None:
817 for i
in range(0, len(aces
)):
820 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
821 sd_clean
.dacl_add(ace
)
824 t
= self
.ace_get_effective_inherited_type(ace
)
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
838 last_inherited_type
= t
841 return (sd_clean
, sd
)
843 if last_inherited_type
is None:
849 cls
= obj
["objectClass"][-1]
854 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
855 attrs
=["isDeleted", "objectClass"],
856 controls
=["show_recycled:1"])
858 is_deleted
= 'isDeleted' in o
and o
['isDeleted'][0].upper() == 'TRUE'
860 # we don't fix deleted objects
862 cls
= o
["objectClass"][-1]
864 t
= self
.lookup_class_schemaIDGUID(cls
)
866 if t
!= last_inherited_type
:
868 return (sd_clean
, sd
)
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
))
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
))
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
))
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
,
950 # Search for a zero invocationID
951 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
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()))
965 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
966 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
969 now
= samba
.unix2nttime(int(time
.time()))
972 # Search for a zero invocationID
973 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
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
985 replBlob
= ndr_pack(repl
)
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
))
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
))
1008 # Search for an invalid attid
1010 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1012 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
1016 def err_replmetadata_unsorted_attid(self
, dn
, attr
, repl_meta_data
):
1017 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1018 str(repl_meta_data
))
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
))
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()
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
):
1045 if "description" not in obj
:
1046 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
1048 if "showInAdvancedViewOnly" not in obj
:
1049 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
1051 if "objectCategory" not in obj
:
1052 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
1054 if "isCriticalSystemObject" not in obj
:
1055 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
1057 if "isRecycled" in obj
:
1058 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
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
))
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
:
1091 if dn
== self
.infrastructure_dn
:
1093 if dn
== self
.naming_dn
:
1095 if dn
== self
.schema_dn
:
1097 if dn
== self
.rid_dn
:
1102 def calculate_instancetype(self
, dn
):
1104 nc_root
= self
.samdb
.get_nc_root(dn
)
1106 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
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
:
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
1120 def get_wellknown_sd(self
, dn
):
1121 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
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
))
1130 def check_object(self
, dn
, attrs
=['*']):
1131 '''check one object'''
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")
1145 attrs
.append("replPropertyMetaData")
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
,
1159 "sd_flags:1:%d" % sd_flags
,
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
)
1170 self
.report("ERROR: Object %s failed to load during check" % dn
)
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
)
1181 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
1182 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
1184 deleted_objects_dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects,%s" % nc_dn
)
1186 object_rdn_attr
= None
1187 object_rdn_val
= None
1192 for attrname
in obj
:
1193 if attrname
== 'dn':
1196 if str(attrname
).lower() == 'objectclass':
1197 got_objectclass
= True
1199 if str(attrname
).lower() == "name":
1200 if len(obj
[attrname
]) != 1:
1202 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1203 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
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:
1211 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1212 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
1214 object_rdn_val
= obj
[attrname
][0]
1216 if str(attrname
).lower() == 'isdeleted':
1217 if obj
[attrname
][0] != "FALSE":
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
]):
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.
1231 (list_attrs_from_md
, list_attid_from_md
) = self
.process_metadata(obj
[attrname
])
1234 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
1237 if sorted(list_attid_from_md
[:-1]) != list_attid_from_md
[:-1]:
1239 self
.err_replmetadata_unsorted_attid(dn
, attrname
, obj
[attrname
])
1241 got_repl_property_meta_data
= True
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
)
1251 if sd
.owner_sid
is None or sd
.group_sid
is None:
1252 self
.err_missing_sd_owner(dn
, sd
)
1256 if self
.reset_well_known_acls
:
1258 well_known_sd
= self
.get_wellknown_sd(dn
)
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()))
1267 self
.err_wrong_default_sd(dn
, well_known_sd
, current_sd
, diff
)
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
]))
1279 if str(attrname
).lower() == 'userparameters':
1280 if len(obj
[attrname
][0]) == 1 and obj
[attrname
][0][0] == '\x20':
1282 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
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
1289 elif obj
[attrname
][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1290 # this is the typical prefix from a windows migration
1292 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
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
1298 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
1301 elif len(obj
[attrname
][0]) % 2 != 0:
1302 # This is a value that isn't even in length
1304 self
.err_odd_userParameters(obj
, attrname
, obj
[attrname
])
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
1310 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
1313 # check for empty attributes
1314 for val
in obj
[attrname
]:
1316 self
.err_empty_attribute(dn
, attrname
)
1320 # get the syntax oid for the attribute, so we can can have
1321 # special handling for some specific attribute types
1323 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
1324 except Exception, msg
:
1325 self
.err_unknown_attribute(obj
, attrname
)
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
])
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
):
1352 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
1354 if not got_objectclass
and ("*" in attrs
or "objectclass" in map(str.lower
, attrs
)):
1356 self
.err_missing_objectclass(dn
)
1358 if ("*" in attrs
or "name" in map(str.lower
, attrs
)):
1359 if name_val
is None:
1361 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
1362 if object_rdn_attr
is None:
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:
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
:
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
:
1384 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
, object_rdn_val
, str(obj
.dn
)))
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()
1398 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
1400 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
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
:
1407 self
.report("On object %s" % dn
)
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
)
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
)
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
)
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
)
1439 ################################################################
1440 # check special @ROOTDSE attributes
1441 def check_rootdse(self
):
1442 '''check the @ROOTDSE special object'''
1443 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
1445 self
.report("Checking object %s" % dn
)
1446 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
1448 self
.report("Object %s disappeared during check" % dn
)
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')
1461 if not self
.confirm('Change dsServiceName to GUID form?'):
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]))
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")
1475 ###############################################
1476 # re-index the database
1477 def reindex_database(self
):
1478 '''re-index the whole database'''
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 ###############################################
1487 def reset_modules(self
):
1488 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
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)