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
38 # vals is a sequence of ldb.bytes objects which are a subclass
39 # of 'byte' type in python3 and just a str type in python2, to
40 # display as string these need to be converted to string via (str)
41 # function in python3 but that may generate a UnicodeDecode error,
42 # if so use repr instead. We need to at least try to get the 'str'
43 # value if possible to allow some tests which check the strings
44 # outputted to pass, these tests compare attr values logged to stdout
45 # against those in various results files.
47 def dump_attr_values(vals
):
53 result
= result
+ str(value
)
54 except UnicodeDecodeError:
55 result
= result
+ repr(value
)
58 class dbcheck(object):
59 """check a SAM database for errors"""
61 def __init__(self
, samdb
, samdb_schema
=None, verbose
=False, fix
=False,
62 yes
=False, quiet
=False, in_transaction
=False,
63 quick_membership_checks
=False,
64 reset_well_known_acls
=False,
65 check_expired_tombstones
=False):
67 self
.dict_oid_name
= None
68 self
.samdb_schema
= (samdb_schema
or samdb
)
69 self
.verbose
= verbose
73 self
.remove_all_unknown_attributes
= False
74 self
.remove_all_empty_attributes
= False
75 self
.fix_all_normalisation
= False
76 self
.fix_all_duplicates
= False
77 self
.fix_all_DN_GUIDs
= False
78 self
.fix_all_binary_dn
= False
79 self
.remove_implausible_deleted_DN_links
= False
80 self
.remove_plausible_deleted_DN_links
= False
81 self
.fix_all_string_dn_component_mismatch
= False
82 self
.fix_all_GUID_dn_component_mismatch
= False
83 self
.fix_all_SID_dn_component_mismatch
= False
84 self
.fix_all_SID_dn_component_missing
= False
85 self
.fix_all_old_dn_string_component_mismatch
= False
86 self
.fix_all_metadata
= False
87 self
.fix_time_metadata
= False
88 self
.fix_undead_linked_attributes
= False
89 self
.fix_all_missing_backlinks
= False
90 self
.fix_all_orphaned_backlinks
= False
91 self
.fix_all_missing_forward_links
= False
92 self
.duplicate_link_cache
= dict()
93 self
.recover_all_forward_links
= False
94 self
.fix_rmd_flags
= False
95 self
.fix_ntsecuritydescriptor
= False
96 self
.fix_ntsecuritydescriptor_owner_group
= False
97 self
.seize_fsmo_role
= False
98 self
.move_to_lost_and_found
= False
99 self
.fix_instancetype
= False
100 self
.fix_replmetadata_zero_invocationid
= False
101 self
.fix_replmetadata_duplicate_attid
= False
102 self
.fix_replmetadata_wrong_attid
= False
103 self
.fix_replmetadata_unsorted_attid
= False
104 self
.fix_deleted_deleted_objects
= False
105 self
.fix_incorrect_deleted_objects
= False
107 self
.fix_base64_userparameters
= False
108 self
.fix_utf8_userparameters
= False
109 self
.fix_doubled_userparameters
= False
110 self
.fix_sid_rid_set_conflict
= False
111 self
.quick_membership_checks
= quick_membership_checks
112 self
.reset_well_known_acls
= reset_well_known_acls
113 self
.check_expired_tombstones
= check_expired_tombstones
114 self
.expired_tombstones
= 0
115 self
.reset_all_well_known_acls
= False
116 self
.in_transaction
= in_transaction
117 self
.infrastructure_dn
= ldb
.Dn(samdb
, "CN=Infrastructure," + samdb
.domain_dn())
118 self
.naming_dn
= ldb
.Dn(samdb
, "CN=Partitions,%s" % samdb
.get_config_basedn())
119 self
.schema_dn
= samdb
.get_schema_basedn()
120 self
.rid_dn
= ldb
.Dn(samdb
, "CN=RID Manager$,CN=System," + samdb
.domain_dn())
121 self
.ntds_dsa
= ldb
.Dn(samdb
, samdb
.get_dsServiceName())
122 self
.class_schemaIDGUID
= {}
123 self
.wellknown_sds
= get_wellknown_sds(self
.samdb
)
124 self
.fix_all_missing_objectclass
= False
125 self
.fix_missing_deleted_objects
= False
126 self
.fix_replica_locations
= False
127 self
.fix_missing_rid_set_master
= False
128 self
.fix_changes_after_deletion_bug
= False
131 self
.link_id_cache
= {}
134 res
= samdb
.search(base
="CN=DnsAdmins,CN=Users,%s" % samdb
.domain_dn(), scope
=ldb
.SCOPE_BASE
,
136 dnsadmins_sid
= ndr_unpack(security
.dom_sid
, res
[0]["objectSid"][0])
137 self
.name_map
['DnsAdmins'] = str(dnsadmins_sid
)
138 except ldb
.LdbError
as e5
:
139 (enum
, estr
) = e5
.args
140 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
144 self
.system_session_info
= system_session()
145 self
.admin_session_info
= admin_session(None, samdb
.get_domain_sid())
147 res
= self
.samdb
.search(base
=self
.ntds_dsa
, scope
=ldb
.SCOPE_BASE
, attrs
=['msDS-hasMasterNCs', 'hasMasterNCs'])
148 if "msDS-hasMasterNCs" in res
[0]:
149 self
.write_ncs
= res
[0]["msDS-hasMasterNCs"]
151 # If the Forest Level is less than 2003 then there is no
152 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
153 # no need to merge as all the NCs that are in hasMasterNCs must
154 # also be in msDS-hasMasterNCs (but not the opposite)
155 if "hasMasterNCs" in res
[0]:
156 self
.write_ncs
= res
[0]["hasMasterNCs"]
158 self
.write_ncs
= None
160 res
= self
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['namingContexts'])
161 self
.deleted_objects_containers
= []
162 self
.ncs_lacking_deleted_containers
= []
163 self
.dns_partitions
= []
165 self
.ncs
= res
[0]["namingContexts"]
173 dn
= self
.samdb
.get_wellknown_dn(ldb
.Dn(self
.samdb
, nc
.decode('utf8')),
174 dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
175 self
.deleted_objects_containers
.append(dn
)
177 self
.ncs_lacking_deleted_containers
.append(ldb
.Dn(self
.samdb
, nc
.decode('utf8')))
179 domaindns_zone
= 'DC=DomainDnsZones,%s' % self
.samdb
.get_default_basedn()
180 forestdns_zone
= 'DC=ForestDnsZones,%s' % self
.samdb
.get_root_basedn()
181 domain
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
182 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
183 base
=self
.samdb
.get_partitions_dn(),
184 expression
="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone
)
186 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, forestdns_zone
), domain
[0]))
188 forest
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
189 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
190 base
=self
.samdb
.get_partitions_dn(),
191 expression
="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone
)
193 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, domaindns_zone
), forest
[0]))
195 fsmo_dn
= ldb
.Dn(self
.samdb
, "CN=RID Manager$,CN=System," + self
.samdb
.domain_dn())
196 rid_master
= get_fsmo_roleowner(self
.samdb
, fsmo_dn
, "rid")
197 if ldb
.Dn(self
.samdb
, self
.samdb
.get_dsServiceName()) == rid_master
:
198 self
.is_rid_master
= True
200 self
.is_rid_master
= False
202 # To get your rid set
204 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, self
.samdb
.get_serverName()),
205 scope
=ldb
.SCOPE_BASE
, attrs
=["serverReference"])
206 # 2. Get server reference
207 self
.server_ref_dn
= ldb
.Dn(self
.samdb
, res
[0]['serverReference'][0].decode('utf8'))
210 res
= self
.samdb
.search(base
=self
.server_ref_dn
,
211 scope
=ldb
.SCOPE_BASE
, attrs
=['rIDSetReferences'])
212 if "rIDSetReferences" in res
[0]:
213 self
.rid_set_dn
= ldb
.Dn(self
.samdb
, res
[0]['rIDSetReferences'][0].decode('utf8'))
215 self
.rid_set_dn
= None
217 ntds_service_dn
= "CN=Directory Service,CN=Windows NT,CN=Services,%s" % \
218 self
.samdb
.get_config_basedn().get_linearized()
219 res
= samdb
.search(base
=ntds_service_dn
,
220 scope
=ldb
.SCOPE_BASE
,
221 expression
="(objectClass=nTDSService)",
222 attrs
=["tombstoneLifetime"])
223 if "tombstoneLifetime" in res
[0]:
224 self
.tombstoneLifetime
= int(res
[0]["tombstoneLifetime"][0])
226 self
.tombstoneLifetime
= 180
228 self
.compatibleFeatures
= []
229 self
.requiredFeatures
= []
232 res
= self
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
234 attrs
=["compatibleFeatures",
236 if "compatibleFeatures" in res
[0]:
237 self
.compatibleFeatures
= res
[0]["compatibleFeatures"]
238 if "requiredFeatures" in res
[0]:
239 self
.requiredFeatures
= res
[0]["requiredFeatures"]
240 except ldb
.LdbError
as e6
:
241 (enum
, estr
) = e6
.args
242 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
246 def check_database(self
, DN
=None, scope
=ldb
.SCOPE_SUBTREE
, controls
=None,
248 '''perform a database check, returning the number of errors found'''
249 res
= self
.samdb
.search(base
=DN
, scope
=scope
, attrs
=['dn'], controls
=controls
)
250 self
.report('Checking %u objects' % len(res
))
253 error_count
+= self
.check_deleted_objects_containers()
255 self
.attribute_or_class_ids
= set()
258 self
.dn_set
.add(str(object.dn
))
259 error_count
+= self
.check_object(object.dn
, attrs
=attrs
)
262 error_count
+= self
.check_rootdse()
264 if self
.expired_tombstones
> 0:
265 self
.report("NOTICE: found %d expired tombstones, "
266 "'samba' will remove them daily, "
267 "'samba-tool domain tombstones expunge' "
268 "would do that immediately." % (
269 self
.expired_tombstones
))
271 if error_count
!= 0 and not self
.fix
:
272 self
.report("Please use --fix to fix these errors")
274 self
.report('Checked %u objects (%u errors)' % (len(res
), error_count
))
277 def check_deleted_objects_containers(self
):
278 """This function only fixes conflicts on the Deleted Objects
279 containers, not the attributes"""
281 for nc
in self
.ncs_lacking_deleted_containers
:
282 if nc
== self
.schema_dn
:
285 self
.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc
)
286 if not self
.confirm_all('Fix missing Deleted Objects container for %s?' % (nc
), 'fix_missing_deleted_objects'):
289 dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects")
294 # If something already exists here, add a conflict
295 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[],
296 controls
=["show_deleted:1", "extended_dn:1:1",
297 "show_recycled:1", "reveal_internals:0"])
299 guid
= res
[0].dn
.get_extended_component("GUID")
300 conflict_dn
= ldb
.Dn(self
.samdb
,
301 "CN=Deleted Objects\\0ACNF:%s" % str(misc
.GUID(guid
)))
302 conflict_dn
.add_base(nc
)
304 except ldb
.LdbError
as e2
:
305 (enum
, estr
) = e2
.args
306 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
309 self
.report("Couldn't check for conflicting Deleted Objects container: %s" % estr
)
312 if conflict_dn
is not None:
314 self
.samdb
.rename(dn
, conflict_dn
, ["show_deleted:1", "relax:0", "show_recycled:1"])
315 except ldb
.LdbError
as e1
:
316 (enum
, estr
) = e1
.args
317 self
.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn
, conflict_dn
, estr
))
320 # Refresh wellKnownObjects links
321 res
= self
.samdb
.search(base
=nc
, scope
=ldb
.SCOPE_BASE
,
322 attrs
=['wellKnownObjects'],
323 controls
=["show_deleted:1", "extended_dn:0",
324 "show_recycled:1", "reveal_internals:0"])
326 self
.report("wellKnownObjects was not found for NC %s" % nc
)
329 # Prevent duplicate deleted objects containers just in case
330 wko
= res
[0]["wellKnownObjects"]
332 proposed_objectguid
= None
334 dsdb_dn
= dsdb_Dn(self
.samdb
, o
.decode('utf8'), dsdb
.DSDB_SYNTAX_BINARY_DN
)
335 if self
.is_deleted_objects_dn(dsdb_dn
):
336 self
.report("wellKnownObjects had duplicate Deleted Objects value %s" % o
)
337 # We really want to put this back in the same spot
338 # as the original one, so that on replication we
339 # merge, rather than conflict.
340 proposed_objectguid
= dsdb_dn
.dn
.get_extended_component("GUID")
341 listwko
.append(str(o
))
343 if proposed_objectguid
is not None:
344 guid_suffix
= "\nobjectGUID: %s" % str(misc
.GUID(proposed_objectguid
))
346 wko_prefix
= "B:32:%s" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
347 listwko
.append('%s:%s' % (wko_prefix
, dn
))
350 # Insert a brand new Deleted Objects container
351 self
.samdb
.add_ldif("""dn: %s
353 objectClass: container
354 description: Container for deleted objects
356 isCriticalSystemObject: TRUE
357 showInAdvancedViewOnly: TRUE
358 systemFlags: -1946157056%s""" % (dn
, guid_suffix
),
359 controls
=["relax:0", "provision:0"])
361 delta
= ldb
.Message()
362 delta
.dn
= ldb
.Dn(self
.samdb
, str(res
[0]["dn"]))
363 delta
["wellKnownObjects"] = ldb
.MessageElement(listwko
,
364 ldb
.FLAG_MOD_REPLACE
,
367 # Insert the link to the brand new container
368 if self
.do_modify(delta
, ["relax:0"],
369 "NC %s lacks Deleted Objects WKGUID" % nc
,
371 self
.report("Added %s well known guid link" % dn
)
373 self
.deleted_objects_containers
.append(dn
)
377 def report(self
, msg
):
378 '''print a message unless quiet is set'''
382 def confirm(self
, msg
, allow_all
=False, forced
=False):
383 '''confirm a change'''
390 return common
.confirm(msg
, forced
=forced
, allow_all
=allow_all
)
392 ################################################################
393 # a local confirm function with support for 'all'
394 def confirm_all(self
, msg
, all_attr
):
395 '''confirm a change with support for "all" '''
398 if getattr(self
, all_attr
) == 'NONE':
400 if getattr(self
, all_attr
) == 'ALL':
406 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
408 setattr(self
, all_attr
, 'ALL')
411 setattr(self
, all_attr
, 'NONE')
415 def do_delete(self
, dn
, controls
, msg
):
416 '''delete dn with optional verbose output'''
418 self
.report("delete DN %s" % dn
)
420 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
421 self
.samdb
.delete(dn
, controls
=controls
)
422 except Exception as err
:
423 if self
.in_transaction
:
424 raise CommandError("%s : %s" % (msg
, err
))
425 self
.report("%s : %s" % (msg
, err
))
429 def do_modify(self
, m
, controls
, msg
, validate
=True):
430 '''perform a modify with optional verbose output'''
431 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
433 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
434 self
.report("controls: %r" % controls
)
436 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
437 except Exception as err
:
438 if self
.in_transaction
:
439 raise CommandError("%s : %s" % (msg
, err
))
440 self
.report("%s : %s" % (msg
, err
))
444 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
445 '''perform a modify with optional verbose output'''
447 self
.report("""dn: %s
451 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
453 to_dn
= to_rdn
+ to_base
454 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
455 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
456 except Exception as err
:
457 if self
.in_transaction
:
458 raise CommandError("%s : %s" % (msg
, err
))
459 self
.report("%s : %s" % (msg
, err
))
463 def get_attr_linkID_and_reverse_name(self
, attrname
):
464 if attrname
in self
.link_id_cache
:
465 return self
.link_id_cache
[attrname
]
466 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
468 revname
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
471 self
.link_id_cache
[attrname
] = (linkID
, revname
)
472 return linkID
, revname
474 def err_empty_attribute(self
, dn
, attrname
):
475 '''fix empty attributes'''
476 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
477 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
478 self
.report("Not fixing empty attribute %s" % attrname
)
483 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
484 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
485 "Failed to remove empty attribute %s" % attrname
, validate
=False):
486 self
.report("Removed empty attribute %s" % attrname
)
488 def err_normalise_mismatch(self
, dn
, attrname
, values
):
489 '''fix attribute normalisation errors'''
490 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
493 normalised
= self
.samdb
.dsdb_normalise_attributes(
494 self
.samdb_schema
, attrname
, [val
])
495 if len(normalised
) != 1:
496 self
.report("Unable to normalise value '%s'" % val
)
497 mod_list
.append((val
, ''))
498 elif (normalised
[0] != val
):
499 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
500 mod_list
.append((val
, normalised
[0]))
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 for i
in range(0, len(mod_list
)):
508 (val
, nval
) = mod_list
[i
]
509 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
511 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
514 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
515 "Failed to normalise attribute %s" % attrname
,
517 self
.report("Normalised attribute %s" % attrname
)
519 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
520 '''fix attribute normalisation errors'''
521 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
522 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
523 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
524 if list(normalised
) == values
:
526 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
527 self
.report("Not fixing attribute '%s'" % attrname
)
532 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
534 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
535 "Failed to normalise attribute %s" % attrname
,
537 self
.report("Normalised attribute %s" % attrname
)
539 def err_duplicate_values(self
, dn
, attrname
, dup_values
, values
):
540 '''fix attribute normalisation errors'''
541 self
.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname
, dn
))
542 self
.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dump_attr_values(dup_values
)), ','.join(dump_attr_values(values
))))
543 if not self
.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_duplicates'):
544 self
.report("Not fixing attribute '%s'" % attrname
)
549 m
[attrname
] = ldb
.MessageElement(values
, ldb
.FLAG_MOD_REPLACE
, attrname
)
551 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
552 "Failed to remove duplicate value on attribute %s" % attrname
,
554 self
.report("Removed duplicate value on attribute %s" % attrname
)
556 def is_deleted_objects_dn(self
, dsdb_dn
):
557 '''see if a dsdb_Dn is the special Deleted Objects DN'''
558 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
560 def err_missing_objectclass(self
, dn
):
561 """handle object without objectclass"""
562 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
)))
563 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'):
564 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
566 if self
.do_delete(dn
, ["relax:0"],
567 "Failed to remove DN %s" % dn
):
568 self
.report("Removed DN %s" % dn
)
570 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, remove_plausible
=False):
571 """handle a DN pointing to a deleted object"""
572 if not remove_plausible
:
573 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
574 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
575 if not self
.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
576 self
.report("Not removing")
579 self
.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
580 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
581 if not self
.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
582 self
.report("Not removing")
587 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
588 if self
.do_modify(m
, ["show_recycled:1",
589 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
590 "Failed to remove deleted DN attribute %s" % attrname
):
591 self
.report("Removed deleted DN on attribute %s" % attrname
)
593 def err_missing_target_dn_or_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
594 """handle a missing target DN (if specified, GUID form can't be found,
595 and otherwise DN string form can't be found)"""
597 # Don't change anything if the object itself is deleted
598 if str(dn
).find('\\0ADEL') != -1:
599 # We don't bump the error count as Samba produces these
600 # in normal operation
601 self
.report("WARNING: no target object found for GUID "
602 "component link %s in deleted object "
603 "%s - %s" % (attrname
, dn
, val
))
604 self
.report("Not removing dangling one-way "
605 "link on deleted object "
606 "(tombstone garbage collection in progress?)")
609 # check if its a backlink
610 linkID
, _
= self
.get_attr_linkID_and_reverse_name(attrname
)
611 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
613 linkID
, reverse_link_name \
614 = self
.get_attr_linkID_and_reverse_name(attrname
)
615 if reverse_link_name
is not None:
616 self
.report("WARNING: no target object found for GUID "
617 "component for one-way forward link "
619 "%s - %s" % (attrname
, dn
, val
))
620 self
.report("Not removing dangling forward link")
623 nc_root
= self
.samdb
.get_nc_root(dn
)
624 target_nc_root
= self
.samdb
.get_nc_root(dsdb_dn
.dn
)
625 if nc_root
!= target_nc_root
:
626 # We don't bump the error count as Samba produces these
627 # in normal operation
628 self
.report("WARNING: no target object found for GUID "
629 "component for cross-partition link "
631 "%s - %s" % (attrname
, dn
, val
))
632 self
.report("Not removing dangling one-way "
633 "cross-partition link "
634 "(we might be mid-replication)")
637 # Due to our link handling one-way links pointing to
638 # missing objects are plausible.
640 # We don't bump the error count as Samba produces these
641 # in normal operation
642 self
.report("WARNING: no target object found for GUID "
643 "component for DN value %s in object "
644 "%s - %s" % (attrname
, dn
, val
))
645 self
.err_deleted_dn(dn
, attrname
, val
,
646 dsdb_dn
, dsdb_dn
, True)
649 # We bump the error count here, as we should have deleted this
650 self
.report("ERROR: no target object found for GUID "
651 "component for link %s in object "
652 "%s - %s" % (attrname
, dn
, val
))
653 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
, False)
656 def err_missing_dn_GUID_component(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
657 """handle a missing GUID extended DN component"""
658 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
659 controls
= ["extended_dn:1:1", "show_recycled:1"]
661 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
662 attrs
=[], controls
=controls
)
663 except ldb
.LdbError
as e7
:
664 (enum
, estr
) = e7
.args
665 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
666 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
668 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
671 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
672 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
674 dsdb_dn
.dn
= res
[0].dn
676 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
677 self
.report("Not fixing %s" % errstr
)
681 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
682 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
684 if self
.do_modify(m
, ["show_recycled:1"],
685 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
686 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
688 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
689 """handle an incorrect binary DN component"""
690 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
692 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
693 self
.report("Not fixing %s" % errstr
)
697 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
698 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
700 if self
.do_modify(m
, ["show_recycled:1"],
701 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
702 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
704 def err_dn_string_component_old(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
705 """handle a DN string being incorrect"""
706 self
.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
707 dsdb_dn
.dn
= correct_dn
709 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
710 'fix_all_old_dn_string_component_mismatch'):
711 self
.report("Not fixing old string component")
715 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
716 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
717 if self
.do_modify(m
, ["show_recycled:1",
718 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
],
719 "Failed to fix old DN string on attribute %s" % (attrname
)):
720 self
.report("Fixed old DN string on attribute %s" % (attrname
))
722 def err_dn_component_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, mismatch_type
):
723 """handle a DN string being incorrect"""
724 self
.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type
, attrname
, dn
, val
))
725 dsdb_dn
.dn
= correct_dn
727 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
728 'fix_all_%s_dn_component_mismatch' % mismatch_type
):
729 self
.report("Not fixing %s component mismatch" % mismatch_type
)
733 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
734 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
735 if self
.do_modify(m
, ["show_recycled:1"],
736 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type
, attrname
)):
737 self
.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type
, attrname
))
739 def err_dn_component_missing_target_sid(self
, dn
, attrname
, val
, dsdb_dn
, target_sid_blob
):
740 """handle a DN string being incorrect"""
741 self
.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname
, dn
, val
))
743 if len(dsdb_dn
.prefix
) != 0:
744 self
.report("Not fixing missing DN SID on DN+BINARY or DN+STRING")
747 correct_dn
= ldb
.Dn(self
.samdb
, dsdb_dn
.dn
.extended_str())
748 correct_dn
.set_extended_component("SID", target_sid_blob
)
750 if not self
.confirm_all('Change DN to %s?' % correct_dn
.extended_str(),
751 'fix_all_SID_dn_component_missing'):
752 self
.report("Not fixing missing DN SID component")
755 target_guid_blob
= correct_dn
.get_extended_component("GUID")
756 guid_sid_dn
= ldb
.Dn(self
.samdb
, "")
757 guid_sid_dn
.set_extended_component("GUID", target_guid_blob
)
758 guid_sid_dn
.set_extended_component("SID", target_sid_blob
)
762 m
['new_value'] = ldb
.MessageElement(guid_sid_dn
.extended_str(), ldb
.FLAG_MOD_ADD
, attrname
)
765 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID
767 if self
.do_modify(m
, controls
,
768 "Failed to ADD missing DN SID on attribute %s" % (attrname
)):
769 self
.report("Fixed missing DN SID on attribute %s" % (attrname
))
771 def err_unknown_attribute(self
, obj
, attrname
):
772 '''handle an unknown attribute error'''
773 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
774 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
775 self
.report("Not removing %s" % attrname
)
779 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
780 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
781 "Failed to remove unknown attribute %s" % attrname
):
782 self
.report("Removed unknown attribute %s" % (attrname
))
784 def err_undead_linked_attribute(self
, obj
, attrname
, val
):
785 '''handle a link that should not be there on a deleted object'''
786 self
.report("ERROR: linked attribute '%s' to '%s' is present on "
787 "deleted object %s" % (attrname
, val
, obj
.dn
))
788 if not self
.confirm_all('Remove linked attribute %s' % attrname
, 'fix_undead_linked_attributes'):
789 self
.report("Not removing linked attribute %s" % attrname
)
793 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
795 if self
.do_modify(m
, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
796 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
797 "Failed to delete forward link %s" % attrname
):
798 self
.report("Fixed undead forward link %s" % (attrname
))
800 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
801 '''handle a missing backlink value'''
802 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
803 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
804 self
.report("Not fixing missing backlink %s" % backlink_name
)
808 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, backlink_name
)
809 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
810 "Failed to fix missing backlink %s" % backlink_name
):
811 self
.report("Fixed missing backlink %s" % (backlink_name
))
813 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
814 '''handle a incorrect RMD_FLAGS value'''
815 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
816 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()))
817 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
818 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
822 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
823 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
824 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
825 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
827 def err_orphaned_backlink(self
, obj_dn
, backlink_attr
, backlink_val
,
828 target_dn
, forward_attr
, forward_syntax
,
829 check_duplicates
=True):
830 '''handle a orphaned backlink value'''
831 if check_duplicates
is True and self
.has_duplicate_links(target_dn
, forward_attr
, forward_syntax
):
832 self
.report("WARNING: Keep orphaned backlink attribute " +
833 "'%s' in '%s' for link '%s' in '%s'" % (
834 backlink_attr
, obj_dn
, forward_attr
, target_dn
))
836 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr
, obj_dn
, forward_attr
, target_dn
))
837 if not self
.confirm_all('Remove orphaned backlink %s' % backlink_attr
, 'fix_all_orphaned_backlinks'):
838 self
.report("Not removing orphaned backlink %s" % backlink_attr
)
842 m
['value'] = ldb
.MessageElement(backlink_val
, ldb
.FLAG_MOD_DELETE
, backlink_attr
)
843 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
844 "Failed to fix orphaned backlink %s" % backlink_attr
):
845 self
.report("Fixed orphaned backlink %s" % (backlink_attr
))
847 def err_recover_forward_links(self
, obj
, forward_attr
, forward_vals
):
848 '''handle a duplicate links value'''
850 self
.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr
, obj
.dn
))
852 if not self
.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr
, 'recover_all_forward_links'):
853 self
.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
854 forward_attr
, obj
.dn
))
858 m
['value'] = ldb
.MessageElement(forward_vals
, ldb
.FLAG_MOD_REPLACE
, forward_attr
)
859 if self
.do_modify(m
, ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS
],
860 "Failed to fix duplicate links in attribute '%s'" % forward_attr
):
861 self
.report("Fixed duplicate links in attribute '%s'" % (forward_attr
))
862 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
863 assert duplicate_cache_key
in self
.duplicate_link_cache
864 self
.duplicate_link_cache
[duplicate_cache_key
] = False
866 def err_no_fsmoRoleOwner(self
, obj
):
867 '''handle a missing fSMORoleOwner'''
868 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
869 res
= self
.samdb
.search("",
870 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
872 serviceName
= str(res
[0]["dsServiceName"][0])
873 if not self
.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
874 self
.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
878 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
879 if self
.do_modify(m
, [],
880 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
881 self
.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
883 def err_missing_parent(self
, obj
):
884 '''handle a missing parent'''
885 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
886 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
887 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
890 keep_transaction
= False
891 self
.samdb
.transaction_start()
893 nc_root
= self
.samdb
.get_nc_root(obj
.dn
)
894 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
895 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
896 new_dn
.remove_base_components(len(new_dn
) - 1)
897 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
898 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
899 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
903 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
905 if self
.do_modify(m
, [],
906 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
907 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
908 keep_transaction
= True
910 self
.samdb
.transaction_cancel()
914 self
.samdb
.transaction_commit()
916 self
.samdb
.transaction_cancel()
918 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
, controls
):
919 '''handle a wrong dn'''
921 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
922 new_rdn
.remove_base_components(len(new_rdn
) - 1)
923 new_parent
= new_dn
.parent()
926 if rdn_val
!= name_val
:
927 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
928 attributes
+= "name=%r" % (name_val
)
930 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
931 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
932 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
935 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, controls
,
936 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
937 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
939 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
940 '''handle a wrong instanceType'''
941 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
942 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
943 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
948 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
949 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
950 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
951 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
953 def err_short_userParameters(self
, obj
, attrname
, value
):
954 # This is a truncated userParameters due to a pre 4.1 replication bug
955 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
)))
957 def err_base64_userParameters(self
, obj
, attrname
, value
):
958 '''handle a wrong userParameters'''
959 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
960 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
961 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
966 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
967 if self
.do_modify(m
, [],
968 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
969 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
971 def err_utf8_userParameters(self
, obj
, attrname
, value
):
972 '''handle a wrong userParameters'''
973 self
.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj
.dn
))
974 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
975 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
980 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
981 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
982 if self
.do_modify(m
, [],
983 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
984 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
986 def err_doubled_userParameters(self
, obj
, attrname
, value
):
987 '''handle a wrong userParameters'''
988 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
989 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
990 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
995 # m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
996 # hmm the above old python2 code doesn't make sense to me and cannot
997 # work in python3 because a string doesn't have a decode method.
998 # However in python2 for some unknown reason this double decode
999 # followed by encode seems to result in what looks like utf8.
1000 # In python2 just .decode('utf-16-le').encode('utf-16-le') does nothing
1001 # but trigger the 'double UTF16 encoded' condition again :/
1003 # In python2 and python3 value.decode('utf-16-le').encode('utf8') seems
1004 # to do the trick and work as expected.
1005 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').encode('utf8'),
1006 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
1008 if self
.do_modify(m
, [],
1009 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
1010 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
1012 def err_odd_userParameters(self
, obj
, attrname
):
1013 # This is a truncated userParameters due to a pre 4.1 replication bug
1014 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
)))
1016 def find_revealed_link(self
, dn
, attrname
, guid
):
1017 '''return a revealed link in an object'''
1018 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
1019 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
1020 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
1021 for val
in res
[0][attrname
]:
1022 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
1023 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
1028 def check_duplicate_links(self
, obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
):
1029 '''check a linked values for duplicate forward links'''
1032 duplicate_dict
= dict()
1033 unique_dict
= dict()
1035 # Only forward links can have this problem
1036 if forward_linkID
& 1:
1037 # If we got the reverse, skip it
1038 return (error_count
, duplicate_dict
, unique_dict
)
1040 if backlink_attr
is None:
1041 return (error_count
, duplicate_dict
, unique_dict
)
1043 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
1044 if duplicate_cache_key
not in self
.duplicate_link_cache
:
1045 self
.duplicate_link_cache
[duplicate_cache_key
] = False
1047 for val
in obj
[forward_attr
]:
1048 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), forward_syntax
)
1050 # all DNs should have a GUID component
1051 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1054 guidstr
= str(misc
.GUID(guid
))
1055 keystr
= guidstr
+ dsdb_dn
.prefix
1056 if keystr
not in unique_dict
:
1057 unique_dict
[keystr
] = dsdb_dn
1060 if keystr
not in duplicate_dict
:
1061 duplicate_dict
[keystr
] = dict()
1062 duplicate_dict
[keystr
]["keep"] = None
1063 duplicate_dict
[keystr
]["delete"] = list()
1065 # Now check for the highest RMD_VERSION
1066 v1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_VERSION"))
1067 v2
= int(dsdb_dn
.dn
.get_extended_component("RMD_VERSION"))
1069 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1070 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
1073 duplicate_dict
[keystr
]["keep"] = dsdb_dn
1074 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
1075 unique_dict
[keystr
] = dsdb_dn
1077 # Fallback to the highest RMD_LOCAL_USN
1078 u1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_LOCAL_USN"))
1079 u2
= int(dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN"))
1081 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1082 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
1084 duplicate_dict
[keystr
]["keep"] = dsdb_dn
1085 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
1086 unique_dict
[keystr
] = dsdb_dn
1088 if error_count
!= 0:
1089 self
.duplicate_link_cache
[duplicate_cache_key
] = True
1091 return (error_count
, duplicate_dict
, unique_dict
)
1093 def has_duplicate_links(self
, dn
, forward_attr
, forward_syntax
):
1094 '''check a linked values for duplicate forward links'''
1097 duplicate_cache_key
= "%s:%s" % (str(dn
), forward_attr
)
1098 if duplicate_cache_key
in self
.duplicate_link_cache
:
1099 return self
.duplicate_link_cache
[duplicate_cache_key
]
1101 forward_linkID
, backlink_attr
= self
.get_attr_linkID_and_reverse_name(forward_attr
)
1103 attrs
= [forward_attr
]
1104 controls
= ["extended_dn:1:1", "reveal_internals:0"]
1106 # check its the right GUID
1108 res
= self
.samdb
.search(base
=str(dn
), scope
=ldb
.SCOPE_BASE
,
1109 attrs
=attrs
, controls
=controls
)
1110 except ldb
.LdbError
as e8
:
1111 (enum
, estr
) = e8
.args
1112 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1118 error_count
, duplicate_dict
, unique_dict
= \
1119 self
.check_duplicate_links(obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
)
1121 if duplicate_cache_key
in self
.duplicate_link_cache
:
1122 return self
.duplicate_link_cache
[duplicate_cache_key
]
1126 def find_missing_forward_links_from_backlinks(self
, obj
,
1130 forward_unique_dict
):
1131 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1132 missing_forward_links
= []
1135 if backlink_attr
is None:
1136 return (missing_forward_links
, error_count
)
1138 if forward_syntax
!= ldb
.SYNTAX_DN
:
1139 self
.report("Not checking for missing forward links for syntax: %s" %
1141 return (missing_forward_links
, error_count
)
1143 if "sortedLinks" in self
.compatibleFeatures
:
1144 self
.report("Not checking for missing forward links because the db " +
1145 "has the sortedLinks feature")
1146 return (missing_forward_links
, error_count
)
1149 obj_guid
= obj
['objectGUID'][0]
1150 obj_guid_str
= str(ndr_unpack(misc
.GUID
, obj_guid
))
1151 filter = "(%s=<GUID=%s>)" % (backlink_attr
, obj_guid_str
)
1153 res
= self
.samdb
.search(expression
=filter,
1154 scope
=ldb
.SCOPE_SUBTREE
, attrs
=["objectGUID"],
1155 controls
=["extended_dn:1:1",
1156 "search_options:1:2",
1157 "paged_results:1:1000"])
1158 except ldb
.LdbError
as e9
:
1159 (enum
, estr
) = e9
.args
1163 target_dn
= dsdb_Dn(self
.samdb
, r
.dn
.extended_str(), forward_syntax
)
1165 guid
= target_dn
.dn
.get_extended_component("GUID")
1166 guidstr
= str(misc
.GUID(guid
))
1167 if guidstr
in forward_unique_dict
:
1170 # A valid forward link looks like this:
1172 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1173 # <RMD_ADDTIME=131607546230000000>;
1174 # <RMD_CHANGETIME=131607546230000000>;
1176 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1177 # <RMD_LOCAL_USN=3765>;
1178 # <RMD_ORIGINATING_USN=3765>;
1180 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1181 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1183 # Note that versions older than Samba 4.8 create
1184 # links with RMD_VERSION=0.
1186 # Try to get the local_usn and time from objectClass
1187 # if possible and fallback to any other one.
1188 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1189 obj
['replPropertyMetadata'][0])
1190 for o
in repl
.ctr
.array
:
1191 local_usn
= o
.local_usn
1192 t
= o
.originating_change_time
1193 if o
.attid
== drsuapi
.DRSUAPI_ATTID_objectClass
:
1196 # We use a magic invocationID for restoring missing
1197 # forward links to recover from bug #13228.
1198 # This should allow some more future magic to fix the
1201 # It also means it looses the conflict resolution
1202 # against almost every real invocation, if the
1203 # version is also 0.
1204 originating_invocid
= misc
.GUID("ffffffff-4700-4700-4700-000000b13228")
1210 rmd_invocid
= originating_invocid
1211 rmd_originating_usn
= originating_usn
1212 rmd_local_usn
= local_usn
1215 target_dn
.dn
.set_extended_component("RMD_ADDTIME", str(rmd_addtime
))
1216 target_dn
.dn
.set_extended_component("RMD_CHANGETIME", str(rmd_changetime
))
1217 target_dn
.dn
.set_extended_component("RMD_FLAGS", str(rmd_flags
))
1218 target_dn
.dn
.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid
))
1219 target_dn
.dn
.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn
))
1220 target_dn
.dn
.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn
))
1221 target_dn
.dn
.set_extended_component("RMD_VERSION", str(rmd_version
))
1224 missing_forward_links
.append(target_dn
)
1226 return (missing_forward_links
, error_count
)
1228 def check_dn(self
, obj
, attrname
, syntax_oid
):
1229 '''check a DN attribute for correctness'''
1231 obj_guid
= obj
['objectGUID'][0]
1233 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
1234 if reverse_link_name
is not None:
1235 reverse_syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(reverse_link_name
)
1237 reverse_syntax_oid
= None
1239 is_member_link
= attrname
in ("member", "memberOf")
1240 if is_member_link
and self
.quick_membership_checks
:
1243 error_count
, duplicate_dict
, unique_dict
= \
1244 self
.check_duplicate_links(obj
, attrname
, syntax_oid
,
1245 linkID
, reverse_link_name
)
1247 if len(duplicate_dict
) != 0:
1249 missing_forward_links
, missing_error_count
= \
1250 self
.find_missing_forward_links_from_backlinks(obj
,
1251 attrname
, syntax_oid
,
1254 error_count
+= missing_error_count
1256 forward_links
= [dn
for dn
in unique_dict
.values()]
1258 if missing_error_count
!= 0:
1259 self
.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1262 self
.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname
, obj
.dn
))
1263 for m
in missing_forward_links
:
1264 self
.report("Missing link '%s'" % (m
))
1265 if not self
.confirm_all("Schedule readding missing forward link for attribute %s" % attrname
,
1266 'fix_all_missing_forward_links'):
1267 self
.err_orphaned_backlink(m
.dn
, reverse_link_name
,
1268 obj
.dn
.extended_str(), obj
.dn
,
1269 attrname
, syntax_oid
,
1270 check_duplicates
=False)
1272 forward_links
+= [m
]
1273 for keystr
in duplicate_dict
.keys():
1274 d
= duplicate_dict
[keystr
]
1275 for dd
in d
["delete"]:
1276 self
.report("Duplicate link '%s'" % dd
)
1277 self
.report("Correct link '%s'" % d
["keep"])
1279 # We now construct the sorted dn values.
1280 # They're sorted by the objectGUID of the target
1281 # See dsdb_Dn.__cmp__()
1282 vals
= [str(dn
) for dn
in sorted(forward_links
)]
1283 self
.err_recover_forward_links(obj
, attrname
, vals
)
1284 # We should continue with the fixed values
1285 obj
[attrname
] = ldb
.MessageElement(vals
, 0, attrname
)
1287 for val
in obj
[attrname
]:
1288 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
1290 # all DNs should have a GUID component
1291 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1294 self
.err_missing_dn_GUID_component(obj
.dn
, attrname
, val
, dsdb_dn
,
1298 guidstr
= str(misc
.GUID(guid
))
1299 attrs
= ['isDeleted', 'replPropertyMetaData']
1301 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
1302 fixing_msDS_HasInstantiatedNCs
= True
1303 attrs
.append("instanceType")
1305 fixing_msDS_HasInstantiatedNCs
= False
1307 if reverse_link_name
is not None:
1308 attrs
.append(reverse_link_name
)
1310 # check its the right GUID
1312 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
1313 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1",
1314 "reveal_internals:0"
1316 except ldb
.LdbError
as e3
:
1317 (enum
, estr
) = e3
.args
1318 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1321 # We don't always want to
1322 error_count
+= self
.err_missing_target_dn_or_GUID(obj
.dn
,
1328 if fixing_msDS_HasInstantiatedNCs
:
1329 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
1330 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
1332 if str(dsdb_dn
) != str(val
):
1334 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
1337 # now we have two cases - the source object might or might not be deleted
1338 is_deleted
= 'isDeleted' in obj
and str(obj
['isDeleted'][0]).upper() == 'TRUE'
1339 target_is_deleted
= 'isDeleted' in res
[0] and str(res
[0]['isDeleted'][0]).upper() == 'TRUE'
1341 if is_deleted
and obj
.dn
not in self
.deleted_objects_containers
and linkID
:
1342 # A fully deleted object should not have any linked
1343 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1344 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1346 self
.err_undead_linked_attribute(obj
, attrname
, val
)
1349 elif target_is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
) and linkID
:
1350 # the target DN is not allowed to be deleted, unless the target DN is the
1351 # special Deleted Objects container
1353 local_usn
= dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN")
1355 if 'replPropertyMetaData' in res
[0]:
1356 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1357 res
[0]['replPropertyMetadata'][0])
1359 for o
in repl
.ctr
.array
:
1360 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1361 deleted_usn
= o
.local_usn
1362 if deleted_usn
>= int(local_usn
):
1363 # If the object was deleted after the link
1364 # was last modified then, clean it up here
1369 self
.err_deleted_dn(obj
.dn
, attrname
,
1370 val
, dsdb_dn
, res
[0].dn
, True)
1373 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
, False)
1376 # We should not check for incorrect
1377 # components on deleted links, as these are allowed to
1378 # go stale (we just need the GUID, not the name)
1379 rmd_blob
= dsdb_dn
.dn
.get_extended_component("RMD_FLAGS")
1381 if rmd_blob
is not None:
1382 rmd_flags
= int(rmd_blob
)
1384 # assert the DN matches in string form, where a reverse
1385 # link exists, otherwise (below) offer to fix it as a non-error.
1386 # The string form is essentially only kept for forensics,
1387 # as we always re-resolve by GUID in normal operations.
1388 if not rmd_flags
& 1 and reverse_link_name
is not None:
1389 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1391 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1392 res
[0].dn
, "string")
1395 if res
[0].dn
.get_extended_component("GUID") != dsdb_dn
.dn
.get_extended_component("GUID"):
1397 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1401 target_sid
= res
[0].dn
.get_extended_component("SID")
1402 link_sid
= dsdb_dn
.dn
.get_extended_component("SID")
1403 if link_sid
is None and target_sid
is not None:
1405 self
.err_dn_component_missing_target_sid(obj
.dn
, attrname
, val
,
1406 dsdb_dn
, target_sid
)
1408 if link_sid
!= target_sid
:
1410 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1414 # Only for non-links, not even forward-only links
1415 # (otherwise this breaks repl_meta_data):
1417 # Now we have checked the GUID and SID, offer to fix old
1418 # DN strings as a non-error (DNs, not links so no
1419 # backlink). Samba does not maintain this string
1420 # otherwise, so we don't increment error_count.
1421 if reverse_link_name
is None:
1422 if linkID
== 0 and str(res
[0].dn
) != str(dsdb_dn
.dn
):
1423 # Pass in the old/bad DN without the <GUID=...> part,
1424 # otherwise the LDB code will correct it on the way through
1425 # (Note: we still want to preserve the DSDB DN prefix in the
1426 # case of binary DNs)
1427 bad_dn
= dsdb_dn
.prefix
+ dsdb_dn
.dn
.get_linearized()
1428 self
.err_dn_string_component_old(obj
.dn
, attrname
, bad_dn
,
1432 if is_member_link
and self
.quick_membership_checks
:
1435 # check the reverse_link is correct if there should be one
1437 if reverse_link_name
in res
[0]:
1438 for v
in res
[0][reverse_link_name
]:
1439 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1440 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1441 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1443 if v_blob
is not None:
1444 v_rmd_flags
= int(v_blob
)
1447 if v_guid
== obj_guid
:
1450 if match_count
!= 1:
1451 if syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
or reverse_syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
:
1453 # Forward binary multi-valued linked attribute
1455 for w
in obj
[attrname
]:
1456 w_guid
= dsdb_Dn(self
.samdb
, w
.decode('utf8')).dn
.get_extended_component("GUID")
1460 if match_count
== forward_count
:
1463 for v
in obj
[attrname
]:
1464 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1465 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1466 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1468 if v_blob
is not None:
1469 v_rmd_flags
= int(v_blob
)
1475 if match_count
== expected_count
:
1478 diff_count
= expected_count
- match_count
1481 # If there's a backward link on binary multi-valued linked attribute,
1482 # let the check on the forward link remedy the value.
1483 # UNLESS, there is no forward link detected.
1484 if match_count
== 0:
1486 self
.err_orphaned_backlink(obj
.dn
, attrname
,
1491 # Only warn here and let the forward link logic fix it.
1492 self
.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1493 attrname
, expected_count
, str(obj
.dn
),
1494 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1497 assert not target_is_deleted
1499 self
.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1500 attrname
, expected_count
, str(obj
.dn
),
1501 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1503 # Loop until the difference between the forward and
1504 # the backward links is resolved.
1505 while diff_count
!= 0:
1508 if match_count
> 0 or diff_count
> 1:
1509 # TODO no method to fix these right now
1510 self
.report("ERROR: Can't fix missing "
1511 "multi-valued backlinks on %s" % str(dsdb_dn
.dn
))
1513 self
.err_missing_backlink(obj
, attrname
,
1514 obj
.dn
.extended_str(),
1519 self
.err_orphaned_backlink(res
[0].dn
, reverse_link_name
,
1520 obj
.dn
.extended_str(), obj
.dn
,
1521 attrname
, syntax_oid
)
1526 def find_repl_attid(self
, repl
, attid
):
1527 for o
in repl
.ctr
.array
:
1528 if o
.attid
== attid
:
1533 def get_originating_time(self
, val
, attid
):
1534 '''Read metadata properties and return the originating time for
1535 a given attributeId.
1537 :return: the originating time or 0 if not found
1540 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1541 o
= self
.find_repl_attid(repl
, attid
)
1543 return o
.originating_change_time
1546 def process_metadata(self
, dn
, val
):
1547 '''Read metadata properties and list attributes in it.
1548 raises KeyError if the attid is unknown.'''
1551 wrong_attids
= set()
1553 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1555 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1557 for o
in repl
.ctr
.array
:
1558 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1559 set_att
.add(att
.lower())
1560 list_attid
.append(o
.attid
)
1561 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
,
1562 is_schema_nc
=in_schema_nc
)
1563 if correct_attid
!= o
.attid
:
1564 wrong_attids
.add(o
.attid
)
1566 return (set_att
, list_attid
, wrong_attids
)
1568 def fix_metadata(self
, obj
, attr
):
1569 '''re-write replPropertyMetaData elements for a single attribute for a
1570 object. This is used to fix missing replPropertyMetaData elements'''
1571 guid_str
= str(ndr_unpack(misc
.GUID
, obj
['objectGUID'][0]))
1572 dn
= ldb
.Dn(self
.samdb
, "<GUID=%s>" % guid_str
)
1573 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attr
],
1574 controls
=["search_options:1:2",
1577 nmsg
= ldb
.Message()
1579 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
1580 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
1581 "Failed to fix metadata for attribute %s" % attr
):
1582 self
.report("Fixed metadata for attribute %s" % attr
)
1584 def ace_get_effective_inherited_type(self
, ace
):
1585 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
1589 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
1591 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
1593 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
1595 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
1601 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
1604 return str(ace
.object.inherited_type
)
1606 def lookup_class_schemaIDGUID(self
, cls
):
1607 if cls
in self
.class_schemaIDGUID
:
1608 return self
.class_schemaIDGUID
[cls
]
1610 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1611 res
= self
.samdb
.search(base
=self
.schema_dn
,
1613 attrs
=["schemaIDGUID"])
1614 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
1616 self
.class_schemaIDGUID
[cls
] = t
1619 def process_sd(self
, dn
, obj
):
1620 sd_attr
= "nTSecurityDescriptor"
1621 sd_val
= obj
[sd_attr
]
1623 sd
= ndr_unpack(security
.descriptor
, sd_val
[0])
1625 is_deleted
= 'isDeleted' in obj
and str(obj
['isDeleted'][0]).upper() == 'TRUE'
1627 # we don't fix deleted objects
1630 sd_clean
= security
.descriptor()
1631 sd_clean
.owner_sid
= sd
.owner_sid
1632 sd_clean
.group_sid
= sd
.group_sid
1633 sd_clean
.type = sd
.type
1634 sd_clean
.revision
= sd
.revision
1637 last_inherited_type
= None
1640 if sd
.sacl
is not None:
1642 for i
in range(0, len(aces
)):
1645 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1646 sd_clean
.sacl_add(ace
)
1649 t
= self
.ace_get_effective_inherited_type(ace
)
1653 if last_inherited_type
is not None:
1654 if t
!= last_inherited_type
:
1655 # if it inherited from more than
1656 # one type it's very likely to be broken
1658 # If not the recalculation will calculate
1663 last_inherited_type
= t
1666 if sd
.dacl
is not None:
1668 for i
in range(0, len(aces
)):
1671 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1672 sd_clean
.dacl_add(ace
)
1675 t
= self
.ace_get_effective_inherited_type(ace
)
1679 if last_inherited_type
is not None:
1680 if t
!= last_inherited_type
:
1681 # if it inherited from more than
1682 # one type it's very likely to be broken
1684 # If not the recalculation will calculate
1689 last_inherited_type
= t
1692 return (sd_clean
, sd
)
1694 if last_inherited_type
is None:
1700 cls
= obj
["objectClass"][-1]
1701 except KeyError as e
:
1705 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1706 attrs
=["isDeleted", "objectClass"],
1707 controls
=["show_recycled:1"])
1709 is_deleted
= 'isDeleted' in o
and str(o
['isDeleted'][0]).upper() == 'TRUE'
1711 # we don't fix deleted objects
1713 cls
= o
["objectClass"][-1]
1715 t
= self
.lookup_class_schemaIDGUID(cls
)
1717 if t
!= last_inherited_type
:
1719 return (sd_clean
, sd
)
1724 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
1725 '''re-write the SD due to incorrect inherited ACEs'''
1726 sd_attr
= "nTSecurityDescriptor"
1727 sd_val
= ndr_pack(sd
)
1728 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1730 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
1731 self
.report('Not fixing %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
)
1737 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1738 "Failed to fix attribute %s" % sd_attr
):
1739 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1741 def err_wrong_default_sd(self
, dn
, sd
, sd_old
, diff
):
1742 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1743 sd_attr
= "nTSecurityDescriptor"
1744 sd_val
= ndr_pack(sd
)
1745 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1746 if sd
.owner_sid
is not None:
1747 sd_flags |
= security
.SECINFO_OWNER
1748 if sd
.group_sid
is not None:
1749 sd_flags |
= security
.SECINFO_GROUP
1751 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
1752 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
1757 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1758 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
1759 "Failed to reset attribute %s" % sd_attr
):
1760 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1762 def err_missing_sd_owner(self
, dn
, sd
):
1763 '''re-write the SD due to a missing owner or group'''
1764 sd_attr
= "nTSecurityDescriptor"
1765 sd_val
= ndr_pack(sd
)
1766 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
1768 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
1769 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
1772 nmsg
= ldb
.Message()
1774 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1776 # By setting the session_info to admin_session_info and
1777 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1778 # flags we cause the descriptor module to set the correct
1779 # owner and group on the SD, replacing the None/NULL values
1780 # for owner_sid and group_sid currently present.
1782 # The admin_session_info matches that used in provision, and
1783 # is the best guess we can make for an existing object that
1784 # hasn't had something specifically set.
1786 # This is important for the dns related naming contexts.
1787 self
.samdb
.set_session_info(self
.admin_session_info
)
1788 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1789 "Failed to fix metadata for attribute %s" % sd_attr
):
1790 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1791 self
.samdb
.set_session_info(self
.system_session_info
)
1793 def is_expired_tombstone(self
, dn
, repl_val
):
1794 if self
.check_expired_tombstones
:
1795 # This is not the default, it's just
1796 # used to keep dbcheck tests work with
1797 # old static provision dumps
1800 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, repl_val
)
1802 isDeleted
= self
.find_repl_attid(repl
, drsuapi
.DRSUAPI_ATTID_isDeleted
)
1804 delete_time
= samba
.nttime2unix(isDeleted
.originating_change_time
)
1805 current_time
= time
.time()
1807 tombstone_delta
= self
.tombstoneLifetime
* (24 * 60 * 60)
1809 delta
= current_time
- delete_time
1810 if delta
<= tombstone_delta
:
1813 self
.report("SKIPING: object %s is an expired tombstone" % dn
)
1814 self
.report("isDeleted: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1817 isDeleted
.originating_invocation_id
,
1818 isDeleted
.originating_usn
,
1819 isDeleted
.local_usn
,
1820 time
.ctime(samba
.nttime2unix(isDeleted
.originating_change_time
))))
1821 self
.expired_tombstones
+= 1
1824 def find_changes_after_deletion(self
, repl_val
):
1825 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, repl_val
)
1827 isDeleted
= self
.find_repl_attid(repl
, drsuapi
.DRSUAPI_ATTID_isDeleted
)
1829 delete_time
= samba
.nttime2unix(isDeleted
.originating_change_time
)
1831 tombstone_delta
= self
.tombstoneLifetime
* (24 * 60 * 60)
1834 for o
in repl
.ctr
.array
:
1835 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1838 if o
.local_usn
<= isDeleted
.local_usn
:
1841 if o
.originating_change_time
<= isDeleted
.originating_change_time
:
1844 change_time
= samba
.nttime2unix(o
.originating_change_time
)
1846 delta
= change_time
- delete_time
1847 if delta
<= tombstone_delta
:
1850 # If the modification happened after the tombstone lifetime
1851 # has passed, we have a bug as the object might be deleted
1852 # already on other DCs and won't be able to replicate
1856 return found
, isDeleted
1858 def has_changes_after_deletion(self
, dn
, repl_val
):
1859 found
, isDeleted
= self
.find_changes_after_deletion(repl_val
)
1863 def report_attid(o
):
1865 attname
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1867 attname
= "<unknown:0x%x08x>" % o
.attid
1869 self
.report("%s: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1870 attname
, o
.attid
, o
.version
,
1871 o
.originating_invocation_id
,
1874 time
.ctime(samba
.nttime2unix(o
.originating_change_time
))))
1876 self
.report("ERROR: object %s, has changes after deletion" % dn
)
1877 report_attid(isDeleted
)
1883 def err_changes_after_deletion(self
, dn
, repl_val
):
1884 found
, isDeleted
= self
.find_changes_after_deletion(repl_val
)
1886 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1887 rdn_attr
= dn
.get_rdn_name()
1888 rdn_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(rdn_attr
,
1889 is_schema_nc
=in_schema_nc
)
1893 if o
.attid
== rdn_attid
:
1895 if o
.attid
== drsuapi
.DRSUAPI_ATTID_name
:
1897 if o
.attid
== drsuapi
.DRSUAPI_ATTID_lastKnownParent
:
1900 attname
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1902 attname
= "<unknown:0x%x08x>" % o
.attid
1903 unexpected
.append(attname
)
1905 if len(unexpected
) > 0:
1906 self
.report('Unexpeted attributes: %s' % ",".join(unexpected
))
1907 self
.report('Not fixing changes after deletion bug')
1910 if not self
.confirm_all('Delete broken tombstone object %s deleted %s days ago?' % (
1911 dn
, self
.tombstoneLifetime
), 'fix_changes_after_deletion_bug'):
1912 self
.report('Not fixing changes after deletion bug')
1915 if self
.do_delete(dn
, ["relax:0"],
1916 "Failed to remove DN %s" % dn
):
1917 self
.report("Removed DN %s" % dn
)
1919 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
1920 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1925 # Search for a zero invocationID
1926 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1930 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1931 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1932 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1933 % (dn
, o
.attid
, o
.version
,
1934 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
1935 self
.samdb
.get_invocation_id()))
1939 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
1940 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1943 now
= samba
.unix2nttime(int(time
.time()))
1946 # Search for a zero invocationID
1947 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1951 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
1952 o
.version
= o
.version
+ 1
1953 o
.originating_change_time
= now
1954 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
1955 o
.originating_usn
= seq
1959 replBlob
= ndr_pack(repl
)
1963 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1964 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1965 self
.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr
, dn
))
1968 nmsg
= ldb
.Message()
1970 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1971 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1972 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1973 "Failed to fix attribute %s" % attr
):
1974 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1976 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
1977 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1981 # Search for an invalid attid
1983 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1985 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
1988 def err_replmetadata_incorrect_attid(self
, dn
, attr
, repl_meta_data
, wrong_attids
):
1989 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1994 remove_attid
= set()
1997 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
2000 # Sort the array, except for the last element. This strange
2001 # construction, creating a new list, due to bugs in samba's
2002 # array handling in IDL generated objects.
2003 ctr
.array
= sorted(ctr
.array
[:], key
=lambda o
: o
.attid
)
2004 # Now walk it in reverse, so we see the low (and so incorrect,
2005 # the correct values are above 0x80000000) values first and
2006 # remove the 'second' value we see.
2007 for o
in reversed(ctr
.array
):
2008 print("%s: 0x%08x" % (dn
, o
.attid
))
2009 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
2010 if att
.lower() in set_att
:
2011 self
.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att
, attr
, dn
))
2012 if not self
.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
2013 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
),
2014 'fix_replmetadata_duplicate_attid'):
2015 self
.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
2016 % (o
.attid
, att
, attr
, dn
))
2019 remove_attid
.add(o
.attid
)
2020 # We want to set the metadata for the most recent
2021 # update to have been applied locally, that is the metadata
2022 # matching the (eg string) value in the attribute
2023 if o
.local_usn
> hash_att
[att
].local_usn
:
2024 # This is always what we would have sent over DRS,
2025 # because the DRS server will have sent the
2026 # msDS-IntID, but with the values from both
2027 # attribute entries.
2028 hash_att
[att
].version
= o
.version
2029 hash_att
[att
].originating_change_time
= o
.originating_change_time
2030 hash_att
[att
].originating_invocation_id
= o
.originating_invocation_id
2031 hash_att
[att
].originating_usn
= o
.originating_usn
2032 hash_att
[att
].local_usn
= o
.local_usn
2034 # Do not re-add the value to the set or overwrite the hash value
2038 set_att
.add(att
.lower())
2040 # Generate a real list we can sort on properly
2041 new_list
= [o
for o
in ctr
.array
if o
.attid
not in remove_attid
]
2043 if (len(wrong_attids
) > 0):
2045 if o
.attid
in wrong_attids
:
2046 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
2047 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
, is_schema_nc
=in_schema_nc
)
2048 self
.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr
, dn
))
2049 if not self
.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
2050 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
), 'fix_replmetadata_wrong_attid'):
2051 self
.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
2052 % (o
.attid
, correct_attid
, att
, attr
, dn
))
2055 o
.attid
= correct_attid
2057 # Sort the array, (we changed the value so must re-sort)
2058 new_list
[:] = sorted(new_list
[:], key
=lambda o
: o
.attid
)
2060 # If we did not already need to fix it, then ask about sorting
2062 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
2063 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
2064 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
2065 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
2068 # The actual sort done is done at the top of the function
2070 ctr
.count
= len(new_list
)
2071 ctr
.array
= new_list
2072 replBlob
= ndr_pack(repl
)
2074 nmsg
= ldb
.Message()
2076 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
2077 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
2078 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
2079 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
2080 "Failed to fix attribute %s" % attr
):
2081 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
2083 def is_deleted_deleted_objects(self
, obj
):
2085 if "description" not in obj
:
2086 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
2088 if "showInAdvancedViewOnly" not in obj
or str(obj
['showInAdvancedViewOnly'][0]).upper() == 'FALSE':
2089 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
2091 if "objectCategory" not in obj
:
2092 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
2094 if "isCriticalSystemObject" not in obj
or str(obj
['isCriticalSystemObject'][0]).upper() == 'FALSE':
2095 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
2097 if "isRecycled" in obj
:
2098 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
2100 if "isDeleted" in obj
and str(obj
['isDeleted'][0]).upper() == 'FALSE':
2101 self
.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj
.dn
)
2103 if "objectClass" not in obj
or (len(obj
['objectClass']) != 2 or
2104 str(obj
['objectClass'][0]) != 'top' or
2105 str(obj
['objectClass'][1]) != 'container'):
2106 self
.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj
.dn
)
2108 if "systemFlags" not in obj
or str(obj
['systemFlags'][0]) != '-1946157056':
2109 self
.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj
.dn
)
2113 def err_deleted_deleted_objects(self
, obj
):
2114 nmsg
= ldb
.Message()
2115 nmsg
.dn
= dn
= obj
.dn
2117 if "description" not in obj
:
2118 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
2119 if "showInAdvancedViewOnly" not in obj
:
2120 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
2121 if "objectCategory" not in obj
:
2122 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
2123 if "isCriticalSystemObject" not in obj
:
2124 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
2125 if "isRecycled" in obj
:
2126 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
2128 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2129 nmsg
["systemFlags"] = ldb
.MessageElement("-1946157056", ldb
.FLAG_MOD_REPLACE
, "systemFlags")
2130 nmsg
["objectClass"] = ldb
.MessageElement(["top", "container"], ldb
.FLAG_MOD_REPLACE
, "objectClass")
2132 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
2133 % (dn
), 'fix_deleted_deleted_objects'):
2134 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
2137 if self
.do_modify(nmsg
, ["relax:0"],
2138 "Failed to fix Deleted Objects container %s" % dn
):
2139 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
2141 def err_replica_locations(self
, obj
, cross_ref
, attr
):
2142 nmsg
= ldb
.Message()
2144 target
= self
.samdb
.get_dsServiceName()
2146 if self
.samdb
.am_rodc():
2147 self
.report('Not fixing %s %s for the RODC' % (attr
, obj
.dn
))
2150 if not self
.confirm_all('Add yourself to the replica locations for %s?'
2151 % (obj
.dn
), 'fix_replica_locations'):
2152 self
.report('Not fixing missing/incorrect attributes on %s\n' % (obj
.dn
))
2155 nmsg
[attr
] = ldb
.MessageElement(target
, ldb
.FLAG_MOD_ADD
, attr
)
2156 if self
.do_modify(nmsg
, [], "Failed to add %s for %s" % (attr
, obj
.dn
)):
2157 self
.report("Fixed %s for %s" % (attr
, obj
.dn
))
2159 def is_fsmo_role(self
, dn
):
2160 if dn
== self
.samdb
.domain_dn
:
2162 if dn
== self
.infrastructure_dn
:
2164 if dn
== self
.naming_dn
:
2166 if dn
== self
.schema_dn
:
2168 if dn
== self
.rid_dn
:
2173 def calculate_instancetype(self
, dn
):
2175 nc_root
= self
.samdb
.get_nc_root(dn
)
2177 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
2179 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
2180 except ldb
.LdbError
as e4
:
2181 (enum
, estr
) = e4
.args
2182 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2185 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
2186 if self
.write_ncs
is not None and str(nc_root
) in [str(x
) for x
in self
.write_ncs
]:
2187 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
2191 def get_wellknown_sd(self
, dn
):
2192 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
2194 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
2195 return ndr_unpack(security
.descriptor
,
2196 descriptor_fn(domain_sid
,
2197 name_map
=self
.name_map
))
2201 def check_object(self
, dn
, attrs
=None):
2202 '''check one object'''
2204 self
.report("Checking object %s" % dn
)
2208 # make a local copy to modify
2210 if "dn" in map(str.lower
, attrs
):
2211 attrs
.append("name")
2212 if "distinguishedname" in map(str.lower
, attrs
):
2213 attrs
.append("name")
2214 if str(dn
.get_rdn_name()).lower() in map(str.lower
, attrs
):
2215 attrs
.append("name")
2216 if 'name' in map(str.lower
, attrs
):
2217 attrs
.append(dn
.get_rdn_name())
2218 attrs
.append("isDeleted")
2219 attrs
.append("systemFlags")
2220 need_replPropertyMetaData
= False
2222 need_replPropertyMetaData
= True
2225 linkID
, _
= self
.get_attr_linkID_and_reverse_name(a
)
2230 need_replPropertyMetaData
= True
2232 if need_replPropertyMetaData
:
2233 attrs
.append("replPropertyMetaData")
2234 attrs
.append("objectGUID")
2238 sd_flags |
= security
.SECINFO_OWNER
2239 sd_flags |
= security
.SECINFO_GROUP
2240 sd_flags |
= security
.SECINFO_DACL
2241 sd_flags |
= security
.SECINFO_SACL
2243 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
2248 "sd_flags:1:%d" % sd_flags
,
2249 "reveal_internals:0",
2252 except ldb
.LdbError
as e10
:
2253 (enum
, estr
) = e10
.args
2254 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2255 if self
.in_transaction
:
2256 self
.report("ERROR: Object %s disappeared during check" % dn
)
2261 self
.report("ERROR: Object %s failed to load during check" % dn
)
2265 set_attrs_from_md
= set()
2266 set_attrs_seen
= set()
2267 got_objectclass
= False
2269 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
2271 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
2272 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
2274 # We have no deleted objects DN for schema, and we check for this above for the other
2276 deleted_objects_dn
= None
2278 object_rdn_attr
= None
2279 object_rdn_val
= None
2283 repl_meta_data_val
= None
2285 for attrname
in obj
:
2286 if str(attrname
).lower() == 'isdeleted':
2287 if str(obj
[attrname
][0]) != "FALSE":
2290 if str(attrname
).lower() == 'systemflags':
2291 systemFlags
= int(obj
[attrname
][0])
2293 if str(attrname
).lower() == 'replpropertymetadata':
2294 repl_meta_data_val
= obj
[attrname
][0]
2296 if isDeleted
and repl_meta_data_val
:
2297 if self
.has_changes_after_deletion(dn
, repl_meta_data_val
):
2299 self
.err_changes_after_deletion(dn
, repl_meta_data_val
)
2301 if self
.is_expired_tombstone(dn
, repl_meta_data_val
):
2304 for attrname
in obj
:
2305 if attrname
== 'dn' or attrname
== "distinguishedName":
2308 if str(attrname
).lower() == 'objectclass':
2309 got_objectclass
= True
2311 if str(attrname
).lower() == "name":
2312 if len(obj
[attrname
]) != 1:
2314 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2315 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2317 name_val
= str(obj
[attrname
][0])
2319 if str(attrname
).lower() == str(obj
.dn
.get_rdn_name()).lower():
2320 object_rdn_attr
= attrname
2321 if len(obj
[attrname
]) != 1:
2323 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2324 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2326 object_rdn_val
= str(obj
[attrname
][0])
2328 if str(attrname
).lower() == 'replpropertymetadata':
2329 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
][0]):
2331 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
][0])
2332 # We don't continue, as we may also have other fixes for this attribute
2333 # based on what other attributes we see.
2336 (set_attrs_from_md
, list_attid_from_md
, wrong_attids
) \
2337 = self
.process_metadata(dn
, obj
[attrname
][0])
2340 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
2343 if len(set_attrs_from_md
) < len(list_attid_from_md
) \
2344 or len(wrong_attids
) > 0 \
2345 or sorted(list_attid_from_md
) != list_attid_from_md
:
2347 self
.err_replmetadata_incorrect_attid(dn
, attrname
, obj
[attrname
][0], wrong_attids
)
2350 # Here we check that the first attid is 0
2352 if list_attid_from_md
[0] != 0:
2354 self
.report("ERROR: Not fixing incorrect initial attributeID in '%s' on '%s', it should be objectClass" %
2355 (attrname
, str(dn
)))
2359 if str(attrname
).lower() == 'ntsecuritydescriptor':
2360 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
2361 if sd_broken
is not None:
2362 self
.err_wrong_sd(dn
, sd
, sd_broken
)
2366 if sd
.owner_sid
is None or sd
.group_sid
is None:
2367 self
.err_missing_sd_owner(dn
, sd
)
2371 if self
.reset_well_known_acls
:
2373 well_known_sd
= self
.get_wellknown_sd(dn
)
2377 current_sd
= ndr_unpack(security
.descriptor
,
2380 diff
= get_diff_sds(well_known_sd
, current_sd
, security
.dom_sid(self
.samdb
.get_domain_sid()))
2382 self
.err_wrong_default_sd(dn
, well_known_sd
, current_sd
, diff
)
2387 if str(attrname
).lower() == 'objectclass':
2388 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
2389 # Do not consider the attribute incorrect if:
2390 # - The sorted (alphabetically) list is the same, inclding case
2391 # - The first and last elements are the same
2393 # This avoids triggering an error due to
2394 # non-determinism in the sort routine in (at least)
2395 # 4.3 and earlier, and the fact that any AUX classes
2396 # in these attributes are also not sorted when
2397 # imported from Windows (they are just in the reverse
2398 # order of last set)
2399 if sorted(normalised
) != sorted(obj
[attrname
]) \
2400 or normalised
[0] != obj
[attrname
][0] \
2401 or normalised
[-1] != obj
[attrname
][-1]:
2402 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
2406 if str(attrname
).lower() == 'userparameters':
2407 if len(obj
[attrname
][0]) == 1 and obj
[attrname
][0][0] == b
'\x20'[0]:
2409 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
2412 elif obj
[attrname
][0][:16] == b
'\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2413 # This is the correct, normal prefix
2416 elif obj
[attrname
][0][:20] == b
'IAAgACAAIAAgACAAIAAg':
2417 # this is the typical prefix from a windows migration
2419 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
2422 #43:00:00:00:74:00:00:00:78
2423 elif obj
[attrname
][0][1] != b
'\x00'[0] and obj
[attrname
][0][3] != b
'\x00'[0] and obj
[attrname
][0][5] != b
'\x00'[0] and obj
[attrname
][0][7] != b
'\x00'[0] and obj
[attrname
][0][9] != b
'\x00'[0]:
2424 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2426 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
2429 elif len(obj
[attrname
][0]) % 2 != 0:
2430 # This is a value that isn't even in length
2432 self
.err_odd_userParameters(obj
, attrname
)
2435 elif obj
[attrname
][0][1] == b
'\x00'[0] and obj
[attrname
][0][2] == b
'\x00'[0] and obj
[attrname
][0][3] == b
'\x00'[0] and obj
[attrname
][0][4] != b
'\x00'[0] and obj
[attrname
][0][5] == b
'\x00'[0]:
2436 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2438 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
2441 if attrname
.lower() == 'attributeid' or attrname
.lower() == 'governsid':
2442 if obj
[attrname
][0] in self
.attribute_or_class_ids
:
2444 self
.report('Error: %s %s on %s already exists as an attributeId or governsId'
2445 % (attrname
, obj
.dn
, obj
[attrname
][0]))
2447 self
.attribute_or_class_ids
.add(obj
[attrname
][0])
2449 # check for empty attributes
2450 for val
in obj
[attrname
]:
2452 self
.err_empty_attribute(dn
, attrname
)
2456 # get the syntax oid for the attribute, so we can can have
2457 # special handling for some specific attribute types
2459 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
2460 except Exception as msg
:
2461 self
.err_unknown_attribute(obj
, attrname
)
2465 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
2467 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
2468 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
2469 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
2471 set_attrs_seen
.add(str(attrname
).lower())
2473 if syntax_oid
in [dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
2474 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
2475 # it's some form of DN, do specialised checking on those
2476 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
2480 # check for incorrectly normalised attributes
2481 for val
in obj
[attrname
]:
2484 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
2485 if len(normalised
) != 1 or normalised
[0] != val
:
2486 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
2490 if len(obj
[attrname
]) != len(values
):
2491 self
.err_duplicate_values(dn
, attrname
, obj
[attrname
], list(values
))
2495 if str(attrname
).lower() == "instancetype":
2496 calculated_instancetype
= self
.calculate_instancetype(dn
)
2497 if len(obj
["instanceType"]) != 1 or int(obj
["instanceType"][0]) != calculated_instancetype
:
2499 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
2501 if not got_objectclass
and ("*" in attrs
or "objectclass" in map(str.lower
, attrs
)):
2503 self
.err_missing_objectclass(dn
)
2505 if ("*" in attrs
or "name" in map(str.lower
, attrs
)):
2506 if name_val
is None:
2508 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
2509 if object_rdn_attr
is None:
2511 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
2513 if name_val
is not None:
2515 controls
= ["show_recycled:1", "relax:0"]
2517 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
2518 parent_dn
= deleted_objects_dn
2519 controls
+= ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
]
2520 if parent_dn
is None:
2521 parent_dn
= obj
.dn
.parent()
2522 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
2523 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
2525 if obj
.dn
== deleted_objects_dn
:
2526 expected_dn
= obj
.dn
2528 if expected_dn
!= obj
.dn
:
2530 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
,
2531 object_rdn_val
, name_val
, controls
)
2532 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
2534 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
, object_rdn_val
, str(obj
.dn
)))
2537 if repl_meta_data_val
:
2538 if obj
.dn
== deleted_objects_dn
:
2539 isDeletedAttId
= 131120
2540 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2542 expectedTimeDo
= 2650466015990000000
2543 originating
= self
.get_originating_time(repl_meta_data_val
, isDeletedAttId
)
2544 if originating
!= expectedTimeDo
:
2545 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
2546 nmsg
= ldb
.Message()
2548 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2550 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
2553 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
2555 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
2557 self
.report("On object %s" % dn
)
2560 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
2561 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
2562 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
2564 self
.fix_metadata(obj
, att
)
2566 if self
.is_fsmo_role(dn
):
2567 if "fSMORoleOwner" not in obj
and ("*" in attrs
or "fsmoroleowner" in map(str.lower
, attrs
)):
2568 self
.err_no_fsmoRoleOwner(obj
)
2572 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
2573 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
2574 controls
=["show_recycled:1", "show_deleted:1"])
2575 except ldb
.LdbError
as e11
:
2576 (enum
, estr
) = e11
.args
2577 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2579 self
.report("WARNING: parent object not found for %s" % (obj
.dn
))
2580 self
.report("Not moving to LostAndFound "
2581 "(tombstone garbage collection in progress?)")
2583 self
.err_missing_parent(obj
)
2588 if dn
in self
.deleted_objects_containers
and '*' in attrs
:
2589 if self
.is_deleted_deleted_objects(obj
):
2590 self
.err_deleted_deleted_objects(obj
)
2593 for (dns_part
, msg
) in self
.dns_partitions
:
2594 if dn
== dns_part
and 'repsFrom' in obj
:
2595 location
= "msDS-NC-Replica-Locations"
2596 if self
.samdb
.am_rodc():
2597 location
= "msDS-NC-RO-Replica-Locations"
2599 if location
not in msg
:
2600 # There are no replica locations!
2601 self
.err_replica_locations(obj
, msg
.dn
, location
)
2606 for loc
in msg
[location
]:
2607 if str(loc
) == self
.samdb
.get_dsServiceName():
2610 # This DC is not in the replica locations
2611 self
.err_replica_locations(obj
, msg
.dn
, location
)
2614 if dn
== self
.server_ref_dn
:
2615 # Check we have a valid RID Set
2616 if "*" in attrs
or "rIDSetReferences" in attrs
:
2617 if "rIDSetReferences" not in obj
:
2618 # NO RID SET reference
2619 # We are RID master, allocate it.
2622 if self
.is_rid_master
:
2623 # Allocate a RID Set
2624 if self
.confirm_all('Allocate the missing RID set for RID master?',
2625 'fix_missing_rid_set_master'):
2627 # We don't have auto-transaction logic on
2628 # extended operations, so we have to do it
2631 self
.samdb
.transaction_start()
2634 self
.samdb
.create_own_rid_set()
2637 self
.samdb
.transaction_cancel()
2640 self
.samdb
.transaction_commit()
2642 elif not self
.samdb
.am_rodc():
2643 self
.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn
)
2645 # Check some details of our own RID Set
2646 if dn
== self
.rid_set_dn
:
2647 res
= self
.samdb
.search(base
=self
.rid_set_dn
, scope
=ldb
.SCOPE_BASE
,
2648 attrs
=["rIDAllocationPool",
2649 "rIDPreviousAllocationPool",
2652 if "rIDAllocationPool" not in res
[0]:
2653 self
.report("No rIDAllocationPool found in %s" % dn
)
2656 next_pool
= int(res
[0]["rIDAllocationPool"][0])
2658 high
= (0xFFFFFFFF00000000 & next_pool
) >> 32
2659 low
= 0x00000000FFFFFFFF & next_pool
2662 self
.report("Invalid RID set %d-%s, %d > %d!" % (low
, high
, low
, high
))
2665 if "rIDNextRID" in res
[0]:
2666 next_free_rid
= int(res
[0]["rIDNextRID"][0])
2670 if next_free_rid
== 0:
2675 # Check the remainder of this pool for conflicts. If
2676 # ridalloc_allocate_rid() moves to a new pool, this
2677 # will be above high, so we will stop.
2678 while next_free_rid
<= high
:
2679 sid
= "%s-%d" % (self
.samdb
.get_domain_sid(), next_free_rid
)
2681 res
= self
.samdb
.search(base
="<SID=%s>" % sid
, scope
=ldb
.SCOPE_BASE
,
2683 except ldb
.LdbError
as e
:
2684 (enum
, estr
) = e
.args
2685 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2689 self
.report("SID %s for %s conflicts with our current RID set in %s" % (sid
, res
[0].dn
, dn
))
2692 if self
.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2694 'fix_sid_rid_set_conflict'):
2695 self
.samdb
.transaction_start()
2697 # This will burn RIDs, which will move
2698 # past the conflict. We then check again
2699 # to see if the new RID conflicts, until
2700 # the end of the current pool. We don't
2701 # look at the next pool to avoid burning
2702 # all RIDs in one go in some strange
2706 allocated_rid
= self
.samdb
.allocate_rid()
2707 if allocated_rid
>= next_free_rid
:
2708 next_free_rid
= allocated_rid
+ 1
2711 self
.samdb
.transaction_cancel()
2714 self
.samdb
.transaction_commit()
2722 ################################################################
2723 # check special @ROOTDSE attributes
2724 def check_rootdse(self
):
2725 '''check the @ROOTDSE special object'''
2726 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
2728 self
.report("Checking object %s" % dn
)
2729 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
2731 self
.report("Object %s disappeared during check" % dn
)
2736 # check that the dsServiceName is in GUID form
2737 if 'dsServiceName' not in obj
:
2738 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
2739 return error_count
+ 1
2741 if not str(obj
['dsServiceName'][0]).startswith('<GUID='):
2742 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2744 if not self
.confirm('Change dsServiceName to GUID form?'):
2746 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0].decode('utf8')),
2747 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
2748 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
2751 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
2752 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
2753 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
2754 self
.report("Changed dsServiceName to GUID form")
2757 ###############################################
2758 # re-index the database
2760 def reindex_database(self
):
2761 '''re-index the whole database'''
2763 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
2764 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
2765 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
2766 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
2768 ###############################################
2770 def reset_modules(self
):
2771 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2773 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
2774 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
2775 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)