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 self
.tombstoneLifetime
= int(res
[0]["tombstoneLifetime"][0])
225 self
.compatibleFeatures
= []
226 self
.requiredFeatures
= []
229 res
= self
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
231 attrs
=["compatibleFeatures",
233 if "compatibleFeatures" in res
[0]:
234 self
.compatibleFeatures
= res
[0]["compatibleFeatures"]
235 if "requiredFeatures" in res
[0]:
236 self
.requiredFeatures
= res
[0]["requiredFeatures"]
237 except ldb
.LdbError
as e6
:
238 (enum
, estr
) = e6
.args
239 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
243 def check_database(self
, DN
=None, scope
=ldb
.SCOPE_SUBTREE
, controls
=None,
245 '''perform a database check, returning the number of errors found'''
246 res
= self
.samdb
.search(base
=DN
, scope
=scope
, attrs
=['dn'], controls
=controls
)
247 self
.report('Checking %u objects' % len(res
))
250 error_count
+= self
.check_deleted_objects_containers()
252 self
.attribute_or_class_ids
= set()
255 self
.dn_set
.add(str(object.dn
))
256 error_count
+= self
.check_object(object.dn
, attrs
=attrs
)
259 error_count
+= self
.check_rootdse()
261 if self
.expired_tombstones
> 0:
262 self
.report("NOTICE: found %d expired tombstones, "
263 "'samba' will remove them daily, "
264 "'samba-tool domain tombstones expunge' "
265 "would do that immediately." % (
266 self
.expired_tombstones
))
268 if error_count
!= 0 and not self
.fix
:
269 self
.report("Please use --fix to fix these errors")
271 self
.report('Checked %u objects (%u errors)' % (len(res
), error_count
))
274 def check_deleted_objects_containers(self
):
275 """This function only fixes conflicts on the Deleted Objects
276 containers, not the attributes"""
278 for nc
in self
.ncs_lacking_deleted_containers
:
279 if nc
== self
.schema_dn
:
282 self
.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc
)
283 if not self
.confirm_all('Fix missing Deleted Objects container for %s?' % (nc
), 'fix_missing_deleted_objects'):
286 dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects")
291 # If something already exists here, add a conflict
292 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[],
293 controls
=["show_deleted:1", "extended_dn:1:1",
294 "show_recycled:1", "reveal_internals:0"])
296 guid
= res
[0].dn
.get_extended_component("GUID")
297 conflict_dn
= ldb
.Dn(self
.samdb
,
298 "CN=Deleted Objects\\0ACNF:%s" % str(misc
.GUID(guid
)))
299 conflict_dn
.add_base(nc
)
301 except ldb
.LdbError
as e2
:
302 (enum
, estr
) = e2
.args
303 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
306 self
.report("Couldn't check for conflicting Deleted Objects container: %s" % estr
)
309 if conflict_dn
is not None:
311 self
.samdb
.rename(dn
, conflict_dn
, ["show_deleted:1", "relax:0", "show_recycled:1"])
312 except ldb
.LdbError
as e1
:
313 (enum
, estr
) = e1
.args
314 self
.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn
, conflict_dn
, estr
))
317 # Refresh wellKnownObjects links
318 res
= self
.samdb
.search(base
=nc
, scope
=ldb
.SCOPE_BASE
,
319 attrs
=['wellKnownObjects'],
320 controls
=["show_deleted:1", "extended_dn:0",
321 "show_recycled:1", "reveal_internals:0"])
323 self
.report("wellKnownObjects was not found for NC %s" % nc
)
326 # Prevent duplicate deleted objects containers just in case
327 wko
= res
[0]["wellKnownObjects"]
329 proposed_objectguid
= None
331 dsdb_dn
= dsdb_Dn(self
.samdb
, o
.decode('utf8'), dsdb
.DSDB_SYNTAX_BINARY_DN
)
332 if self
.is_deleted_objects_dn(dsdb_dn
):
333 self
.report("wellKnownObjects had duplicate Deleted Objects value %s" % o
)
334 # We really want to put this back in the same spot
335 # as the original one, so that on replication we
336 # merge, rather than conflict.
337 proposed_objectguid
= dsdb_dn
.dn
.get_extended_component("GUID")
338 listwko
.append(str(o
))
340 if proposed_objectguid
is not None:
341 guid_suffix
= "\nobjectGUID: %s" % str(misc
.GUID(proposed_objectguid
))
343 wko_prefix
= "B:32:%s" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
344 listwko
.append('%s:%s' % (wko_prefix
, dn
))
347 # Insert a brand new Deleted Objects container
348 self
.samdb
.add_ldif("""dn: %s
350 objectClass: container
351 description: Container for deleted objects
353 isCriticalSystemObject: TRUE
354 showInAdvancedViewOnly: TRUE
355 systemFlags: -1946157056%s""" % (dn
, guid_suffix
),
356 controls
=["relax:0", "provision:0"])
358 delta
= ldb
.Message()
359 delta
.dn
= ldb
.Dn(self
.samdb
, str(res
[0]["dn"]))
360 delta
["wellKnownObjects"] = ldb
.MessageElement(listwko
,
361 ldb
.FLAG_MOD_REPLACE
,
364 # Insert the link to the brand new container
365 if self
.do_modify(delta
, ["relax:0"],
366 "NC %s lacks Deleted Objects WKGUID" % nc
,
368 self
.report("Added %s well known guid link" % dn
)
370 self
.deleted_objects_containers
.append(dn
)
374 def report(self
, msg
):
375 '''print a message unless quiet is set'''
379 def confirm(self
, msg
, allow_all
=False, forced
=False):
380 '''confirm a change'''
387 return common
.confirm(msg
, forced
=forced
, allow_all
=allow_all
)
389 ################################################################
390 # a local confirm function with support for 'all'
391 def confirm_all(self
, msg
, all_attr
):
392 '''confirm a change with support for "all" '''
395 if getattr(self
, all_attr
) == 'NONE':
397 if getattr(self
, all_attr
) == 'ALL':
403 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
405 setattr(self
, all_attr
, 'ALL')
408 setattr(self
, all_attr
, 'NONE')
412 def do_delete(self
, dn
, controls
, msg
):
413 '''delete dn with optional verbose output'''
415 self
.report("delete DN %s" % dn
)
417 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
418 self
.samdb
.delete(dn
, controls
=controls
)
419 except Exception as err
:
420 if self
.in_transaction
:
421 raise CommandError("%s : %s" % (msg
, err
))
422 self
.report("%s : %s" % (msg
, err
))
426 def do_modify(self
, m
, controls
, msg
, validate
=True):
427 '''perform a modify with optional verbose output'''
428 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
430 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
431 self
.report("controls: %r" % controls
)
433 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
434 except Exception as err
:
435 if self
.in_transaction
:
436 raise CommandError("%s : %s" % (msg
, err
))
437 self
.report("%s : %s" % (msg
, err
))
441 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
442 '''perform a modify with optional verbose output'''
444 self
.report("""dn: %s
448 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
450 to_dn
= to_rdn
+ to_base
451 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
452 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
453 except Exception as err
:
454 if self
.in_transaction
:
455 raise CommandError("%s : %s" % (msg
, err
))
456 self
.report("%s : %s" % (msg
, err
))
460 def get_attr_linkID_and_reverse_name(self
, attrname
):
461 if attrname
in self
.link_id_cache
:
462 return self
.link_id_cache
[attrname
]
463 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
465 revname
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
468 self
.link_id_cache
[attrname
] = (linkID
, revname
)
469 return linkID
, revname
471 def err_empty_attribute(self
, dn
, attrname
):
472 '''fix empty attributes'''
473 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
474 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
475 self
.report("Not fixing empty attribute %s" % attrname
)
480 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
481 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
482 "Failed to remove empty attribute %s" % attrname
, validate
=False):
483 self
.report("Removed empty attribute %s" % attrname
)
485 def err_normalise_mismatch(self
, dn
, attrname
, values
):
486 '''fix attribute normalisation errors'''
487 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
490 normalised
= self
.samdb
.dsdb_normalise_attributes(
491 self
.samdb_schema
, attrname
, [val
])
492 if len(normalised
) != 1:
493 self
.report("Unable to normalise value '%s'" % val
)
494 mod_list
.append((val
, ''))
495 elif (normalised
[0] != val
):
496 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
497 mod_list
.append((val
, normalised
[0]))
498 if not self
.confirm_all('Fix normalisation for %s from %s?' % (attrname
, dn
), 'fix_all_normalisation'):
499 self
.report("Not fixing attribute %s" % attrname
)
504 for i
in range(0, len(mod_list
)):
505 (val
, nval
) = mod_list
[i
]
506 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
508 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
511 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
512 "Failed to normalise attribute %s" % attrname
,
514 self
.report("Normalised attribute %s" % attrname
)
516 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
517 '''fix attribute normalisation errors'''
518 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
519 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
520 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
521 if list(normalised
) == values
:
523 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
524 self
.report("Not fixing attribute '%s'" % attrname
)
529 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
531 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
532 "Failed to normalise attribute %s" % attrname
,
534 self
.report("Normalised attribute %s" % attrname
)
536 def err_duplicate_values(self
, dn
, attrname
, dup_values
, values
):
537 '''fix attribute normalisation errors'''
538 self
.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname
, dn
))
539 self
.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dump_attr_values(dup_values
)), ','.join(dump_attr_values(values
))))
540 if not self
.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_duplicates'):
541 self
.report("Not fixing attribute '%s'" % attrname
)
546 m
[attrname
] = ldb
.MessageElement(values
, ldb
.FLAG_MOD_REPLACE
, attrname
)
548 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
549 "Failed to remove duplicate value on attribute %s" % attrname
,
551 self
.report("Removed duplicate value on attribute %s" % attrname
)
553 def is_deleted_objects_dn(self
, dsdb_dn
):
554 '''see if a dsdb_Dn is the special Deleted Objects DN'''
555 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
557 def err_missing_objectclass(self
, dn
):
558 """handle object without objectclass"""
559 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
)))
560 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'):
561 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
563 if self
.do_delete(dn
, ["relax:0"],
564 "Failed to remove DN %s" % dn
):
565 self
.report("Removed DN %s" % dn
)
567 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, remove_plausible
=False):
568 """handle a DN pointing to a deleted object"""
569 if not remove_plausible
:
570 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
571 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
572 if not self
.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
573 self
.report("Not removing")
576 self
.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
577 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
578 if not self
.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
579 self
.report("Not removing")
584 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
585 if self
.do_modify(m
, ["show_recycled:1",
586 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
587 "Failed to remove deleted DN attribute %s" % attrname
):
588 self
.report("Removed deleted DN on attribute %s" % attrname
)
590 def err_missing_target_dn_or_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
591 """handle a missing target DN (if specified, GUID form can't be found,
592 and otherwise DN string form can't be found)"""
594 # Don't change anything if the object itself is deleted
595 if str(dn
).find('\\0ADEL') != -1:
596 # We don't bump the error count as Samba produces these
597 # in normal operation
598 self
.report("WARNING: no target object found for GUID "
599 "component link %s in deleted object "
600 "%s - %s" % (attrname
, dn
, val
))
601 self
.report("Not removing dangling one-way "
602 "link on deleted object "
603 "(tombstone garbage collection in progress?)")
606 # check if its a backlink
607 linkID
, _
= self
.get_attr_linkID_and_reverse_name(attrname
)
608 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
610 linkID
, reverse_link_name \
611 = self
.get_attr_linkID_and_reverse_name(attrname
)
612 if reverse_link_name
is not None:
613 self
.report("WARNING: no target object found for GUID "
614 "component for one-way forward link "
616 "%s - %s" % (attrname
, dn
, val
))
617 self
.report("Not removing dangling forward link")
620 nc_root
= self
.samdb
.get_nc_root(dn
)
621 target_nc_root
= self
.samdb
.get_nc_root(dsdb_dn
.dn
)
622 if nc_root
!= target_nc_root
:
623 # We don't bump the error count as Samba produces these
624 # in normal operation
625 self
.report("WARNING: no target object found for GUID "
626 "component for cross-partition link "
628 "%s - %s" % (attrname
, dn
, val
))
629 self
.report("Not removing dangling one-way "
630 "cross-partition link "
631 "(we might be mid-replication)")
634 # Due to our link handling one-way links pointing to
635 # missing objects are plausible.
637 # We don't bump the error count as Samba produces these
638 # in normal operation
639 self
.report("WARNING: no target object found for GUID "
640 "component for DN value %s in object "
641 "%s - %s" % (attrname
, dn
, val
))
642 self
.err_deleted_dn(dn
, attrname
, val
,
643 dsdb_dn
, dsdb_dn
, True)
646 # We bump the error count here, as we should have deleted this
647 self
.report("ERROR: no target object found for GUID "
648 "component for link %s in object "
649 "%s - %s" % (attrname
, dn
, val
))
650 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
, False)
653 def err_missing_dn_GUID_component(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
654 """handle a missing GUID extended DN component"""
655 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
656 controls
= ["extended_dn:1:1", "show_recycled:1"]
658 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
659 attrs
=[], controls
=controls
)
660 except ldb
.LdbError
as e7
:
661 (enum
, estr
) = e7
.args
662 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
663 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
665 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
668 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
669 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
671 dsdb_dn
.dn
= res
[0].dn
673 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
674 self
.report("Not fixing %s" % errstr
)
678 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
679 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
681 if self
.do_modify(m
, ["show_recycled:1"],
682 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
683 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
685 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
686 """handle an incorrect binary DN component"""
687 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
689 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
690 self
.report("Not fixing %s" % errstr
)
694 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
695 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
697 if self
.do_modify(m
, ["show_recycled:1"],
698 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
699 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
701 def err_dn_string_component_old(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
702 """handle a DN string being incorrect"""
703 self
.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
704 dsdb_dn
.dn
= correct_dn
706 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
707 'fix_all_old_dn_string_component_mismatch'):
708 self
.report("Not fixing old string component")
712 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
713 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
714 if self
.do_modify(m
, ["show_recycled:1",
715 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
],
716 "Failed to fix old DN string on attribute %s" % (attrname
)):
717 self
.report("Fixed old DN string on attribute %s" % (attrname
))
719 def err_dn_component_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, mismatch_type
):
720 """handle a DN string being incorrect"""
721 self
.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type
, attrname
, dn
, val
))
722 dsdb_dn
.dn
= correct_dn
724 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
725 'fix_all_%s_dn_component_mismatch' % mismatch_type
):
726 self
.report("Not fixing %s component mismatch" % mismatch_type
)
730 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
731 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
732 if self
.do_modify(m
, ["show_recycled:1"],
733 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type
, attrname
)):
734 self
.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type
, attrname
))
736 def err_dn_component_missing_target_sid(self
, dn
, attrname
, val
, dsdb_dn
, target_sid_blob
):
737 """handle a DN string being incorrect"""
738 self
.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname
, dn
, val
))
740 if len(dsdb_dn
.prefix
) != 0:
741 self
.report("Not fixing missing DN SID on DN+BINARY or DN+STRING")
744 correct_dn
= ldb
.Dn(self
.samdb
, dsdb_dn
.dn
.extended_str())
745 correct_dn
.set_extended_component("SID", target_sid_blob
)
747 if not self
.confirm_all('Change DN to %s?' % correct_dn
.extended_str(),
748 'fix_all_SID_dn_component_missing'):
749 self
.report("Not fixing missing DN SID component")
752 target_guid_blob
= correct_dn
.get_extended_component("GUID")
753 guid_sid_dn
= ldb
.Dn(self
.samdb
, "")
754 guid_sid_dn
.set_extended_component("GUID", target_guid_blob
)
755 guid_sid_dn
.set_extended_component("SID", target_sid_blob
)
759 m
['new_value'] = ldb
.MessageElement(guid_sid_dn
.extended_str(), ldb
.FLAG_MOD_ADD
, attrname
)
762 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID
764 if self
.do_modify(m
, controls
,
765 "Failed to ADD missing DN SID on attribute %s" % (attrname
)):
766 self
.report("Fixed missing DN SID on attribute %s" % (attrname
))
768 def err_unknown_attribute(self
, obj
, attrname
):
769 '''handle an unknown attribute error'''
770 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
771 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
772 self
.report("Not removing %s" % attrname
)
776 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
777 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
778 "Failed to remove unknown attribute %s" % attrname
):
779 self
.report("Removed unknown attribute %s" % (attrname
))
781 def err_undead_linked_attribute(self
, obj
, attrname
, val
):
782 '''handle a link that should not be there on a deleted object'''
783 self
.report("ERROR: linked attribute '%s' to '%s' is present on "
784 "deleted object %s" % (attrname
, val
, obj
.dn
))
785 if not self
.confirm_all('Remove linked attribute %s' % attrname
, 'fix_undead_linked_attributes'):
786 self
.report("Not removing linked attribute %s" % attrname
)
790 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
792 if self
.do_modify(m
, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
793 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
794 "Failed to delete forward link %s" % attrname
):
795 self
.report("Fixed undead forward link %s" % (attrname
))
797 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
798 '''handle a missing backlink value'''
799 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
800 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
801 self
.report("Not fixing missing backlink %s" % backlink_name
)
805 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, backlink_name
)
806 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
807 "Failed to fix missing backlink %s" % backlink_name
):
808 self
.report("Fixed missing backlink %s" % (backlink_name
))
810 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
811 '''handle a incorrect RMD_FLAGS value'''
812 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
813 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()))
814 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
815 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
819 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
820 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
821 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
822 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
824 def err_orphaned_backlink(self
, obj_dn
, backlink_attr
, backlink_val
,
825 target_dn
, forward_attr
, forward_syntax
,
826 check_duplicates
=True):
827 '''handle a orphaned backlink value'''
828 if check_duplicates
is True and self
.has_duplicate_links(target_dn
, forward_attr
, forward_syntax
):
829 self
.report("WARNING: Keep orphaned backlink attribute " +
830 "'%s' in '%s' for link '%s' in '%s'" % (
831 backlink_attr
, obj_dn
, forward_attr
, target_dn
))
833 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr
, obj_dn
, forward_attr
, target_dn
))
834 if not self
.confirm_all('Remove orphaned backlink %s' % backlink_attr
, 'fix_all_orphaned_backlinks'):
835 self
.report("Not removing orphaned backlink %s" % backlink_attr
)
839 m
['value'] = ldb
.MessageElement(backlink_val
, ldb
.FLAG_MOD_DELETE
, backlink_attr
)
840 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
841 "Failed to fix orphaned backlink %s" % backlink_attr
):
842 self
.report("Fixed orphaned backlink %s" % (backlink_attr
))
844 def err_recover_forward_links(self
, obj
, forward_attr
, forward_vals
):
845 '''handle a duplicate links value'''
847 self
.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr
, obj
.dn
))
849 if not self
.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr
, 'recover_all_forward_links'):
850 self
.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
851 forward_attr
, obj
.dn
))
855 m
['value'] = ldb
.MessageElement(forward_vals
, ldb
.FLAG_MOD_REPLACE
, forward_attr
)
856 if self
.do_modify(m
, ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS
],
857 "Failed to fix duplicate links in attribute '%s'" % forward_attr
):
858 self
.report("Fixed duplicate links in attribute '%s'" % (forward_attr
))
859 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
860 assert duplicate_cache_key
in self
.duplicate_link_cache
861 self
.duplicate_link_cache
[duplicate_cache_key
] = False
863 def err_no_fsmoRoleOwner(self
, obj
):
864 '''handle a missing fSMORoleOwner'''
865 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
866 res
= self
.samdb
.search("",
867 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
869 serviceName
= str(res
[0]["dsServiceName"][0])
870 if not self
.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
871 self
.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
875 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
876 if self
.do_modify(m
, [],
877 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
878 self
.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
880 def err_missing_parent(self
, obj
):
881 '''handle a missing parent'''
882 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
883 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
884 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
887 keep_transaction
= False
888 self
.samdb
.transaction_start()
890 nc_root
= self
.samdb
.get_nc_root(obj
.dn
)
891 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
892 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
893 new_dn
.remove_base_components(len(new_dn
) - 1)
894 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
895 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
896 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
900 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
902 if self
.do_modify(m
, [],
903 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
904 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
905 keep_transaction
= True
907 self
.samdb
.transaction_cancel()
911 self
.samdb
.transaction_commit()
913 self
.samdb
.transaction_cancel()
915 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
, controls
):
916 '''handle a wrong dn'''
918 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
919 new_rdn
.remove_base_components(len(new_rdn
) - 1)
920 new_parent
= new_dn
.parent()
923 if rdn_val
!= name_val
:
924 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
925 attributes
+= "name=%r" % (name_val
)
927 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
928 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
929 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
932 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, controls
,
933 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
934 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
936 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
937 '''handle a wrong instanceType'''
938 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
939 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
940 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
945 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
946 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
947 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
948 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
950 def err_short_userParameters(self
, obj
, attrname
, value
):
951 # This is a truncated userParameters due to a pre 4.1 replication bug
952 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
)))
954 def err_base64_userParameters(self
, obj
, attrname
, value
):
955 '''handle a wrong userParameters'''
956 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
957 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
958 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
963 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
964 if self
.do_modify(m
, [],
965 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
966 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
968 def err_utf8_userParameters(self
, obj
, attrname
, value
):
969 '''handle a wrong userParameters'''
970 self
.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj
.dn
))
971 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
972 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
977 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
978 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
979 if self
.do_modify(m
, [],
980 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
981 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
983 def err_doubled_userParameters(self
, obj
, attrname
, value
):
984 '''handle a wrong userParameters'''
985 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
986 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
987 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
992 # m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
993 # hmm the above old python2 code doesn't make sense to me and cannot
994 # work in python3 because a string doesn't have a decode method.
995 # However in python2 for some unknown reason this double decode
996 # followed by encode seems to result in what looks like utf8.
997 # In python2 just .decode('utf-16-le').encode('utf-16-le') does nothing
998 # but trigger the 'double UTF16 encoded' condition again :/
1000 # In python2 and python3 value.decode('utf-16-le').encode('utf8') seems
1001 # to do the trick and work as expected.
1002 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').encode('utf8'),
1003 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
1005 if self
.do_modify(m
, [],
1006 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
1007 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
1009 def err_odd_userParameters(self
, obj
, attrname
):
1010 # This is a truncated userParameters due to a pre 4.1 replication bug
1011 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
)))
1013 def find_revealed_link(self
, dn
, attrname
, guid
):
1014 '''return a revealed link in an object'''
1015 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
1016 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
1017 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
1018 for val
in res
[0][attrname
]:
1019 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
1020 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
1025 def check_duplicate_links(self
, obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
):
1026 '''check a linked values for duplicate forward links'''
1029 duplicate_dict
= dict()
1030 unique_dict
= dict()
1032 # Only forward links can have this problem
1033 if forward_linkID
& 1:
1034 # If we got the reverse, skip it
1035 return (error_count
, duplicate_dict
, unique_dict
)
1037 if backlink_attr
is None:
1038 return (error_count
, duplicate_dict
, unique_dict
)
1040 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
1041 if duplicate_cache_key
not in self
.duplicate_link_cache
:
1042 self
.duplicate_link_cache
[duplicate_cache_key
] = False
1044 for val
in obj
[forward_attr
]:
1045 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), forward_syntax
)
1047 # all DNs should have a GUID component
1048 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1051 guidstr
= str(misc
.GUID(guid
))
1052 keystr
= guidstr
+ dsdb_dn
.prefix
1053 if keystr
not in unique_dict
:
1054 unique_dict
[keystr
] = dsdb_dn
1057 if keystr
not in duplicate_dict
:
1058 duplicate_dict
[keystr
] = dict()
1059 duplicate_dict
[keystr
]["keep"] = None
1060 duplicate_dict
[keystr
]["delete"] = list()
1062 # Now check for the highest RMD_VERSION
1063 v1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_VERSION"))
1064 v2
= int(dsdb_dn
.dn
.get_extended_component("RMD_VERSION"))
1066 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1067 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
1070 duplicate_dict
[keystr
]["keep"] = dsdb_dn
1071 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
1072 unique_dict
[keystr
] = dsdb_dn
1074 # Fallback to the highest RMD_LOCAL_USN
1075 u1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_LOCAL_USN"))
1076 u2
= int(dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN"))
1078 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1079 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
1081 duplicate_dict
[keystr
]["keep"] = dsdb_dn
1082 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
1083 unique_dict
[keystr
] = dsdb_dn
1085 if error_count
!= 0:
1086 self
.duplicate_link_cache
[duplicate_cache_key
] = True
1088 return (error_count
, duplicate_dict
, unique_dict
)
1090 def has_duplicate_links(self
, dn
, forward_attr
, forward_syntax
):
1091 '''check a linked values for duplicate forward links'''
1094 duplicate_cache_key
= "%s:%s" % (str(dn
), forward_attr
)
1095 if duplicate_cache_key
in self
.duplicate_link_cache
:
1096 return self
.duplicate_link_cache
[duplicate_cache_key
]
1098 forward_linkID
, backlink_attr
= self
.get_attr_linkID_and_reverse_name(forward_attr
)
1100 attrs
= [forward_attr
]
1101 controls
= ["extended_dn:1:1", "reveal_internals:0"]
1103 # check its the right GUID
1105 res
= self
.samdb
.search(base
=str(dn
), scope
=ldb
.SCOPE_BASE
,
1106 attrs
=attrs
, controls
=controls
)
1107 except ldb
.LdbError
as e8
:
1108 (enum
, estr
) = e8
.args
1109 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1115 error_count
, duplicate_dict
, unique_dict
= \
1116 self
.check_duplicate_links(obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
)
1118 if duplicate_cache_key
in self
.duplicate_link_cache
:
1119 return self
.duplicate_link_cache
[duplicate_cache_key
]
1123 def find_missing_forward_links_from_backlinks(self
, obj
,
1127 forward_unique_dict
):
1128 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1129 missing_forward_links
= []
1132 if backlink_attr
is None:
1133 return (missing_forward_links
, error_count
)
1135 if forward_syntax
!= ldb
.SYNTAX_DN
:
1136 self
.report("Not checking for missing forward links for syntax: %s" %
1138 return (missing_forward_links
, error_count
)
1140 if "sortedLinks" in self
.compatibleFeatures
:
1141 self
.report("Not checking for missing forward links because the db " +
1142 "has the sortedLinks feature")
1143 return (missing_forward_links
, error_count
)
1146 obj_guid
= obj
['objectGUID'][0]
1147 obj_guid_str
= str(ndr_unpack(misc
.GUID
, obj_guid
))
1148 filter = "(%s=<GUID=%s>)" % (backlink_attr
, obj_guid_str
)
1150 res
= self
.samdb
.search(expression
=filter,
1151 scope
=ldb
.SCOPE_SUBTREE
, attrs
=["objectGUID"],
1152 controls
=["extended_dn:1:1",
1153 "search_options:1:2",
1154 "paged_results:1:1000"])
1155 except ldb
.LdbError
as e9
:
1156 (enum
, estr
) = e9
.args
1160 target_dn
= dsdb_Dn(self
.samdb
, r
.dn
.extended_str(), forward_syntax
)
1162 guid
= target_dn
.dn
.get_extended_component("GUID")
1163 guidstr
= str(misc
.GUID(guid
))
1164 if guidstr
in forward_unique_dict
:
1167 # A valid forward link looks like this:
1169 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1170 # <RMD_ADDTIME=131607546230000000>;
1171 # <RMD_CHANGETIME=131607546230000000>;
1173 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1174 # <RMD_LOCAL_USN=3765>;
1175 # <RMD_ORIGINATING_USN=3765>;
1177 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1178 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1180 # Note that versions older than Samba 4.8 create
1181 # links with RMD_VERSION=0.
1183 # Try to get the local_usn and time from objectClass
1184 # if possible and fallback to any other one.
1185 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1186 obj
['replPropertyMetadata'][0])
1187 for o
in repl
.ctr
.array
:
1188 local_usn
= o
.local_usn
1189 t
= o
.originating_change_time
1190 if o
.attid
== drsuapi
.DRSUAPI_ATTID_objectClass
:
1193 # We use a magic invocationID for restoring missing
1194 # forward links to recover from bug #13228.
1195 # This should allow some more future magic to fix the
1198 # It also means it looses the conflict resolution
1199 # against almost every real invocation, if the
1200 # version is also 0.
1201 originating_invocid
= misc
.GUID("ffffffff-4700-4700-4700-000000b13228")
1207 rmd_invocid
= originating_invocid
1208 rmd_originating_usn
= originating_usn
1209 rmd_local_usn
= local_usn
1212 target_dn
.dn
.set_extended_component("RMD_ADDTIME", str(rmd_addtime
))
1213 target_dn
.dn
.set_extended_component("RMD_CHANGETIME", str(rmd_changetime
))
1214 target_dn
.dn
.set_extended_component("RMD_FLAGS", str(rmd_flags
))
1215 target_dn
.dn
.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid
))
1216 target_dn
.dn
.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn
))
1217 target_dn
.dn
.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn
))
1218 target_dn
.dn
.set_extended_component("RMD_VERSION", str(rmd_version
))
1221 missing_forward_links
.append(target_dn
)
1223 return (missing_forward_links
, error_count
)
1225 def check_dn(self
, obj
, attrname
, syntax_oid
):
1226 '''check a DN attribute for correctness'''
1228 obj_guid
= obj
['objectGUID'][0]
1230 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
1231 if reverse_link_name
is not None:
1232 reverse_syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(reverse_link_name
)
1234 reverse_syntax_oid
= None
1236 is_member_link
= attrname
in ("member", "memberOf")
1237 if is_member_link
and self
.quick_membership_checks
:
1240 error_count
, duplicate_dict
, unique_dict
= \
1241 self
.check_duplicate_links(obj
, attrname
, syntax_oid
,
1242 linkID
, reverse_link_name
)
1244 if len(duplicate_dict
) != 0:
1246 missing_forward_links
, missing_error_count
= \
1247 self
.find_missing_forward_links_from_backlinks(obj
,
1248 attrname
, syntax_oid
,
1251 error_count
+= missing_error_count
1253 forward_links
= [dn
for dn
in unique_dict
.values()]
1255 if missing_error_count
!= 0:
1256 self
.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1259 self
.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname
, obj
.dn
))
1260 for m
in missing_forward_links
:
1261 self
.report("Missing link '%s'" % (m
))
1262 if not self
.confirm_all("Schedule readding missing forward link for attribute %s" % attrname
,
1263 'fix_all_missing_forward_links'):
1264 self
.err_orphaned_backlink(m
.dn
, reverse_link_name
,
1265 obj
.dn
.extended_str(), obj
.dn
,
1266 attrname
, syntax_oid
,
1267 check_duplicates
=False)
1269 forward_links
+= [m
]
1270 for keystr
in duplicate_dict
.keys():
1271 d
= duplicate_dict
[keystr
]
1272 for dd
in d
["delete"]:
1273 self
.report("Duplicate link '%s'" % dd
)
1274 self
.report("Correct link '%s'" % d
["keep"])
1276 # We now construct the sorted dn values.
1277 # They're sorted by the objectGUID of the target
1278 # See dsdb_Dn.__cmp__()
1279 vals
= [str(dn
) for dn
in sorted(forward_links
)]
1280 self
.err_recover_forward_links(obj
, attrname
, vals
)
1281 # We should continue with the fixed values
1282 obj
[attrname
] = ldb
.MessageElement(vals
, 0, attrname
)
1284 for val
in obj
[attrname
]:
1285 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
1287 # all DNs should have a GUID component
1288 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1291 self
.err_missing_dn_GUID_component(obj
.dn
, attrname
, val
, dsdb_dn
,
1295 guidstr
= str(misc
.GUID(guid
))
1296 attrs
= ['isDeleted', 'replPropertyMetaData']
1298 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
1299 fixing_msDS_HasInstantiatedNCs
= True
1300 attrs
.append("instanceType")
1302 fixing_msDS_HasInstantiatedNCs
= False
1304 if reverse_link_name
is not None:
1305 attrs
.append(reverse_link_name
)
1307 # check its the right GUID
1309 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
1310 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1",
1311 "reveal_internals:0"
1313 except ldb
.LdbError
as e3
:
1314 (enum
, estr
) = e3
.args
1315 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1318 # We don't always want to
1319 error_count
+= self
.err_missing_target_dn_or_GUID(obj
.dn
,
1325 if fixing_msDS_HasInstantiatedNCs
:
1326 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
1327 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
1329 if str(dsdb_dn
) != str(val
):
1331 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
1334 # now we have two cases - the source object might or might not be deleted
1335 is_deleted
= 'isDeleted' in obj
and str(obj
['isDeleted'][0]).upper() == 'TRUE'
1336 target_is_deleted
= 'isDeleted' in res
[0] and str(res
[0]['isDeleted'][0]).upper() == 'TRUE'
1338 if is_deleted
and obj
.dn
not in self
.deleted_objects_containers
and linkID
:
1339 # A fully deleted object should not have any linked
1340 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1341 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1343 self
.err_undead_linked_attribute(obj
, attrname
, val
)
1346 elif target_is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
) and linkID
:
1347 # the target DN is not allowed to be deleted, unless the target DN is the
1348 # special Deleted Objects container
1350 local_usn
= dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN")
1352 if 'replPropertyMetaData' in res
[0]:
1353 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1354 res
[0]['replPropertyMetadata'][0])
1356 for o
in repl
.ctr
.array
:
1357 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1358 deleted_usn
= o
.local_usn
1359 if deleted_usn
>= int(local_usn
):
1360 # If the object was deleted after the link
1361 # was last modified then, clean it up here
1366 self
.err_deleted_dn(obj
.dn
, attrname
,
1367 val
, dsdb_dn
, res
[0].dn
, True)
1370 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
, False)
1373 # We should not check for incorrect
1374 # components on deleted links, as these are allowed to
1375 # go stale (we just need the GUID, not the name)
1376 rmd_blob
= dsdb_dn
.dn
.get_extended_component("RMD_FLAGS")
1378 if rmd_blob
is not None:
1379 rmd_flags
= int(rmd_blob
)
1381 # assert the DN matches in string form, where a reverse
1382 # link exists, otherwise (below) offer to fix it as a non-error.
1383 # The string form is essentially only kept for forensics,
1384 # as we always re-resolve by GUID in normal operations.
1385 if not rmd_flags
& 1 and reverse_link_name
is not None:
1386 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1388 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1389 res
[0].dn
, "string")
1392 if res
[0].dn
.get_extended_component("GUID") != dsdb_dn
.dn
.get_extended_component("GUID"):
1394 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1398 target_sid
= res
[0].dn
.get_extended_component("SID")
1399 link_sid
= dsdb_dn
.dn
.get_extended_component("SID")
1400 if link_sid
is None and target_sid
is not None:
1402 self
.err_dn_component_missing_target_sid(obj
.dn
, attrname
, val
,
1403 dsdb_dn
, target_sid
)
1405 if link_sid
!= target_sid
:
1407 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1411 # Only for non-links, not even forward-only links
1412 # (otherwise this breaks repl_meta_data):
1414 # Now we have checked the GUID and SID, offer to fix old
1415 # DN strings as a non-error (DNs, not links so no
1416 # backlink). Samba does not maintain this string
1417 # otherwise, so we don't increment error_count.
1418 if reverse_link_name
is None:
1419 if linkID
== 0 and str(res
[0].dn
) != str(dsdb_dn
.dn
):
1420 # Pass in the old/bad DN without the <GUID=...> part,
1421 # otherwise the LDB code will correct it on the way through
1422 # (Note: we still want to preserve the DSDB DN prefix in the
1423 # case of binary DNs)
1424 bad_dn
= dsdb_dn
.prefix
+ dsdb_dn
.dn
.get_linearized()
1425 self
.err_dn_string_component_old(obj
.dn
, attrname
, bad_dn
,
1429 if is_member_link
and self
.quick_membership_checks
:
1432 # check the reverse_link is correct if there should be one
1434 if reverse_link_name
in res
[0]:
1435 for v
in res
[0][reverse_link_name
]:
1436 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1437 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1438 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1440 if v_blob
is not None:
1441 v_rmd_flags
= int(v_blob
)
1444 if v_guid
== obj_guid
:
1447 if match_count
!= 1:
1448 if syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
or reverse_syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
:
1450 # Forward binary multi-valued linked attribute
1452 for w
in obj
[attrname
]:
1453 w_guid
= dsdb_Dn(self
.samdb
, w
.decode('utf8')).dn
.get_extended_component("GUID")
1457 if match_count
== forward_count
:
1460 for v
in obj
[attrname
]:
1461 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1462 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1463 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1465 if v_blob
is not None:
1466 v_rmd_flags
= int(v_blob
)
1472 if match_count
== expected_count
:
1475 diff_count
= expected_count
- match_count
1478 # If there's a backward link on binary multi-valued linked attribute,
1479 # let the check on the forward link remedy the value.
1480 # UNLESS, there is no forward link detected.
1481 if match_count
== 0:
1483 self
.err_orphaned_backlink(obj
.dn
, attrname
,
1488 # Only warn here and let the forward link logic fix it.
1489 self
.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1490 attrname
, expected_count
, str(obj
.dn
),
1491 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1494 assert not target_is_deleted
1496 self
.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1497 attrname
, expected_count
, str(obj
.dn
),
1498 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1500 # Loop until the difference between the forward and
1501 # the backward links is resolved.
1502 while diff_count
!= 0:
1505 if match_count
> 0 or diff_count
> 1:
1506 # TODO no method to fix these right now
1507 self
.report("ERROR: Can't fix missing "
1508 "multi-valued backlinks on %s" % str(dsdb_dn
.dn
))
1510 self
.err_missing_backlink(obj
, attrname
,
1511 obj
.dn
.extended_str(),
1516 self
.err_orphaned_backlink(res
[0].dn
, reverse_link_name
,
1517 obj
.dn
.extended_str(), obj
.dn
,
1518 attrname
, syntax_oid
)
1523 def find_repl_attid(self
, repl
, attid
):
1524 for o
in repl
.ctr
.array
:
1525 if o
.attid
== attid
:
1530 def get_originating_time(self
, val
, attid
):
1531 '''Read metadata properties and return the originating time for
1532 a given attributeId.
1534 :return: the originating time or 0 if not found
1537 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1538 o
= self
.find_repl_attid(repl
, attid
)
1540 return o
.originating_change_time
1543 def process_metadata(self
, dn
, val
):
1544 '''Read metadata properties and list attributes in it.
1545 raises KeyError if the attid is unknown.'''
1548 wrong_attids
= set()
1550 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1552 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1554 for o
in repl
.ctr
.array
:
1555 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1556 set_att
.add(att
.lower())
1557 list_attid
.append(o
.attid
)
1558 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
,
1559 is_schema_nc
=in_schema_nc
)
1560 if correct_attid
!= o
.attid
:
1561 wrong_attids
.add(o
.attid
)
1563 return (set_att
, list_attid
, wrong_attids
)
1565 def fix_metadata(self
, obj
, attr
):
1566 '''re-write replPropertyMetaData elements for a single attribute for a
1567 object. This is used to fix missing replPropertyMetaData elements'''
1568 guid_str
= str(ndr_unpack(misc
.GUID
, obj
['objectGUID'][0]))
1569 dn
= ldb
.Dn(self
.samdb
, "<GUID=%s>" % guid_str
)
1570 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attr
],
1571 controls
=["search_options:1:2",
1574 nmsg
= ldb
.Message()
1576 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
1577 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
1578 "Failed to fix metadata for attribute %s" % attr
):
1579 self
.report("Fixed metadata for attribute %s" % attr
)
1581 def ace_get_effective_inherited_type(self
, ace
):
1582 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
1586 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
1588 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
1590 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
1592 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
1598 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
1601 return str(ace
.object.inherited_type
)
1603 def lookup_class_schemaIDGUID(self
, cls
):
1604 if cls
in self
.class_schemaIDGUID
:
1605 return self
.class_schemaIDGUID
[cls
]
1607 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1608 res
= self
.samdb
.search(base
=self
.schema_dn
,
1610 attrs
=["schemaIDGUID"])
1611 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
1613 self
.class_schemaIDGUID
[cls
] = t
1616 def process_sd(self
, dn
, obj
):
1617 sd_attr
= "nTSecurityDescriptor"
1618 sd_val
= obj
[sd_attr
]
1620 sd
= ndr_unpack(security
.descriptor
, sd_val
[0])
1622 is_deleted
= 'isDeleted' in obj
and str(obj
['isDeleted'][0]).upper() == 'TRUE'
1624 # we don't fix deleted objects
1627 sd_clean
= security
.descriptor()
1628 sd_clean
.owner_sid
= sd
.owner_sid
1629 sd_clean
.group_sid
= sd
.group_sid
1630 sd_clean
.type = sd
.type
1631 sd_clean
.revision
= sd
.revision
1634 last_inherited_type
= None
1637 if sd
.sacl
is not None:
1639 for i
in range(0, len(aces
)):
1642 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1643 sd_clean
.sacl_add(ace
)
1646 t
= self
.ace_get_effective_inherited_type(ace
)
1650 if last_inherited_type
is not None:
1651 if t
!= last_inherited_type
:
1652 # if it inherited from more than
1653 # one type it's very likely to be broken
1655 # If not the recalculation will calculate
1660 last_inherited_type
= t
1663 if sd
.dacl
is not None:
1665 for i
in range(0, len(aces
)):
1668 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1669 sd_clean
.dacl_add(ace
)
1672 t
= self
.ace_get_effective_inherited_type(ace
)
1676 if last_inherited_type
is not None:
1677 if t
!= last_inherited_type
:
1678 # if it inherited from more than
1679 # one type it's very likely to be broken
1681 # If not the recalculation will calculate
1686 last_inherited_type
= t
1689 return (sd_clean
, sd
)
1691 if last_inherited_type
is None:
1697 cls
= obj
["objectClass"][-1]
1698 except KeyError as e
:
1702 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1703 attrs
=["isDeleted", "objectClass"],
1704 controls
=["show_recycled:1"])
1706 is_deleted
= 'isDeleted' in o
and str(o
['isDeleted'][0]).upper() == 'TRUE'
1708 # we don't fix deleted objects
1710 cls
= o
["objectClass"][-1]
1712 t
= self
.lookup_class_schemaIDGUID(cls
)
1714 if t
!= last_inherited_type
:
1716 return (sd_clean
, sd
)
1721 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
1722 '''re-write the SD due to incorrect inherited ACEs'''
1723 sd_attr
= "nTSecurityDescriptor"
1724 sd_val
= ndr_pack(sd
)
1725 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1727 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
1728 self
.report('Not fixing %s on %s\n' % (sd_attr
, dn
))
1731 nmsg
= ldb
.Message()
1733 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1734 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1735 "Failed to fix attribute %s" % sd_attr
):
1736 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1738 def err_wrong_default_sd(self
, dn
, sd
, sd_old
, diff
):
1739 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1740 sd_attr
= "nTSecurityDescriptor"
1741 sd_val
= ndr_pack(sd
)
1742 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1743 if sd
.owner_sid
is not None:
1744 sd_flags |
= security
.SECINFO_OWNER
1745 if sd
.group_sid
is not None:
1746 sd_flags |
= security
.SECINFO_GROUP
1748 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
1749 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
1754 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1755 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
1756 "Failed to reset attribute %s" % sd_attr
):
1757 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1759 def err_missing_sd_owner(self
, dn
, sd
):
1760 '''re-write the SD due to a missing owner or group'''
1761 sd_attr
= "nTSecurityDescriptor"
1762 sd_val
= ndr_pack(sd
)
1763 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
1765 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
1766 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
1769 nmsg
= ldb
.Message()
1771 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1773 # By setting the session_info to admin_session_info and
1774 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1775 # flags we cause the descriptor module to set the correct
1776 # owner and group on the SD, replacing the None/NULL values
1777 # for owner_sid and group_sid currently present.
1779 # The admin_session_info matches that used in provision, and
1780 # is the best guess we can make for an existing object that
1781 # hasn't had something specifically set.
1783 # This is important for the dns related naming contexts.
1784 self
.samdb
.set_session_info(self
.admin_session_info
)
1785 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1786 "Failed to fix metadata for attribute %s" % sd_attr
):
1787 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1788 self
.samdb
.set_session_info(self
.system_session_info
)
1790 def is_expired_tombstone(self
, dn
, repl_val
):
1791 if self
.check_expired_tombstones
:
1792 # This is not the default, it's just
1793 # used to keep dbcheck tests work with
1794 # old static provision dumps
1797 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, repl_val
)
1799 isDeleted
= self
.find_repl_attid(repl
, drsuapi
.DRSUAPI_ATTID_isDeleted
)
1801 delete_time
= samba
.nttime2unix(isDeleted
.originating_change_time
)
1802 current_time
= time
.time()
1804 tombstone_delta
= self
.tombstoneLifetime
* (24 * 60 * 60)
1806 delta
= current_time
- delete_time
1807 if delta
<= tombstone_delta
:
1810 self
.report("SKIPING: object %s is an expired tombstone" % dn
)
1811 self
.report("isDeleted: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1814 isDeleted
.originating_invocation_id
,
1815 isDeleted
.originating_usn
,
1816 isDeleted
.local_usn
,
1817 time
.ctime(samba
.nttime2unix(isDeleted
.originating_change_time
))))
1818 self
.expired_tombstones
+= 1
1821 def find_changes_after_deletion(self
, repl_val
):
1822 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, repl_val
)
1824 isDeleted
= self
.find_repl_attid(repl
, drsuapi
.DRSUAPI_ATTID_isDeleted
)
1826 delete_time
= samba
.nttime2unix(isDeleted
.originating_change_time
)
1828 tombstone_delta
= self
.tombstoneLifetime
* (24 * 60 * 60)
1831 for o
in repl
.ctr
.array
:
1832 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1835 if o
.local_usn
<= isDeleted
.local_usn
:
1838 if o
.originating_change_time
<= isDeleted
.originating_change_time
:
1841 change_time
= samba
.nttime2unix(o
.originating_change_time
)
1843 delta
= change_time
- delete_time
1844 if delta
<= tombstone_delta
:
1847 # If the modification happened after the tombstone lifetime
1848 # has passed, we have a bug as the object might be deleted
1849 # already on other DCs and won't be able to replicate
1853 return found
, isDeleted
1855 def has_changes_after_deletion(self
, dn
, repl_val
):
1856 found
, isDeleted
= self
.find_changes_after_deletion(repl_val
)
1860 def report_attid(o
):
1862 attname
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1864 attname
= "<unknown:0x%x08x>" % o
.attid
1866 self
.report("%s: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1867 attname
, o
.attid
, o
.version
,
1868 o
.originating_invocation_id
,
1871 time
.ctime(samba
.nttime2unix(o
.originating_change_time
))))
1873 self
.report("ERROR: object %s, has changes after deletion" % dn
)
1874 report_attid(isDeleted
)
1880 def err_changes_after_deletion(self
, dn
, repl_val
):
1881 found
, isDeleted
= self
.find_changes_after_deletion(repl_val
)
1883 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1884 rdn_attr
= dn
.get_rdn_name()
1885 rdn_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(rdn_attr
,
1886 is_schema_nc
=in_schema_nc
)
1890 if o
.attid
== rdn_attid
:
1892 if o
.attid
== drsuapi
.DRSUAPI_ATTID_name
:
1894 if o
.attid
== drsuapi
.DRSUAPI_ATTID_lastKnownParent
:
1897 attname
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1899 attname
= "<unknown:0x%x08x>" % o
.attid
1900 unexpected
.append(attname
)
1902 if len(unexpected
) > 0:
1903 self
.report('Unexpeted attributes: %s' % ",".join(unexpected
))
1904 self
.report('Not fixing changes after deletion bug')
1907 if not self
.confirm_all('Delete broken tombstone object %s deleted %s days ago?' % (
1908 dn
, self
.tombstoneLifetime
), 'fix_changes_after_deletion_bug'):
1909 self
.report('Not fixing changes after deletion bug')
1912 if self
.do_delete(dn
, ["relax:0"],
1913 "Failed to remove DN %s" % dn
):
1914 self
.report("Removed DN %s" % dn
)
1916 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
1917 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1922 # Search for a zero invocationID
1923 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1927 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1928 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1929 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1930 % (dn
, o
.attid
, o
.version
,
1931 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
1932 self
.samdb
.get_invocation_id()))
1936 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
1937 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1940 now
= samba
.unix2nttime(int(time
.time()))
1943 # Search for a zero invocationID
1944 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1948 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
1949 o
.version
= o
.version
+ 1
1950 o
.originating_change_time
= now
1951 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
1952 o
.originating_usn
= seq
1956 replBlob
= ndr_pack(repl
)
1960 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1961 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1962 self
.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr
, dn
))
1965 nmsg
= ldb
.Message()
1967 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1968 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1969 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1970 "Failed to fix attribute %s" % attr
):
1971 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1973 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
1974 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1978 # Search for an invalid attid
1980 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1982 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
1985 def err_replmetadata_incorrect_attid(self
, dn
, attr
, repl_meta_data
, wrong_attids
):
1986 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1991 remove_attid
= set()
1994 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1997 # Sort the array, except for the last element. This strange
1998 # construction, creating a new list, due to bugs in samba's
1999 # array handling in IDL generated objects.
2000 ctr
.array
= sorted(ctr
.array
[:], key
=lambda o
: o
.attid
)
2001 # Now walk it in reverse, so we see the low (and so incorrect,
2002 # the correct values are above 0x80000000) values first and
2003 # remove the 'second' value we see.
2004 for o
in reversed(ctr
.array
):
2005 print("%s: 0x%08x" % (dn
, o
.attid
))
2006 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
2007 if att
.lower() in set_att
:
2008 self
.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att
, attr
, dn
))
2009 if not self
.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
2010 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
),
2011 'fix_replmetadata_duplicate_attid'):
2012 self
.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
2013 % (o
.attid
, att
, attr
, dn
))
2016 remove_attid
.add(o
.attid
)
2017 # We want to set the metadata for the most recent
2018 # update to have been applied locally, that is the metadata
2019 # matching the (eg string) value in the attribute
2020 if o
.local_usn
> hash_att
[att
].local_usn
:
2021 # This is always what we would have sent over DRS,
2022 # because the DRS server will have sent the
2023 # msDS-IntID, but with the values from both
2024 # attribute entries.
2025 hash_att
[att
].version
= o
.version
2026 hash_att
[att
].originating_change_time
= o
.originating_change_time
2027 hash_att
[att
].originating_invocation_id
= o
.originating_invocation_id
2028 hash_att
[att
].originating_usn
= o
.originating_usn
2029 hash_att
[att
].local_usn
= o
.local_usn
2031 # Do not re-add the value to the set or overwrite the hash value
2035 set_att
.add(att
.lower())
2037 # Generate a real list we can sort on properly
2038 new_list
= [o
for o
in ctr
.array
if o
.attid
not in remove_attid
]
2040 if (len(wrong_attids
) > 0):
2042 if o
.attid
in wrong_attids
:
2043 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
2044 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
, is_schema_nc
=in_schema_nc
)
2045 self
.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr
, dn
))
2046 if not self
.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
2047 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
), 'fix_replmetadata_wrong_attid'):
2048 self
.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
2049 % (o
.attid
, correct_attid
, att
, attr
, dn
))
2052 o
.attid
= correct_attid
2054 # Sort the array, (we changed the value so must re-sort)
2055 new_list
[:] = sorted(new_list
[:], key
=lambda o
: o
.attid
)
2057 # If we did not already need to fix it, then ask about sorting
2059 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
2060 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
2061 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
2062 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
2065 # The actual sort done is done at the top of the function
2067 ctr
.count
= len(new_list
)
2068 ctr
.array
= new_list
2069 replBlob
= ndr_pack(repl
)
2071 nmsg
= ldb
.Message()
2073 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
2074 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
2075 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
2076 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
2077 "Failed to fix attribute %s" % attr
):
2078 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
2080 def is_deleted_deleted_objects(self
, obj
):
2082 if "description" not in obj
:
2083 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
2085 if "showInAdvancedViewOnly" not in obj
or str(obj
['showInAdvancedViewOnly'][0]).upper() == 'FALSE':
2086 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
2088 if "objectCategory" not in obj
:
2089 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
2091 if "isCriticalSystemObject" not in obj
or str(obj
['isCriticalSystemObject'][0]).upper() == 'FALSE':
2092 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
2094 if "isRecycled" in obj
:
2095 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
2097 if "isDeleted" in obj
and str(obj
['isDeleted'][0]).upper() == 'FALSE':
2098 self
.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj
.dn
)
2100 if "objectClass" not in obj
or (len(obj
['objectClass']) != 2 or
2101 str(obj
['objectClass'][0]) != 'top' or
2102 str(obj
['objectClass'][1]) != 'container'):
2103 self
.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj
.dn
)
2105 if "systemFlags" not in obj
or str(obj
['systemFlags'][0]) != '-1946157056':
2106 self
.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj
.dn
)
2110 def err_deleted_deleted_objects(self
, obj
):
2111 nmsg
= ldb
.Message()
2112 nmsg
.dn
= dn
= obj
.dn
2114 if "description" not in obj
:
2115 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
2116 if "showInAdvancedViewOnly" not in obj
:
2117 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
2118 if "objectCategory" not in obj
:
2119 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
2120 if "isCriticalSystemObject" not in obj
:
2121 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
2122 if "isRecycled" in obj
:
2123 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
2125 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2126 nmsg
["systemFlags"] = ldb
.MessageElement("-1946157056", ldb
.FLAG_MOD_REPLACE
, "systemFlags")
2127 nmsg
["objectClass"] = ldb
.MessageElement(["top", "container"], ldb
.FLAG_MOD_REPLACE
, "objectClass")
2129 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
2130 % (dn
), 'fix_deleted_deleted_objects'):
2131 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
2134 if self
.do_modify(nmsg
, ["relax:0"],
2135 "Failed to fix Deleted Objects container %s" % dn
):
2136 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
2138 def err_replica_locations(self
, obj
, cross_ref
, attr
):
2139 nmsg
= ldb
.Message()
2141 target
= self
.samdb
.get_dsServiceName()
2143 if self
.samdb
.am_rodc():
2144 self
.report('Not fixing %s %s for the RODC' % (attr
, obj
.dn
))
2147 if not self
.confirm_all('Add yourself to the replica locations for %s?'
2148 % (obj
.dn
), 'fix_replica_locations'):
2149 self
.report('Not fixing missing/incorrect attributes on %s\n' % (obj
.dn
))
2152 nmsg
[attr
] = ldb
.MessageElement(target
, ldb
.FLAG_MOD_ADD
, attr
)
2153 if self
.do_modify(nmsg
, [], "Failed to add %s for %s" % (attr
, obj
.dn
)):
2154 self
.report("Fixed %s for %s" % (attr
, obj
.dn
))
2156 def is_fsmo_role(self
, dn
):
2157 if dn
== self
.samdb
.domain_dn
:
2159 if dn
== self
.infrastructure_dn
:
2161 if dn
== self
.naming_dn
:
2163 if dn
== self
.schema_dn
:
2165 if dn
== self
.rid_dn
:
2170 def calculate_instancetype(self
, dn
):
2172 nc_root
= self
.samdb
.get_nc_root(dn
)
2174 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
2176 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
2177 except ldb
.LdbError
as e4
:
2178 (enum
, estr
) = e4
.args
2179 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2182 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
2183 if self
.write_ncs
is not None and str(nc_root
) in [str(x
) for x
in self
.write_ncs
]:
2184 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
2188 def get_wellknown_sd(self
, dn
):
2189 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
2191 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
2192 return ndr_unpack(security
.descriptor
,
2193 descriptor_fn(domain_sid
,
2194 name_map
=self
.name_map
))
2198 def check_object(self
, dn
, attrs
=None):
2199 '''check one object'''
2201 self
.report("Checking object %s" % dn
)
2205 # make a local copy to modify
2207 if "dn" in map(str.lower
, attrs
):
2208 attrs
.append("name")
2209 if "distinguishedname" in map(str.lower
, attrs
):
2210 attrs
.append("name")
2211 if str(dn
.get_rdn_name()).lower() in map(str.lower
, attrs
):
2212 attrs
.append("name")
2213 if 'name' in map(str.lower
, attrs
):
2214 attrs
.append(dn
.get_rdn_name())
2215 attrs
.append("isDeleted")
2216 attrs
.append("systemFlags")
2217 need_replPropertyMetaData
= False
2219 need_replPropertyMetaData
= True
2222 linkID
, _
= self
.get_attr_linkID_and_reverse_name(a
)
2227 need_replPropertyMetaData
= True
2229 if need_replPropertyMetaData
:
2230 attrs
.append("replPropertyMetaData")
2231 attrs
.append("objectGUID")
2235 sd_flags |
= security
.SECINFO_OWNER
2236 sd_flags |
= security
.SECINFO_GROUP
2237 sd_flags |
= security
.SECINFO_DACL
2238 sd_flags |
= security
.SECINFO_SACL
2240 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
2245 "sd_flags:1:%d" % sd_flags
,
2246 "reveal_internals:0",
2249 except ldb
.LdbError
as e10
:
2250 (enum
, estr
) = e10
.args
2251 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2252 if self
.in_transaction
:
2253 self
.report("ERROR: Object %s disappeared during check" % dn
)
2258 self
.report("ERROR: Object %s failed to load during check" % dn
)
2262 set_attrs_from_md
= set()
2263 set_attrs_seen
= set()
2264 got_objectclass
= False
2266 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
2268 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
2269 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
2271 # We have no deleted objects DN for schema, and we check for this above for the other
2273 deleted_objects_dn
= None
2275 object_rdn_attr
= None
2276 object_rdn_val
= None
2280 repl_meta_data_val
= None
2282 for attrname
in obj
:
2283 if str(attrname
).lower() == 'isdeleted':
2284 if str(obj
[attrname
][0]) != "FALSE":
2287 if str(attrname
).lower() == 'systemflags':
2288 systemFlags
= int(obj
[attrname
][0])
2290 if str(attrname
).lower() == 'replpropertymetadata':
2291 repl_meta_data_val
= obj
[attrname
][0]
2293 if isDeleted
and repl_meta_data_val
:
2294 if self
.has_changes_after_deletion(dn
, repl_meta_data_val
):
2296 self
.err_changes_after_deletion(dn
, repl_meta_data_val
)
2298 if self
.is_expired_tombstone(dn
, repl_meta_data_val
):
2301 for attrname
in obj
:
2302 if attrname
== 'dn' or attrname
== "distinguishedName":
2305 if str(attrname
).lower() == 'objectclass':
2306 got_objectclass
= True
2308 if str(attrname
).lower() == "name":
2309 if len(obj
[attrname
]) != 1:
2311 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2312 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2314 name_val
= obj
[attrname
][0]
2316 if str(attrname
).lower() == str(obj
.dn
.get_rdn_name()).lower():
2317 object_rdn_attr
= attrname
2318 if len(obj
[attrname
]) != 1:
2320 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2321 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2323 object_rdn_val
= str(obj
[attrname
][0])
2325 if str(attrname
).lower() == 'replpropertymetadata':
2326 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
][0]):
2328 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
][0])
2329 # We don't continue, as we may also have other fixes for this attribute
2330 # based on what other attributes we see.
2333 (set_attrs_from_md
, list_attid_from_md
, wrong_attids
) \
2334 = self
.process_metadata(dn
, obj
[attrname
][0])
2337 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
2340 if len(set_attrs_from_md
) < len(list_attid_from_md
) \
2341 or len(wrong_attids
) > 0 \
2342 or sorted(list_attid_from_md
) != list_attid_from_md
:
2344 self
.err_replmetadata_incorrect_attid(dn
, attrname
, obj
[attrname
][0], wrong_attids
)
2347 # Here we check that the first attid is 0
2349 if list_attid_from_md
[0] != 0:
2351 self
.report("ERROR: Not fixing incorrect initial attributeID in '%s' on '%s', it should be objectClass" %
2352 (attrname
, str(dn
)))
2356 if str(attrname
).lower() == 'ntsecuritydescriptor':
2357 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
2358 if sd_broken
is not None:
2359 self
.err_wrong_sd(dn
, sd
, sd_broken
)
2363 if sd
.owner_sid
is None or sd
.group_sid
is None:
2364 self
.err_missing_sd_owner(dn
, sd
)
2368 if self
.reset_well_known_acls
:
2370 well_known_sd
= self
.get_wellknown_sd(dn
)
2374 current_sd
= ndr_unpack(security
.descriptor
,
2377 diff
= get_diff_sds(well_known_sd
, current_sd
, security
.dom_sid(self
.samdb
.get_domain_sid()))
2379 self
.err_wrong_default_sd(dn
, well_known_sd
, current_sd
, diff
)
2384 if str(attrname
).lower() == 'objectclass':
2385 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
2386 # Do not consider the attribute incorrect if:
2387 # - The sorted (alphabetically) list is the same, inclding case
2388 # - The first and last elements are the same
2390 # This avoids triggering an error due to
2391 # non-determinism in the sort routine in (at least)
2392 # 4.3 and earlier, and the fact that any AUX classes
2393 # in these attributes are also not sorted when
2394 # imported from Windows (they are just in the reverse
2395 # order of last set)
2396 if sorted(normalised
) != sorted(obj
[attrname
]) \
2397 or normalised
[0] != obj
[attrname
][0] \
2398 or normalised
[-1] != obj
[attrname
][-1]:
2399 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
2403 if str(attrname
).lower() == 'userparameters':
2404 if len(obj
[attrname
][0]) == 1 and obj
[attrname
][0][0] == b
'\x20'[0]:
2406 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
2409 elif obj
[attrname
][0][:16] == b
'\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2410 # This is the correct, normal prefix
2413 elif obj
[attrname
][0][:20] == b
'IAAgACAAIAAgACAAIAAg':
2414 # this is the typical prefix from a windows migration
2416 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
2419 #43:00:00:00:74:00:00:00:78
2420 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]:
2421 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2423 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
2426 elif len(obj
[attrname
][0]) % 2 != 0:
2427 # This is a value that isn't even in length
2429 self
.err_odd_userParameters(obj
, attrname
)
2432 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]:
2433 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2435 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
2438 if attrname
.lower() == 'attributeid' or attrname
.lower() == 'governsid':
2439 if obj
[attrname
][0] in self
.attribute_or_class_ids
:
2441 self
.report('Error: %s %s on %s already exists as an attributeId or governsId'
2442 % (attrname
, obj
.dn
, obj
[attrname
][0]))
2444 self
.attribute_or_class_ids
.add(obj
[attrname
][0])
2446 # check for empty attributes
2447 for val
in obj
[attrname
]:
2449 self
.err_empty_attribute(dn
, attrname
)
2453 # get the syntax oid for the attribute, so we can can have
2454 # special handling for some specific attribute types
2456 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
2457 except Exception as msg
:
2458 self
.err_unknown_attribute(obj
, attrname
)
2462 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
2464 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
2465 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
2466 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
2468 set_attrs_seen
.add(str(attrname
).lower())
2470 if syntax_oid
in [dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
2471 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
2472 # it's some form of DN, do specialised checking on those
2473 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
2477 # check for incorrectly normalised attributes
2478 for val
in obj
[attrname
]:
2481 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
2482 if len(normalised
) != 1 or normalised
[0] != val
:
2483 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
2487 if len(obj
[attrname
]) != len(values
):
2488 self
.err_duplicate_values(dn
, attrname
, obj
[attrname
], list(values
))
2492 if str(attrname
).lower() == "instancetype":
2493 calculated_instancetype
= self
.calculate_instancetype(dn
)
2494 if len(obj
["instanceType"]) != 1 or int(obj
["instanceType"][0]) != calculated_instancetype
:
2496 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
2498 if not got_objectclass
and ("*" in attrs
or "objectclass" in map(str.lower
, attrs
)):
2500 self
.err_missing_objectclass(dn
)
2502 if ("*" in attrs
or "name" in map(str.lower
, attrs
)):
2503 if name_val
is None:
2505 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
2506 if object_rdn_attr
is None:
2508 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
2510 if name_val
is not None:
2512 controls
= ["show_recycled:1", "relax:0"]
2514 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
2515 parent_dn
= deleted_objects_dn
2516 controls
+= ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
]
2517 if parent_dn
is None:
2518 parent_dn
= obj
.dn
.parent()
2519 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
2520 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
2522 if obj
.dn
== deleted_objects_dn
:
2523 expected_dn
= obj
.dn
2525 if expected_dn
!= obj
.dn
:
2527 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
,
2528 object_rdn_val
, name_val
, controls
)
2529 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
2531 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
, object_rdn_val
, str(obj
.dn
)))
2534 if repl_meta_data_val
:
2535 if obj
.dn
== deleted_objects_dn
:
2536 isDeletedAttId
= 131120
2537 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2539 expectedTimeDo
= 2650466015990000000
2540 originating
= self
.get_originating_time(repl_meta_data_val
, isDeletedAttId
)
2541 if originating
!= expectedTimeDo
:
2542 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
2543 nmsg
= ldb
.Message()
2545 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2547 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
2550 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
2552 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
2554 self
.report("On object %s" % dn
)
2557 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
2558 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
2559 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
2561 self
.fix_metadata(obj
, att
)
2563 if self
.is_fsmo_role(dn
):
2564 if "fSMORoleOwner" not in obj
and ("*" in attrs
or "fsmoroleowner" in map(str.lower
, attrs
)):
2565 self
.err_no_fsmoRoleOwner(obj
)
2569 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
2570 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
2571 controls
=["show_recycled:1", "show_deleted:1"])
2572 except ldb
.LdbError
as e11
:
2573 (enum
, estr
) = e11
.args
2574 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2576 self
.report("WARNING: parent object not found for %s" % (obj
.dn
))
2577 self
.report("Not moving to LostAndFound "
2578 "(tombstone garbage collection in progress?)")
2580 self
.err_missing_parent(obj
)
2585 if dn
in self
.deleted_objects_containers
and '*' in attrs
:
2586 if self
.is_deleted_deleted_objects(obj
):
2587 self
.err_deleted_deleted_objects(obj
)
2590 for (dns_part
, msg
) in self
.dns_partitions
:
2591 if dn
== dns_part
and 'repsFrom' in obj
:
2592 location
= "msDS-NC-Replica-Locations"
2593 if self
.samdb
.am_rodc():
2594 location
= "msDS-NC-RO-Replica-Locations"
2596 if location
not in msg
:
2597 # There are no replica locations!
2598 self
.err_replica_locations(obj
, msg
.dn
, location
)
2603 for loc
in msg
[location
]:
2604 if str(loc
) == self
.samdb
.get_dsServiceName():
2607 # This DC is not in the replica locations
2608 self
.err_replica_locations(obj
, msg
.dn
, location
)
2611 if dn
== self
.server_ref_dn
:
2612 # Check we have a valid RID Set
2613 if "*" in attrs
or "rIDSetReferences" in attrs
:
2614 if "rIDSetReferences" not in obj
:
2615 # NO RID SET reference
2616 # We are RID master, allocate it.
2619 if self
.is_rid_master
:
2620 # Allocate a RID Set
2621 if self
.confirm_all('Allocate the missing RID set for RID master?',
2622 'fix_missing_rid_set_master'):
2624 # We don't have auto-transaction logic on
2625 # extended operations, so we have to do it
2628 self
.samdb
.transaction_start()
2631 self
.samdb
.create_own_rid_set()
2634 self
.samdb
.transaction_cancel()
2637 self
.samdb
.transaction_commit()
2639 elif not self
.samdb
.am_rodc():
2640 self
.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn
)
2642 # Check some details of our own RID Set
2643 if dn
== self
.rid_set_dn
:
2644 res
= self
.samdb
.search(base
=self
.rid_set_dn
, scope
=ldb
.SCOPE_BASE
,
2645 attrs
=["rIDAllocationPool",
2646 "rIDPreviousAllocationPool",
2649 if "rIDAllocationPool" not in res
[0]:
2650 self
.report("No rIDAllocationPool found in %s" % dn
)
2653 next_pool
= int(res
[0]["rIDAllocationPool"][0])
2655 high
= (0xFFFFFFFF00000000 & next_pool
) >> 32
2656 low
= 0x00000000FFFFFFFF & next_pool
2659 self
.report("Invalid RID set %d-%s, %d > %d!" % (low
, high
, low
, high
))
2662 if "rIDNextRID" in res
[0]:
2663 next_free_rid
= int(res
[0]["rIDNextRID"][0])
2667 if next_free_rid
== 0:
2672 # Check the remainder of this pool for conflicts. If
2673 # ridalloc_allocate_rid() moves to a new pool, this
2674 # will be above high, so we will stop.
2675 while next_free_rid
<= high
:
2676 sid
= "%s-%d" % (self
.samdb
.get_domain_sid(), next_free_rid
)
2678 res
= self
.samdb
.search(base
="<SID=%s>" % sid
, scope
=ldb
.SCOPE_BASE
,
2680 except ldb
.LdbError
as e
:
2681 (enum
, estr
) = e
.args
2682 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2686 self
.report("SID %s for %s conflicts with our current RID set in %s" % (sid
, res
[0].dn
, dn
))
2689 if self
.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2691 'fix_sid_rid_set_conflict'):
2692 self
.samdb
.transaction_start()
2694 # This will burn RIDs, which will move
2695 # past the conflict. We then check again
2696 # to see if the new RID conflicts, until
2697 # the end of the current pool. We don't
2698 # look at the next pool to avoid burning
2699 # all RIDs in one go in some strange
2703 allocated_rid
= self
.samdb
.allocate_rid()
2704 if allocated_rid
>= next_free_rid
:
2705 next_free_rid
= allocated_rid
+ 1
2708 self
.samdb
.transaction_cancel()
2711 self
.samdb
.transaction_commit()
2719 ################################################################
2720 # check special @ROOTDSE attributes
2721 def check_rootdse(self
):
2722 '''check the @ROOTDSE special object'''
2723 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
2725 self
.report("Checking object %s" % dn
)
2726 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
2728 self
.report("Object %s disappeared during check" % dn
)
2733 # check that the dsServiceName is in GUID form
2734 if 'dsServiceName' not in obj
:
2735 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
2736 return error_count
+ 1
2738 if not str(obj
['dsServiceName'][0]).startswith('<GUID='):
2739 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2741 if not self
.confirm('Change dsServiceName to GUID form?'):
2743 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0].decode('utf8')),
2744 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
2745 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
2748 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
2749 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
2750 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
2751 self
.report("Changed dsServiceName to GUID form")
2754 ###############################################
2755 # re-index the database
2757 def reindex_database(self
):
2758 '''re-index the whole database'''
2760 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
2761 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
2762 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
2763 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
2765 ###############################################
2767 def reset_modules(self
):
2768 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2770 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
2771 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
2772 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)