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
34 from samba
.netcmd
import CommandError
37 class dbcheck(object):
38 """check a SAM database for errors"""
40 def __init__(self
, samdb
, samdb_schema
=None, verbose
=False, fix
=False,
41 yes
=False, quiet
=False, in_transaction
=False,
42 reset_well_known_acls
=False):
44 self
.dict_oid_name
= None
45 self
.samdb_schema
= (samdb_schema
or samdb
)
46 self
.verbose
= verbose
50 self
.remove_all_unknown_attributes
= False
51 self
.remove_all_empty_attributes
= False
52 self
.fix_all_normalisation
= False
53 self
.fix_all_duplicates
= False
54 self
.fix_all_DN_GUIDs
= False
55 self
.fix_all_binary_dn
= False
56 self
.remove_all_deleted_DN_links
= False
57 self
.fix_all_target_mismatch
= False
58 self
.fix_all_metadata
= False
59 self
.fix_time_metadata
= False
60 self
.fix_all_missing_backlinks
= False
61 self
.fix_all_orphaned_backlinks
= False
62 self
.fix_rmd_flags
= False
63 self
.fix_ntsecuritydescriptor
= False
64 self
.fix_ntsecuritydescriptor_owner_group
= False
65 self
.seize_fsmo_role
= False
66 self
.move_to_lost_and_found
= False
67 self
.fix_instancetype
= False
68 self
.fix_replmetadata_zero_invocationid
= False
69 self
.fix_replmetadata_duplicate_attid
= False
70 self
.fix_replmetadata_wrong_attid
= False
71 self
.fix_replmetadata_unsorted_attid
= False
72 self
.fix_deleted_deleted_objects
= False
74 self
.fix_base64_userparameters
= False
75 self
.fix_utf8_userparameters
= False
76 self
.fix_doubled_userparameters
= False
77 self
.reset_well_known_acls
= reset_well_known_acls
78 self
.reset_all_well_known_acls
= False
79 self
.in_transaction
= in_transaction
80 self
.infrastructure_dn
= ldb
.Dn(samdb
, "CN=Infrastructure," + samdb
.domain_dn())
81 self
.naming_dn
= ldb
.Dn(samdb
, "CN=Partitions,%s" % samdb
.get_config_basedn())
82 self
.schema_dn
= samdb
.get_schema_basedn()
83 self
.rid_dn
= ldb
.Dn(samdb
, "CN=RID Manager$,CN=System," + samdb
.domain_dn())
84 self
.ntds_dsa
= ldb
.Dn(samdb
, samdb
.get_dsServiceName())
85 self
.class_schemaIDGUID
= {}
86 self
.wellknown_sds
= get_wellknown_sds(self
.samdb
)
87 self
.fix_all_missing_objectclass
= False
93 res
= samdb
.search(base
="CN=DnsAdmins,CN=Users,%s" % samdb
.domain_dn(), scope
=ldb
.SCOPE_BASE
,
95 dnsadmins_sid
= ndr_unpack(security
.dom_sid
, res
[0]["objectSid"][0])
96 self
.name_map
['DnsAdmins'] = str(dnsadmins_sid
)
97 except ldb
.LdbError
, (enum
, estr
):
98 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
102 self
.system_session_info
= system_session()
103 self
.admin_session_info
= admin_session(None, samdb
.get_domain_sid())
105 res
= self
.samdb
.search(base
=self
.ntds_dsa
, scope
=ldb
.SCOPE_BASE
, attrs
=['msDS-hasMasterNCs', 'hasMasterNCs'])
106 if "msDS-hasMasterNCs" in res
[0]:
107 self
.write_ncs
= res
[0]["msDS-hasMasterNCs"]
109 # If the Forest Level is less than 2003 then there is no
110 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
111 # no need to merge as all the NCs that are in hasMasterNCs must
112 # also be in msDS-hasMasterNCs (but not the opposite)
113 if "hasMasterNCs" in res
[0]:
114 self
.write_ncs
= res
[0]["hasMasterNCs"]
116 self
.write_ncs
= None
118 res
= self
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['namingContexts'])
120 ncs
= res
[0]["namingContexts"]
121 self
.deleted_objects_containers
= []
124 dn
= self
.samdb
.get_wellknown_dn(ldb
.Dn(self
.samdb
, nc
),
125 dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
126 self
.deleted_objects_containers
.append(dn
)
134 def check_database(self
, DN
=None, scope
=ldb
.SCOPE_SUBTREE
, controls
=[], attrs
=['*']):
135 '''perform a database check, returning the number of errors found'''
136 res
= self
.samdb
.search(base
=DN
, scope
=scope
, attrs
=['dn'], controls
=controls
)
137 self
.report('Checking %u objects' % len(res
))
141 self
.dn_set
.add(str(object.dn
))
142 error_count
+= self
.check_object(object.dn
, attrs
=attrs
)
145 error_count
+= self
.check_rootdse()
147 if error_count
!= 0 and not self
.fix
:
148 self
.report("Please use --fix to fix these errors")
150 self
.report('Checked %u objects (%u errors)' % (len(res
), error_count
))
153 def report(self
, msg
):
154 '''print a message unless quiet is set'''
158 def confirm(self
, msg
, allow_all
=False, forced
=False):
159 '''confirm a change'''
166 return common
.confirm(msg
, forced
=forced
, allow_all
=allow_all
)
168 ################################################################
169 # a local confirm function with support for 'all'
170 def confirm_all(self
, msg
, all_attr
):
171 '''confirm a change with support for "all" '''
176 if getattr(self
, all_attr
) == 'NONE':
178 if getattr(self
, all_attr
) == 'ALL':
182 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
184 setattr(self
, all_attr
, 'ALL')
187 setattr(self
, all_attr
, 'NONE')
191 def do_delete(self
, dn
, controls
, msg
):
192 '''delete dn with optional verbose output'''
194 self
.report("delete DN %s" % dn
)
196 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
197 self
.samdb
.delete(dn
, controls
=controls
)
198 except Exception, err
:
199 if self
.in_transaction
:
200 raise CommandError("%s : %s" % (msg
, err
))
201 self
.report("%s : %s" % (msg
, err
))
205 def do_modify(self
, m
, controls
, msg
, validate
=True):
206 '''perform a modify with optional verbose output'''
208 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
210 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
211 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
212 except Exception, err
:
213 if self
.in_transaction
:
214 raise CommandError("%s : %s" % (msg
, err
))
215 self
.report("%s : %s" % (msg
, err
))
219 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
220 '''perform a modify with optional verbose output'''
222 self
.report("""dn: %s
226 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
228 to_dn
= to_rdn
+ to_base
229 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
230 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
231 except Exception, err
:
232 if self
.in_transaction
:
233 raise CommandError("%s : %s" % (msg
, err
))
234 self
.report("%s : %s" % (msg
, err
))
238 def err_empty_attribute(self
, dn
, attrname
):
239 '''fix empty attributes'''
240 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
241 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
242 self
.report("Not fixing empty attribute %s" % attrname
)
247 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
248 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
249 "Failed to remove empty attribute %s" % attrname
, validate
=False):
250 self
.report("Removed empty attribute %s" % attrname
)
252 def err_normalise_mismatch(self
, dn
, attrname
, values
):
253 '''fix attribute normalisation errors'''
254 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
257 normalised
= self
.samdb
.dsdb_normalise_attributes(
258 self
.samdb_schema
, attrname
, [val
])
259 if len(normalised
) != 1:
260 self
.report("Unable to normalise value '%s'" % val
)
261 mod_list
.append((val
, ''))
262 elif (normalised
[0] != val
):
263 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
264 mod_list
.append((val
, normalised
[0]))
265 if not self
.confirm_all('Fix normalisation for %s from %s?' % (attrname
, dn
), 'fix_all_normalisation'):
266 self
.report("Not fixing attribute %s" % attrname
)
271 for i
in range(0, len(mod_list
)):
272 (val
, nval
) = mod_list
[i
]
273 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
275 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
278 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
279 "Failed to normalise attribute %s" % attrname
,
281 self
.report("Normalised attribute %s" % attrname
)
283 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
284 '''fix attribute normalisation errors'''
285 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
286 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
287 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
288 if list(normalised
) == values
:
290 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
291 self
.report("Not fixing attribute '%s'" % attrname
)
296 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
298 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
299 "Failed to normalise attribute %s" % attrname
,
301 self
.report("Normalised attribute %s" % attrname
)
303 def err_duplicate_values(self
, dn
, attrname
, dup_values
, values
):
304 '''fix attribute normalisation errors'''
305 self
.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname
, dn
))
306 self
.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values
), ','.join(values
)))
307 if not self
.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_duplicates'):
308 self
.report("Not fixing attribute '%s'" % attrname
)
313 m
[attrname
] = ldb
.MessageElement(values
, ldb
.FLAG_MOD_REPLACE
, attrname
)
315 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
316 "Failed to remove duplicate value on attribute %s" % attrname
,
318 self
.report("Removed duplicate value on attribute %s" % attrname
)
320 def is_deleted_objects_dn(self
, dsdb_dn
):
321 '''see if a dsdb_Dn is the special Deleted Objects DN'''
322 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
324 def err_missing_objectclass(self
, dn
):
325 """handle object without objectclass"""
326 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
)))
327 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'):
328 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
330 if self
.do_delete(dn
, ["relax:0"],
331 "Failed to remove DN %s" % dn
):
332 self
.report("Removed DN %s" % dn
)
334 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
335 """handle a DN pointing to a deleted object"""
336 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
337 self
.report("Target GUID points at deleted DN %s" % correct_dn
)
338 if not self
.confirm_all('Remove DN link?', 'remove_all_deleted_DN_links'):
339 self
.report("Not removing")
343 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
344 if self
.do_modify(m
, ["show_recycled:1", "local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
],
345 "Failed to remove deleted DN attribute %s" % attrname
):
346 self
.report("Removed deleted DN on attribute %s" % attrname
)
348 def err_missing_dn_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
349 """handle a missing target DN (both GUID and DN string form are missing)"""
350 # check if its a backlink
351 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
352 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
353 self
.report("Not removing dangling forward link")
355 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
)
357 def err_incorrect_dn_GUID(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
358 """handle a missing GUID extended DN component"""
359 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
360 controls
=["extended_dn:1:1", "show_recycled:1"]
362 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
363 attrs
=[], controls
=controls
)
364 except ldb
.LdbError
, (enum
, estr
):
365 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
366 self
.err_missing_dn_GUID(dn
, attrname
, val
, dsdb_dn
)
369 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
370 self
.err_missing_dn_GUID(dn
, attrname
, val
, dsdb_dn
)
372 dsdb_dn
.dn
= res
[0].dn
374 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
375 self
.report("Not fixing %s" % errstr
)
379 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
380 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
382 if self
.do_modify(m
, ["show_recycled:1"],
383 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
384 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
386 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
387 """handle an incorrect binary DN component"""
388 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
389 controls
=["extended_dn:1:1", "show_recycled:1"]
391 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
392 self
.report("Not fixing %s" % errstr
)
396 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
397 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
399 if self
.do_modify(m
, ["show_recycled:1"],
400 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
401 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
403 def err_dn_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, errstr
):
404 """handle a DN string being incorrect"""
405 self
.report("ERROR: incorrect DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
406 dsdb_dn
.dn
= correct_dn
408 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_target_mismatch'):
409 self
.report("Not fixing %s" % errstr
)
413 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
414 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
415 if self
.do_modify(m
, ["show_recycled:1"],
416 "Failed to fix incorrect DN string on attribute %s" % attrname
):
417 self
.report("Fixed incorrect DN string on attribute %s" % (attrname
))
419 def err_unknown_attribute(self
, obj
, attrname
):
420 '''handle an unknown attribute error'''
421 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
422 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
423 self
.report("Not removing %s" % attrname
)
427 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
428 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
429 "Failed to remove unknown attribute %s" % attrname
):
430 self
.report("Removed unknown attribute %s" % (attrname
))
432 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
433 '''handle a missing backlink value'''
434 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
435 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
436 self
.report("Not fixing missing backlink %s" % backlink_name
)
440 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
441 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, attrname
)
442 if self
.do_modify(m
, ["show_recycled:1"],
443 "Failed to fix missing backlink %s" % backlink_name
):
444 self
.report("Fixed missing backlink %s" % (backlink_name
))
446 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
447 '''handle a incorrect RMD_FLAGS value'''
448 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
449 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()))
450 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
451 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
455 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
456 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
457 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
458 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
460 def err_orphaned_backlink(self
, obj
, attrname
, val
, link_name
, target_dn
):
461 '''handle a orphaned backlink value'''
462 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (attrname
, obj
.dn
, link_name
, target_dn
))
463 if not self
.confirm_all('Remove orphaned backlink %s' % link_name
, 'fix_all_orphaned_backlinks'):
464 self
.report("Not removing orphaned backlink %s" % link_name
)
468 m
['value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
469 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
470 "Failed to fix orphaned backlink %s" % link_name
):
471 self
.report("Fixed orphaned backlink %s" % (link_name
))
473 def err_no_fsmoRoleOwner(self
, obj
):
474 '''handle a missing fSMORoleOwner'''
475 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
476 res
= self
.samdb
.search("",
477 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
479 serviceName
= res
[0]["dsServiceName"][0]
480 if not self
.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
481 self
.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
485 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
486 if self
.do_modify(m
, [],
487 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
488 self
.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
490 def err_missing_parent(self
, obj
):
491 '''handle a missing parent'''
492 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
493 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
494 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
497 keep_transaction
= True
498 self
.samdb
.transaction_start()
500 nc_root
= self
.samdb
.get_nc_root(obj
.dn
);
501 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
502 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
503 new_dn
.remove_base_components(len(new_dn
) - 1)
504 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
505 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
506 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
510 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
512 if self
.do_modify(m
, [],
513 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
514 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
515 keep_transaction
= True
517 self
.samdb
.transaction_cancel()
521 self
.samdb
.transaction_commit()
523 self
.samdb
.transaction_cancel()
525 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
):
526 '''handle a wrong dn'''
528 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
529 new_rdn
.remove_base_components(len(new_rdn
) - 1)
530 new_parent
= new_dn
.parent()
533 if rdn_val
!= name_val
:
534 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
535 attributes
+= "name=%r" % (name_val
)
537 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
538 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
539 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
542 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, ["show_recycled:1", "relax:0"],
543 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
544 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
546 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
547 '''handle a wrong instanceType'''
548 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
549 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
550 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
555 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
556 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
557 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
558 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
560 def err_short_userParameters(self
, obj
, attrname
, value
):
561 # This is a truncated userParameters due to a pre 4.1 replication bug
562 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
)))
564 def err_base64_userParameters(self
, obj
, attrname
, value
):
565 '''handle a wrong userParameters'''
566 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
567 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
568 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
573 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
574 if self
.do_modify(m
, [],
575 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
576 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
578 def err_utf8_userParameters(self
, obj
, attrname
, value
):
579 '''handle a wrong userParameters'''
580 self
.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj
.dn
))
581 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
582 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
587 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
588 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
589 if self
.do_modify(m
, [],
590 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
591 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
593 def err_doubled_userParameters(self
, obj
, attrname
, value
):
594 '''handle a wrong userParameters'''
595 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
596 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
597 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
602 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
603 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
604 if self
.do_modify(m
, [],
605 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
606 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
608 def err_odd_userParameters(self
, obj
, attrname
):
609 # This is a truncated userParameters due to a pre 4.1 replication bug
610 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
)))
612 def find_revealed_link(self
, dn
, attrname
, guid
):
613 '''return a revealed link in an object'''
614 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
615 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
616 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
617 for val
in res
[0][attrname
]:
618 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, syntax_oid
)
619 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
624 def check_dn(self
, obj
, attrname
, syntax_oid
):
625 '''check a DN attribute for correctness'''
627 for val
in obj
[attrname
]:
628 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, syntax_oid
)
630 # all DNs should have a GUID component
631 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
634 self
.err_incorrect_dn_GUID(obj
.dn
, attrname
, val
, dsdb_dn
,
638 guidstr
= str(misc
.GUID(guid
))
640 attrs
= ['isDeleted']
642 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
643 fixing_msDS_HasInstantiatedNCs
= True
644 attrs
.append("instanceType")
646 fixing_msDS_HasInstantiatedNCs
= False
648 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
649 reverse_link_name
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
650 if reverse_link_name
is not None:
651 attrs
.append(reverse_link_name
)
653 # check its the right GUID
655 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
656 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1"])
657 except ldb
.LdbError
, (enum
, estr
):
659 self
.err_incorrect_dn_GUID(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect GUID")
662 if fixing_msDS_HasInstantiatedNCs
:
663 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
664 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
666 if str(dsdb_dn
) != val
:
668 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
671 # now we have two cases - the source object might or might not be deleted
672 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
673 target_is_deleted
= 'isDeleted' in res
[0] and res
[0]['isDeleted'][0].upper() == 'TRUE'
675 # the target DN is not allowed to be deleted, unless the target DN is the
676 # special Deleted Objects container
677 if target_is_deleted
and not is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
):
679 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
)
682 # check the DN matches in string form
683 if res
[0].dn
.extended_str() != dsdb_dn
.dn
.extended_str():
685 self
.err_dn_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
686 res
[0].dn
, "incorrect string version of DN")
689 if is_deleted
and not target_is_deleted
and reverse_link_name
is not None:
690 revealed_dn
= self
.find_revealed_link(obj
.dn
, attrname
, guid
)
691 rmd_flags
= revealed_dn
.dn
.get_extended_component("RMD_FLAGS")
692 if rmd_flags
is not None and (int(rmd_flags
) & 1) == 0:
693 # the RMD_FLAGS for this link should be 1, as the target is deleted
694 self
.err_incorrect_rmd_flags(obj
, attrname
, revealed_dn
)
697 # check the reverse_link is correct if there should be one
698 if reverse_link_name
is not None:
700 if reverse_link_name
in res
[0]:
701 for v
in res
[0][reverse_link_name
]:
702 if v
== obj
.dn
.extended_str():
707 self
.err_orphaned_backlink(obj
, attrname
, val
, reverse_link_name
, dsdb_dn
.dn
)
709 self
.err_missing_backlink(obj
, attrname
, val
, reverse_link_name
, dsdb_dn
.dn
)
715 def get_originating_time(self
, val
, attid
):
716 '''Read metadata properties and return the originating time for
719 :return: the originating time or 0 if not found
722 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
725 for o
in repl
.ctr
.array
:
727 return o
.originating_change_time
731 def process_metadata(self
, dn
, val
):
732 '''Read metadata properties and list attributes in it.
733 raises KeyError if the attid is unknown.'''
738 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
740 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
743 for o
in repl
.ctr
.array
:
744 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
745 set_att
.add(att
.lower())
746 list_attid
.append(o
.attid
)
747 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
,
748 is_schema_nc
=in_schema_nc
)
749 if correct_attid
!= o
.attid
:
750 wrong_attids
.add(o
.attid
)
752 return (set_att
, list_attid
, wrong_attids
)
755 def fix_metadata(self
, dn
, attr
):
756 '''re-write replPropertyMetaData elements for a single attribute for a
757 object. This is used to fix missing replPropertyMetaData elements'''
758 res
= self
.samdb
.search(base
= dn
, scope
=ldb
.SCOPE_BASE
, attrs
= [attr
],
759 controls
= ["search_options:1:2", "show_recycled:1"])
763 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
764 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
765 "Failed to fix metadata for attribute %s" % attr
):
766 self
.report("Fixed metadata for attribute %s" % attr
)
768 def ace_get_effective_inherited_type(self
, ace
):
769 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
773 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
775 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
777 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
779 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
785 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
788 return str(ace
.object.inherited_type
)
790 def lookup_class_schemaIDGUID(self
, cls
):
791 if cls
in self
.class_schemaIDGUID
:
792 return self
.class_schemaIDGUID
[cls
]
794 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
795 res
= self
.samdb
.search(base
=self
.schema_dn
,
797 attrs
=["schemaIDGUID"])
798 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
800 self
.class_schemaIDGUID
[cls
] = t
803 def process_sd(self
, dn
, obj
):
804 sd_attr
= "nTSecurityDescriptor"
805 sd_val
= obj
[sd_attr
]
807 sd
= ndr_unpack(security
.descriptor
, str(sd_val
))
809 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
811 # we don't fix deleted objects
814 sd_clean
= security
.descriptor()
815 sd_clean
.owner_sid
= sd
.owner_sid
816 sd_clean
.group_sid
= sd
.group_sid
817 sd_clean
.type = sd
.type
818 sd_clean
.revision
= sd
.revision
821 last_inherited_type
= None
824 if sd
.sacl
is not None:
826 for i
in range(0, len(aces
)):
829 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
830 sd_clean
.sacl_add(ace
)
833 t
= self
.ace_get_effective_inherited_type(ace
)
837 if last_inherited_type
is not None:
838 if t
!= last_inherited_type
:
839 # if it inherited from more than
840 # one type it's very likely to be broken
842 # If not the recalculation will calculate
847 last_inherited_type
= t
850 if sd
.dacl
is not None:
852 for i
in range(0, len(aces
)):
855 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
856 sd_clean
.dacl_add(ace
)
859 t
= self
.ace_get_effective_inherited_type(ace
)
863 if last_inherited_type
is not None:
864 if t
!= last_inherited_type
:
865 # if it inherited from more than
866 # one type it's very likely to be broken
868 # If not the recalculation will calculate
873 last_inherited_type
= t
876 return (sd_clean
, sd
)
878 if last_inherited_type
is None:
884 cls
= obj
["objectClass"][-1]
889 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
890 attrs
=["isDeleted", "objectClass"],
891 controls
=["show_recycled:1"])
893 is_deleted
= 'isDeleted' in o
and o
['isDeleted'][0].upper() == 'TRUE'
895 # we don't fix deleted objects
897 cls
= o
["objectClass"][-1]
899 t
= self
.lookup_class_schemaIDGUID(cls
)
901 if t
!= last_inherited_type
:
903 return (sd_clean
, sd
)
908 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
909 '''re-write the SD due to incorrect inherited ACEs'''
910 sd_attr
= "nTSecurityDescriptor"
911 sd_val
= ndr_pack(sd
)
912 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
914 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
915 self
.report('Not fixing %s on %s\n' % (sd_attr
, dn
))
920 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
921 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
922 "Failed to fix attribute %s" % sd_attr
):
923 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
925 def err_wrong_default_sd(self
, dn
, sd
, sd_old
, diff
):
926 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
927 sd_attr
= "nTSecurityDescriptor"
928 sd_val
= ndr_pack(sd
)
929 sd_old_val
= ndr_pack(sd_old
)
930 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
931 if sd
.owner_sid
is not None:
932 sd_flags |
= security
.SECINFO_OWNER
933 if sd
.group_sid
is not None:
934 sd_flags |
= security
.SECINFO_GROUP
936 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
937 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
942 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
943 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
944 "Failed to reset attribute %s" % sd_attr
):
945 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
947 def err_missing_sd_owner(self
, dn
, sd
):
948 '''re-write the SD due to a missing owner or group'''
949 sd_attr
= "nTSecurityDescriptor"
950 sd_val
= ndr_pack(sd
)
951 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
953 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
954 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
959 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
961 # By setting the session_info to admin_session_info and
962 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
963 # flags we cause the descriptor module to set the correct
964 # owner and group on the SD, replacing the None/NULL values
965 # for owner_sid and group_sid currently present.
967 # The admin_session_info matches that used in provision, and
968 # is the best guess we can make for an existing object that
969 # hasn't had something specifically set.
971 # This is important for the dns related naming contexts.
972 self
.samdb
.set_session_info(self
.admin_session_info
)
973 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
974 "Failed to fix metadata for attribute %s" % sd_attr
):
975 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
976 self
.samdb
.set_session_info(self
.system_session_info
)
979 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
980 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
985 # Search for a zero invocationID
986 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
990 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
991 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
992 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
993 % (dn
, o
.attid
, o
.version
,
994 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
995 self
.samdb
.get_invocation_id()))
1000 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
1001 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1002 str(repl_meta_data
))
1004 now
= samba
.unix2nttime(int(time
.time()))
1007 # Search for a zero invocationID
1008 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1012 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
1013 o
.version
= o
.version
+ 1
1014 o
.originating_change_time
= now
1015 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
1016 o
.originating_usn
= seq
1020 replBlob
= ndr_pack(repl
)
1024 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1025 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1026 self
.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr
, dn
))
1029 nmsg
= ldb
.Message()
1031 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1032 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1033 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1034 "Failed to fix attribute %s" % attr
):
1035 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1038 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
1039 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1040 str(repl_meta_data
))
1043 # Search for an invalid attid
1045 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1047 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
1051 def err_replmetadata_incorrect_attid(self
, dn
, attr
, repl_meta_data
, wrong_attids
):
1052 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1053 str(repl_meta_data
))
1057 remove_attid
= set()
1060 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1063 # Sort the array, except for the last element. This strange
1064 # construction, creating a new list, due to bugs in samba's
1065 # array handling in IDL generated objects.
1066 ctr
.array
= sorted(ctr
.array
[:-1], key
=lambda o
: o
.attid
) + [ctr
.array
[-1]]
1067 # Now walk it in reverse, so we see the low (and so incorrect,
1068 # the correct values are above 0x80000000) values first and
1069 # remove the 'second' value we see.
1070 for o
in reversed(ctr
.array
):
1071 print "%s: 0x%08x" % (dn
, o
.attid
)
1072 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1073 if att
.lower() in set_att
:
1074 self
.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att
, attr
, dn
))
1075 if not self
.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1076 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
),
1077 'fix_replmetadata_duplicate_attid'):
1078 self
.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1079 % (o
.attid
, att
, attr
, dn
))
1082 remove_attid
.add(o
.attid
)
1083 # We want to set the metadata for the most recent
1084 # update to have been applied locally, that is the metadata
1085 # matching the (eg string) value in the attribute
1086 if o
.local_usn
> hash_att
[att
].local_usn
:
1087 # This is always what we would have sent over DRS,
1088 # because the DRS server will have sent the
1089 # msDS-IntID, but with the values from both
1090 # attribute entries.
1091 hash_att
[att
].version
= o
.version
1092 hash_att
[att
].originating_change_time
= o
.originating_change_time
1093 hash_att
[att
].originating_invocation_id
= o
.originating_invocation_id
1094 hash_att
[att
].originating_usn
= o
.originating_usn
1095 hash_att
[att
].local_usn
= o
.local_usn
1097 # Do not re-add the value to the set or overwrite the hash value
1101 set_att
.add(att
.lower())
1103 # Generate a real list we can sort on properly
1104 new_list
= [o
for o
in ctr
.array
if o
.attid
not in remove_attid
]
1106 if (len(wrong_attids
) > 0):
1108 if o
.attid
in wrong_attids
:
1109 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1110 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
, is_schema_nc
=in_schema_nc
)
1111 self
.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr
, dn
))
1112 if not self
.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1113 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
), 'fix_replmetadata_wrong_attid'):
1114 self
.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1115 % (o
.attid
, correct_attid
, att
, attr
, dn
))
1118 o
.attid
= correct_attid
1120 # Sort the array, except for the last element (we changed
1121 # the value so must re-sort)
1122 new_list
[:-1] = sorted(new_list
[:-1], key
=lambda o
: o
.attid
)
1124 # If we did not already need to fix it, then ask about sorting
1126 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
1127 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
1128 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
1129 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
1132 # The actual sort done is done at the top of the function
1134 ctr
.count
= len(new_list
)
1135 ctr
.array
= new_list
1136 replBlob
= ndr_pack(repl
)
1138 nmsg
= ldb
.Message()
1140 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1141 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1142 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1143 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1144 "Failed to fix attribute %s" % attr
):
1145 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1148 def is_deleted_deleted_objects(self
, obj
):
1150 if "description" not in obj
:
1151 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
1153 if "showInAdvancedViewOnly" not in obj
:
1154 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
1156 if "objectCategory" not in obj
:
1157 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
1159 if "isCriticalSystemObject" not in obj
:
1160 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
1162 if "isRecycled" in obj
:
1163 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
1168 def err_deleted_deleted_objects(self
, obj
):
1169 nmsg
= ldb
.Message()
1170 nmsg
.dn
= dn
= obj
.dn
1172 if "description" not in obj
:
1173 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
1174 if "showInAdvancedViewOnly" not in obj
:
1175 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
1176 if "objectCategory" not in obj
:
1177 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
1178 if "isCriticalSystemObject" not in obj
:
1179 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
1180 if "isRecycled" in obj
:
1181 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
1183 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1184 % (dn
), 'fix_deleted_deleted_objects'):
1185 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
1188 if self
.do_modify(nmsg
, ["relax:0"],
1189 "Failed to fix Deleted Objects container %s" % dn
):
1190 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
1193 def is_fsmo_role(self
, dn
):
1194 if dn
== self
.samdb
.domain_dn
:
1196 if dn
== self
.infrastructure_dn
:
1198 if dn
== self
.naming_dn
:
1200 if dn
== self
.schema_dn
:
1202 if dn
== self
.rid_dn
:
1207 def calculate_instancetype(self
, dn
):
1209 nc_root
= self
.samdb
.get_nc_root(dn
)
1211 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
1213 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
1214 except ldb
.LdbError
, (enum
, estr
):
1215 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1218 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
1220 if self
.write_ncs
is not None and str(nc_root
) in self
.write_ncs
:
1221 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
1225 def get_wellknown_sd(self
, dn
):
1226 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
1228 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
1229 return ndr_unpack(security
.descriptor
,
1230 descriptor_fn(domain_sid
,
1231 name_map
=self
.name_map
))
1235 def check_object(self
, dn
, attrs
=['*']):
1236 '''check one object'''
1238 self
.report("Checking object %s" % dn
)
1239 if "dn" in map(str.lower
, attrs
):
1240 attrs
.append("name")
1241 if "distinguishedname" in map(str.lower
, attrs
):
1242 attrs
.append("name")
1243 if str(dn
.get_rdn_name()).lower() in map(str.lower
, attrs
):
1244 attrs
.append("name")
1245 if 'name' in map(str.lower
, attrs
):
1246 attrs
.append(dn
.get_rdn_name())
1247 attrs
.append("isDeleted")
1248 attrs
.append("systemFlags")
1250 attrs
.append("replPropertyMetaData")
1254 sd_flags |
= security
.SECINFO_OWNER
1255 sd_flags |
= security
.SECINFO_GROUP
1256 sd_flags |
= security
.SECINFO_DACL
1257 sd_flags |
= security
.SECINFO_SACL
1259 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1264 "sd_flags:1:%d" % sd_flags
,
1267 except ldb
.LdbError
, (enum
, estr
):
1268 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
1269 if self
.in_transaction
:
1270 self
.report("ERROR: Object %s disappeared during check" % dn
)
1275 self
.report("ERROR: Object %s failed to load during check" % dn
)
1279 set_attrs_from_md
= set()
1280 set_attrs_seen
= set()
1281 got_repl_property_meta_data
= False
1282 got_objectclass
= False
1284 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
1286 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
1287 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
1289 deleted_objects_dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects,%s" % nc_dn
)
1291 object_rdn_attr
= None
1292 object_rdn_val
= None
1297 for attrname
in obj
:
1298 if attrname
== 'dn':
1301 if str(attrname
).lower() == 'objectclass':
1302 got_objectclass
= True
1304 if str(attrname
).lower() == "name":
1305 if len(obj
[attrname
]) != 1:
1307 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1308 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
1310 name_val
= obj
[attrname
][0]
1312 if str(attrname
).lower() == str(obj
.dn
.get_rdn_name()).lower():
1313 object_rdn_attr
= attrname
1314 if len(obj
[attrname
]) != 1:
1316 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1317 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
1319 object_rdn_val
= obj
[attrname
][0]
1321 if str(attrname
).lower() == 'isdeleted':
1322 if obj
[attrname
][0] != "FALSE":
1325 if str(attrname
).lower() == 'systemflags':
1326 systemFlags
= int(obj
[attrname
][0])
1328 if str(attrname
).lower() == 'replpropertymetadata':
1329 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
]):
1331 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
])
1332 # We don't continue, as we may also have other fixes for this attribute
1333 # based on what other attributes we see.
1336 (set_attrs_from_md
, list_attid_from_md
, wrong_attids
) \
1337 = self
.process_metadata(dn
, obj
[attrname
])
1340 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
1343 if len(set_attrs_from_md
) < len(list_attid_from_md
) \
1344 or len(wrong_attids
) > 0 \
1345 or sorted(list_attid_from_md
[:-1]) != list_attid_from_md
[:-1]:
1347 self
.err_replmetadata_incorrect_attid(dn
, attrname
, obj
[attrname
], wrong_attids
)
1350 # Here we check that the first attid is 0
1351 # (objectClass) and that the last on is the RDN
1353 rdn_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(dn
.get_rdn_name())
1354 if list_attid_from_md
[-1] != rdn_attid
:
1356 self
.report("ERROR: Not fixing incorrect final attributeID in '%s' on '%s', it should match the RDN %s" %
1357 (attrname
, str(dn
), dn
.get_rdn_name()))
1359 if list_attid_from_md
[0] != 0:
1361 self
.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
1362 (attrname
, str(dn
)))
1364 got_repl_property_meta_data
= True
1367 if str(attrname
).lower() == 'ntsecuritydescriptor':
1368 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
1369 if sd_broken
is not None:
1370 self
.err_wrong_sd(dn
, sd
, sd_broken
)
1374 if sd
.owner_sid
is None or sd
.group_sid
is None:
1375 self
.err_missing_sd_owner(dn
, sd
)
1379 if self
.reset_well_known_acls
:
1381 well_known_sd
= self
.get_wellknown_sd(dn
)
1385 current_sd
= ndr_unpack(security
.descriptor
,
1386 str(obj
[attrname
][0]))
1388 diff
= get_diff_sds(well_known_sd
, current_sd
, security
.dom_sid(self
.samdb
.get_domain_sid()))
1390 self
.err_wrong_default_sd(dn
, well_known_sd
, current_sd
, diff
)
1395 if str(attrname
).lower() == 'objectclass':
1396 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
1397 # Do not consider the attribute incorrect if:
1398 # - The sorted (alphabetically) list is the same, inclding case
1399 # - The first and last elements are the same
1401 # This avoids triggering an error due to
1402 # non-determinism in the sort routine in (at least)
1403 # 4.3 and earlier, and the fact that any AUX classes
1404 # in these attributes are also not sorted when
1405 # imported from Windows (they are just in the reverse
1406 # order of last set)
1407 if sorted(normalised
) != sorted(obj
[attrname
]) \
1408 or normalised
[0] != obj
[attrname
][0] \
1409 or normalised
[-1] != obj
[attrname
][-1]:
1410 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
1414 if str(attrname
).lower() == 'userparameters':
1415 if len(obj
[attrname
][0]) == 1 and obj
[attrname
][0][0] == '\x20':
1417 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
1420 elif obj
[attrname
][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1421 # This is the correct, normal prefix
1424 elif obj
[attrname
][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1425 # this is the typical prefix from a windows migration
1427 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
1430 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':
1431 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1433 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
1436 elif len(obj
[attrname
][0]) % 2 != 0:
1437 # This is a value that isn't even in length
1439 self
.err_odd_userParameters(obj
, attrname
, obj
[attrname
])
1442 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':
1443 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
1445 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
1448 # check for empty attributes
1449 for val
in obj
[attrname
]:
1451 self
.err_empty_attribute(dn
, attrname
)
1455 # get the syntax oid for the attribute, so we can can have
1456 # special handling for some specific attribute types
1458 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
1459 except Exception, msg
:
1460 self
.err_unknown_attribute(obj
, attrname
)
1464 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
1465 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
1466 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
1467 and not self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)):
1468 set_attrs_seen
.add(str(attrname
).lower())
1470 if syntax_oid
in [ dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
1471 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
1472 # it's some form of DN, do specialised checking on those
1473 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
1476 # check for incorrectly normalised attributes
1477 for val
in obj
[attrname
]:
1478 values
.add(str(val
))
1480 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
1481 if len(normalised
) != 1 or normalised
[0] != val
:
1482 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
1486 if len(obj
[attrname
]) != len(values
):
1487 self
.err_duplicate_values(dn
, attrname
, obj
[attrname
], list(values
))
1491 if str(attrname
).lower() == "instancetype":
1492 calculated_instancetype
= self
.calculate_instancetype(dn
)
1493 if len(obj
["instanceType"]) != 1 or obj
["instanceType"][0] != str(calculated_instancetype
):
1495 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
1497 if not got_objectclass
and ("*" in attrs
or "objectclass" in map(str.lower
, attrs
)):
1499 self
.err_missing_objectclass(dn
)
1501 if ("*" in attrs
or "name" in map(str.lower
, attrs
)):
1502 if name_val
is None:
1504 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
1505 if object_rdn_attr
is None:
1507 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
1509 if name_val
is not None:
1512 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
1513 parent_dn
= deleted_objects_dn
1514 if parent_dn
is None:
1515 parent_dn
= obj
.dn
.parent()
1516 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
1517 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
1519 if obj
.dn
== deleted_objects_dn
:
1520 expected_dn
= obj
.dn
1522 if expected_dn
!= obj
.dn
:
1524 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
, object_rdn_val
, name_val
)
1525 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
1527 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
, object_rdn_val
, str(obj
.dn
)))
1530 if got_repl_property_meta_data
:
1531 if obj
.dn
== deleted_objects_dn
:
1532 isDeletedAttId
= 131120
1533 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
1535 expectedTimeDo
= 2650466015990000000
1536 originating
= self
.get_originating_time(obj
["replPropertyMetaData"], isDeletedAttId
)
1537 if originating
!= expectedTimeDo
:
1538 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
1539 nmsg
= ldb
.Message()
1541 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
1543 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
1546 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
1548 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
1550 self
.report("On object %s" % dn
)
1553 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
1554 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
1555 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
1557 self
.fix_metadata(dn
, att
)
1559 if self
.is_fsmo_role(dn
):
1560 if "fSMORoleOwner" not in obj
and ("*" in attrs
or "fsmoroleowner" in map(str.lower
, attrs
)):
1561 self
.err_no_fsmoRoleOwner(obj
)
1565 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
1566 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
1567 controls
=["show_recycled:1", "show_deleted:1"])
1568 except ldb
.LdbError
, (enum
, estr
):
1569 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
1570 self
.err_missing_parent(obj
)
1575 if dn
in self
.deleted_objects_containers
and '*' in attrs
:
1576 if self
.is_deleted_deleted_objects(obj
):
1577 self
.err_deleted_deleted_objects(obj
)
1582 ################################################################
1583 # check special @ROOTDSE attributes
1584 def check_rootdse(self
):
1585 '''check the @ROOTDSE special object'''
1586 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
1588 self
.report("Checking object %s" % dn
)
1589 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
1591 self
.report("Object %s disappeared during check" % dn
)
1596 # check that the dsServiceName is in GUID form
1597 if not 'dsServiceName' in obj
:
1598 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
1599 return error_count
+1
1601 if not obj
['dsServiceName'][0].startswith('<GUID='):
1602 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
1604 if not self
.confirm('Change dsServiceName to GUID form?'):
1606 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0]),
1607 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
1608 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
1611 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
1612 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
1613 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
1614 self
.report("Changed dsServiceName to GUID form")
1618 ###############################################
1619 # re-index the database
1620 def reindex_database(self
):
1621 '''re-index the whole database'''
1623 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
1624 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
1625 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
1626 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
1628 ###############################################
1630 def reset_modules(self
):
1631 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
1633 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
1634 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
1635 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)