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
89 res
= samdb
.search(base
="CN=DnsAdmins,CN=Users,%s" % samdb
.domain_dn(), scope
=ldb
.SCOPE_BASE
,
91 dnsadmins_sid
= ndr_unpack(security
.dom_sid
, res
[0]["objectSid"][0])
92 self
.name_map
['DnsAdmins'] = str(dnsadmins_sid
)
93 except ldb
.LdbError
, (enum
, estr
):
94 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
98 self
.system_session_info
= system_session()
99 self
.admin_session_info
= admin_session(None, samdb
.get_domain_sid())
101 res
= self
.samdb
.search(base
=self
.ntds_dsa
, scope
=ldb
.SCOPE_BASE
, attrs
=['msDS-hasMasterNCs', 'hasMasterNCs'])
102 if "msDS-hasMasterNCs" in res
[0]:
103 self
.write_ncs
= res
[0]["msDS-hasMasterNCs"]
105 # If the Forest Level is less than 2003 then there is no
106 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
107 # no need to merge as all the NCs that are in hasMasterNCs must
108 # also be in msDS-hasMasterNCs (but not the opposite)
109 if "hasMasterNCs" in res
[0]:
110 self
.write_ncs
= res
[0]["hasMasterNCs"]
112 self
.write_ncs
= None
114 res
= self
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['namingContexts'])
116 ncs
= res
[0]["namingContexts"]
117 self
.deleted_objects_containers
= []
120 dn
= self
.samdb
.get_wellknown_dn(ldb
.Dn(self
.samdb
, nc
),
121 dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
122 self
.deleted_objects_containers
.append(dn
)
130 def check_database(self
, DN
=None, scope
=ldb
.SCOPE_SUBTREE
, controls
=[], attrs
=['*']):
131 '''perform a database check, returning the number of errors found'''
132 res
= self
.samdb
.search(base
=DN
, scope
=scope
, attrs
=['dn'], controls
=controls
)
133 self
.report('Checking %u objects' % len(res
))
137 self
.dn_set
.add(str(object.dn
))
138 error_count
+= self
.check_object(object.dn
, attrs
=attrs
)
141 error_count
+= self
.check_rootdse()
143 if error_count
!= 0 and not self
.fix
:
144 self
.report("Please use --fix to fix these errors")
146 self
.report('Checked %u objects (%u errors)' % (len(res
), error_count
))
149 def report(self
, msg
):
150 '''print a message unless quiet is set'''
154 def confirm(self
, msg
, allow_all
=False, forced
=False):
155 '''confirm a change'''
162 return common
.confirm(msg
, forced
=forced
, allow_all
=allow_all
)
164 ################################################################
165 # a local confirm function with support for 'all'
166 def confirm_all(self
, msg
, all_attr
):
167 '''confirm a change with support for "all" '''
172 if getattr(self
, all_attr
) == 'NONE':
174 if getattr(self
, all_attr
) == 'ALL':
178 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
180 setattr(self
, all_attr
, 'ALL')
183 setattr(self
, all_attr
, 'NONE')
187 def do_delete(self
, dn
, controls
, msg
):
188 '''delete dn with optional verbose output'''
190 self
.report("delete DN %s" % dn
)
192 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
193 self
.samdb
.delete(dn
, controls
=controls
)
194 except Exception, err
:
195 self
.report("%s : %s" % (msg
, err
))
199 def do_modify(self
, m
, controls
, msg
, validate
=True):
200 '''perform a modify with optional verbose output'''
202 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
204 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
205 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
206 except Exception, err
:
207 self
.report("%s : %s" % (msg
, err
))
211 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
212 '''perform a modify with optional verbose output'''
214 self
.report("""dn: %s
218 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
220 to_dn
= to_rdn
+ to_base
221 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
222 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
223 except Exception, err
:
224 self
.report("%s : %s" % (msg
, err
))
228 def err_empty_attribute(self
, dn
, attrname
):
229 '''fix empty attributes'''
230 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
231 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
232 self
.report("Not fixing empty attribute %s" % attrname
)
237 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
238 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
239 "Failed to remove empty attribute %s" % attrname
, validate
=False):
240 self
.report("Removed empty attribute %s" % attrname
)
242 def err_normalise_mismatch(self
, dn
, attrname
, values
):
243 '''fix attribute normalisation errors'''
244 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
247 normalised
= self
.samdb
.dsdb_normalise_attributes(
248 self
.samdb_schema
, attrname
, [val
])
249 if len(normalised
) != 1:
250 self
.report("Unable to normalise value '%s'" % val
)
251 mod_list
.append((val
, ''))
252 elif (normalised
[0] != val
):
253 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
254 mod_list
.append((val
, normalised
[0]))
255 if not self
.confirm_all('Fix normalisation for %s from %s?' % (attrname
, dn
), 'fix_all_normalisation'):
256 self
.report("Not fixing attribute %s" % attrname
)
261 for i
in range(0, len(mod_list
)):
262 (val
, nval
) = mod_list
[i
]
263 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
265 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
268 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
269 "Failed to normalise attribute %s" % attrname
,
271 self
.report("Normalised attribute %s" % attrname
)
273 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
274 '''fix attribute normalisation errors'''
275 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
276 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
277 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
278 if list(normalised
) == values
:
280 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
281 self
.report("Not fixing attribute '%s'" % attrname
)
286 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
288 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
289 "Failed to normalise attribute %s" % attrname
,
291 self
.report("Normalised attribute %s" % attrname
)
293 def is_deleted_objects_dn(self
, dsdb_dn
):
294 '''see if a dsdb_Dn is the special Deleted Objects DN'''
295 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
297 def err_missing_objectclass(self
, dn
):
298 """handle object without objectclass"""
299 self
.report("ERROR: missing objectclass in object %s. If you have another working DC, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (dn
, self
.samdb
.get_nc_root(dn
)))
300 if not self
.confirm_all("If you cannot re-sync from another DC, do you wish to delete object '%s'?" % dn
, 'fix_all_missing_objectclass'):
301 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
303 if self
.do_delete(dn
, ["relax:0"],
304 "Failed to remove DN %s" % dn
):
305 self
.report("Removed DN %s" % dn
)
307 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
308 """handle a DN pointing to a deleted object"""
309 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
310 self
.report("Target GUID points at deleted DN %s" % correct_dn
)
311 if not self
.confirm_all('Remove DN link?', 'remove_all_deleted_DN_links'):
312 self
.report("Not removing")
316 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
317 if self
.do_modify(m
, ["show_recycled:1", "local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
],
318 "Failed to remove deleted DN attribute %s" % attrname
):
319 self
.report("Removed deleted DN on attribute %s" % attrname
)
321 def err_missing_dn_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
322 """handle a missing target DN (both GUID and DN string form are missing)"""
323 # check if its a backlink
324 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
325 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
326 self
.report("Not removing dangling forward link")
328 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
)
330 def err_incorrect_dn_GUID(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
331 """handle a missing GUID extended DN component"""
332 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
333 controls
=["extended_dn:1:1", "show_recycled:1"]
335 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
336 attrs
=[], controls
=controls
)
337 except ldb
.LdbError
, (enum
, estr
):
338 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
339 self
.err_missing_dn_GUID(dn
, attrname
, val
, dsdb_dn
)
342 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
343 self
.err_missing_dn_GUID(dn
, attrname
, val
, dsdb_dn
)
345 dsdb_dn
.dn
= res
[0].dn
347 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
348 self
.report("Not fixing %s" % errstr
)
352 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
353 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
355 if self
.do_modify(m
, ["show_recycled:1"],
356 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
357 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
359 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
360 """handle an incorrect binary DN component"""
361 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
362 controls
=["extended_dn:1:1", "show_recycled:1"]
364 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
365 self
.report("Not fixing %s" % errstr
)
369 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
370 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
372 if self
.do_modify(m
, ["show_recycled:1"],
373 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
374 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
376 def err_dn_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, errstr
):
377 """handle a DN string being incorrect"""
378 self
.report("ERROR: incorrect DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
379 dsdb_dn
.dn
= correct_dn
381 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_target_mismatch'):
382 self
.report("Not fixing %s" % errstr
)
386 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
387 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
388 if self
.do_modify(m
, ["show_recycled:1"],
389 "Failed to fix incorrect DN string on attribute %s" % attrname
):
390 self
.report("Fixed incorrect DN string on attribute %s" % (attrname
))
392 def err_unknown_attribute(self
, obj
, attrname
):
393 '''handle an unknown attribute error'''
394 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
395 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
396 self
.report("Not removing %s" % attrname
)
400 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
401 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
402 "Failed to remove unknown attribute %s" % attrname
):
403 self
.report("Removed unknown attribute %s" % (attrname
))
405 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
406 '''handle a missing backlink value'''
407 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
408 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
409 self
.report("Not fixing missing backlink %s" % backlink_name
)
413 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
414 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, attrname
)
415 if self
.do_modify(m
, ["show_recycled:1"],
416 "Failed to fix missing backlink %s" % backlink_name
):
417 self
.report("Fixed missing backlink %s" % (backlink_name
))
419 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
420 '''handle a incorrect RMD_FLAGS value'''
421 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
422 self
.report("ERROR: incorrect RMD_FLAGS value %u for attribute '%s' in %s for link %s" % (rmd_flags
, attrname
, obj
.dn
, revealed_dn
.dn
.extended_str()))
423 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
424 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
428 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
429 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
430 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
431 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
433 def err_orphaned_backlink(self
, obj
, attrname
, val
, link_name
, target_dn
):
434 '''handle a orphaned backlink value'''
435 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (attrname
, obj
.dn
, link_name
, target_dn
))
436 if not self
.confirm_all('Remove orphaned backlink %s' % link_name
, 'fix_all_orphaned_backlinks'):
437 self
.report("Not removing orphaned backlink %s" % link_name
)
441 m
['value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
442 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
443 "Failed to fix orphaned backlink %s" % link_name
):
444 self
.report("Fixed orphaned backlink %s" % (link_name
))
446 def err_no_fsmoRoleOwner(self
, obj
):
447 '''handle a missing fSMORoleOwner'''
448 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
449 res
= self
.samdb
.search("",
450 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
452 serviceName
= res
[0]["dsServiceName"][0]
453 if not self
.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
454 self
.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
458 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
459 if self
.do_modify(m
, [],
460 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
461 self
.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
463 def err_missing_parent(self
, obj
):
464 '''handle a missing parent'''
465 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
466 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
467 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
470 keep_transaction
= True
471 self
.samdb
.transaction_start()
473 nc_root
= self
.samdb
.get_nc_root(obj
.dn
);
474 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
475 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
476 new_dn
.remove_base_components(len(new_dn
) - 1)
477 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
478 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
479 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
483 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
485 if self
.do_modify(m
, [],
486 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
487 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
488 keep_transaction
= True
490 self
.samdb
.transaction_cancel()
494 self
.samdb
.transaction_commit()
496 self
.samdb
.transaction_cancel()
498 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
):
499 '''handle a wrong dn'''
501 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
502 new_rdn
.remove_base_components(len(new_rdn
) - 1)
503 new_parent
= new_dn
.parent()
506 if rdn_val
!= name_val
:
507 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
508 attributes
+= "name=%r" % (name_val
)
510 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
511 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
512 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
515 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, ["show_recycled:1", "relax:0"],
516 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
517 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
519 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
520 '''handle a wrong instanceType'''
521 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
522 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
523 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
528 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
529 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
530 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
531 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
533 def err_short_userParameters(self
, obj
, attrname
, value
):
534 # This is a truncated userParameters due to a pre 4.1 replication bug
535 self
.report("ERROR: incorrect userParameters value on object %s. If you have another working DC that does not give this warning, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (obj
.dn
, self
.samdb
.get_nc_root(obj
.dn
)))
537 def err_base64_userParameters(self
, obj
, attrname
, value
):
538 '''handle a wrong userParameters'''
539 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
540 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
541 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
546 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
547 if self
.do_modify(m
, [],
548 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
549 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
551 def err_utf8_userParameters(self
, obj
, attrname
, value
):
552 '''handle a wrong userParameters'''
553 self
.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj
.dn
))
554 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
555 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
560 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
561 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
562 if self
.do_modify(m
, [],
563 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
564 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
566 def err_doubled_userParameters(self
, obj
, attrname
, value
):
567 '''handle a wrong userParameters'''
568 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
569 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
570 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
575 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
576 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
577 if self
.do_modify(m
, [],
578 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
579 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
581 def err_odd_userParameters(self
, obj
, attrname
):
582 # This is a truncated userParameters due to a pre 4.1 replication bug
583 self
.report("ERROR: incorrect userParameters value on object %s (odd length). If you have another working DC that does not give this warning, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (obj
.dn
, self
.samdb
.get_nc_root(obj
.dn
)))
585 def find_revealed_link(self
, dn
, attrname
, guid
):
586 '''return a revealed link in an object'''
587 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
588 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
589 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
590 for val
in res
[0][attrname
]:
591 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, syntax_oid
)
592 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
597 def check_dn(self
, obj
, attrname
, syntax_oid
):
598 '''check a DN attribute for correctness'''
600 for val
in obj
[attrname
]:
601 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, syntax_oid
)
603 # all DNs should have a GUID component
604 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
607 self
.err_incorrect_dn_GUID(obj
.dn
, attrname
, val
, dsdb_dn
,
611 guidstr
= str(misc
.GUID(guid
))
613 attrs
= ['isDeleted']
615 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
616 fixing_msDS_HasInstantiatedNCs
= True
617 attrs
.append("instanceType")
619 fixing_msDS_HasInstantiatedNCs
= False
621 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
622 reverse_link_name
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
623 if reverse_link_name
is not None:
624 attrs
.append(reverse_link_name
)
626 # check its the right GUID
628 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
629 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1"])
630 except ldb
.LdbError
, (enum
, estr
):
632 self
.err_incorrect_dn_GUID(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect GUID")
635 if fixing_msDS_HasInstantiatedNCs
:
636 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
637 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
639 if str(dsdb_dn
) != val
:
641 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
644 # now we have two cases - the source object might or might not be deleted
645 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
646 target_is_deleted
= 'isDeleted' in res
[0] and res
[0]['isDeleted'][0].upper() == 'TRUE'
648 # the target DN is not allowed to be deleted, unless the target DN is the
649 # special Deleted Objects container
650 if target_is_deleted
and not is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
):
652 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
)
655 # check the DN matches in string form
656 if res
[0].dn
.extended_str() != dsdb_dn
.dn
.extended_str():
658 self
.err_dn_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
659 res
[0].dn
, "incorrect string version of DN")
662 if is_deleted
and not target_is_deleted
and reverse_link_name
is not None:
663 revealed_dn
= self
.find_revealed_link(obj
.dn
, attrname
, guid
)
664 rmd_flags
= revealed_dn
.dn
.get_extended_component("RMD_FLAGS")
665 if rmd_flags
is not None and (int(rmd_flags
) & 1) == 0:
666 # the RMD_FLAGS for this link should be 1, as the target is deleted
667 self
.err_incorrect_rmd_flags(obj
, attrname
, revealed_dn
)
670 # check the reverse_link is correct if there should be one
671 if reverse_link_name
is not None:
673 if reverse_link_name
in res
[0]:
674 for v
in res
[0][reverse_link_name
]:
675 if v
== obj
.dn
.extended_str():
680 self
.err_orphaned_backlink(obj
, attrname
, val
, reverse_link_name
, dsdb_dn
.dn
)
682 self
.err_missing_backlink(obj
, attrname
, val
, reverse_link_name
, dsdb_dn
.dn
)
688 def get_originating_time(self
, val
, attid
):
689 '''Read metadata properties and return the originating time for
692 :return: the originating time or 0 if not found
695 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
698 for o
in repl
.ctr
.array
:
700 return o
.originating_change_time
704 def process_metadata(self
, val
):
705 '''Read metadata properties and list attributes in it.
706 raises KeyError if the attid is unknown.'''
711 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
714 for o
in repl
.ctr
.array
:
715 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
716 set_att
.add(att
.lower())
717 list_attid
.append(o
.attid
)
719 return (set_att
, list_attid
)
722 def fix_metadata(self
, dn
, attr
):
723 '''re-write replPropertyMetaData elements for a single attribute for a
724 object. This is used to fix missing replPropertyMetaData elements'''
725 res
= self
.samdb
.search(base
= dn
, scope
=ldb
.SCOPE_BASE
, attrs
= [attr
],
726 controls
= ["search_options:1:2", "show_recycled:1"])
730 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
731 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
732 "Failed to fix metadata for attribute %s" % attr
):
733 self
.report("Fixed metadata for attribute %s" % attr
)
735 def ace_get_effective_inherited_type(self
, ace
):
736 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
740 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
742 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
744 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
746 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
752 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
755 return str(ace
.object.inherited_type
)
757 def lookup_class_schemaIDGUID(self
, cls
):
758 if cls
in self
.class_schemaIDGUID
:
759 return self
.class_schemaIDGUID
[cls
]
761 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
762 res
= self
.samdb
.search(base
=self
.schema_dn
,
764 attrs
=["schemaIDGUID"])
765 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
767 self
.class_schemaIDGUID
[cls
] = t
770 def process_sd(self
, dn
, obj
):
771 sd_attr
= "nTSecurityDescriptor"
772 sd_val
= obj
[sd_attr
]
774 sd
= ndr_unpack(security
.descriptor
, str(sd_val
))
776 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
778 # we don't fix deleted objects
781 sd_clean
= security
.descriptor()
782 sd_clean
.owner_sid
= sd
.owner_sid
783 sd_clean
.group_sid
= sd
.group_sid
784 sd_clean
.type = sd
.type
785 sd_clean
.revision
= sd
.revision
788 last_inherited_type
= None
791 if sd
.sacl
is not None:
793 for i
in range(0, len(aces
)):
796 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
797 sd_clean
.sacl_add(ace
)
800 t
= self
.ace_get_effective_inherited_type(ace
)
804 if last_inherited_type
is not None:
805 if t
!= last_inherited_type
:
806 # if it inherited from more than
807 # one type it's very likely to be broken
809 # If not the recalculation will calculate
814 last_inherited_type
= t
817 if sd
.dacl
is not None:
819 for i
in range(0, len(aces
)):
822 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
823 sd_clean
.dacl_add(ace
)
826 t
= self
.ace_get_effective_inherited_type(ace
)
830 if last_inherited_type
is not None:
831 if t
!= last_inherited_type
:
832 # if it inherited from more than
833 # one type it's very likely to be broken
835 # If not the recalculation will calculate
840 last_inherited_type
= t
843 return (sd_clean
, sd
)
845 if last_inherited_type
is None:
851 cls
= obj
["objectClass"][-1]
856 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
857 attrs
=["isDeleted", "objectClass"],
858 controls
=["show_recycled:1"])
860 is_deleted
= 'isDeleted' in o
and o
['isDeleted'][0].upper() == 'TRUE'
862 # we don't fix deleted objects
864 cls
= o
["objectClass"][-1]
866 t
= self
.lookup_class_schemaIDGUID(cls
)
868 if t
!= last_inherited_type
:
870 return (sd_clean
, sd
)
875 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
876 '''re-write the SD due to incorrect inherited ACEs'''
877 sd_attr
= "nTSecurityDescriptor"
878 sd_val
= ndr_pack(sd
)
879 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
881 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
882 self
.report('Not fixing %s on %s\n' % (sd_attr
, dn
))
887 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
888 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
889 "Failed to fix attribute %s" % sd_attr
):
890 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
892 def err_wrong_default_sd(self
, dn
, sd
, sd_old
, diff
):
893 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
894 sd_attr
= "nTSecurityDescriptor"
895 sd_val
= ndr_pack(sd
)
896 sd_old_val
= ndr_pack(sd_old
)
897 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
898 if sd
.owner_sid
is not None:
899 sd_flags |
= security
.SECINFO_OWNER
900 if sd
.group_sid
is not None:
901 sd_flags |
= security
.SECINFO_GROUP
903 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
904 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
909 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
910 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
911 "Failed to reset attribute %s" % sd_attr
):
912 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
914 def err_missing_sd_owner(self
, dn
, sd
):
915 '''re-write the SD due to a missing owner or group'''
916 sd_attr
= "nTSecurityDescriptor"
917 sd_val
= ndr_pack(sd
)
918 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
920 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
921 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
926 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
928 # By setting the session_info to admin_session_info and
929 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
930 # flags we cause the descriptor module to set the correct
931 # owner and group on the SD, replacing the None/NULL values
932 # for owner_sid and group_sid currently present.
934 # The admin_session_info matches that used in provision, and
935 # is the best guess we can make for an existing object that
936 # hasn't had something specifically set.
938 # This is important for the dns related naming contexts.
939 self
.samdb
.set_session_info(self
.admin_session_info
)
940 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
941 "Failed to fix metadata for attribute %s" % sd_attr
):
942 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
943 self
.samdb
.set_session_info(self
.system_session_info
)
946 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
947 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
952 # Search for a zero invocationID
953 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
957 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
958 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
959 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
960 % (dn
, o
.attid
, o
.version
,
961 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
962 self
.samdb
.get_invocation_id()))
967 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
968 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
971 now
= samba
.unix2nttime(int(time
.time()))
974 # Search for a zero invocationID
975 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
979 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
980 o
.version
= o
.version
+ 1
981 o
.originating_change_time
= now
982 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
983 o
.originating_usn
= seq
987 replBlob
= ndr_pack(repl
)
991 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
992 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
993 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
998 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
999 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1000 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1001 "Failed to fix attribute %s" % attr
):
1002 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1005 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
1006 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1007 str(repl_meta_data
))
1010 # Search for an invalid attid
1012 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1014 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
1018 def err_replmetadata_unsorted_attid(self
, dn
, attr
, repl_meta_data
):
1019 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1020 str(repl_meta_data
))
1024 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
1025 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
1026 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
1027 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
1030 # Sort the array, except for the last element
1031 ctr
.array
[:-1] = sorted(ctr
.array
[:-1], key
=lambda o
: o
.attid
)
1033 replBlob
= ndr_pack(repl
)
1035 nmsg
= ldb
.Message()
1037 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1038 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1039 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1040 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1041 "Failed to fix attribute %s" % attr
):
1042 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1045 def is_deleted_deleted_objects(self
, obj
):
1047 if "description" not in obj
:
1048 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
1050 if "showInAdvancedViewOnly" not in obj
:
1051 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
1053 if "objectCategory" not in obj
:
1054 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
1056 if "isCriticalSystemObject" not in obj
:
1057 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
1059 if "isRecycled" in obj
:
1060 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
1065 def err_deleted_deleted_objects(self
, obj
):
1066 nmsg
= ldb
.Message()
1067 nmsg
.dn
= dn
= obj
.dn
1069 if "description" not in obj
:
1070 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
1071 if "showInAdvancedViewOnly" not in obj
:
1072 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
1073 if "objectCategory" not in obj
:
1074 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
1075 if "isCriticalSystemObject" not in obj
:
1076 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
1077 if "isRecycled" in obj
:
1078 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
1080 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1081 % (dn
), 'fix_deleted_deleted_objects'):
1082 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
1085 if self
.do_modify(nmsg
, ["relax:0"],
1086 "Failed to fix Deleted Objects container %s" % dn
):
1087 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
1090 def is_fsmo_role(self
, dn
):
1091 if dn
== self
.samdb
.domain_dn
:
1093 if dn
== self
.infrastructure_dn
:
1095 if dn
== self
.naming_dn
:
1097 if dn
== self
.schema_dn
:
1099 if dn
== self
.rid_dn
:
1104 def calculate_instancetype(self
, dn
):
1106 nc_root
= self
.samdb
.get_nc_root(dn
)
1108 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
1110 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
1111 except ldb
.LdbError
, (enum
, estr
):
1112 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1115 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
1117 if self
.write_ncs
is not None and str(nc_root
) in self
.write_ncs
:
1118 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
1122 def get_wellknown_sd(self
, dn
):
1123 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
1125 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
1126 return ndr_unpack(security
.descriptor
,
1127 descriptor_fn(domain_sid
,
1128 name_map
=self
.name_map
))
1132 def check_object(self
, dn
, attrs
=['*']):
1133 '''check one object'''
1135 self
.report("Checking object %s" % dn
)
1136 if "dn" in map(str.lower
, attrs
):
1137 attrs
.append("name")
1138 if "distinguishedname" in map(str.lower
, attrs
):
1139 attrs
.append("name")
1140 if str(dn
.get_rdn_name()).lower() in map(str.lower
, attrs
):
1141 attrs
.append("name")
1142 if 'name' in map(str.lower
, attrs
):
1143 attrs
.append(dn
.get_rdn_name())
1144 attrs
.append("isDeleted")
1145 attrs
.append("systemFlags")
1147 attrs
.append("replPropertyMetaData")
1151 sd_flags |
= security
.SECINFO_OWNER
1152 sd_flags |
= security
.SECINFO_GROUP
1153 sd_flags |
= security
.SECINFO_DACL
1154 sd_flags |
= security
.SECINFO_SACL
1156 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1161 "sd_flags:1:%d" % sd_flags
,
1164 except ldb
.LdbError
, (enum
, estr
):
1165 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
1166 if self
.in_transaction
:
1167 self
.report("ERROR: Object %s disappeared during check" % dn
)
1172 self
.report("ERROR: Object %s failed to load during check" % dn
)
1176 set_attrs_from_md
= set()
1177 set_attrs_seen
= set()
1178 got_repl_property_meta_data
= False
1179 got_objectclass
= False
1181 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
1183 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
1184 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
1186 deleted_objects_dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects,%s" % nc_dn
)
1188 object_rdn_attr
= None
1189 object_rdn_val
= None
1194 for attrname
in obj
:
1195 if attrname
== 'dn':
1198 if str(attrname
).lower() == 'objectclass':
1199 got_objectclass
= True
1201 if str(attrname
).lower() == "name":
1202 if len(obj
[attrname
]) != 1:
1204 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1205 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
1207 name_val
= obj
[attrname
][0]
1209 if str(attrname
).lower() == str(obj
.dn
.get_rdn_name()).lower():
1210 object_rdn_attr
= attrname
1211 if len(obj
[attrname
]) != 1:
1213 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1214 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
1216 object_rdn_val
= obj
[attrname
][0]
1218 if str(attrname
).lower() == 'isdeleted':
1219 if obj
[attrname
][0] != "FALSE":
1222 if str(attrname
).lower() == 'systemflags':
1223 systemFlags
= int(obj
[attrname
][0])
1225 if str(attrname
).lower() == 'replpropertymetadata':
1226 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
]):
1228 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
])
1229 # We don't continue, as we may also have other fixes for this attribute
1230 # based on what other attributes we see.
1233 (set_attrs_from_md
, list_attid_from_md
) = self
.process_metadata(obj
[attrname
])
1236 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
1239 if sorted(list_attid_from_md
[:-1]) != list_attid_from_md
[:-1]:
1241 self
.err_replmetadata_unsorted_attid(dn
, attrname
, obj
[attrname
])
1243 # Here we check that the first attid is 0
1244 # (objectClass) and that the last on is the RDN
1246 rdn_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(dn
.get_rdn_name())
1247 if list_attid_from_md
[-1] != rdn_attid
:
1249 self
.report("ERROR: Not fixing incorrect final attributeID in '%s' on '%s', it should match the RDN %s" %
1250 (attrname
, str(dn
), dn
.get_rdn_name()))
1252 if list_attid_from_md
[0] != 0:
1254 self
.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
1255 (attrname
, str(dn
)))
1257 got_repl_property_meta_data
= True
1260 if str(attrname
).lower() == 'ntsecuritydescriptor':
1261 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
1262 if sd_broken
is not None:
1263 self
.err_wrong_sd(dn
, sd
, sd_broken
)
1267 if sd
.owner_sid
is None or sd
.group_sid
is None:
1268 self
.err_missing_sd_owner(dn
, sd
)
1272 if self
.reset_well_known_acls
:
1274 well_known_sd
= self
.get_wellknown_sd(dn
)
1278 current_sd
= ndr_unpack(security
.descriptor
,
1279 str(obj
[attrname
][0]))
1281 diff
= get_diff_sds(well_known_sd
, current_sd
, security
.dom_sid(self
.samdb
.get_domain_sid()))
1283 self
.err_wrong_default_sd(dn
, well_known_sd
, current_sd
, diff
)
1288 if str(attrname
).lower() == 'objectclass':
1289 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
1290 if normalised
!= obj
[attrname
]:
1291 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
1295 if str(attrname
).lower() == 'userparameters':
1296 if len(obj
[attrname
][0]) == 1 and obj
[attrname
][0][0] == '\x20':
1298 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
1301 elif obj
[attrname
][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1302 # This is the correct, normal prefix
1305 elif obj
[attrname
][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1306 # this is the typical prefix from a windows migration
1308 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
1311 elif obj
[attrname
][0][1] != '\x00' and obj
[attrname
][0][3] != '\x00' and obj
[attrname
][0][5] != '\x00' and obj
[attrname
][0][7] != '\x00' and obj
[attrname
][0][9] != '\x00':
1312 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1314 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
1317 elif len(obj
[attrname
][0]) % 2 != 0:
1318 # This is a value that isn't even in length
1320 self
.err_odd_userParameters(obj
, attrname
, obj
[attrname
])
1323 elif obj
[attrname
][0][1] == '\x00' and obj
[attrname
][0][2] == '\x00' and obj
[attrname
][0][3] == '\x00' and obj
[attrname
][0][4] != '\x00' and obj
[attrname
][0][5] == '\x00':
1324 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
1326 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
1329 # check for empty attributes
1330 for val
in obj
[attrname
]:
1332 self
.err_empty_attribute(dn
, attrname
)
1336 # get the syntax oid for the attribute, so we can can have
1337 # special handling for some specific attribute types
1339 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
1340 except Exception, msg
:
1341 self
.err_unknown_attribute(obj
, attrname
)
1345 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
1346 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
1347 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
1348 and not self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)):
1349 set_attrs_seen
.add(str(attrname
).lower())
1351 if syntax_oid
in [ dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
1352 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
1353 # it's some form of DN, do specialised checking on those
1354 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
1356 # check for incorrectly normalised attributes
1357 for val
in obj
[attrname
]:
1358 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
1359 if len(normalised
) != 1 or normalised
[0] != val
:
1360 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
1364 if str(attrname
).lower() == "instancetype":
1365 calculated_instancetype
= self
.calculate_instancetype(dn
)
1366 if len(obj
["instanceType"]) != 1 or obj
["instanceType"][0] != str(calculated_instancetype
):
1368 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
1370 if not got_objectclass
and ("*" in attrs
or "objectclass" in map(str.lower
, attrs
)):
1372 self
.err_missing_objectclass(dn
)
1374 if ("*" in attrs
or "name" in map(str.lower
, attrs
)):
1375 if name_val
is None:
1377 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
1378 if object_rdn_attr
is None:
1380 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
1382 if name_val
is not None:
1385 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
1386 parent_dn
= deleted_objects_dn
1387 if parent_dn
is None:
1388 parent_dn
= obj
.dn
.parent()
1389 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
1390 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
1392 if obj
.dn
== deleted_objects_dn
:
1393 expected_dn
= obj
.dn
1395 if expected_dn
!= obj
.dn
:
1397 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
, object_rdn_val
, name_val
)
1398 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
1400 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
, object_rdn_val
, str(obj
.dn
)))
1403 if got_repl_property_meta_data
:
1404 if obj
.dn
== deleted_objects_dn
:
1405 isDeletedAttId
= 131120
1406 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
1408 expectedTimeDo
= 2650466015990000000
1409 originating
= self
.get_originating_time(obj
["replPropertyMetaData"], isDeletedAttId
)
1410 if originating
!= expectedTimeDo
:
1411 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
1412 nmsg
= ldb
.Message()
1414 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
1416 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
1419 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
1421 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
1423 self
.report("On object %s" % dn
)
1426 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
1427 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
1428 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
1430 self
.fix_metadata(dn
, att
)
1432 if self
.is_fsmo_role(dn
):
1433 if "fSMORoleOwner" not in obj
and ("*" in attrs
or "fsmoroleowner" in map(str.lower
, attrs
)):
1434 self
.err_no_fsmoRoleOwner(obj
)
1438 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
1439 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
1440 controls
=["show_recycled:1", "show_deleted:1"])
1441 except ldb
.LdbError
, (enum
, estr
):
1442 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
1443 self
.err_missing_parent(obj
)
1448 if dn
in self
.deleted_objects_containers
and '*' in attrs
:
1449 if self
.is_deleted_deleted_objects(obj
):
1450 self
.err_deleted_deleted_objects(obj
)
1455 ################################################################
1456 # check special @ROOTDSE attributes
1457 def check_rootdse(self
):
1458 '''check the @ROOTDSE special object'''
1459 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
1461 self
.report("Checking object %s" % dn
)
1462 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
1464 self
.report("Object %s disappeared during check" % dn
)
1469 # check that the dsServiceName is in GUID form
1470 if not 'dsServiceName' in obj
:
1471 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
1472 return error_count
+1
1474 if not obj
['dsServiceName'][0].startswith('<GUID='):
1475 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
1477 if not self
.confirm('Change dsServiceName to GUID form?'):
1479 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0]),
1480 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
1481 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
1484 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
1485 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
1486 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
1487 self
.report("Changed dsServiceName to GUID form")
1491 ###############################################
1492 # re-index the database
1493 def reindex_database(self
):
1494 '''re-index the whole database'''
1496 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
1497 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
1498 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
1499 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
1501 ###############################################
1503 def reset_modules(self
):
1504 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
1506 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
1507 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
1508 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)