1 # Samba4 AD database checker
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from __future__
import print_function
24 from base64
import b64decode
25 from samba
import dsdb
26 from samba
import common
27 from samba
.dcerpc
import misc
28 from samba
.dcerpc
import drsuapi
29 from samba
.ndr
import ndr_unpack
, ndr_pack
30 from samba
.dcerpc
import drsblobs
31 from samba
.common
import dsdb_Dn
32 from samba
.dcerpc
import security
33 from samba
.descriptor
import get_wellknown_sds
, get_diff_sds
34 from samba
.auth
import system_session
, admin_session
35 from samba
.netcmd
import CommandError
36 from samba
.netcmd
.fsmo
import get_fsmo_roleowner
39 class dbcheck(object):
40 """check a SAM database for errors"""
42 def __init__(self
, samdb
, samdb_schema
=None, verbose
=False, fix
=False,
43 yes
=False, quiet
=False, in_transaction
=False,
44 reset_well_known_acls
=False,
45 check_expired_tombstones
=False):
47 self
.dict_oid_name
= None
48 self
.samdb_schema
= (samdb_schema
or samdb
)
49 self
.verbose
= verbose
53 self
.remove_all_unknown_attributes
= False
54 self
.remove_all_empty_attributes
= False
55 self
.fix_all_normalisation
= False
56 self
.fix_all_duplicates
= False
57 self
.fix_all_DN_GUIDs
= False
58 self
.fix_all_binary_dn
= False
59 self
.remove_implausible_deleted_DN_links
= False
60 self
.remove_plausible_deleted_DN_links
= False
61 self
.fix_all_string_dn_component_mismatch
= False
62 self
.fix_all_GUID_dn_component_mismatch
= False
63 self
.fix_all_SID_dn_component_mismatch
= False
64 self
.fix_all_SID_dn_component_missing
= False
65 self
.fix_all_old_dn_string_component_mismatch
= False
66 self
.fix_all_metadata
= False
67 self
.fix_time_metadata
= False
68 self
.fix_undead_linked_attributes
= False
69 self
.fix_all_missing_backlinks
= False
70 self
.fix_all_orphaned_backlinks
= False
71 self
.fix_all_missing_forward_links
= False
72 self
.duplicate_link_cache
= dict()
73 self
.recover_all_forward_links
= False
74 self
.fix_rmd_flags
= False
75 self
.fix_ntsecuritydescriptor
= False
76 self
.fix_ntsecuritydescriptor_owner_group
= False
77 self
.seize_fsmo_role
= False
78 self
.move_to_lost_and_found
= False
79 self
.fix_instancetype
= False
80 self
.fix_replmetadata_zero_invocationid
= False
81 self
.fix_replmetadata_duplicate_attid
= False
82 self
.fix_replmetadata_wrong_attid
= False
83 self
.fix_replmetadata_unsorted_attid
= False
84 self
.fix_deleted_deleted_objects
= False
85 self
.fix_incorrect_deleted_objects
= False
87 self
.fix_base64_userparameters
= False
88 self
.fix_utf8_userparameters
= False
89 self
.fix_doubled_userparameters
= False
90 self
.fix_sid_rid_set_conflict
= False
91 self
.reset_well_known_acls
= reset_well_known_acls
92 self
.check_expired_tombstones
= check_expired_tombstones
93 self
.expired_tombstones
= 0
94 self
.reset_all_well_known_acls
= False
95 self
.in_transaction
= in_transaction
96 self
.infrastructure_dn
= ldb
.Dn(samdb
, "CN=Infrastructure," + samdb
.domain_dn())
97 self
.naming_dn
= ldb
.Dn(samdb
, "CN=Partitions,%s" % samdb
.get_config_basedn())
98 self
.schema_dn
= samdb
.get_schema_basedn()
99 self
.rid_dn
= ldb
.Dn(samdb
, "CN=RID Manager$,CN=System," + samdb
.domain_dn())
100 self
.ntds_dsa
= ldb
.Dn(samdb
, samdb
.get_dsServiceName())
101 self
.class_schemaIDGUID
= {}
102 self
.wellknown_sds
= get_wellknown_sds(self
.samdb
)
103 self
.fix_all_missing_objectclass
= False
104 self
.fix_missing_deleted_objects
= False
105 self
.fix_replica_locations
= False
106 self
.fix_missing_rid_set_master
= False
107 self
.fix_changes_after_deletion_bug
= False
110 self
.link_id_cache
= {}
113 res
= samdb
.search(base
="CN=DnsAdmins,CN=Users,%s" % samdb
.domain_dn(), scope
=ldb
.SCOPE_BASE
,
115 dnsadmins_sid
= ndr_unpack(security
.dom_sid
, res
[0]["objectSid"][0])
116 self
.name_map
['DnsAdmins'] = str(dnsadmins_sid
)
117 except ldb
.LdbError
as e5
:
118 (enum
, estr
) = e5
.args
119 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
123 self
.system_session_info
= system_session()
124 self
.admin_session_info
= admin_session(None, samdb
.get_domain_sid())
126 res
= self
.samdb
.search(base
=self
.ntds_dsa
, scope
=ldb
.SCOPE_BASE
, attrs
=['msDS-hasMasterNCs', 'hasMasterNCs'])
127 if "msDS-hasMasterNCs" in res
[0]:
128 self
.write_ncs
= res
[0]["msDS-hasMasterNCs"]
130 # If the Forest Level is less than 2003 then there is no
131 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
132 # no need to merge as all the NCs that are in hasMasterNCs must
133 # also be in msDS-hasMasterNCs (but not the opposite)
134 if "hasMasterNCs" in res
[0]:
135 self
.write_ncs
= res
[0]["hasMasterNCs"]
137 self
.write_ncs
= None
139 res
= self
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['namingContexts'])
140 self
.deleted_objects_containers
= []
141 self
.ncs_lacking_deleted_containers
= []
142 self
.dns_partitions
= []
144 self
.ncs
= res
[0]["namingContexts"]
152 dn
= self
.samdb
.get_wellknown_dn(ldb
.Dn(self
.samdb
, nc
.decode('utf8')),
153 dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
154 self
.deleted_objects_containers
.append(dn
)
156 self
.ncs_lacking_deleted_containers
.append(ldb
.Dn(self
.samdb
, nc
.decode('utf8')))
158 domaindns_zone
= 'DC=DomainDnsZones,%s' % self
.samdb
.get_default_basedn()
159 forestdns_zone
= 'DC=ForestDnsZones,%s' % self
.samdb
.get_root_basedn()
160 domain
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
161 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
162 base
=self
.samdb
.get_partitions_dn(),
163 expression
="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone
)
165 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, forestdns_zone
), domain
[0]))
167 forest
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
168 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
169 base
=self
.samdb
.get_partitions_dn(),
170 expression
="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone
)
172 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, domaindns_zone
), forest
[0]))
174 fsmo_dn
= ldb
.Dn(self
.samdb
, "CN=RID Manager$,CN=System," + self
.samdb
.domain_dn())
175 rid_master
= get_fsmo_roleowner(self
.samdb
, fsmo_dn
, "rid")
176 if ldb
.Dn(self
.samdb
, self
.samdb
.get_dsServiceName()) == rid_master
:
177 self
.is_rid_master
= True
179 self
.is_rid_master
= False
181 # To get your rid set
183 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, self
.samdb
.get_serverName()),
184 scope
=ldb
.SCOPE_BASE
, attrs
=["serverReference"])
185 # 2. Get server reference
186 self
.server_ref_dn
= ldb
.Dn(self
.samdb
, res
[0]['serverReference'][0].decode('utf8'))
189 res
= self
.samdb
.search(base
=self
.server_ref_dn
,
190 scope
=ldb
.SCOPE_BASE
, attrs
=['rIDSetReferences'])
191 if "rIDSetReferences" in res
[0]:
192 self
.rid_set_dn
= ldb
.Dn(self
.samdb
, res
[0]['rIDSetReferences'][0].decode('utf8'))
194 self
.rid_set_dn
= None
196 ntds_service_dn
= "CN=Directory Service,CN=Windows NT,CN=Services,%s" % \
197 self
.samdb
.get_config_basedn().get_linearized()
198 res
= samdb
.search(base
=ntds_service_dn
,
199 scope
=ldb
.SCOPE_BASE
,
200 expression
="(objectClass=nTDSService)",
201 attrs
=["tombstoneLifetime"])
202 self
.tombstoneLifetime
= int(res
[0]["tombstoneLifetime"][0])
204 self
.compatibleFeatures
= []
205 self
.requiredFeatures
= []
208 res
= self
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
210 attrs
=["compatibleFeatures",
212 if "compatibleFeatures" in res
[0]:
213 self
.compatibleFeatures
= res
[0]["compatibleFeatures"]
214 if "requiredFeatures" in res
[0]:
215 self
.requiredFeatures
= res
[0]["requiredFeatures"]
216 except ldb
.LdbError
as e6
:
217 (enum
, estr
) = e6
.args
218 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
222 def check_database(self
, DN
=None, scope
=ldb
.SCOPE_SUBTREE
, controls
=[], attrs
=['*']):
223 '''perform a database check, returning the number of errors found'''
224 res
= self
.samdb
.search(base
=DN
, scope
=scope
, attrs
=['dn'], controls
=controls
)
225 self
.report('Checking %u objects' % len(res
))
228 error_count
+= self
.check_deleted_objects_containers()
230 self
.attribute_or_class_ids
= set()
233 self
.dn_set
.add(str(object.dn
))
234 error_count
+= self
.check_object(object.dn
, attrs
=attrs
)
237 error_count
+= self
.check_rootdse()
239 if self
.expired_tombstones
> 0:
240 self
.report("NOTICE: found %d expired tombstones, "
241 "'samba' will remove them daily, "
242 "'samba-tool domain tombstones expunge' "
243 "would do that immediately." % (
244 self
.expired_tombstones
))
246 if error_count
!= 0 and not self
.fix
:
247 self
.report("Please use --fix to fix these errors")
249 self
.report('Checked %u objects (%u errors)' % (len(res
), error_count
))
252 def check_deleted_objects_containers(self
):
253 """This function only fixes conflicts on the Deleted Objects
254 containers, not the attributes"""
256 for nc
in self
.ncs_lacking_deleted_containers
:
257 if nc
== self
.schema_dn
:
260 self
.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc
)
261 if not self
.confirm_all('Fix missing Deleted Objects container for %s?' % (nc
), 'fix_missing_deleted_objects'):
264 dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects")
269 # If something already exists here, add a conflict
270 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[],
271 controls
=["show_deleted:1", "extended_dn:1:1",
272 "show_recycled:1", "reveal_internals:0"])
274 guid
= res
[0].dn
.get_extended_component("GUID")
275 conflict_dn
= ldb
.Dn(self
.samdb
,
276 "CN=Deleted Objects\\0ACNF:%s" % str(misc
.GUID(guid
)))
277 conflict_dn
.add_base(nc
)
279 except ldb
.LdbError
as e2
:
280 (enum
, estr
) = e2
.args
281 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
284 self
.report("Couldn't check for conflicting Deleted Objects container: %s" % estr
)
287 if conflict_dn
is not None:
289 self
.samdb
.rename(dn
, conflict_dn
, ["show_deleted:1", "relax:0", "show_recycled:1"])
290 except ldb
.LdbError
as e1
:
291 (enum
, estr
) = e1
.args
292 self
.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn
, conflict_dn
, estr
))
295 # Refresh wellKnownObjects links
296 res
= self
.samdb
.search(base
=nc
, scope
=ldb
.SCOPE_BASE
,
297 attrs
=['wellKnownObjects'],
298 controls
=["show_deleted:1", "extended_dn:0",
299 "show_recycled:1", "reveal_internals:0"])
301 self
.report("wellKnownObjects was not found for NC %s" % nc
)
304 # Prevent duplicate deleted objects containers just in case
305 wko
= res
[0]["wellKnownObjects"]
307 proposed_objectguid
= None
309 dsdb_dn
= dsdb_Dn(self
.samdb
, o
.decode('utf8'), dsdb
.DSDB_SYNTAX_BINARY_DN
)
310 if self
.is_deleted_objects_dn(dsdb_dn
):
311 self
.report("wellKnownObjects had duplicate Deleted Objects value %s" % o
)
312 # We really want to put this back in the same spot
313 # as the original one, so that on replication we
314 # merge, rather than conflict.
315 proposed_objectguid
= dsdb_dn
.dn
.get_extended_component("GUID")
318 if proposed_objectguid
is not None:
319 guid_suffix
= "\nobjectGUID: %s" % str(misc
.GUID(proposed_objectguid
))
321 wko_prefix
= "B:32:%s" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
322 listwko
.append('%s:%s' % (wko_prefix
, dn
))
325 # Insert a brand new Deleted Objects container
326 self
.samdb
.add_ldif("""dn: %s
328 objectClass: container
329 description: Container for deleted objects
331 isCriticalSystemObject: TRUE
332 showInAdvancedViewOnly: TRUE
333 systemFlags: -1946157056%s""" % (dn
, guid_suffix
),
334 controls
=["relax:0", "provision:0"])
336 delta
= ldb
.Message()
337 delta
.dn
= ldb
.Dn(self
.samdb
, str(res
[0]["dn"]))
338 delta
["wellKnownObjects"] = ldb
.MessageElement(listwko
,
339 ldb
.FLAG_MOD_REPLACE
,
342 # Insert the link to the brand new container
343 if self
.do_modify(delta
, ["relax:0"],
344 "NC %s lacks Deleted Objects WKGUID" % nc
,
346 self
.report("Added %s well known guid link" % dn
)
348 self
.deleted_objects_containers
.append(dn
)
352 def report(self
, msg
):
353 '''print a message unless quiet is set'''
357 def confirm(self
, msg
, allow_all
=False, forced
=False):
358 '''confirm a change'''
365 return common
.confirm(msg
, forced
=forced
, allow_all
=allow_all
)
367 ################################################################
368 # a local confirm function with support for 'all'
369 def confirm_all(self
, msg
, all_attr
):
370 '''confirm a change with support for "all" '''
373 if getattr(self
, all_attr
) == 'NONE':
375 if getattr(self
, all_attr
) == 'ALL':
381 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
383 setattr(self
, all_attr
, 'ALL')
386 setattr(self
, all_attr
, 'NONE')
390 def do_delete(self
, dn
, controls
, msg
):
391 '''delete dn with optional verbose output'''
393 self
.report("delete DN %s" % dn
)
395 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
396 self
.samdb
.delete(dn
, controls
=controls
)
397 except Exception as err
:
398 if self
.in_transaction
:
399 raise CommandError("%s : %s" % (msg
, err
))
400 self
.report("%s : %s" % (msg
, err
))
404 def do_modify(self
, m
, controls
, msg
, validate
=True):
405 '''perform a modify with optional verbose output'''
406 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
408 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
409 self
.report("controls: %r" % controls
)
411 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
412 except Exception as err
:
413 if self
.in_transaction
:
414 raise CommandError("%s : %s" % (msg
, err
))
415 self
.report("%s : %s" % (msg
, err
))
419 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
420 '''perform a modify with optional verbose output'''
422 self
.report("""dn: %s
426 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
428 to_dn
= to_rdn
+ to_base
429 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
430 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
431 except Exception as err
:
432 if self
.in_transaction
:
433 raise CommandError("%s : %s" % (msg
, err
))
434 self
.report("%s : %s" % (msg
, err
))
438 def get_attr_linkID_and_reverse_name(self
, attrname
):
439 if attrname
in self
.link_id_cache
:
440 return self
.link_id_cache
[attrname
]
441 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
443 revname
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
446 self
.link_id_cache
[attrname
] = (linkID
, revname
)
447 return linkID
, revname
449 def err_empty_attribute(self
, dn
, attrname
):
450 '''fix empty attributes'''
451 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
452 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
453 self
.report("Not fixing empty attribute %s" % attrname
)
458 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
459 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
460 "Failed to remove empty attribute %s" % attrname
, validate
=False):
461 self
.report("Removed empty attribute %s" % attrname
)
463 def err_normalise_mismatch(self
, dn
, attrname
, values
):
464 '''fix attribute normalisation errors'''
465 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
468 normalised
= self
.samdb
.dsdb_normalise_attributes(
469 self
.samdb_schema
, attrname
, [val
])
470 if len(normalised
) != 1:
471 self
.report("Unable to normalise value '%s'" % val
)
472 mod_list
.append((val
, ''))
473 elif (normalised
[0] != val
):
474 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
475 mod_list
.append((val
, normalised
[0]))
476 if not self
.confirm_all('Fix normalisation for %s from %s?' % (attrname
, dn
), 'fix_all_normalisation'):
477 self
.report("Not fixing attribute %s" % attrname
)
482 for i
in range(0, len(mod_list
)):
483 (val
, nval
) = mod_list
[i
]
484 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
486 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
489 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
490 "Failed to normalise attribute %s" % attrname
,
492 self
.report("Normalised attribute %s" % attrname
)
494 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
495 '''fix attribute normalisation errors'''
496 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
497 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
498 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
499 if list(normalised
) == values
:
501 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
502 self
.report("Not fixing attribute '%s'" % attrname
)
507 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
509 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
510 "Failed to normalise attribute %s" % attrname
,
512 self
.report("Normalised attribute %s" % attrname
)
514 def err_duplicate_values(self
, dn
, attrname
, dup_values
, values
):
515 '''fix attribute normalisation errors'''
516 self
.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname
, dn
))
517 self
.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values
), ','.join(values
)))
518 if not self
.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_duplicates'):
519 self
.report("Not fixing attribute '%s'" % attrname
)
524 m
[attrname
] = ldb
.MessageElement(values
, ldb
.FLAG_MOD_REPLACE
, attrname
)
526 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
527 "Failed to remove duplicate value on attribute %s" % attrname
,
529 self
.report("Removed duplicate value on attribute %s" % attrname
)
531 def is_deleted_objects_dn(self
, dsdb_dn
):
532 '''see if a dsdb_Dn is the special Deleted Objects DN'''
533 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
535 def err_missing_objectclass(self
, dn
):
536 """handle object without objectclass"""
537 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
)))
538 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'):
539 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
541 if self
.do_delete(dn
, ["relax:0"],
542 "Failed to remove DN %s" % dn
):
543 self
.report("Removed DN %s" % dn
)
545 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, remove_plausible
=False):
546 """handle a DN pointing to a deleted object"""
547 if not remove_plausible
:
548 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
549 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
550 if not self
.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
551 self
.report("Not removing")
554 self
.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
555 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
556 if not self
.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
557 self
.report("Not removing")
562 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
563 if self
.do_modify(m
, ["show_recycled:1",
564 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
565 "Failed to remove deleted DN attribute %s" % attrname
):
566 self
.report("Removed deleted DN on attribute %s" % attrname
)
568 def err_missing_target_dn_or_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
569 """handle a missing target DN (if specified, GUID form can't be found,
570 and otherwise DN string form can't be found)"""
572 # Don't change anything if the object itself is deleted
573 if str(dn
).find('\\0ADEL') != -1:
574 # We don't bump the error count as Samba produces these
575 # in normal operation
576 self
.report("WARNING: no target object found for GUID "
577 "component link %s in deleted object "
578 "%s - %s" % (attrname
, dn
, val
))
579 self
.report("Not removing dangling one-way "
580 "link on deleted object "
581 "(tombstone garbage collection in progress?)")
584 # check if its a backlink
585 linkID
, _
= self
.get_attr_linkID_and_reverse_name(attrname
)
586 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
588 linkID
, reverse_link_name \
589 = self
.get_attr_linkID_and_reverse_name(attrname
)
590 if reverse_link_name
is not None:
591 self
.report("WARNING: no target object found for GUID "
592 "component for one-way forward link "
594 "%s - %s" % (attrname
, dn
, val
))
595 self
.report("Not removing dangling forward link")
598 nc_root
= self
.samdb
.get_nc_root(dn
)
599 target_nc_root
= self
.samdb
.get_nc_root(dsdb_dn
.dn
)
600 if nc_root
!= target_nc_root
:
601 # We don't bump the error count as Samba produces these
602 # in normal operation
603 self
.report("WARNING: no target object found for GUID "
604 "component for cross-partition link "
606 "%s - %s" % (attrname
, dn
, val
))
607 self
.report("Not removing dangling one-way "
608 "cross-partition link "
609 "(we might be mid-replication)")
612 # Due to our link handling one-way links pointing to
613 # missing objects are plausible.
615 # We don't bump the error count as Samba produces these
616 # in normal operation
617 self
.report("WARNING: no target object found for GUID "
618 "component for DN value %s in object "
619 "%s - %s" % (attrname
, dn
, val
))
620 self
.err_deleted_dn(dn
, attrname
, val
,
621 dsdb_dn
, dsdb_dn
, True)
624 # We bump the error count here, as we should have deleted this
625 self
.report("ERROR: no target object found for GUID "
626 "component for link %s in object "
627 "%s - %s" % (attrname
, dn
, val
))
628 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
, False)
631 def err_missing_dn_GUID_component(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
632 """handle a missing GUID extended DN component"""
633 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
634 controls
=["extended_dn:1:1", "show_recycled:1"]
636 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
637 attrs
=[], controls
=controls
)
638 except ldb
.LdbError
as e7
:
639 (enum
, estr
) = e7
.args
640 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
641 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
643 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
646 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
647 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
649 dsdb_dn
.dn
= res
[0].dn
651 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
652 self
.report("Not fixing %s" % errstr
)
656 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
657 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
659 if self
.do_modify(m
, ["show_recycled:1"],
660 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
661 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
663 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
664 """handle an incorrect binary DN component"""
665 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
666 controls
=["extended_dn:1:1", "show_recycled:1"]
668 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
669 self
.report("Not fixing %s" % errstr
)
673 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
674 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
676 if self
.do_modify(m
, ["show_recycled:1"],
677 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
678 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
680 def err_dn_string_component_old(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
681 """handle a DN string being incorrect"""
682 self
.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
683 dsdb_dn
.dn
= correct_dn
685 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
686 'fix_all_old_dn_string_component_mismatch'):
687 self
.report("Not fixing old string component")
691 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
692 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
693 if self
.do_modify(m
, ["show_recycled:1",
694 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
],
695 "Failed to fix old DN string on attribute %s" % (attrname
)):
696 self
.report("Fixed old DN string on attribute %s" % (attrname
))
698 def err_dn_component_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, mismatch_type
):
699 """handle a DN string being incorrect"""
700 self
.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type
, attrname
, dn
, val
))
701 dsdb_dn
.dn
= correct_dn
703 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
704 'fix_all_%s_dn_component_mismatch' % mismatch_type
):
705 self
.report("Not fixing %s component mismatch" % mismatch_type
)
709 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
710 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
711 if self
.do_modify(m
, ["show_recycled:1"],
712 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type
, attrname
)):
713 self
.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type
, attrname
))
715 def err_dn_component_missing_target_sid(self
, dn
, attrname
, val
, dsdb_dn
, target_sid_blob
):
716 """handle a DN string being incorrect"""
717 self
.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname
, dn
, val
))
719 if len(dsdb_dn
.prefix
) != 0:
720 self
.report("Not fixing missing DN SID on DN+BINARY or DN+STRING")
723 correct_dn
= ldb
.Dn(self
.samdb
, dsdb_dn
.dn
.extended_str())
724 correct_dn
.set_extended_component("SID", target_sid_blob
)
726 if not self
.confirm_all('Change DN to %s?' % correct_dn
.extended_str(),
727 'fix_all_SID_dn_component_missing'):
728 self
.report("Not fixing missing DN SID component")
731 target_guid_blob
= correct_dn
.get_extended_component("GUID")
732 guid_sid_dn
= ldb
.Dn(self
.samdb
, "")
733 guid_sid_dn
.set_extended_component("GUID", target_guid_blob
)
734 guid_sid_dn
.set_extended_component("SID", target_sid_blob
)
738 m
['new_value'] = ldb
.MessageElement(guid_sid_dn
.extended_str(), ldb
.FLAG_MOD_ADD
, attrname
)
741 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID
743 if self
.do_modify(m
, controls
,
744 "Failed to ADD missing DN SID on attribute %s" % (attrname
)):
745 self
.report("Fixed missing DN SID on attribute %s" % (attrname
))
747 def err_unknown_attribute(self
, obj
, attrname
):
748 '''handle an unknown attribute error'''
749 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
750 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
751 self
.report("Not removing %s" % attrname
)
755 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
756 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
757 "Failed to remove unknown attribute %s" % attrname
):
758 self
.report("Removed unknown attribute %s" % (attrname
))
760 def err_undead_linked_attribute(self
, obj
, attrname
, val
):
761 '''handle a link that should not be there on a deleted object'''
762 self
.report("ERROR: linked attribute '%s' to '%s' is present on "
763 "deleted object %s" % (attrname
, val
, obj
.dn
))
764 if not self
.confirm_all('Remove linked attribute %s' % attrname
, 'fix_undead_linked_attributes'):
765 self
.report("Not removing linked attribute %s" % attrname
)
769 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
771 if self
.do_modify(m
, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
772 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
773 "Failed to delete forward link %s" % attrname
):
774 self
.report("Fixed undead forward link %s" % (attrname
))
776 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
777 '''handle a missing backlink value'''
778 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
779 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
780 self
.report("Not fixing missing backlink %s" % backlink_name
)
784 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, backlink_name
)
785 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
786 "Failed to fix missing backlink %s" % backlink_name
):
787 self
.report("Fixed missing backlink %s" % (backlink_name
))
789 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
790 '''handle a incorrect RMD_FLAGS value'''
791 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
792 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()))
793 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
794 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
798 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
799 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
800 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
801 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
803 def err_orphaned_backlink(self
, obj_dn
, backlink_attr
, backlink_val
,
804 target_dn
, forward_attr
, forward_syntax
,
805 check_duplicates
=True):
806 '''handle a orphaned backlink value'''
807 if check_duplicates
is True and self
.has_duplicate_links(target_dn
, forward_attr
, forward_syntax
):
808 self
.report("WARNING: Keep orphaned backlink attribute " + \
809 "'%s' in '%s' for link '%s' in '%s'" % (
810 backlink_attr
, obj_dn
, forward_attr
, target_dn
))
812 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr
, obj_dn
, forward_attr
, target_dn
))
813 if not self
.confirm_all('Remove orphaned backlink %s' % backlink_attr
, 'fix_all_orphaned_backlinks'):
814 self
.report("Not removing orphaned backlink %s" % backlink_attr
)
818 m
['value'] = ldb
.MessageElement(backlink_val
, ldb
.FLAG_MOD_DELETE
, backlink_attr
)
819 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
820 "Failed to fix orphaned backlink %s" % backlink_attr
):
821 self
.report("Fixed orphaned backlink %s" % (backlink_attr
))
823 def err_recover_forward_links(self
, obj
, forward_attr
, forward_vals
):
824 '''handle a duplicate links value'''
826 self
.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr
, obj
.dn
))
828 if not self
.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr
, 'recover_all_forward_links'):
829 self
.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
830 forward_attr
, obj
.dn
))
834 m
['value'] = ldb
.MessageElement(forward_vals
, ldb
.FLAG_MOD_REPLACE
, forward_attr
)
835 if self
.do_modify(m
, ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS
],
836 "Failed to fix duplicate links in attribute '%s'" % forward_attr
):
837 self
.report("Fixed duplicate links in attribute '%s'" % (forward_attr
))
838 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
839 assert duplicate_cache_key
in self
.duplicate_link_cache
840 self
.duplicate_link_cache
[duplicate_cache_key
] = False
842 def err_no_fsmoRoleOwner(self
, obj
):
843 '''handle a missing fSMORoleOwner'''
844 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
845 res
= self
.samdb
.search("",
846 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
848 serviceName
= res
[0]["dsServiceName"][0]
849 if not self
.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
850 self
.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
854 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
855 if self
.do_modify(m
, [],
856 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
857 self
.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
859 def err_missing_parent(self
, obj
):
860 '''handle a missing parent'''
861 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
862 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
863 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
866 keep_transaction
= False
867 self
.samdb
.transaction_start()
869 nc_root
= self
.samdb
.get_nc_root(obj
.dn
);
870 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
871 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
872 new_dn
.remove_base_components(len(new_dn
) - 1)
873 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
874 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
875 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
879 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
881 if self
.do_modify(m
, [],
882 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
883 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
884 keep_transaction
= True
886 self
.samdb
.transaction_cancel()
890 self
.samdb
.transaction_commit()
892 self
.samdb
.transaction_cancel()
894 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
, controls
):
895 '''handle a wrong dn'''
897 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
898 new_rdn
.remove_base_components(len(new_rdn
) - 1)
899 new_parent
= new_dn
.parent()
902 if rdn_val
!= name_val
:
903 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
904 attributes
+= "name=%r" % (name_val
)
906 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
907 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
908 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
911 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, controls
,
912 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
913 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
915 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
916 '''handle a wrong instanceType'''
917 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
918 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
919 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
924 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
925 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
926 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
927 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
929 def err_short_userParameters(self
, obj
, attrname
, value
):
930 # This is a truncated userParameters due to a pre 4.1 replication bug
931 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
)))
933 def err_base64_userParameters(self
, obj
, attrname
, value
):
934 '''handle a wrong userParameters'''
935 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
936 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
937 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
942 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
943 if self
.do_modify(m
, [],
944 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
945 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
947 def err_utf8_userParameters(self
, obj
, attrname
, value
):
948 '''handle a wrong userParameters'''
949 self
.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj
.dn
))
950 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
951 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
956 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
957 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
958 if self
.do_modify(m
, [],
959 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
960 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
962 def err_doubled_userParameters(self
, obj
, attrname
, value
):
963 '''handle a wrong userParameters'''
964 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
965 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
966 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
971 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
972 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
973 if self
.do_modify(m
, [],
974 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
975 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
977 def err_odd_userParameters(self
, obj
, attrname
):
978 # This is a truncated userParameters due to a pre 4.1 replication bug
979 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
)))
981 def find_revealed_link(self
, dn
, attrname
, guid
):
982 '''return a revealed link in an object'''
983 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
984 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
985 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
986 for val
in res
[0][attrname
]:
987 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
988 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
993 def check_duplicate_links(self
, obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
):
994 '''check a linked values for duplicate forward links'''
997 duplicate_dict
= dict()
1000 # Only forward links can have this problem
1001 if forward_linkID
& 1:
1002 # If we got the reverse, skip it
1003 return (error_count
, duplicate_dict
, unique_dict
)
1005 if backlink_attr
is None:
1006 return (error_count
, duplicate_dict
, unique_dict
)
1008 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
1009 if duplicate_cache_key
not in self
.duplicate_link_cache
:
1010 self
.duplicate_link_cache
[duplicate_cache_key
] = False
1012 for val
in obj
[forward_attr
]:
1013 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), forward_syntax
)
1015 # all DNs should have a GUID component
1016 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1019 guidstr
= str(misc
.GUID(guid
))
1020 keystr
= guidstr
+ dsdb_dn
.prefix
1021 if keystr
not in unique_dict
:
1022 unique_dict
[keystr
] = dsdb_dn
1025 if keystr
not in duplicate_dict
:
1026 duplicate_dict
[keystr
] = dict()
1027 duplicate_dict
[keystr
]["keep"] = None
1028 duplicate_dict
[keystr
]["delete"] = list()
1030 # Now check for the highest RMD_VERSION
1031 v1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_VERSION"))
1032 v2
= int(dsdb_dn
.dn
.get_extended_component("RMD_VERSION"))
1034 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1035 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
1038 duplicate_dict
[keystr
]["keep"] = dsdb_dn
1039 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
1040 unique_dict
[keystr
] = dsdb_dn
1042 # Fallback to the highest RMD_LOCAL_USN
1043 u1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_LOCAL_USN"))
1044 u2
= int(dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN"))
1046 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1047 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
1049 duplicate_dict
[keystr
]["keep"] = dsdb_dn
1050 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
1051 unique_dict
[keystr
] = dsdb_dn
1053 if error_count
!= 0:
1054 self
.duplicate_link_cache
[duplicate_cache_key
] = True
1056 return (error_count
, duplicate_dict
, unique_dict
)
1058 def has_duplicate_links(self
, dn
, forward_attr
, forward_syntax
):
1059 '''check a linked values for duplicate forward links'''
1062 duplicate_cache_key
= "%s:%s" % (str(dn
), forward_attr
)
1063 if duplicate_cache_key
in self
.duplicate_link_cache
:
1064 return self
.duplicate_link_cache
[duplicate_cache_key
]
1066 forward_linkID
, backlink_attr
= self
.get_attr_linkID_and_reverse_name(forward_attr
)
1068 attrs
= [forward_attr
]
1069 controls
= ["extended_dn:1:1", "reveal_internals:0"]
1071 # check its the right GUID
1073 res
= self
.samdb
.search(base
=str(dn
), scope
=ldb
.SCOPE_BASE
,
1074 attrs
=attrs
, controls
=controls
)
1075 except ldb
.LdbError
as e8
:
1076 (enum
, estr
) = e8
.args
1077 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1083 error_count
, duplicate_dict
, unique_dict
= \
1084 self
.check_duplicate_links(obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
)
1086 if duplicate_cache_key
in self
.duplicate_link_cache
:
1087 return self
.duplicate_link_cache
[duplicate_cache_key
]
1091 def find_missing_forward_links_from_backlinks(self
, obj
,
1095 forward_unique_dict
):
1096 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1097 missing_forward_links
= []
1100 if backlink_attr
is None:
1101 return (missing_forward_links
, error_count
)
1103 if forward_syntax
!= ldb
.SYNTAX_DN
:
1104 self
.report("Not checking for missing forward links for syntax: %s",
1106 return (missing_forward_links
, error_count
)
1108 if "sortedLinks" in self
.compatibleFeatures
:
1109 self
.report("Not checking for missing forward links because the db " + \
1110 "has the sortedLinks feature")
1111 return (missing_forward_links
, error_count
)
1114 obj_guid
= obj
['objectGUID'][0]
1115 obj_guid_str
= str(ndr_unpack(misc
.GUID
, obj_guid
))
1116 filter = "(%s=<GUID=%s>)" % (backlink_attr
, obj_guid_str
)
1118 res
= self
.samdb
.search(expression
=filter,
1119 scope
=ldb
.SCOPE_SUBTREE
, attrs
=["objectGUID"],
1120 controls
=["extended_dn:1:1",
1121 "search_options:1:2",
1122 "paged_results:1:1000"])
1123 except ldb
.LdbError
as e9
:
1124 (enum
, estr
) = e9
.args
1128 target_dn
= dsdb_Dn(self
.samdb
, r
.dn
.extended_str(), forward_syntax
)
1130 guid
= target_dn
.dn
.get_extended_component("GUID")
1131 guidstr
= str(misc
.GUID(guid
))
1132 if guidstr
in forward_unique_dict
:
1135 # A valid forward link looks like this:
1137 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1138 # <RMD_ADDTIME=131607546230000000>;
1139 # <RMD_CHANGETIME=131607546230000000>;
1141 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1142 # <RMD_LOCAL_USN=3765>;
1143 # <RMD_ORIGINATING_USN=3765>;
1145 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1146 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1148 # Note that versions older than Samba 4.8 create
1149 # links with RMD_VERSION=0.
1151 # Try to get the local_usn and time from objectClass
1152 # if possible and fallback to any other one.
1153 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1154 obj
['replPropertyMetadata'][0])
1155 for o
in repl
.ctr
.array
:
1156 local_usn
= o
.local_usn
1157 t
= o
.originating_change_time
1158 if o
.attid
== drsuapi
.DRSUAPI_ATTID_objectClass
:
1161 # We use a magic invocationID for restoring missing
1162 # forward links to recover from bug #13228.
1163 # This should allow some more future magic to fix the
1166 # It also means it looses the conflict resolution
1167 # against almost every real invocation, if the
1168 # version is also 0.
1169 originating_invocid
= misc
.GUID("ffffffff-4700-4700-4700-000000b13228")
1175 rmd_invocid
= originating_invocid
1176 rmd_originating_usn
= originating_usn
1177 rmd_local_usn
= local_usn
1180 target_dn
.dn
.set_extended_component("RMD_ADDTIME", str(rmd_addtime
))
1181 target_dn
.dn
.set_extended_component("RMD_CHANGETIME", str(rmd_changetime
))
1182 target_dn
.dn
.set_extended_component("RMD_FLAGS", str(rmd_flags
))
1183 target_dn
.dn
.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid
))
1184 target_dn
.dn
.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn
))
1185 target_dn
.dn
.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn
))
1186 target_dn
.dn
.set_extended_component("RMD_VERSION", str(rmd_version
))
1189 missing_forward_links
.append(target_dn
)
1191 return (missing_forward_links
, error_count
)
1193 def check_dn(self
, obj
, attrname
, syntax_oid
):
1194 '''check a DN attribute for correctness'''
1196 obj_guid
= obj
['objectGUID'][0]
1198 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
1199 if reverse_link_name
is not None:
1200 reverse_syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(reverse_link_name
)
1202 reverse_syntax_oid
= None
1204 error_count
, duplicate_dict
, unique_dict
= \
1205 self
.check_duplicate_links(obj
, attrname
, syntax_oid
, linkID
, reverse_link_name
)
1207 if len(duplicate_dict
) != 0:
1209 missing_forward_links
, missing_error_count
= \
1210 self
.find_missing_forward_links_from_backlinks(obj
,
1211 attrname
, syntax_oid
,
1214 error_count
+= missing_error_count
1216 forward_links
= [dn
for dn
in unique_dict
.values()]
1218 if missing_error_count
!= 0:
1219 self
.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1222 self
.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname
, obj
.dn
))
1223 for m
in missing_forward_links
:
1224 self
.report("Missing link '%s'" % (m
))
1225 if not self
.confirm_all("Schedule readding missing forward link for attribute %s" % attrname
,
1226 'fix_all_missing_forward_links'):
1227 self
.err_orphaned_backlink(m
.dn
, reverse_link_name
,
1228 obj
.dn
.extended_str(), obj
.dn
,
1229 attrname
, syntax_oid
,
1230 check_duplicates
=False)
1232 forward_links
+= [m
]
1233 for keystr
in duplicate_dict
.keys():
1234 d
= duplicate_dict
[keystr
]
1235 for dd
in d
["delete"]:
1236 self
.report("Duplicate link '%s'" % dd
)
1237 self
.report("Correct link '%s'" % d
["keep"])
1239 # We now construct the sorted dn values.
1240 # They're sorted by the objectGUID of the target
1241 # See dsdb_Dn.__cmp__()
1242 vals
= [str(dn
) for dn
in sorted(forward_links
)]
1243 self
.err_recover_forward_links(obj
, attrname
, vals
)
1244 # We should continue with the fixed values
1245 obj
[attrname
] = ldb
.MessageElement(vals
, 0, attrname
)
1247 for val
in obj
[attrname
]:
1248 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
1250 # all DNs should have a GUID component
1251 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1254 self
.err_missing_dn_GUID_component(obj
.dn
, attrname
, val
, dsdb_dn
,
1258 guidstr
= str(misc
.GUID(guid
))
1259 attrs
= ['isDeleted', 'replPropertyMetaData']
1261 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
1262 fixing_msDS_HasInstantiatedNCs
= True
1263 attrs
.append("instanceType")
1265 fixing_msDS_HasInstantiatedNCs
= False
1267 if reverse_link_name
is not None:
1268 attrs
.append(reverse_link_name
)
1270 # check its the right GUID
1272 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
1273 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1",
1274 "reveal_internals:0"
1276 except ldb
.LdbError
as e3
:
1277 (enum
, estr
) = e3
.args
1278 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1281 # We don't always want to
1282 error_count
+= self
.err_missing_target_dn_or_GUID(obj
.dn
,
1288 if fixing_msDS_HasInstantiatedNCs
:
1289 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
1290 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
1292 if str(dsdb_dn
) != val
:
1294 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
1297 # now we have two cases - the source object might or might not be deleted
1298 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
1299 target_is_deleted
= 'isDeleted' in res
[0] and res
[0]['isDeleted'][0].upper() == 'TRUE'
1302 if is_deleted
and not obj
.dn
in self
.deleted_objects_containers
and linkID
:
1303 # A fully deleted object should not have any linked
1304 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1305 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1307 self
.err_undead_linked_attribute(obj
, attrname
, val
)
1310 elif target_is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
) and linkID
:
1311 # the target DN is not allowed to be deleted, unless the target DN is the
1312 # special Deleted Objects container
1314 local_usn
= dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN")
1316 if 'replPropertyMetaData' in res
[0]:
1317 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1318 str(res
[0]['replPropertyMetadata']))
1320 for o
in repl
.ctr
.array
:
1321 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1322 deleted_usn
= o
.local_usn
1323 if deleted_usn
>= int(local_usn
):
1324 # If the object was deleted after the link
1325 # was last modified then, clean it up here
1330 self
.err_deleted_dn(obj
.dn
, attrname
,
1331 val
, dsdb_dn
, res
[0].dn
, True)
1334 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
, False)
1337 # We should not check for incorrect
1338 # components on deleted links, as these are allowed to
1339 # go stale (we just need the GUID, not the name)
1340 rmd_blob
= dsdb_dn
.dn
.get_extended_component("RMD_FLAGS")
1342 if rmd_blob
is not None:
1343 rmd_flags
= int(rmd_blob
)
1345 # assert the DN matches in string form, where a reverse
1346 # link exists, otherwise (below) offer to fix it as a non-error.
1347 # The string form is essentially only kept for forensics,
1348 # as we always re-resolve by GUID in normal operations.
1349 if not rmd_flags
& 1 and reverse_link_name
is not None:
1350 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1352 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1353 res
[0].dn
, "string")
1356 if res
[0].dn
.get_extended_component("GUID") != dsdb_dn
.dn
.get_extended_component("GUID"):
1358 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1362 target_sid
= res
[0].dn
.get_extended_component("SID")
1363 link_sid
= dsdb_dn
.dn
.get_extended_component("SID")
1364 if link_sid
is None and target_sid
is not None:
1366 self
.err_dn_component_missing_target_sid(obj
.dn
, attrname
, val
,
1367 dsdb_dn
, target_sid
)
1369 if link_sid
!= target_sid
:
1371 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1375 # Only for non-links, not even forward-only links
1376 # (otherwise this breaks repl_meta_data):
1378 # Now we have checked the GUID and SID, offer to fix old
1379 # DN strings as a non-error (DNs, not links so no
1380 # backlink). Samba does not maintain this string
1381 # otherwise, so we don't increment error_count.
1382 if reverse_link_name
is None:
1383 if linkID
== 0 and str(res
[0].dn
) != str(dsdb_dn
.dn
):
1384 # Pass in the old/bad DN without the <GUID=...> part,
1385 # otherwise the LDB code will correct it on the way through
1386 # (Note: we still want to preserve the DSDB DN prefix in the
1387 # case of binary DNs)
1388 bad_dn
= dsdb_dn
.prefix
+ dsdb_dn
.dn
.get_linearized()
1389 self
.err_dn_string_component_old(obj
.dn
, attrname
, bad_dn
,
1393 # check the reverse_link is correct if there should be one
1395 if reverse_link_name
in res
[0]:
1396 for v
in res
[0][reverse_link_name
]:
1397 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1398 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1399 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1401 if v_blob
is not None:
1402 v_rmd_flags
= int(v_blob
)
1405 if v_guid
== obj_guid
:
1408 if match_count
!= 1:
1409 if syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
or reverse_syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
:
1411 # Forward binary multi-valued linked attribute
1413 for w
in obj
[attrname
]:
1414 w_guid
= dsdb_Dn(self
.samdb
, w
.decode('utf8')).dn
.get_extended_component("GUID")
1418 if match_count
== forward_count
:
1421 for v
in obj
[attrname
]:
1422 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1423 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1424 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1426 if v_blob
is not None:
1427 v_rmd_flags
= int(v_blob
)
1433 if match_count
== expected_count
:
1436 diff_count
= expected_count
- match_count
1439 # If there's a backward link on binary multi-valued linked attribute,
1440 # let the check on the forward link remedy the value.
1441 # UNLESS, there is no forward link detected.
1442 if match_count
== 0:
1444 self
.err_orphaned_backlink(obj
.dn
, attrname
,
1449 # Only warn here and let the forward link logic fix it.
1450 self
.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1451 attrname
, expected_count
, str(obj
.dn
),
1452 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1455 assert not target_is_deleted
1457 self
.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1458 attrname
, expected_count
, str(obj
.dn
),
1459 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1461 # Loop until the difference between the forward and
1462 # the backward links is resolved.
1463 while diff_count
!= 0:
1466 if match_count
> 0 or diff_count
> 1:
1467 # TODO no method to fix these right now
1468 self
.report("ERROR: Can't fix missing "
1469 "multi-valued backlinks on %s" % str(dsdb_dn
.dn
))
1471 self
.err_missing_backlink(obj
, attrname
,
1472 obj
.dn
.extended_str(),
1477 self
.err_orphaned_backlink(res
[0].dn
, reverse_link_name
,
1478 obj
.dn
.extended_str(), obj
.dn
,
1479 attrname
, syntax_oid
)
1485 def find_repl_attid(self
, repl
, attid
):
1486 for o
in repl
.ctr
.array
:
1487 if o
.attid
== attid
:
1492 def get_originating_time(self
, val
, attid
):
1493 '''Read metadata properties and return the originating time for
1494 a given attributeId.
1496 :return: the originating time or 0 if not found
1499 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
1500 o
= self
.find_repl_attid(repl
, attid
)
1502 return o
.originating_change_time
1505 def process_metadata(self
, dn
, val
):
1506 '''Read metadata properties and list attributes in it.
1507 raises KeyError if the attid is unknown.'''
1510 wrong_attids
= set()
1512 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1514 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
1517 for o
in repl
.ctr
.array
:
1518 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1519 set_att
.add(att
.lower())
1520 list_attid
.append(o
.attid
)
1521 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
,
1522 is_schema_nc
=in_schema_nc
)
1523 if correct_attid
!= o
.attid
:
1524 wrong_attids
.add(o
.attid
)
1526 return (set_att
, list_attid
, wrong_attids
)
1529 def fix_metadata(self
, obj
, attr
):
1530 '''re-write replPropertyMetaData elements for a single attribute for a
1531 object. This is used to fix missing replPropertyMetaData elements'''
1532 guid_str
= str(ndr_unpack(misc
.GUID
, obj
['objectGUID'][0]))
1533 dn
= ldb
.Dn(self
.samdb
, "<GUID=%s>" % guid_str
)
1534 res
= self
.samdb
.search(base
= dn
, scope
=ldb
.SCOPE_BASE
, attrs
= [attr
],
1535 controls
= ["search_options:1:2",
1538 nmsg
= ldb
.Message()
1540 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
1541 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
1542 "Failed to fix metadata for attribute %s" % attr
):
1543 self
.report("Fixed metadata for attribute %s" % attr
)
1545 def ace_get_effective_inherited_type(self
, ace
):
1546 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
1550 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
1552 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
1554 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
1556 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
1562 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
1565 return str(ace
.object.inherited_type
)
1567 def lookup_class_schemaIDGUID(self
, cls
):
1568 if cls
in self
.class_schemaIDGUID
:
1569 return self
.class_schemaIDGUID
[cls
]
1571 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1572 res
= self
.samdb
.search(base
=self
.schema_dn
,
1574 attrs
=["schemaIDGUID"])
1575 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
1577 self
.class_schemaIDGUID
[cls
] = t
1580 def process_sd(self
, dn
, obj
):
1581 sd_attr
= "nTSecurityDescriptor"
1582 sd_val
= obj
[sd_attr
]
1584 sd
= ndr_unpack(security
.descriptor
, str(sd_val
))
1586 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
1588 # we don't fix deleted objects
1591 sd_clean
= security
.descriptor()
1592 sd_clean
.owner_sid
= sd
.owner_sid
1593 sd_clean
.group_sid
= sd
.group_sid
1594 sd_clean
.type = sd
.type
1595 sd_clean
.revision
= sd
.revision
1598 last_inherited_type
= None
1601 if sd
.sacl
is not None:
1603 for i
in range(0, len(aces
)):
1606 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1607 sd_clean
.sacl_add(ace
)
1610 t
= self
.ace_get_effective_inherited_type(ace
)
1614 if last_inherited_type
is not None:
1615 if t
!= last_inherited_type
:
1616 # if it inherited from more than
1617 # one type it's very likely to be broken
1619 # If not the recalculation will calculate
1624 last_inherited_type
= t
1627 if sd
.dacl
is not None:
1629 for i
in range(0, len(aces
)):
1632 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1633 sd_clean
.dacl_add(ace
)
1636 t
= self
.ace_get_effective_inherited_type(ace
)
1640 if last_inherited_type
is not None:
1641 if t
!= last_inherited_type
:
1642 # if it inherited from more than
1643 # one type it's very likely to be broken
1645 # If not the recalculation will calculate
1650 last_inherited_type
= t
1653 return (sd_clean
, sd
)
1655 if last_inherited_type
is None:
1661 cls
= obj
["objectClass"][-1]
1662 except KeyError as e
:
1666 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1667 attrs
=["isDeleted", "objectClass"],
1668 controls
=["show_recycled:1"])
1670 is_deleted
= 'isDeleted' in o
and o
['isDeleted'][0].upper() == 'TRUE'
1672 # we don't fix deleted objects
1674 cls
= o
["objectClass"][-1]
1676 t
= self
.lookup_class_schemaIDGUID(cls
)
1678 if t
!= last_inherited_type
:
1680 return (sd_clean
, sd
)
1685 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
1686 '''re-write the SD due to incorrect inherited ACEs'''
1687 sd_attr
= "nTSecurityDescriptor"
1688 sd_val
= ndr_pack(sd
)
1689 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1691 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
1692 self
.report('Not fixing %s on %s\n' % (sd_attr
, dn
))
1695 nmsg
= ldb
.Message()
1697 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1698 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1699 "Failed to fix attribute %s" % sd_attr
):
1700 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1702 def err_wrong_default_sd(self
, dn
, sd
, sd_old
, diff
):
1703 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1704 sd_attr
= "nTSecurityDescriptor"
1705 sd_val
= ndr_pack(sd
)
1706 sd_old_val
= ndr_pack(sd_old
)
1707 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1708 if sd
.owner_sid
is not None:
1709 sd_flags |
= security
.SECINFO_OWNER
1710 if sd
.group_sid
is not None:
1711 sd_flags |
= security
.SECINFO_GROUP
1713 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
1714 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
1719 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1720 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
1721 "Failed to reset attribute %s" % sd_attr
):
1722 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1724 def err_missing_sd_owner(self
, dn
, sd
):
1725 '''re-write the SD due to a missing owner or group'''
1726 sd_attr
= "nTSecurityDescriptor"
1727 sd_val
= ndr_pack(sd
)
1728 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
1730 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
1731 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
1734 nmsg
= ldb
.Message()
1736 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1738 # By setting the session_info to admin_session_info and
1739 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1740 # flags we cause the descriptor module to set the correct
1741 # owner and group on the SD, replacing the None/NULL values
1742 # for owner_sid and group_sid currently present.
1744 # The admin_session_info matches that used in provision, and
1745 # is the best guess we can make for an existing object that
1746 # hasn't had something specifically set.
1748 # This is important for the dns related naming contexts.
1749 self
.samdb
.set_session_info(self
.admin_session_info
)
1750 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1751 "Failed to fix metadata for attribute %s" % sd_attr
):
1752 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1753 self
.samdb
.set_session_info(self
.system_session_info
)
1755 def is_expired_tombstone(self
, dn
, repl_val
):
1756 if self
.check_expired_tombstones
:
1757 # This is not the default, it's just
1758 # used to keep dbcheck tests work with
1759 # old static provision dumps
1762 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, repl_val
)
1764 isDeleted
= self
.find_repl_attid(repl
, drsuapi
.DRSUAPI_ATTID_isDeleted
)
1766 delete_time
= samba
.nttime2unix(isDeleted
.originating_change_time
)
1767 current_time
= time
.time()
1769 tombstone_delta
= self
.tombstoneLifetime
* (24 * 60 * 60)
1771 delta
= current_time
- delete_time
1772 if delta
<= tombstone_delta
:
1775 self
.report("SKIPING: object %s is an expired tombstone" % dn
)
1776 self
.report("isDeleted: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1779 isDeleted
.originating_invocation_id
,
1780 isDeleted
.originating_usn
,
1781 isDeleted
.local_usn
,
1782 time
.ctime(samba
.nttime2unix(isDeleted
.originating_change_time
))))
1783 self
.expired_tombstones
+= 1
1786 def find_changes_after_deletion(self
, repl_val
):
1787 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, repl_val
)
1789 isDeleted
= self
.find_repl_attid(repl
, drsuapi
.DRSUAPI_ATTID_isDeleted
)
1791 delete_time
= samba
.nttime2unix(isDeleted
.originating_change_time
)
1793 tombstone_delta
= self
.tombstoneLifetime
* (24 * 60 * 60)
1796 for o
in repl
.ctr
.array
:
1797 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1800 if o
.local_usn
<= isDeleted
.local_usn
:
1803 if o
.originating_change_time
<= isDeleted
.originating_change_time
:
1806 change_time
= samba
.nttime2unix(o
.originating_change_time
)
1808 delta
= change_time
- delete_time
1809 if delta
<= tombstone_delta
:
1812 # If the modification happened after the tombstone lifetime
1813 # has passed, we have a bug as the object might be deleted
1814 # already on other DCs and won't be able to replicate
1818 return found
, isDeleted
1820 def has_changes_after_deletion(self
, dn
, repl_val
):
1821 found
, isDeleted
= self
.find_changes_after_deletion(repl_val
)
1825 def report_attid(o
):
1827 attname
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1829 attname
= "<unknown:0x%x08x>" % o
.attid
1831 self
.report("%s: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1832 attname
, o
.attid
, o
.version
,
1833 o
.originating_invocation_id
,
1836 time
.ctime(samba
.nttime2unix(o
.originating_change_time
))))
1838 self
.report("ERROR: object %s, has changes after deletion" % dn
)
1839 report_attid(isDeleted
)
1845 def err_changes_after_deletion(self
, dn
, repl_val
):
1846 found
, isDeleted
= self
.find_changes_after_deletion(repl_val
)
1848 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1849 rdn_attr
= dn
.get_rdn_name()
1850 rdn_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(rdn_attr
,
1851 is_schema_nc
=in_schema_nc
)
1855 if o
.attid
== rdn_attid
:
1857 if o
.attid
== drsuapi
.DRSUAPI_ATTID_name
:
1859 if o
.attid
== drsuapi
.DRSUAPI_ATTID_lastKnownParent
:
1862 attname
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1864 attname
= "<unknown:0x%x08x>" % o
.attid
1865 unexpected
.append(attname
)
1867 if len(unexpected
) > 0:
1868 self
.report('Unexpeted attributes: %s' % ",".join(unexpected
))
1869 self
.report('Not fixing changes after deletion bug')
1872 if not self
.confirm_all('Delete broken tombstone object %s deleted %s days ago?' % (
1873 dn
, self
.tombstoneLifetime
), 'fix_changes_after_deletion_bug'):
1874 self
.report('Not fixing changes after deletion bug')
1877 if self
.do_delete(dn
, ["relax:0"],
1878 "Failed to remove DN %s" % dn
):
1879 self
.report("Removed DN %s" % dn
)
1881 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
1882 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1883 str(repl_meta_data
))
1887 # Search for a zero invocationID
1888 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1892 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1893 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1894 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1895 % (dn
, o
.attid
, o
.version
,
1896 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
1897 self
.samdb
.get_invocation_id()))
1902 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
1903 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1904 str(repl_meta_data
))
1906 now
= samba
.unix2nttime(int(time
.time()))
1909 # Search for a zero invocationID
1910 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1914 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
1915 o
.version
= o
.version
+ 1
1916 o
.originating_change_time
= now
1917 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
1918 o
.originating_usn
= seq
1922 replBlob
= ndr_pack(repl
)
1926 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1927 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1928 self
.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr
, dn
))
1931 nmsg
= ldb
.Message()
1933 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1934 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1935 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1936 "Failed to fix attribute %s" % attr
):
1937 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1940 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
1941 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1942 str(repl_meta_data
))
1945 # Search for an invalid attid
1947 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1949 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
1953 def err_replmetadata_incorrect_attid(self
, dn
, attr
, repl_meta_data
, wrong_attids
):
1954 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1955 str(repl_meta_data
))
1959 remove_attid
= set()
1962 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1965 # Sort the array, except for the last element. This strange
1966 # construction, creating a new list, due to bugs in samba's
1967 # array handling in IDL generated objects.
1968 ctr
.array
= sorted(ctr
.array
[:], key
=lambda o
: o
.attid
)
1969 # Now walk it in reverse, so we see the low (and so incorrect,
1970 # the correct values are above 0x80000000) values first and
1971 # remove the 'second' value we see.
1972 for o
in reversed(ctr
.array
):
1973 print("%s: 0x%08x" % (dn
, o
.attid
))
1974 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1975 if att
.lower() in set_att
:
1976 self
.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att
, attr
, dn
))
1977 if not self
.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1978 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
),
1979 'fix_replmetadata_duplicate_attid'):
1980 self
.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1981 % (o
.attid
, att
, attr
, dn
))
1984 remove_attid
.add(o
.attid
)
1985 # We want to set the metadata for the most recent
1986 # update to have been applied locally, that is the metadata
1987 # matching the (eg string) value in the attribute
1988 if o
.local_usn
> hash_att
[att
].local_usn
:
1989 # This is always what we would have sent over DRS,
1990 # because the DRS server will have sent the
1991 # msDS-IntID, but with the values from both
1992 # attribute entries.
1993 hash_att
[att
].version
= o
.version
1994 hash_att
[att
].originating_change_time
= o
.originating_change_time
1995 hash_att
[att
].originating_invocation_id
= o
.originating_invocation_id
1996 hash_att
[att
].originating_usn
= o
.originating_usn
1997 hash_att
[att
].local_usn
= o
.local_usn
1999 # Do not re-add the value to the set or overwrite the hash value
2003 set_att
.add(att
.lower())
2005 # Generate a real list we can sort on properly
2006 new_list
= [o
for o
in ctr
.array
if o
.attid
not in remove_attid
]
2008 if (len(wrong_attids
) > 0):
2010 if o
.attid
in wrong_attids
:
2011 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
2012 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
, is_schema_nc
=in_schema_nc
)
2013 self
.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr
, dn
))
2014 if not self
.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
2015 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
), 'fix_replmetadata_wrong_attid'):
2016 self
.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
2017 % (o
.attid
, correct_attid
, att
, attr
, dn
))
2020 o
.attid
= correct_attid
2022 # Sort the array, (we changed the value so must re-sort)
2023 new_list
[:] = sorted(new_list
[:], key
=lambda o
: o
.attid
)
2025 # If we did not already need to fix it, then ask about sorting
2027 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
2028 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
2029 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
2030 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
2033 # The actual sort done is done at the top of the function
2035 ctr
.count
= len(new_list
)
2036 ctr
.array
= new_list
2037 replBlob
= ndr_pack(repl
)
2039 nmsg
= ldb
.Message()
2041 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
2042 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
2043 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
2044 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
2045 "Failed to fix attribute %s" % attr
):
2046 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
2049 def is_deleted_deleted_objects(self
, obj
):
2051 if "description" not in obj
:
2052 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
2054 if "showInAdvancedViewOnly" not in obj
or obj
['showInAdvancedViewOnly'][0].upper() == 'FALSE':
2055 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
2057 if "objectCategory" not in obj
:
2058 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
2060 if "isCriticalSystemObject" not in obj
or obj
['isCriticalSystemObject'][0].upper() == 'FALSE':
2061 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
2063 if "isRecycled" in obj
:
2064 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
2066 if "isDeleted" in obj
and obj
['isDeleted'][0].upper() == 'FALSE':
2067 self
.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj
.dn
)
2069 if "objectClass" not in obj
or (len(obj
['objectClass']) != 2 or
2070 obj
['objectClass'][0] != 'top' or
2071 obj
['objectClass'][1] != 'container'):
2072 self
.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj
.dn
)
2074 if "systemFlags" not in obj
or obj
['systemFlags'][0] != '-1946157056':
2075 self
.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj
.dn
)
2079 def err_deleted_deleted_objects(self
, obj
):
2080 nmsg
= ldb
.Message()
2081 nmsg
.dn
= dn
= obj
.dn
2083 if "description" not in obj
:
2084 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
2085 if "showInAdvancedViewOnly" not in obj
:
2086 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
2087 if "objectCategory" not in obj
:
2088 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
2089 if "isCriticalSystemObject" not in obj
:
2090 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
2091 if "isRecycled" in obj
:
2092 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
2094 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2095 nmsg
["systemFlags"] = ldb
.MessageElement("-1946157056", ldb
.FLAG_MOD_REPLACE
, "systemFlags")
2096 nmsg
["objectClass"] = ldb
.MessageElement(["top", "container"], ldb
.FLAG_MOD_REPLACE
, "objectClass")
2098 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
2099 % (dn
), 'fix_deleted_deleted_objects'):
2100 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
2103 if self
.do_modify(nmsg
, ["relax:0"],
2104 "Failed to fix Deleted Objects container %s" % dn
):
2105 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
2107 def err_replica_locations(self
, obj
, cross_ref
, attr
):
2108 nmsg
= ldb
.Message()
2110 target
= self
.samdb
.get_dsServiceName()
2112 if self
.samdb
.am_rodc():
2113 self
.report('Not fixing %s for the RODC' % (attr
, obj
.dn
))
2116 if not self
.confirm_all('Add yourself to the replica locations for %s?'
2117 % (obj
.dn
), 'fix_replica_locations'):
2118 self
.report('Not fixing missing/incorrect attributes on %s\n' % (obj
.dn
))
2121 nmsg
[attr
] = ldb
.MessageElement(target
, ldb
.FLAG_MOD_ADD
, attr
)
2122 if self
.do_modify(nmsg
, [], "Failed to add %s for %s" % (attr
, obj
.dn
)):
2123 self
.report("Fixed %s for %s" % (attr
, obj
.dn
))
2125 def is_fsmo_role(self
, dn
):
2126 if dn
== self
.samdb
.domain_dn
:
2128 if dn
== self
.infrastructure_dn
:
2130 if dn
== self
.naming_dn
:
2132 if dn
== self
.schema_dn
:
2134 if dn
== self
.rid_dn
:
2139 def calculate_instancetype(self
, dn
):
2141 nc_root
= self
.samdb
.get_nc_root(dn
)
2143 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
2145 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
2146 except ldb
.LdbError
as e4
:
2147 (enum
, estr
) = e4
.args
2148 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2151 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
2152 if self
.write_ncs
is not None and str(nc_root
) in [str(x
) for x
in self
.write_ncs
]:
2153 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
2157 def get_wellknown_sd(self
, dn
):
2158 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
2160 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
2161 return ndr_unpack(security
.descriptor
,
2162 descriptor_fn(domain_sid
,
2163 name_map
=self
.name_map
))
2167 def check_object(self
, dn
, attrs
=['*']):
2168 '''check one object'''
2170 self
.report("Checking object %s" % dn
)
2172 # If we modify the pass-by-reference attrs variable, then we get a
2173 # replPropertyMetadata for every object that we check.
2175 if "dn" in map(str.lower
, attrs
):
2176 attrs
.append("name")
2177 if "distinguishedname" in map(str.lower
, attrs
):
2178 attrs
.append("name")
2179 if str(dn
.get_rdn_name()).lower() in map(str.lower
, attrs
):
2180 attrs
.append("name")
2181 if 'name' in map(str.lower
, attrs
):
2182 attrs
.append(dn
.get_rdn_name())
2183 attrs
.append("isDeleted")
2184 attrs
.append("systemFlags")
2185 need_replPropertyMetaData
= False
2187 need_replPropertyMetaData
= True
2190 linkID
, _
= self
.get_attr_linkID_and_reverse_name(a
)
2195 need_replPropertyMetaData
= True
2197 if need_replPropertyMetaData
:
2198 attrs
.append("replPropertyMetaData")
2199 attrs
.append("objectGUID")
2203 sd_flags |
= security
.SECINFO_OWNER
2204 sd_flags |
= security
.SECINFO_GROUP
2205 sd_flags |
= security
.SECINFO_DACL
2206 sd_flags |
= security
.SECINFO_SACL
2208 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
2213 "sd_flags:1:%d" % sd_flags
,
2214 "reveal_internals:0",
2217 except ldb
.LdbError
as e10
:
2218 (enum
, estr
) = e10
.args
2219 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2220 if self
.in_transaction
:
2221 self
.report("ERROR: Object %s disappeared during check" % dn
)
2226 self
.report("ERROR: Object %s failed to load during check" % dn
)
2230 set_attrs_from_md
= set()
2231 set_attrs_seen
= set()
2232 got_objectclass
= False
2234 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
2236 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
2237 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
2239 # We have no deleted objects DN for schema, and we check for this above for the other
2241 deleted_objects_dn
= None
2244 object_rdn_attr
= None
2245 object_rdn_val
= None
2249 repl_meta_data_val
= None
2251 for attrname
in obj
:
2252 if str(attrname
).lower() == 'isdeleted':
2253 if str(obj
[attrname
][0]) != "FALSE":
2256 if str(attrname
).lower() == 'systemflags':
2257 systemFlags
= int(obj
[attrname
][0])
2259 if str(attrname
).lower() == 'replpropertymetadata':
2260 repl_meta_data_val
= obj
[attrname
][0]
2262 if isDeleted
and repl_meta_data_val
:
2263 if self
.has_changes_after_deletion(dn
, repl_meta_data_val
):
2265 self
.err_changes_after_deletion(dn
, repl_meta_data_val
)
2267 if self
.is_expired_tombstone(dn
, repl_meta_data_val
):
2270 for attrname
in obj
:
2271 if attrname
== 'dn' or attrname
== "distinguishedName":
2274 if str(attrname
).lower() == 'objectclass':
2275 got_objectclass
= True
2277 if str(attrname
).lower() == "name":
2278 if len(obj
[attrname
]) != 1:
2280 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2281 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2283 name_val
= obj
[attrname
][0]
2285 if str(attrname
).lower() == str(obj
.dn
.get_rdn_name()).lower():
2286 object_rdn_attr
= attrname
2287 if len(obj
[attrname
]) != 1:
2289 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2290 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2292 object_rdn_val
= str(obj
[attrname
][0])
2294 if str(attrname
).lower() == 'replpropertymetadata':
2295 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
]):
2297 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
])
2298 # We don't continue, as we may also have other fixes for this attribute
2299 # based on what other attributes we see.
2302 (set_attrs_from_md
, list_attid_from_md
, wrong_attids
) \
2303 = self
.process_metadata(dn
, obj
[attrname
])
2306 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
2309 if len(set_attrs_from_md
) < len(list_attid_from_md
) \
2310 or len(wrong_attids
) > 0 \
2311 or sorted(list_attid_from_md
) != list_attid_from_md
:
2313 self
.err_replmetadata_incorrect_attid(dn
, attrname
, obj
[attrname
], wrong_attids
)
2316 # Here we check that the first attid is 0
2318 if list_attid_from_md
[0] != 0:
2320 self
.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2321 (attrname
, str(dn
)))
2325 if str(attrname
).lower() == 'ntsecuritydescriptor':
2326 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
2327 if sd_broken
is not None:
2328 self
.err_wrong_sd(dn
, sd
, sd_broken
)
2332 if sd
.owner_sid
is None or sd
.group_sid
is None:
2333 self
.err_missing_sd_owner(dn
, sd
)
2337 if self
.reset_well_known_acls
:
2339 well_known_sd
= self
.get_wellknown_sd(dn
)
2343 current_sd
= ndr_unpack(security
.descriptor
,
2344 str(obj
[attrname
][0]))
2346 diff
= get_diff_sds(well_known_sd
, current_sd
, security
.dom_sid(self
.samdb
.get_domain_sid()))
2348 self
.err_wrong_default_sd(dn
, well_known_sd
, current_sd
, diff
)
2353 if str(attrname
).lower() == 'objectclass':
2354 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
2355 # Do not consider the attribute incorrect if:
2356 # - The sorted (alphabetically) list is the same, inclding case
2357 # - The first and last elements are the same
2359 # This avoids triggering an error due to
2360 # non-determinism in the sort routine in (at least)
2361 # 4.3 and earlier, and the fact that any AUX classes
2362 # in these attributes are also not sorted when
2363 # imported from Windows (they are just in the reverse
2364 # order of last set)
2365 if sorted(normalised
) != sorted(obj
[attrname
]) \
2366 or normalised
[0] != obj
[attrname
][0] \
2367 or normalised
[-1] != obj
[attrname
][-1]:
2368 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
2372 if str(attrname
).lower() == 'userparameters':
2373 if len(obj
[attrname
][0]) == 1 and obj
[attrname
][0][0] == '\x20':
2375 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
2378 elif obj
[attrname
][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2379 # This is the correct, normal prefix
2382 elif obj
[attrname
][0][:20] == 'IAAgACAAIAAgACAAIAAg':
2383 # this is the typical prefix from a windows migration
2385 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
2388 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':
2389 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2391 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
2394 elif len(obj
[attrname
][0]) % 2 != 0:
2395 # This is a value that isn't even in length
2397 self
.err_odd_userParameters(obj
, attrname
, obj
[attrname
])
2400 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':
2401 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2403 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
2406 if attrname
.lower() == 'attributeid' or attrname
.lower() == 'governsid':
2407 if obj
[attrname
][0] in self
.attribute_or_class_ids
:
2409 self
.report('Error: %s %s on %s already exists as an attributeId or governsId'
2410 % (attrname
, obj
.dn
, obj
[attrname
][0]))
2412 self
.attribute_or_class_ids
.add(obj
[attrname
][0])
2414 # check for empty attributes
2415 for val
in obj
[attrname
]:
2417 self
.err_empty_attribute(dn
, attrname
)
2421 # get the syntax oid for the attribute, so we can can have
2422 # special handling for some specific attribute types
2424 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
2425 except Exception as msg
:
2426 self
.err_unknown_attribute(obj
, attrname
)
2430 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
2432 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
2433 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
2434 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
2436 set_attrs_seen
.add(str(attrname
).lower())
2438 if syntax_oid
in [ dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
2439 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
2440 # it's some form of DN, do specialised checking on those
2441 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
2445 # check for incorrectly normalised attributes
2446 for val
in obj
[attrname
]:
2447 values
.add(str(val
))
2449 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
2450 if len(normalised
) != 1 or normalised
[0] != val
:
2451 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
2455 if len(obj
[attrname
]) != len(values
):
2456 self
.err_duplicate_values(dn
, attrname
, obj
[attrname
], list(values
))
2460 if str(attrname
).lower() == "instancetype":
2461 calculated_instancetype
= self
.calculate_instancetype(dn
)
2462 if len(obj
["instanceType"]) != 1 or int(obj
["instanceType"][0]) != calculated_instancetype
:
2464 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
2466 if not got_objectclass
and ("*" in attrs
or "objectclass" in map(str.lower
, attrs
)):
2468 self
.err_missing_objectclass(dn
)
2470 if ("*" in attrs
or "name" in map(str.lower
, attrs
)):
2471 if name_val
is None:
2473 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
2474 if object_rdn_attr
is None:
2476 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
2478 if name_val
is not None:
2480 controls
= ["show_recycled:1", "relax:0"]
2482 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
2483 parent_dn
= deleted_objects_dn
2484 controls
+= ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
]
2485 if parent_dn
is None:
2486 parent_dn
= obj
.dn
.parent()
2487 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
2488 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
2490 if obj
.dn
== deleted_objects_dn
:
2491 expected_dn
= obj
.dn
2493 if expected_dn
!= obj
.dn
:
2495 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
,
2496 object_rdn_val
, name_val
, controls
)
2497 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
2499 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
, object_rdn_val
, str(obj
.dn
)))
2502 if repl_meta_data_val
:
2503 if obj
.dn
== deleted_objects_dn
:
2504 isDeletedAttId
= 131120
2505 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2507 expectedTimeDo
= 2650466015990000000
2508 originating
= self
.get_originating_time(repl_meta_data_val
, isDeletedAttId
)
2509 if originating
!= expectedTimeDo
:
2510 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
2511 nmsg
= ldb
.Message()
2513 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2515 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
2518 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
2520 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
2522 self
.report("On object %s" % dn
)
2525 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
2526 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
2527 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
2529 self
.fix_metadata(obj
, att
)
2531 if self
.is_fsmo_role(dn
):
2532 if "fSMORoleOwner" not in obj
and ("*" in attrs
or "fsmoroleowner" in map(str.lower
, attrs
)):
2533 self
.err_no_fsmoRoleOwner(obj
)
2537 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
2538 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
2539 controls
=["show_recycled:1", "show_deleted:1"])
2540 except ldb
.LdbError
as e11
:
2541 (enum
, estr
) = e11
.args
2542 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2544 self
.report("WARNING: parent object not found for %s" % (obj
.dn
))
2545 self
.report("Not moving to LostAndFound "
2546 "(tombstone garbage collection in progress?)")
2548 self
.err_missing_parent(obj
)
2553 if dn
in self
.deleted_objects_containers
and '*' in attrs
:
2554 if self
.is_deleted_deleted_objects(obj
):
2555 self
.err_deleted_deleted_objects(obj
)
2558 for (dns_part
, msg
) in self
.dns_partitions
:
2559 if dn
== dns_part
and 'repsFrom' in obj
:
2560 location
= "msDS-NC-Replica-Locations"
2561 if self
.samdb
.am_rodc():
2562 location
= "msDS-NC-RO-Replica-Locations"
2564 if location
not in msg
:
2565 # There are no replica locations!
2566 self
.err_replica_locations(obj
, msg
.dn
, location
)
2571 for loc
in msg
[location
]:
2572 if loc
== self
.samdb
.get_dsServiceName():
2575 # This DC is not in the replica locations
2576 self
.err_replica_locations(obj
, msg
.dn
, location
)
2579 if dn
== self
.server_ref_dn
:
2580 # Check we have a valid RID Set
2581 if "*" in attrs
or "rIDSetReferences" in attrs
:
2582 if "rIDSetReferences" not in obj
:
2583 # NO RID SET reference
2584 # We are RID master, allocate it.
2587 if self
.is_rid_master
:
2588 # Allocate a RID Set
2589 if self
.confirm_all('Allocate the missing RID set for RID master?',
2590 'fix_missing_rid_set_master'):
2592 # We don't have auto-transaction logic on
2593 # extended operations, so we have to do it
2596 self
.samdb
.transaction_start()
2599 self
.samdb
.create_own_rid_set()
2602 self
.samdb
.transaction_cancel()
2605 self
.samdb
.transaction_commit()
2608 elif not self
.samdb
.am_rodc():
2609 self
.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn
)
2612 # Check some details of our own RID Set
2613 if dn
== self
.rid_set_dn
:
2614 res
= self
.samdb
.search(base
=self
.rid_set_dn
, scope
=ldb
.SCOPE_BASE
,
2615 attrs
=["rIDAllocationPool",
2616 "rIDPreviousAllocationPool",
2619 if "rIDAllocationPool" not in res
[0]:
2620 self
.report("No rIDAllocationPool found in %s" % dn
)
2623 next_pool
= int(res
[0]["rIDAllocationPool"][0])
2625 high
= (0xFFFFFFFF00000000 & next_pool
) >> 32
2626 low
= 0x00000000FFFFFFFF & next_pool
2629 self
.report("Invalid RID set %d-%s, %d > %d!" % (low
, high
, low
, high
))
2632 if "rIDNextRID" in res
[0]:
2633 next_free_rid
= int(res
[0]["rIDNextRID"][0])
2637 if next_free_rid
== 0:
2642 # Check the remainder of this pool for conflicts. If
2643 # ridalloc_allocate_rid() moves to a new pool, this
2644 # will be above high, so we will stop.
2645 while next_free_rid
<= high
:
2646 sid
= "%s-%d" % (self
.samdb
.get_domain_sid(), next_free_rid
)
2648 res
= self
.samdb
.search(base
="<SID=%s>" % sid
, scope
=ldb
.SCOPE_BASE
,
2650 except ldb
.LdbError
as e
:
2651 (enum
, estr
) = e
.args
2652 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2656 self
.report("SID %s for %s conflicts with our current RID set in %s" % (sid
, res
[0].dn
, dn
))
2659 if self
.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2661 'fix_sid_rid_set_conflict'):
2662 self
.samdb
.transaction_start()
2664 # This will burn RIDs, which will move
2665 # past the conflict. We then check again
2666 # to see if the new RID conflicts, until
2667 # the end of the current pool. We don't
2668 # look at the next pool to avoid burning
2669 # all RIDs in one go in some strange
2673 allocated_rid
= self
.samdb
.allocate_rid()
2674 if allocated_rid
>= next_free_rid
:
2675 next_free_rid
= allocated_rid
+ 1
2678 self
.samdb
.transaction_cancel()
2681 self
.samdb
.transaction_commit()
2690 ################################################################
2691 # check special @ROOTDSE attributes
2692 def check_rootdse(self
):
2693 '''check the @ROOTDSE special object'''
2694 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
2696 self
.report("Checking object %s" % dn
)
2697 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
2699 self
.report("Object %s disappeared during check" % dn
)
2704 # check that the dsServiceName is in GUID form
2705 if not 'dsServiceName' in obj
:
2706 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
2707 return error_count
+1
2709 if not obj
['dsServiceName'][0].startswith('<GUID='):
2710 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2712 if not self
.confirm('Change dsServiceName to GUID form?'):
2714 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0].decode('utf8')),
2715 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
2716 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
2719 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
2720 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
2721 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
2722 self
.report("Changed dsServiceName to GUID form")
2726 ###############################################
2727 # re-index the database
2728 def reindex_database(self
):
2729 '''re-index the whole database'''
2731 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
2732 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
2733 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
2734 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
2736 ###############################################
2738 def reset_modules(self
):
2739 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2741 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
2742 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
2743 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)