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):
66 self
.dict_oid_name
= None
67 self
.samdb_schema
= (samdb_schema
or samdb
)
68 self
.verbose
= verbose
72 self
.remove_all_unknown_attributes
= False
73 self
.remove_all_empty_attributes
= False
74 self
.fix_all_normalisation
= False
75 self
.fix_all_duplicates
= False
76 self
.fix_all_DN_GUIDs
= False
77 self
.fix_all_binary_dn
= False
78 self
.remove_implausible_deleted_DN_links
= False
79 self
.remove_plausible_deleted_DN_links
= False
80 self
.fix_all_string_dn_component_mismatch
= False
81 self
.fix_all_GUID_dn_component_mismatch
= False
82 self
.fix_all_SID_dn_component_mismatch
= False
83 self
.fix_all_SID_dn_component_missing
= False
84 self
.fix_all_old_dn_string_component_mismatch
= False
85 self
.fix_all_metadata
= False
86 self
.fix_time_metadata
= False
87 self
.fix_undead_linked_attributes
= False
88 self
.fix_all_missing_backlinks
= False
89 self
.fix_all_orphaned_backlinks
= False
90 self
.fix_all_missing_forward_links
= False
91 self
.duplicate_link_cache
= dict()
92 self
.recover_all_forward_links
= False
93 self
.fix_rmd_flags
= False
94 self
.fix_ntsecuritydescriptor
= False
95 self
.fix_ntsecuritydescriptor_owner_group
= False
96 self
.seize_fsmo_role
= False
97 self
.move_to_lost_and_found
= False
98 self
.fix_instancetype
= False
99 self
.fix_replmetadata_zero_invocationid
= False
100 self
.fix_replmetadata_duplicate_attid
= False
101 self
.fix_replmetadata_wrong_attid
= False
102 self
.fix_replmetadata_unsorted_attid
= False
103 self
.fix_deleted_deleted_objects
= False
104 self
.fix_incorrect_deleted_objects
= False
106 self
.fix_base64_userparameters
= False
107 self
.fix_utf8_userparameters
= False
108 self
.fix_doubled_userparameters
= False
109 self
.fix_sid_rid_set_conflict
= False
110 self
.quick_membership_checks
= quick_membership_checks
111 self
.reset_well_known_acls
= reset_well_known_acls
112 self
.reset_all_well_known_acls
= False
113 self
.in_transaction
= in_transaction
114 self
.infrastructure_dn
= ldb
.Dn(samdb
, "CN=Infrastructure," + samdb
.domain_dn())
115 self
.naming_dn
= ldb
.Dn(samdb
, "CN=Partitions,%s" % samdb
.get_config_basedn())
116 self
.schema_dn
= samdb
.get_schema_basedn()
117 self
.rid_dn
= ldb
.Dn(samdb
, "CN=RID Manager$,CN=System," + samdb
.domain_dn())
118 self
.ntds_dsa
= ldb
.Dn(samdb
, samdb
.get_dsServiceName())
119 self
.class_schemaIDGUID
= {}
120 self
.wellknown_sds
= get_wellknown_sds(self
.samdb
)
121 self
.fix_all_missing_objectclass
= False
122 self
.fix_missing_deleted_objects
= False
123 self
.fix_replica_locations
= False
124 self
.fix_missing_rid_set_master
= False
127 self
.link_id_cache
= {}
130 res
= samdb
.search(base
="CN=DnsAdmins,CN=Users,%s" % samdb
.domain_dn(), scope
=ldb
.SCOPE_BASE
,
132 dnsadmins_sid
= ndr_unpack(security
.dom_sid
, res
[0]["objectSid"][0])
133 self
.name_map
['DnsAdmins'] = str(dnsadmins_sid
)
134 except ldb
.LdbError
as e5
:
135 (enum
, estr
) = e5
.args
136 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
140 self
.system_session_info
= system_session()
141 self
.admin_session_info
= admin_session(None, samdb
.get_domain_sid())
143 res
= self
.samdb
.search(base
=self
.ntds_dsa
, scope
=ldb
.SCOPE_BASE
, attrs
=['msDS-hasMasterNCs', 'hasMasterNCs'])
144 if "msDS-hasMasterNCs" in res
[0]:
145 self
.write_ncs
= res
[0]["msDS-hasMasterNCs"]
147 # If the Forest Level is less than 2003 then there is no
148 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
149 # no need to merge as all the NCs that are in hasMasterNCs must
150 # also be in msDS-hasMasterNCs (but not the opposite)
151 if "hasMasterNCs" in res
[0]:
152 self
.write_ncs
= res
[0]["hasMasterNCs"]
154 self
.write_ncs
= None
156 res
= self
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['namingContexts'])
157 self
.deleted_objects_containers
= []
158 self
.ncs_lacking_deleted_containers
= []
159 self
.dns_partitions
= []
161 self
.ncs
= res
[0]["namingContexts"]
169 dn
= self
.samdb
.get_wellknown_dn(ldb
.Dn(self
.samdb
, nc
.decode('utf8')),
170 dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
171 self
.deleted_objects_containers
.append(dn
)
173 self
.ncs_lacking_deleted_containers
.append(ldb
.Dn(self
.samdb
, nc
.decode('utf8')))
175 domaindns_zone
= 'DC=DomainDnsZones,%s' % self
.samdb
.get_default_basedn()
176 forestdns_zone
= 'DC=ForestDnsZones,%s' % self
.samdb
.get_root_basedn()
177 domain
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
178 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
179 base
=self
.samdb
.get_partitions_dn(),
180 expression
="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone
)
182 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, forestdns_zone
), domain
[0]))
184 forest
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
185 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
186 base
=self
.samdb
.get_partitions_dn(),
187 expression
="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone
)
189 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, domaindns_zone
), forest
[0]))
191 fsmo_dn
= ldb
.Dn(self
.samdb
, "CN=RID Manager$,CN=System," + self
.samdb
.domain_dn())
192 rid_master
= get_fsmo_roleowner(self
.samdb
, fsmo_dn
, "rid")
193 if ldb
.Dn(self
.samdb
, self
.samdb
.get_dsServiceName()) == rid_master
:
194 self
.is_rid_master
= True
196 self
.is_rid_master
= False
198 # To get your rid set
200 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, self
.samdb
.get_serverName()),
201 scope
=ldb
.SCOPE_BASE
, attrs
=["serverReference"])
202 # 2. Get server reference
203 self
.server_ref_dn
= ldb
.Dn(self
.samdb
, res
[0]['serverReference'][0].decode('utf8'))
206 res
= self
.samdb
.search(base
=self
.server_ref_dn
,
207 scope
=ldb
.SCOPE_BASE
, attrs
=['rIDSetReferences'])
208 if "rIDSetReferences" in res
[0]:
209 self
.rid_set_dn
= ldb
.Dn(self
.samdb
, res
[0]['rIDSetReferences'][0].decode('utf8'))
211 self
.rid_set_dn
= None
213 self
.compatibleFeatures
= []
214 self
.requiredFeatures
= []
217 res
= self
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
219 attrs
=["compatibleFeatures",
221 if "compatibleFeatures" in res
[0]:
222 self
.compatibleFeatures
= res
[0]["compatibleFeatures"]
223 if "requiredFeatures" in res
[0]:
224 self
.requiredFeatures
= res
[0]["requiredFeatures"]
225 except ldb
.LdbError
as e6
:
226 (enum
, estr
) = e6
.args
227 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
231 def check_database(self
, DN
=None, scope
=ldb
.SCOPE_SUBTREE
, controls
=None,
233 '''perform a database check, returning the number of errors found'''
234 res
= self
.samdb
.search(base
=DN
, scope
=scope
, attrs
=['dn'], controls
=controls
)
235 self
.report('Checking %u objects' % len(res
))
238 error_count
+= self
.check_deleted_objects_containers()
240 self
.attribute_or_class_ids
= set()
243 self
.dn_set
.add(str(object.dn
))
244 error_count
+= self
.check_object(object.dn
, attrs
=attrs
)
247 error_count
+= self
.check_rootdse()
249 if error_count
!= 0 and not self
.fix
:
250 self
.report("Please use --fix to fix these errors")
252 self
.report('Checked %u objects (%u errors)' % (len(res
), error_count
))
255 def check_deleted_objects_containers(self
):
256 """This function only fixes conflicts on the Deleted Objects
257 containers, not the attributes"""
259 for nc
in self
.ncs_lacking_deleted_containers
:
260 if nc
== self
.schema_dn
:
263 self
.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc
)
264 if not self
.confirm_all('Fix missing Deleted Objects container for %s?' % (nc
), 'fix_missing_deleted_objects'):
267 dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects")
272 # If something already exists here, add a conflict
273 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[],
274 controls
=["show_deleted:1", "extended_dn:1:1",
275 "show_recycled:1", "reveal_internals:0"])
277 guid
= res
[0].dn
.get_extended_component("GUID")
278 conflict_dn
= ldb
.Dn(self
.samdb
,
279 "CN=Deleted Objects\\0ACNF:%s" % str(misc
.GUID(guid
)))
280 conflict_dn
.add_base(nc
)
282 except ldb
.LdbError
as e2
:
283 (enum
, estr
) = e2
.args
284 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
287 self
.report("Couldn't check for conflicting Deleted Objects container: %s" % estr
)
290 if conflict_dn
is not None:
292 self
.samdb
.rename(dn
, conflict_dn
, ["show_deleted:1", "relax:0", "show_recycled:1"])
293 except ldb
.LdbError
as e1
:
294 (enum
, estr
) = e1
.args
295 self
.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn
, conflict_dn
, estr
))
298 # Refresh wellKnownObjects links
299 res
= self
.samdb
.search(base
=nc
, scope
=ldb
.SCOPE_BASE
,
300 attrs
=['wellKnownObjects'],
301 controls
=["show_deleted:1", "extended_dn:0",
302 "show_recycled:1", "reveal_internals:0"])
304 self
.report("wellKnownObjects was not found for NC %s" % nc
)
307 # Prevent duplicate deleted objects containers just in case
308 wko
= res
[0]["wellKnownObjects"]
310 proposed_objectguid
= None
312 dsdb_dn
= dsdb_Dn(self
.samdb
, o
.decode('utf8'), dsdb
.DSDB_SYNTAX_BINARY_DN
)
313 if self
.is_deleted_objects_dn(dsdb_dn
):
314 self
.report("wellKnownObjects had duplicate Deleted Objects value %s" % o
)
315 # We really want to put this back in the same spot
316 # as the original one, so that on replication we
317 # merge, rather than conflict.
318 proposed_objectguid
= dsdb_dn
.dn
.get_extended_component("GUID")
319 listwko
.append(str(o
))
321 if proposed_objectguid
is not None:
322 guid_suffix
= "\nobjectGUID: %s" % str(misc
.GUID(proposed_objectguid
))
324 wko_prefix
= "B:32:%s" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
325 listwko
.append('%s:%s' % (wko_prefix
, dn
))
328 # Insert a brand new Deleted Objects container
329 self
.samdb
.add_ldif("""dn: %s
331 objectClass: container
332 description: Container for deleted objects
334 isCriticalSystemObject: TRUE
335 showInAdvancedViewOnly: TRUE
336 systemFlags: -1946157056%s""" % (dn
, guid_suffix
),
337 controls
=["relax:0", "provision:0"])
339 delta
= ldb
.Message()
340 delta
.dn
= ldb
.Dn(self
.samdb
, str(res
[0]["dn"]))
341 delta
["wellKnownObjects"] = ldb
.MessageElement(listwko
,
342 ldb
.FLAG_MOD_REPLACE
,
345 # Insert the link to the brand new container
346 if self
.do_modify(delta
, ["relax:0"],
347 "NC %s lacks Deleted Objects WKGUID" % nc
,
349 self
.report("Added %s well known guid link" % dn
)
351 self
.deleted_objects_containers
.append(dn
)
355 def report(self
, msg
):
356 '''print a message unless quiet is set'''
360 def confirm(self
, msg
, allow_all
=False, forced
=False):
361 '''confirm a change'''
368 return common
.confirm(msg
, forced
=forced
, allow_all
=allow_all
)
370 ################################################################
371 # a local confirm function with support for 'all'
372 def confirm_all(self
, msg
, all_attr
):
373 '''confirm a change with support for "all" '''
376 if getattr(self
, all_attr
) == 'NONE':
378 if getattr(self
, all_attr
) == 'ALL':
384 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
386 setattr(self
, all_attr
, 'ALL')
389 setattr(self
, all_attr
, 'NONE')
393 def do_delete(self
, dn
, controls
, msg
):
394 '''delete dn with optional verbose output'''
396 self
.report("delete DN %s" % dn
)
398 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
399 self
.samdb
.delete(dn
, controls
=controls
)
400 except Exception as err
:
401 if self
.in_transaction
:
402 raise CommandError("%s : %s" % (msg
, err
))
403 self
.report("%s : %s" % (msg
, err
))
407 def do_modify(self
, m
, controls
, msg
, validate
=True):
408 '''perform a modify with optional verbose output'''
409 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
411 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
412 self
.report("controls: %r" % controls
)
414 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
415 except Exception as err
:
416 if self
.in_transaction
:
417 raise CommandError("%s : %s" % (msg
, err
))
418 self
.report("%s : %s" % (msg
, err
))
422 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
423 '''perform a modify with optional verbose output'''
425 self
.report("""dn: %s
429 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
431 to_dn
= to_rdn
+ to_base
432 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
433 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
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 get_attr_linkID_and_reverse_name(self
, attrname
):
442 if attrname
in self
.link_id_cache
:
443 return self
.link_id_cache
[attrname
]
444 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
446 revname
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
449 self
.link_id_cache
[attrname
] = (linkID
, revname
)
450 return linkID
, revname
452 def err_empty_attribute(self
, dn
, attrname
):
453 '''fix empty attributes'''
454 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
455 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
456 self
.report("Not fixing empty attribute %s" % attrname
)
461 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
462 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
463 "Failed to remove empty attribute %s" % attrname
, validate
=False):
464 self
.report("Removed empty attribute %s" % attrname
)
466 def err_normalise_mismatch(self
, dn
, attrname
, values
):
467 '''fix attribute normalisation errors'''
468 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
471 normalised
= self
.samdb
.dsdb_normalise_attributes(
472 self
.samdb_schema
, attrname
, [val
])
473 if len(normalised
) != 1:
474 self
.report("Unable to normalise value '%s'" % val
)
475 mod_list
.append((val
, ''))
476 elif (normalised
[0] != val
):
477 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
478 mod_list
.append((val
, normalised
[0]))
479 if not self
.confirm_all('Fix normalisation for %s from %s?' % (attrname
, dn
), 'fix_all_normalisation'):
480 self
.report("Not fixing attribute %s" % attrname
)
485 for i
in range(0, len(mod_list
)):
486 (val
, nval
) = mod_list
[i
]
487 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
489 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
492 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
493 "Failed to normalise attribute %s" % attrname
,
495 self
.report("Normalised attribute %s" % attrname
)
497 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
498 '''fix attribute normalisation errors'''
499 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
500 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
501 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
502 if list(normalised
) == values
:
504 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
505 self
.report("Not fixing attribute '%s'" % attrname
)
510 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
512 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
513 "Failed to normalise attribute %s" % attrname
,
515 self
.report("Normalised attribute %s" % attrname
)
517 def err_duplicate_values(self
, dn
, attrname
, dup_values
, values
):
518 '''fix attribute normalisation errors'''
519 self
.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname
, dn
))
520 self
.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dump_attr_values(dup_values
)), ','.join(dump_attr_values(values
))))
521 if not self
.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_duplicates'):
522 self
.report("Not fixing attribute '%s'" % attrname
)
527 m
[attrname
] = ldb
.MessageElement(values
, ldb
.FLAG_MOD_REPLACE
, attrname
)
529 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
530 "Failed to remove duplicate value on attribute %s" % attrname
,
532 self
.report("Removed duplicate value on attribute %s" % attrname
)
534 def is_deleted_objects_dn(self
, dsdb_dn
):
535 '''see if a dsdb_Dn is the special Deleted Objects DN'''
536 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
538 def err_missing_objectclass(self
, dn
):
539 """handle object without objectclass"""
540 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
)))
541 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'):
542 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
544 if self
.do_delete(dn
, ["relax:0"],
545 "Failed to remove DN %s" % dn
):
546 self
.report("Removed DN %s" % dn
)
548 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, remove_plausible
=False):
549 """handle a DN pointing to a deleted object"""
550 if not remove_plausible
:
551 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
552 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
553 if not self
.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
554 self
.report("Not removing")
557 self
.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
558 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
559 if not self
.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
560 self
.report("Not removing")
565 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
566 if self
.do_modify(m
, ["show_recycled:1",
567 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
568 "Failed to remove deleted DN attribute %s" % attrname
):
569 self
.report("Removed deleted DN on attribute %s" % attrname
)
571 def err_missing_target_dn_or_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
572 """handle a missing target DN (if specified, GUID form can't be found,
573 and otherwise DN string form can't be found)"""
574 # check if its a backlink
575 linkID
, _
= self
.get_attr_linkID_and_reverse_name(attrname
)
576 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
578 linkID
, reverse_link_name \
579 = self
.get_attr_linkID_and_reverse_name(attrname
)
580 if reverse_link_name
is not None:
581 self
.report("WARNING: no target object found for GUID "
582 "component for one-way forward link "
584 "%s - %s" % (attrname
, dn
, val
))
585 self
.report("Not removing dangling forward link")
588 nc_root
= self
.samdb
.get_nc_root(dn
)
589 target_nc_root
= self
.samdb
.get_nc_root(dsdb_dn
.dn
)
590 if nc_root
!= target_nc_root
:
591 # We don't bump the error count as Samba produces these
592 # in normal operation
593 self
.report("WARNING: no target object found for GUID "
594 "component for cross-partition link "
596 "%s - %s" % (attrname
, dn
, val
))
597 self
.report("Not removing dangling one-way "
598 "cross-partition link "
599 "(we might be mid-replication)")
602 # Due to our link handling one-way links pointing to
603 # missing objects are plausible.
605 # We don't bump the error count as Samba produces these
606 # in normal operation
607 self
.report("WARNING: no target object found for GUID "
608 "component for DN value %s in object "
609 "%s - %s" % (attrname
, dn
, val
))
610 self
.err_deleted_dn(dn
, attrname
, val
,
611 dsdb_dn
, dsdb_dn
, True)
614 # We bump the error count here, as we should have deleted this
615 self
.report("ERROR: no target object found for GUID "
616 "component for link %s in object "
617 "%s - %s" % (attrname
, dn
, val
))
618 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
, False)
621 def err_missing_dn_GUID_component(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
622 """handle a missing GUID extended DN component"""
623 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
624 controls
= ["extended_dn:1:1", "show_recycled:1"]
626 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
627 attrs
=[], controls
=controls
)
628 except ldb
.LdbError
as e7
:
629 (enum
, estr
) = e7
.args
630 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
631 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
633 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
636 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
637 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
639 dsdb_dn
.dn
= res
[0].dn
641 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
642 self
.report("Not fixing %s" % errstr
)
646 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
647 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
649 if self
.do_modify(m
, ["show_recycled:1"],
650 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
651 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
653 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
654 """handle an incorrect binary DN component"""
655 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
657 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
658 self
.report("Not fixing %s" % errstr
)
662 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
663 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
665 if self
.do_modify(m
, ["show_recycled:1"],
666 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
667 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
669 def err_dn_string_component_old(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
670 """handle a DN string being incorrect"""
671 self
.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
672 dsdb_dn
.dn
= correct_dn
674 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
675 'fix_all_old_dn_string_component_mismatch'):
676 self
.report("Not fixing old string component")
680 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
681 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
682 if self
.do_modify(m
, ["show_recycled:1",
683 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
],
684 "Failed to fix old DN string on attribute %s" % (attrname
)):
685 self
.report("Fixed old DN string on attribute %s" % (attrname
))
687 def err_dn_component_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, mismatch_type
):
688 """handle a DN string being incorrect"""
689 self
.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type
, attrname
, dn
, val
))
690 dsdb_dn
.dn
= correct_dn
692 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
693 'fix_all_%s_dn_component_mismatch' % mismatch_type
):
694 self
.report("Not fixing %s component mismatch" % mismatch_type
)
698 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
699 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
700 if self
.do_modify(m
, ["show_recycled:1"],
701 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type
, attrname
)):
702 self
.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type
, attrname
))
704 def err_dn_component_missing_target_sid(self
, dn
, attrname
, val
, dsdb_dn
, target_sid_blob
):
705 """handle a DN string being incorrect"""
706 self
.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname
, dn
, val
))
708 if len(dsdb_dn
.prefix
) != 0:
709 self
.report("Not fixing missing DN SID on DN+BINARY or DN+STRING")
712 correct_dn
= ldb
.Dn(self
.samdb
, dsdb_dn
.dn
.extended_str())
713 correct_dn
.set_extended_component("SID", target_sid_blob
)
715 if not self
.confirm_all('Change DN to %s?' % correct_dn
.extended_str(),
716 'fix_all_SID_dn_component_missing'):
717 self
.report("Not fixing missing DN SID component")
720 target_guid_blob
= correct_dn
.get_extended_component("GUID")
721 guid_sid_dn
= ldb
.Dn(self
.samdb
, "")
722 guid_sid_dn
.set_extended_component("GUID", target_guid_blob
)
723 guid_sid_dn
.set_extended_component("SID", target_sid_blob
)
727 m
['new_value'] = ldb
.MessageElement(guid_sid_dn
.extended_str(), ldb
.FLAG_MOD_ADD
, attrname
)
730 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID
732 if self
.do_modify(m
, controls
,
733 "Failed to ADD missing DN SID on attribute %s" % (attrname
)):
734 self
.report("Fixed missing DN SID on attribute %s" % (attrname
))
736 def err_unknown_attribute(self
, obj
, attrname
):
737 '''handle an unknown attribute error'''
738 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
739 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
740 self
.report("Not removing %s" % attrname
)
744 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
745 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
746 "Failed to remove unknown attribute %s" % attrname
):
747 self
.report("Removed unknown attribute %s" % (attrname
))
749 def err_undead_linked_attribute(self
, obj
, attrname
, val
):
750 '''handle a link that should not be there on a deleted object'''
751 self
.report("ERROR: linked attribute '%s' to '%s' is present on "
752 "deleted object %s" % (attrname
, val
, obj
.dn
))
753 if not self
.confirm_all('Remove linked attribute %s' % attrname
, 'fix_undead_linked_attributes'):
754 self
.report("Not removing linked attribute %s" % attrname
)
758 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
760 if self
.do_modify(m
, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
761 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
762 "Failed to delete forward link %s" % attrname
):
763 self
.report("Fixed undead forward link %s" % (attrname
))
765 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
766 '''handle a missing backlink value'''
767 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
768 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
769 self
.report("Not fixing missing backlink %s" % backlink_name
)
773 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, backlink_name
)
774 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
775 "Failed to fix missing backlink %s" % backlink_name
):
776 self
.report("Fixed missing backlink %s" % (backlink_name
))
778 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
779 '''handle a incorrect RMD_FLAGS value'''
780 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
781 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()))
782 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
783 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
787 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
788 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
789 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
790 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
792 def err_orphaned_backlink(self
, obj_dn
, backlink_attr
, backlink_val
,
793 target_dn
, forward_attr
, forward_syntax
,
794 check_duplicates
=True):
795 '''handle a orphaned backlink value'''
796 if check_duplicates
is True and self
.has_duplicate_links(target_dn
, forward_attr
, forward_syntax
):
797 self
.report("WARNING: Keep orphaned backlink attribute " +
798 "'%s' in '%s' for link '%s' in '%s'" % (
799 backlink_attr
, obj_dn
, forward_attr
, target_dn
))
801 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr
, obj_dn
, forward_attr
, target_dn
))
802 if not self
.confirm_all('Remove orphaned backlink %s' % backlink_attr
, 'fix_all_orphaned_backlinks'):
803 self
.report("Not removing orphaned backlink %s" % backlink_attr
)
807 m
['value'] = ldb
.MessageElement(backlink_val
, ldb
.FLAG_MOD_DELETE
, backlink_attr
)
808 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
809 "Failed to fix orphaned backlink %s" % backlink_attr
):
810 self
.report("Fixed orphaned backlink %s" % (backlink_attr
))
812 def err_recover_forward_links(self
, obj
, forward_attr
, forward_vals
):
813 '''handle a duplicate links value'''
815 self
.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr
, obj
.dn
))
817 if not self
.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr
, 'recover_all_forward_links'):
818 self
.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
819 forward_attr
, obj
.dn
))
823 m
['value'] = ldb
.MessageElement(forward_vals
, ldb
.FLAG_MOD_REPLACE
, forward_attr
)
824 if self
.do_modify(m
, ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS
],
825 "Failed to fix duplicate links in attribute '%s'" % forward_attr
):
826 self
.report("Fixed duplicate links in attribute '%s'" % (forward_attr
))
827 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
828 assert duplicate_cache_key
in self
.duplicate_link_cache
829 self
.duplicate_link_cache
[duplicate_cache_key
] = False
831 def err_no_fsmoRoleOwner(self
, obj
):
832 '''handle a missing fSMORoleOwner'''
833 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
834 res
= self
.samdb
.search("",
835 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
837 serviceName
= str(res
[0]["dsServiceName"][0])
838 if not self
.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
839 self
.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
843 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
844 if self
.do_modify(m
, [],
845 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
846 self
.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
848 def err_missing_parent(self
, obj
):
849 '''handle a missing parent'''
850 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
851 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
852 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
855 keep_transaction
= False
856 self
.samdb
.transaction_start()
858 nc_root
= self
.samdb
.get_nc_root(obj
.dn
)
859 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
860 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
861 new_dn
.remove_base_components(len(new_dn
) - 1)
862 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
863 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
864 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
868 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
870 if self
.do_modify(m
, [],
871 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
872 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
873 keep_transaction
= True
875 self
.samdb
.transaction_cancel()
879 self
.samdb
.transaction_commit()
881 self
.samdb
.transaction_cancel()
883 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
, controls
):
884 '''handle a wrong dn'''
886 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
887 new_rdn
.remove_base_components(len(new_rdn
) - 1)
888 new_parent
= new_dn
.parent()
891 if rdn_val
!= name_val
:
892 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
893 attributes
+= "name=%r" % (name_val
)
895 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
896 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
897 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
900 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, controls
,
901 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
902 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
904 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
905 '''handle a wrong instanceType'''
906 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
907 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
908 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
913 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
914 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
915 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
916 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
918 def err_short_userParameters(self
, obj
, attrname
, value
):
919 # This is a truncated userParameters due to a pre 4.1 replication bug
920 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
)))
922 def err_base64_userParameters(self
, obj
, attrname
, value
):
923 '''handle a wrong userParameters'''
924 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
925 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
926 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
931 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
932 if self
.do_modify(m
, [],
933 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
934 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
936 def err_utf8_userParameters(self
, obj
, attrname
, value
):
937 '''handle a wrong userParameters'''
938 self
.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj
.dn
))
939 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
940 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
945 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
946 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
947 if self
.do_modify(m
, [],
948 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
949 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
951 def err_doubled_userParameters(self
, obj
, attrname
, value
):
952 '''handle a wrong userParameters'''
953 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
954 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
955 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
960 # m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
961 # hmm the above old python2 code doesn't make sense to me and cannot
962 # work in python3 because a string doesn't have a decode method.
963 # However in python2 for some unknown reason this double decode
964 # followed by encode seems to result in what looks like utf8.
965 # In python2 just .decode('utf-16-le').encode('utf-16-le') does nothing
966 # but trigger the 'double UTF16 encoded' condition again :/
968 # In python2 and python3 value.decode('utf-16-le').encode('utf8') seems
969 # to do the trick and work as expected.
970 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').encode('utf8'),
971 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
973 if self
.do_modify(m
, [],
974 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
975 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
977 def err_odd_userParameters(self
, obj
, attrname
):
978 # This is a truncated userParameters due to a pre 4.1 replication bug
979 self
.report("ERROR: incorrect userParameters value on object %s (odd length). If you have another working DC that does not give this warning, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (obj
.dn
, self
.samdb
.get_nc_root(obj
.dn
)))
981 def find_revealed_link(self
, dn
, attrname
, guid
):
982 '''return a revealed link in an object'''
983 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
984 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
985 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
986 for val
in res
[0][attrname
]:
987 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
988 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
993 def check_duplicate_links(self
, obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
):
994 '''check a linked values for duplicate forward links'''
997 duplicate_dict
= dict()
1000 # Only forward links can have this problem
1001 if forward_linkID
& 1:
1002 # If we got the reverse, skip it
1003 return (error_count
, duplicate_dict
, unique_dict
)
1005 if backlink_attr
is None:
1006 return (error_count
, duplicate_dict
, unique_dict
)
1008 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
1009 if duplicate_cache_key
not in self
.duplicate_link_cache
:
1010 self
.duplicate_link_cache
[duplicate_cache_key
] = False
1012 for val
in obj
[forward_attr
]:
1013 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), forward_syntax
)
1015 # all DNs should have a GUID component
1016 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1019 guidstr
= str(misc
.GUID(guid
))
1020 keystr
= guidstr
+ dsdb_dn
.prefix
1021 if keystr
not in unique_dict
:
1022 unique_dict
[keystr
] = dsdb_dn
1025 if keystr
not in duplicate_dict
:
1026 duplicate_dict
[keystr
] = dict()
1027 duplicate_dict
[keystr
]["keep"] = None
1028 duplicate_dict
[keystr
]["delete"] = list()
1030 # Now check for the highest RMD_VERSION
1031 v1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_VERSION"))
1032 v2
= int(dsdb_dn
.dn
.get_extended_component("RMD_VERSION"))
1034 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1035 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
1038 duplicate_dict
[keystr
]["keep"] = dsdb_dn
1039 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
1040 unique_dict
[keystr
] = dsdb_dn
1042 # Fallback to the highest RMD_LOCAL_USN
1043 u1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_LOCAL_USN"))
1044 u2
= int(dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN"))
1046 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1047 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
1049 duplicate_dict
[keystr
]["keep"] = dsdb_dn
1050 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
1051 unique_dict
[keystr
] = dsdb_dn
1053 if error_count
!= 0:
1054 self
.duplicate_link_cache
[duplicate_cache_key
] = True
1056 return (error_count
, duplicate_dict
, unique_dict
)
1058 def has_duplicate_links(self
, dn
, forward_attr
, forward_syntax
):
1059 '''check a linked values for duplicate forward links'''
1062 duplicate_cache_key
= "%s:%s" % (str(dn
), forward_attr
)
1063 if duplicate_cache_key
in self
.duplicate_link_cache
:
1064 return self
.duplicate_link_cache
[duplicate_cache_key
]
1066 forward_linkID
, backlink_attr
= self
.get_attr_linkID_and_reverse_name(forward_attr
)
1068 attrs
= [forward_attr
]
1069 controls
= ["extended_dn:1:1", "reveal_internals:0"]
1071 # check its the right GUID
1073 res
= self
.samdb
.search(base
=str(dn
), scope
=ldb
.SCOPE_BASE
,
1074 attrs
=attrs
, controls
=controls
)
1075 except ldb
.LdbError
as e8
:
1076 (enum
, estr
) = e8
.args
1077 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1083 error_count
, duplicate_dict
, unique_dict
= \
1084 self
.check_duplicate_links(obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
)
1086 if duplicate_cache_key
in self
.duplicate_link_cache
:
1087 return self
.duplicate_link_cache
[duplicate_cache_key
]
1091 def find_missing_forward_links_from_backlinks(self
, obj
,
1095 forward_unique_dict
):
1096 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1097 missing_forward_links
= []
1100 if backlink_attr
is None:
1101 return (missing_forward_links
, error_count
)
1103 if forward_syntax
!= ldb
.SYNTAX_DN
:
1104 self
.report("Not checking for missing forward links for syntax: %s" %
1106 return (missing_forward_links
, error_count
)
1108 if "sortedLinks" in self
.compatibleFeatures
:
1109 self
.report("Not checking for missing forward links because the db " +
1110 "has the sortedLinks feature")
1111 return (missing_forward_links
, error_count
)
1114 obj_guid
= obj
['objectGUID'][0]
1115 obj_guid_str
= str(ndr_unpack(misc
.GUID
, obj_guid
))
1116 filter = "(%s=<GUID=%s>)" % (backlink_attr
, obj_guid_str
)
1118 res
= self
.samdb
.search(expression
=filter,
1119 scope
=ldb
.SCOPE_SUBTREE
, attrs
=["objectGUID"],
1120 controls
=["extended_dn:1:1",
1121 "search_options:1:2",
1122 "paged_results:1:1000"])
1123 except ldb
.LdbError
as e9
:
1124 (enum
, estr
) = e9
.args
1128 target_dn
= dsdb_Dn(self
.samdb
, r
.dn
.extended_str(), forward_syntax
)
1130 guid
= target_dn
.dn
.get_extended_component("GUID")
1131 guidstr
= str(misc
.GUID(guid
))
1132 if guidstr
in forward_unique_dict
:
1135 # A valid forward link looks like this:
1137 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1138 # <RMD_ADDTIME=131607546230000000>;
1139 # <RMD_CHANGETIME=131607546230000000>;
1141 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1142 # <RMD_LOCAL_USN=3765>;
1143 # <RMD_ORIGINATING_USN=3765>;
1145 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1146 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1148 # Note that versions older than Samba 4.8 create
1149 # links with RMD_VERSION=0.
1151 # Try to get the local_usn and time from objectClass
1152 # if possible and fallback to any other one.
1153 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1154 obj
['replPropertyMetadata'][0])
1155 for o
in repl
.ctr
.array
:
1156 local_usn
= o
.local_usn
1157 t
= o
.originating_change_time
1158 if o
.attid
== drsuapi
.DRSUAPI_ATTID_objectClass
:
1161 # We use a magic invocationID for restoring missing
1162 # forward links to recover from bug #13228.
1163 # This should allow some more future magic to fix the
1166 # It also means it looses the conflict resolution
1167 # against almost every real invocation, if the
1168 # version is also 0.
1169 originating_invocid
= misc
.GUID("ffffffff-4700-4700-4700-000000b13228")
1175 rmd_invocid
= originating_invocid
1176 rmd_originating_usn
= originating_usn
1177 rmd_local_usn
= local_usn
1180 target_dn
.dn
.set_extended_component("RMD_ADDTIME", str(rmd_addtime
))
1181 target_dn
.dn
.set_extended_component("RMD_CHANGETIME", str(rmd_changetime
))
1182 target_dn
.dn
.set_extended_component("RMD_FLAGS", str(rmd_flags
))
1183 target_dn
.dn
.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid
))
1184 target_dn
.dn
.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn
))
1185 target_dn
.dn
.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn
))
1186 target_dn
.dn
.set_extended_component("RMD_VERSION", str(rmd_version
))
1189 missing_forward_links
.append(target_dn
)
1191 return (missing_forward_links
, error_count
)
1193 def check_dn(self
, obj
, attrname
, syntax_oid
):
1194 '''check a DN attribute for correctness'''
1196 obj_guid
= obj
['objectGUID'][0]
1198 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
1199 if reverse_link_name
is not None:
1200 reverse_syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(reverse_link_name
)
1202 reverse_syntax_oid
= None
1204 is_member_link
= attrname
in ("member", "memberOf")
1205 if is_member_link
and self
.quick_membership_checks
:
1208 error_count
, duplicate_dict
, unique_dict
= \
1209 self
.check_duplicate_links(obj
, attrname
, syntax_oid
,
1210 linkID
, reverse_link_name
)
1212 if len(duplicate_dict
) != 0:
1214 missing_forward_links
, missing_error_count
= \
1215 self
.find_missing_forward_links_from_backlinks(obj
,
1216 attrname
, syntax_oid
,
1219 error_count
+= missing_error_count
1221 forward_links
= [dn
for dn
in unique_dict
.values()]
1223 if missing_error_count
!= 0:
1224 self
.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1227 self
.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname
, obj
.dn
))
1228 for m
in missing_forward_links
:
1229 self
.report("Missing link '%s'" % (m
))
1230 if not self
.confirm_all("Schedule readding missing forward link for attribute %s" % attrname
,
1231 'fix_all_missing_forward_links'):
1232 self
.err_orphaned_backlink(m
.dn
, reverse_link_name
,
1233 obj
.dn
.extended_str(), obj
.dn
,
1234 attrname
, syntax_oid
,
1235 check_duplicates
=False)
1237 forward_links
+= [m
]
1238 for keystr
in duplicate_dict
.keys():
1239 d
= duplicate_dict
[keystr
]
1240 for dd
in d
["delete"]:
1241 self
.report("Duplicate link '%s'" % dd
)
1242 self
.report("Correct link '%s'" % d
["keep"])
1244 # We now construct the sorted dn values.
1245 # They're sorted by the objectGUID of the target
1246 # See dsdb_Dn.__cmp__()
1247 vals
= [str(dn
) for dn
in sorted(forward_links
)]
1248 self
.err_recover_forward_links(obj
, attrname
, vals
)
1249 # We should continue with the fixed values
1250 obj
[attrname
] = ldb
.MessageElement(vals
, 0, attrname
)
1252 for val
in obj
[attrname
]:
1253 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
1255 # all DNs should have a GUID component
1256 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1259 self
.err_missing_dn_GUID_component(obj
.dn
, attrname
, val
, dsdb_dn
,
1263 guidstr
= str(misc
.GUID(guid
))
1264 attrs
= ['isDeleted', 'replPropertyMetaData']
1266 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
1267 fixing_msDS_HasInstantiatedNCs
= True
1268 attrs
.append("instanceType")
1270 fixing_msDS_HasInstantiatedNCs
= False
1272 if reverse_link_name
is not None:
1273 attrs
.append(reverse_link_name
)
1275 # check its the right GUID
1277 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
1278 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1",
1279 "reveal_internals:0"
1281 except ldb
.LdbError
as e3
:
1282 (enum
, estr
) = e3
.args
1283 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1286 # We don't always want to
1287 error_count
+= self
.err_missing_target_dn_or_GUID(obj
.dn
,
1293 if fixing_msDS_HasInstantiatedNCs
:
1294 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
1295 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
1297 if str(dsdb_dn
) != str(val
):
1299 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
1302 # now we have two cases - the source object might or might not be deleted
1303 is_deleted
= 'isDeleted' in obj
and str(obj
['isDeleted'][0]).upper() == 'TRUE'
1304 target_is_deleted
= 'isDeleted' in res
[0] and str(res
[0]['isDeleted'][0]).upper() == 'TRUE'
1306 if is_deleted
and obj
.dn
not in self
.deleted_objects_containers
and linkID
:
1307 # A fully deleted object should not have any linked
1308 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1309 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1311 self
.err_undead_linked_attribute(obj
, attrname
, val
)
1314 elif target_is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
) and linkID
:
1315 # the target DN is not allowed to be deleted, unless the target DN is the
1316 # special Deleted Objects container
1318 local_usn
= dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN")
1320 if 'replPropertyMetaData' in res
[0]:
1321 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1322 res
[0]['replPropertyMetadata'][0])
1324 for o
in repl
.ctr
.array
:
1325 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1326 deleted_usn
= o
.local_usn
1327 if deleted_usn
>= int(local_usn
):
1328 # If the object was deleted after the link
1329 # was last modified then, clean it up here
1334 self
.err_deleted_dn(obj
.dn
, attrname
,
1335 val
, dsdb_dn
, res
[0].dn
, True)
1338 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
, False)
1341 # We should not check for incorrect
1342 # components on deleted links, as these are allowed to
1343 # go stale (we just need the GUID, not the name)
1344 rmd_blob
= dsdb_dn
.dn
.get_extended_component("RMD_FLAGS")
1346 if rmd_blob
is not None:
1347 rmd_flags
= int(rmd_blob
)
1349 # assert the DN matches in string form, where a reverse
1350 # link exists, otherwise (below) offer to fix it as a non-error.
1351 # The string form is essentially only kept for forensics,
1352 # as we always re-resolve by GUID in normal operations.
1353 if not rmd_flags
& 1 and reverse_link_name
is not None:
1354 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1356 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1357 res
[0].dn
, "string")
1360 if res
[0].dn
.get_extended_component("GUID") != dsdb_dn
.dn
.get_extended_component("GUID"):
1362 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1366 target_sid
= res
[0].dn
.get_extended_component("SID")
1367 link_sid
= dsdb_dn
.dn
.get_extended_component("SID")
1368 if link_sid
is None and target_sid
is not None:
1370 self
.err_dn_component_missing_target_sid(obj
.dn
, attrname
, val
,
1371 dsdb_dn
, target_sid
)
1373 if link_sid
!= target_sid
:
1375 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1379 # Only for non-links, not even forward-only links
1380 # (otherwise this breaks repl_meta_data):
1382 # Now we have checked the GUID and SID, offer to fix old
1383 # DN strings as a non-error (DNs, not links so no
1384 # backlink). Samba does not maintain this string
1385 # otherwise, so we don't increment error_count.
1386 if reverse_link_name
is None:
1387 if linkID
== 0 and str(res
[0].dn
) != str(dsdb_dn
.dn
):
1388 # Pass in the old/bad DN without the <GUID=...> part,
1389 # otherwise the LDB code will correct it on the way through
1390 # (Note: we still want to preserve the DSDB DN prefix in the
1391 # case of binary DNs)
1392 bad_dn
= dsdb_dn
.prefix
+ dsdb_dn
.dn
.get_linearized()
1393 self
.err_dn_string_component_old(obj
.dn
, attrname
, bad_dn
,
1397 if is_member_link
and self
.quick_membership_checks
:
1400 # check the reverse_link is correct if there should be one
1402 if reverse_link_name
in res
[0]:
1403 for v
in res
[0][reverse_link_name
]:
1404 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1405 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1406 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1408 if v_blob
is not None:
1409 v_rmd_flags
= int(v_blob
)
1412 if v_guid
== obj_guid
:
1415 if match_count
!= 1:
1416 if syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
or reverse_syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
:
1418 # Forward binary multi-valued linked attribute
1420 for w
in obj
[attrname
]:
1421 w_guid
= dsdb_Dn(self
.samdb
, w
.decode('utf8')).dn
.get_extended_component("GUID")
1425 if match_count
== forward_count
:
1428 for v
in obj
[attrname
]:
1429 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1430 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1431 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1433 if v_blob
is not None:
1434 v_rmd_flags
= int(v_blob
)
1440 if match_count
== expected_count
:
1443 diff_count
= expected_count
- match_count
1446 # If there's a backward link on binary multi-valued linked attribute,
1447 # let the check on the forward link remedy the value.
1448 # UNLESS, there is no forward link detected.
1449 if match_count
== 0:
1451 self
.err_orphaned_backlink(obj
.dn
, attrname
,
1456 # Only warn here and let the forward link logic fix it.
1457 self
.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1458 attrname
, expected_count
, str(obj
.dn
),
1459 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1462 assert not target_is_deleted
1464 self
.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1465 attrname
, expected_count
, str(obj
.dn
),
1466 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1468 # Loop until the difference between the forward and
1469 # the backward links is resolved.
1470 while diff_count
!= 0:
1473 if match_count
> 0 or diff_count
> 1:
1474 # TODO no method to fix these right now
1475 self
.report("ERROR: Can't fix missing "
1476 "multi-valued backlinks on %s" % str(dsdb_dn
.dn
))
1478 self
.err_missing_backlink(obj
, attrname
,
1479 obj
.dn
.extended_str(),
1484 self
.err_orphaned_backlink(res
[0].dn
, reverse_link_name
,
1485 obj
.dn
.extended_str(), obj
.dn
,
1486 attrname
, syntax_oid
)
1491 def get_originating_time(self
, val
, attid
):
1492 '''Read metadata properties and return the originating time for
1493 a given attributeId.
1495 :return: the originating time or 0 if not found
1498 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1500 for o
in repl
.ctr
.array
:
1501 if o
.attid
== attid
:
1502 return o
.originating_change_time
1506 def process_metadata(self
, dn
, val
):
1507 '''Read metadata properties and list attributes in it.
1508 raises KeyError if the attid is unknown.'''
1511 wrong_attids
= set()
1513 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1515 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1517 for o
in repl
.ctr
.array
:
1518 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1519 set_att
.add(att
.lower())
1520 list_attid
.append(o
.attid
)
1521 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
,
1522 is_schema_nc
=in_schema_nc
)
1523 if correct_attid
!= o
.attid
:
1524 wrong_attids
.add(o
.attid
)
1526 return (set_att
, list_attid
, wrong_attids
)
1528 def fix_metadata(self
, obj
, attr
):
1529 '''re-write replPropertyMetaData elements for a single attribute for a
1530 object. This is used to fix missing replPropertyMetaData elements'''
1531 guid_str
= str(ndr_unpack(misc
.GUID
, obj
['objectGUID'][0]))
1532 dn
= ldb
.Dn(self
.samdb
, "<GUID=%s>" % guid_str
)
1533 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attr
],
1534 controls
=["search_options:1:2",
1537 nmsg
= ldb
.Message()
1539 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
1540 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
1541 "Failed to fix metadata for attribute %s" % attr
):
1542 self
.report("Fixed metadata for attribute %s" % attr
)
1544 def ace_get_effective_inherited_type(self
, ace
):
1545 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
1549 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
1551 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
1553 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
1555 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
1561 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
1564 return str(ace
.object.inherited_type
)
1566 def lookup_class_schemaIDGUID(self
, cls
):
1567 if cls
in self
.class_schemaIDGUID
:
1568 return self
.class_schemaIDGUID
[cls
]
1570 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1571 res
= self
.samdb
.search(base
=self
.schema_dn
,
1573 attrs
=["schemaIDGUID"])
1574 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
1576 self
.class_schemaIDGUID
[cls
] = t
1579 def process_sd(self
, dn
, obj
):
1580 sd_attr
= "nTSecurityDescriptor"
1581 sd_val
= obj
[sd_attr
]
1583 sd
= ndr_unpack(security
.descriptor
, sd_val
[0])
1585 is_deleted
= 'isDeleted' in obj
and str(obj
['isDeleted'][0]).upper() == 'TRUE'
1587 # we don't fix deleted objects
1590 sd_clean
= security
.descriptor()
1591 sd_clean
.owner_sid
= sd
.owner_sid
1592 sd_clean
.group_sid
= sd
.group_sid
1593 sd_clean
.type = sd
.type
1594 sd_clean
.revision
= sd
.revision
1597 last_inherited_type
= None
1600 if sd
.sacl
is not None:
1602 for i
in range(0, len(aces
)):
1605 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1606 sd_clean
.sacl_add(ace
)
1609 t
= self
.ace_get_effective_inherited_type(ace
)
1613 if last_inherited_type
is not None:
1614 if t
!= last_inherited_type
:
1615 # if it inherited from more than
1616 # one type it's very likely to be broken
1618 # If not the recalculation will calculate
1623 last_inherited_type
= t
1626 if sd
.dacl
is not None:
1628 for i
in range(0, len(aces
)):
1631 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1632 sd_clean
.dacl_add(ace
)
1635 t
= self
.ace_get_effective_inherited_type(ace
)
1639 if last_inherited_type
is not None:
1640 if t
!= last_inherited_type
:
1641 # if it inherited from more than
1642 # one type it's very likely to be broken
1644 # If not the recalculation will calculate
1649 last_inherited_type
= t
1652 return (sd_clean
, sd
)
1654 if last_inherited_type
is None:
1660 cls
= obj
["objectClass"][-1]
1661 except KeyError as e
:
1665 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1666 attrs
=["isDeleted", "objectClass"],
1667 controls
=["show_recycled:1"])
1669 is_deleted
= 'isDeleted' in o
and str(o
['isDeleted'][0]).upper() == 'TRUE'
1671 # we don't fix deleted objects
1673 cls
= o
["objectClass"][-1]
1675 t
= self
.lookup_class_schemaIDGUID(cls
)
1677 if t
!= last_inherited_type
:
1679 return (sd_clean
, sd
)
1684 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
1685 '''re-write the SD due to incorrect inherited ACEs'''
1686 sd_attr
= "nTSecurityDescriptor"
1687 sd_val
= ndr_pack(sd
)
1688 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1690 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
1691 self
.report('Not fixing %s on %s\n' % (sd_attr
, dn
))
1694 nmsg
= ldb
.Message()
1696 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1697 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1698 "Failed to fix attribute %s" % sd_attr
):
1699 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1701 def err_wrong_default_sd(self
, dn
, sd
, sd_old
, diff
):
1702 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1703 sd_attr
= "nTSecurityDescriptor"
1704 sd_val
= ndr_pack(sd
)
1705 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1706 if sd
.owner_sid
is not None:
1707 sd_flags |
= security
.SECINFO_OWNER
1708 if sd
.group_sid
is not None:
1709 sd_flags |
= security
.SECINFO_GROUP
1711 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
1712 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
1717 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1718 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
1719 "Failed to reset attribute %s" % sd_attr
):
1720 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1722 def err_missing_sd_owner(self
, dn
, sd
):
1723 '''re-write the SD due to a missing owner or group'''
1724 sd_attr
= "nTSecurityDescriptor"
1725 sd_val
= ndr_pack(sd
)
1726 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
1728 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
1729 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
1732 nmsg
= ldb
.Message()
1734 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1736 # By setting the session_info to admin_session_info and
1737 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1738 # flags we cause the descriptor module to set the correct
1739 # owner and group on the SD, replacing the None/NULL values
1740 # for owner_sid and group_sid currently present.
1742 # The admin_session_info matches that used in provision, and
1743 # is the best guess we can make for an existing object that
1744 # hasn't had something specifically set.
1746 # This is important for the dns related naming contexts.
1747 self
.samdb
.set_session_info(self
.admin_session_info
)
1748 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1749 "Failed to fix metadata for attribute %s" % sd_attr
):
1750 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1751 self
.samdb
.set_session_info(self
.system_session_info
)
1753 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
1754 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1759 # Search for a zero invocationID
1760 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1764 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1765 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1766 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1767 % (dn
, o
.attid
, o
.version
,
1768 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
1769 self
.samdb
.get_invocation_id()))
1773 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
1774 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1777 now
= samba
.unix2nttime(int(time
.time()))
1780 # Search for a zero invocationID
1781 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1785 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
1786 o
.version
= o
.version
+ 1
1787 o
.originating_change_time
= now
1788 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
1789 o
.originating_usn
= seq
1793 replBlob
= ndr_pack(repl
)
1797 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1798 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1799 self
.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr
, dn
))
1802 nmsg
= ldb
.Message()
1804 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1805 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1806 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1807 "Failed to fix attribute %s" % attr
):
1808 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1810 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
1811 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1815 # Search for an invalid attid
1817 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1819 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
1822 def err_replmetadata_incorrect_attid(self
, dn
, attr
, repl_meta_data
, wrong_attids
):
1823 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1828 remove_attid
= set()
1831 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1834 # Sort the array, except for the last element. This strange
1835 # construction, creating a new list, due to bugs in samba's
1836 # array handling in IDL generated objects.
1837 ctr
.array
= sorted(ctr
.array
[:], key
=lambda o
: o
.attid
)
1838 # Now walk it in reverse, so we see the low (and so incorrect,
1839 # the correct values are above 0x80000000) values first and
1840 # remove the 'second' value we see.
1841 for o
in reversed(ctr
.array
):
1842 print("%s: 0x%08x" % (dn
, o
.attid
))
1843 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1844 if att
.lower() in set_att
:
1845 self
.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att
, attr
, dn
))
1846 if not self
.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1847 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
),
1848 'fix_replmetadata_duplicate_attid'):
1849 self
.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1850 % (o
.attid
, att
, attr
, dn
))
1853 remove_attid
.add(o
.attid
)
1854 # We want to set the metadata for the most recent
1855 # update to have been applied locally, that is the metadata
1856 # matching the (eg string) value in the attribute
1857 if o
.local_usn
> hash_att
[att
].local_usn
:
1858 # This is always what we would have sent over DRS,
1859 # because the DRS server will have sent the
1860 # msDS-IntID, but with the values from both
1861 # attribute entries.
1862 hash_att
[att
].version
= o
.version
1863 hash_att
[att
].originating_change_time
= o
.originating_change_time
1864 hash_att
[att
].originating_invocation_id
= o
.originating_invocation_id
1865 hash_att
[att
].originating_usn
= o
.originating_usn
1866 hash_att
[att
].local_usn
= o
.local_usn
1868 # Do not re-add the value to the set or overwrite the hash value
1872 set_att
.add(att
.lower())
1874 # Generate a real list we can sort on properly
1875 new_list
= [o
for o
in ctr
.array
if o
.attid
not in remove_attid
]
1877 if (len(wrong_attids
) > 0):
1879 if o
.attid
in wrong_attids
:
1880 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1881 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
, is_schema_nc
=in_schema_nc
)
1882 self
.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr
, dn
))
1883 if not self
.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1884 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
), 'fix_replmetadata_wrong_attid'):
1885 self
.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1886 % (o
.attid
, correct_attid
, att
, attr
, dn
))
1889 o
.attid
= correct_attid
1891 # Sort the array, (we changed the value so must re-sort)
1892 new_list
[:] = sorted(new_list
[:], key
=lambda o
: o
.attid
)
1894 # If we did not already need to fix it, then ask about sorting
1896 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
1897 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
1898 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
1899 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
1902 # The actual sort done is done at the top of the function
1904 ctr
.count
= len(new_list
)
1905 ctr
.array
= new_list
1906 replBlob
= ndr_pack(repl
)
1908 nmsg
= ldb
.Message()
1910 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1911 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1912 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1913 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1914 "Failed to fix attribute %s" % attr
):
1915 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1917 def is_deleted_deleted_objects(self
, obj
):
1919 if "description" not in obj
:
1920 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
1922 if "showInAdvancedViewOnly" not in obj
or str(obj
['showInAdvancedViewOnly'][0]).upper() == 'FALSE':
1923 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
1925 if "objectCategory" not in obj
:
1926 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
1928 if "isCriticalSystemObject" not in obj
or str(obj
['isCriticalSystemObject'][0]).upper() == 'FALSE':
1929 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
1931 if "isRecycled" in obj
:
1932 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
1934 if "isDeleted" in obj
and str(obj
['isDeleted'][0]).upper() == 'FALSE':
1935 self
.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj
.dn
)
1937 if "objectClass" not in obj
or (len(obj
['objectClass']) != 2 or
1938 str(obj
['objectClass'][0]) != 'top' or
1939 str(obj
['objectClass'][1]) != 'container'):
1940 self
.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj
.dn
)
1942 if "systemFlags" not in obj
or str(obj
['systemFlags'][0]) != '-1946157056':
1943 self
.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj
.dn
)
1947 def err_deleted_deleted_objects(self
, obj
):
1948 nmsg
= ldb
.Message()
1949 nmsg
.dn
= dn
= obj
.dn
1951 if "description" not in obj
:
1952 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
1953 if "showInAdvancedViewOnly" not in obj
:
1954 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
1955 if "objectCategory" not in obj
:
1956 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
1957 if "isCriticalSystemObject" not in obj
:
1958 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
1959 if "isRecycled" in obj
:
1960 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
1962 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
1963 nmsg
["systemFlags"] = ldb
.MessageElement("-1946157056", ldb
.FLAG_MOD_REPLACE
, "systemFlags")
1964 nmsg
["objectClass"] = ldb
.MessageElement(["top", "container"], ldb
.FLAG_MOD_REPLACE
, "objectClass")
1966 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1967 % (dn
), 'fix_deleted_deleted_objects'):
1968 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
1971 if self
.do_modify(nmsg
, ["relax:0"],
1972 "Failed to fix Deleted Objects container %s" % dn
):
1973 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
1975 def err_replica_locations(self
, obj
, cross_ref
, attr
):
1976 nmsg
= ldb
.Message()
1978 target
= self
.samdb
.get_dsServiceName()
1980 if self
.samdb
.am_rodc():
1981 self
.report('Not fixing %s %s for the RODC' % (attr
, obj
.dn
))
1984 if not self
.confirm_all('Add yourself to the replica locations for %s?'
1985 % (obj
.dn
), 'fix_replica_locations'):
1986 self
.report('Not fixing missing/incorrect attributes on %s\n' % (obj
.dn
))
1989 nmsg
[attr
] = ldb
.MessageElement(target
, ldb
.FLAG_MOD_ADD
, attr
)
1990 if self
.do_modify(nmsg
, [], "Failed to add %s for %s" % (attr
, obj
.dn
)):
1991 self
.report("Fixed %s for %s" % (attr
, obj
.dn
))
1993 def is_fsmo_role(self
, dn
):
1994 if dn
== self
.samdb
.domain_dn
:
1996 if dn
== self
.infrastructure_dn
:
1998 if dn
== self
.naming_dn
:
2000 if dn
== self
.schema_dn
:
2002 if dn
== self
.rid_dn
:
2007 def calculate_instancetype(self
, dn
):
2009 nc_root
= self
.samdb
.get_nc_root(dn
)
2011 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
2013 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
2014 except ldb
.LdbError
as e4
:
2015 (enum
, estr
) = e4
.args
2016 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2019 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
2020 if self
.write_ncs
is not None and str(nc_root
) in [str(x
) for x
in self
.write_ncs
]:
2021 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
2025 def get_wellknown_sd(self
, dn
):
2026 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
2028 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
2029 return ndr_unpack(security
.descriptor
,
2030 descriptor_fn(domain_sid
,
2031 name_map
=self
.name_map
))
2035 def check_object(self
, dn
, attrs
=None):
2036 '''check one object'''
2038 self
.report("Checking object %s" % dn
)
2042 # make a local copy to modify
2044 if "dn" in map(str.lower
, attrs
):
2045 attrs
.append("name")
2046 if "distinguishedname" in map(str.lower
, attrs
):
2047 attrs
.append("name")
2048 if str(dn
.get_rdn_name()).lower() in map(str.lower
, attrs
):
2049 attrs
.append("name")
2050 if 'name' in map(str.lower
, attrs
):
2051 attrs
.append(dn
.get_rdn_name())
2052 attrs
.append("isDeleted")
2053 attrs
.append("systemFlags")
2054 need_replPropertyMetaData
= False
2056 need_replPropertyMetaData
= True
2059 linkID
, _
= self
.get_attr_linkID_and_reverse_name(a
)
2064 need_replPropertyMetaData
= True
2066 if need_replPropertyMetaData
:
2067 attrs
.append("replPropertyMetaData")
2068 attrs
.append("objectGUID")
2072 sd_flags |
= security
.SECINFO_OWNER
2073 sd_flags |
= security
.SECINFO_GROUP
2074 sd_flags |
= security
.SECINFO_DACL
2075 sd_flags |
= security
.SECINFO_SACL
2077 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
2082 "sd_flags:1:%d" % sd_flags
,
2083 "reveal_internals:0",
2086 except ldb
.LdbError
as e10
:
2087 (enum
, estr
) = e10
.args
2088 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2089 if self
.in_transaction
:
2090 self
.report("ERROR: Object %s disappeared during check" % dn
)
2095 self
.report("ERROR: Object %s failed to load during check" % dn
)
2099 set_attrs_from_md
= set()
2100 set_attrs_seen
= set()
2101 got_repl_property_meta_data
= False
2102 got_objectclass
= False
2104 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
2106 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
2107 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
2109 # We have no deleted objects DN for schema, and we check for this above for the other
2111 deleted_objects_dn
= None
2113 object_rdn_attr
= None
2114 object_rdn_val
= None
2119 for attrname
in obj
:
2120 if attrname
== 'dn' or attrname
== "distinguishedName":
2123 if str(attrname
).lower() == 'objectclass':
2124 got_objectclass
= True
2126 if str(attrname
).lower() == "name":
2127 if len(obj
[attrname
]) != 1:
2129 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2130 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2132 name_val
= obj
[attrname
][0]
2134 if str(attrname
).lower() == str(obj
.dn
.get_rdn_name()).lower():
2135 object_rdn_attr
= attrname
2136 if len(obj
[attrname
]) != 1:
2138 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2139 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2141 object_rdn_val
= str(obj
[attrname
][0])
2143 if str(attrname
).lower() == 'isdeleted':
2144 if str(obj
[attrname
][0]) != "FALSE":
2147 if str(attrname
).lower() == 'systemflags':
2148 systemFlags
= int(obj
[attrname
][0])
2150 if str(attrname
).lower() == 'replpropertymetadata':
2151 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
][0]):
2153 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
][0])
2154 # We don't continue, as we may also have other fixes for this attribute
2155 # based on what other attributes we see.
2158 (set_attrs_from_md
, list_attid_from_md
, wrong_attids
) \
2159 = self
.process_metadata(dn
, obj
[attrname
][0])
2162 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
2165 if len(set_attrs_from_md
) < len(list_attid_from_md
) \
2166 or len(wrong_attids
) > 0 \
2167 or sorted(list_attid_from_md
) != list_attid_from_md
:
2169 self
.err_replmetadata_incorrect_attid(dn
, attrname
, obj
[attrname
][0], wrong_attids
)
2172 # Here we check that the first attid is 0
2174 if list_attid_from_md
[0] != 0:
2176 self
.report("ERROR: Not fixing incorrect initial attributeID in '%s' on '%s', it should be objectClass" %
2177 (attrname
, str(dn
)))
2179 got_repl_property_meta_data
= True
2182 if str(attrname
).lower() == 'ntsecuritydescriptor':
2183 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
2184 if sd_broken
is not None:
2185 self
.err_wrong_sd(dn
, sd
, sd_broken
)
2189 if sd
.owner_sid
is None or sd
.group_sid
is None:
2190 self
.err_missing_sd_owner(dn
, sd
)
2194 if self
.reset_well_known_acls
:
2196 well_known_sd
= self
.get_wellknown_sd(dn
)
2200 current_sd
= ndr_unpack(security
.descriptor
,
2203 diff
= get_diff_sds(well_known_sd
, current_sd
, security
.dom_sid(self
.samdb
.get_domain_sid()))
2205 self
.err_wrong_default_sd(dn
, well_known_sd
, current_sd
, diff
)
2210 if str(attrname
).lower() == 'objectclass':
2211 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
2212 # Do not consider the attribute incorrect if:
2213 # - The sorted (alphabetically) list is the same, inclding case
2214 # - The first and last elements are the same
2216 # This avoids triggering an error due to
2217 # non-determinism in the sort routine in (at least)
2218 # 4.3 and earlier, and the fact that any AUX classes
2219 # in these attributes are also not sorted when
2220 # imported from Windows (they are just in the reverse
2221 # order of last set)
2222 if sorted(normalised
) != sorted(obj
[attrname
]) \
2223 or normalised
[0] != obj
[attrname
][0] \
2224 or normalised
[-1] != obj
[attrname
][-1]:
2225 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
2229 if str(attrname
).lower() == 'userparameters':
2230 if len(obj
[attrname
][0]) == 1 and obj
[attrname
][0][0] == b
'\x20'[0]:
2232 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
2235 elif obj
[attrname
][0][:16] == b
'\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2236 # This is the correct, normal prefix
2239 elif obj
[attrname
][0][:20] == b
'IAAgACAAIAAgACAAIAAg':
2240 # this is the typical prefix from a windows migration
2242 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
2245 #43:00:00:00:74:00:00:00:78
2246 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]:
2247 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2249 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
2252 elif len(obj
[attrname
][0]) % 2 != 0:
2253 # This is a value that isn't even in length
2255 self
.err_odd_userParameters(obj
, attrname
)
2258 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]:
2259 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2261 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
2264 if attrname
.lower() == 'attributeid' or attrname
.lower() == 'governsid':
2265 if obj
[attrname
][0] in self
.attribute_or_class_ids
:
2267 self
.report('Error: %s %s on %s already exists as an attributeId or governsId'
2268 % (attrname
, obj
.dn
, obj
[attrname
][0]))
2270 self
.attribute_or_class_ids
.add(obj
[attrname
][0])
2272 # check for empty attributes
2273 for val
in obj
[attrname
]:
2275 self
.err_empty_attribute(dn
, attrname
)
2279 # get the syntax oid for the attribute, so we can can have
2280 # special handling for some specific attribute types
2282 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
2283 except Exception as msg
:
2284 self
.err_unknown_attribute(obj
, attrname
)
2288 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
2290 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
2291 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
2292 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
2294 set_attrs_seen
.add(str(attrname
).lower())
2296 if syntax_oid
in [dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
2297 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
2298 # it's some form of DN, do specialised checking on those
2299 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
2303 # check for incorrectly normalised attributes
2304 for val
in obj
[attrname
]:
2307 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
2308 if len(normalised
) != 1 or normalised
[0] != val
:
2309 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
2313 if len(obj
[attrname
]) != len(values
):
2314 self
.err_duplicate_values(dn
, attrname
, obj
[attrname
], list(values
))
2318 if str(attrname
).lower() == "instancetype":
2319 calculated_instancetype
= self
.calculate_instancetype(dn
)
2320 if len(obj
["instanceType"]) != 1 or int(obj
["instanceType"][0]) != calculated_instancetype
:
2322 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
2324 if not got_objectclass
and ("*" in attrs
or "objectclass" in map(str.lower
, attrs
)):
2326 self
.err_missing_objectclass(dn
)
2328 if ("*" in attrs
or "name" in map(str.lower
, attrs
)):
2329 if name_val
is None:
2331 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
2332 if object_rdn_attr
is None:
2334 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
2336 if name_val
is not None:
2338 controls
= ["show_recycled:1", "relax:0"]
2340 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
2341 parent_dn
= deleted_objects_dn
2342 controls
+= ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
]
2343 if parent_dn
is None:
2344 parent_dn
= obj
.dn
.parent()
2345 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
2346 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
2348 if obj
.dn
== deleted_objects_dn
:
2349 expected_dn
= obj
.dn
2351 if expected_dn
!= obj
.dn
:
2353 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
,
2354 object_rdn_val
, name_val
, controls
)
2355 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
2357 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
, object_rdn_val
, str(obj
.dn
)))
2360 if got_repl_property_meta_data
:
2361 if obj
.dn
== deleted_objects_dn
:
2362 isDeletedAttId
= 131120
2363 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2365 expectedTimeDo
= 2650466015990000000
2366 originating
= self
.get_originating_time(obj
["replPropertyMetaData"][0], isDeletedAttId
)
2367 if originating
!= expectedTimeDo
:
2368 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
2369 nmsg
= ldb
.Message()
2371 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2373 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
2376 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
2378 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
2380 self
.report("On object %s" % dn
)
2383 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
2384 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
2385 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
2387 self
.fix_metadata(obj
, att
)
2389 if self
.is_fsmo_role(dn
):
2390 if "fSMORoleOwner" not in obj
and ("*" in attrs
or "fsmoroleowner" in map(str.lower
, attrs
)):
2391 self
.err_no_fsmoRoleOwner(obj
)
2395 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
2396 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
2397 controls
=["show_recycled:1", "show_deleted:1"])
2398 except ldb
.LdbError
as e11
:
2399 (enum
, estr
) = e11
.args
2400 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2401 self
.err_missing_parent(obj
)
2406 if dn
in self
.deleted_objects_containers
and '*' in attrs
:
2407 if self
.is_deleted_deleted_objects(obj
):
2408 self
.err_deleted_deleted_objects(obj
)
2411 for (dns_part
, msg
) in self
.dns_partitions
:
2412 if dn
== dns_part
and 'repsFrom' in obj
:
2413 location
= "msDS-NC-Replica-Locations"
2414 if self
.samdb
.am_rodc():
2415 location
= "msDS-NC-RO-Replica-Locations"
2417 if location
not in msg
:
2418 # There are no replica locations!
2419 self
.err_replica_locations(obj
, msg
.dn
, location
)
2424 for loc
in msg
[location
]:
2425 if str(loc
) == self
.samdb
.get_dsServiceName():
2428 # This DC is not in the replica locations
2429 self
.err_replica_locations(obj
, msg
.dn
, location
)
2432 if dn
== self
.server_ref_dn
:
2433 # Check we have a valid RID Set
2434 if "*" in attrs
or "rIDSetReferences" in attrs
:
2435 if "rIDSetReferences" not in obj
:
2436 # NO RID SET reference
2437 # We are RID master, allocate it.
2440 if self
.is_rid_master
:
2441 # Allocate a RID Set
2442 if self
.confirm_all('Allocate the missing RID set for RID master?',
2443 'fix_missing_rid_set_master'):
2445 # We don't have auto-transaction logic on
2446 # extended operations, so we have to do it
2449 self
.samdb
.transaction_start()
2452 self
.samdb
.create_own_rid_set()
2455 self
.samdb
.transaction_cancel()
2458 self
.samdb
.transaction_commit()
2460 elif not self
.samdb
.am_rodc():
2461 self
.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn
)
2463 # Check some details of our own RID Set
2464 if dn
== self
.rid_set_dn
:
2465 res
= self
.samdb
.search(base
=self
.rid_set_dn
, scope
=ldb
.SCOPE_BASE
,
2466 attrs
=["rIDAllocationPool",
2467 "rIDPreviousAllocationPool",
2470 if "rIDAllocationPool" not in res
[0]:
2471 self
.report("No rIDAllocationPool found in %s" % dn
)
2474 next_pool
= int(res
[0]["rIDAllocationPool"][0])
2476 high
= (0xFFFFFFFF00000000 & next_pool
) >> 32
2477 low
= 0x00000000FFFFFFFF & next_pool
2480 self
.report("Invalid RID set %d-%s, %d > %d!" % (low
, high
, low
, high
))
2483 if "rIDNextRID" in res
[0]:
2484 next_free_rid
= int(res
[0]["rIDNextRID"][0])
2488 if next_free_rid
== 0:
2493 # Check the remainder of this pool for conflicts. If
2494 # ridalloc_allocate_rid() moves to a new pool, this
2495 # will be above high, so we will stop.
2496 while next_free_rid
<= high
:
2497 sid
= "%s-%d" % (self
.samdb
.get_domain_sid(), next_free_rid
)
2499 res
= self
.samdb
.search(base
="<SID=%s>" % sid
, scope
=ldb
.SCOPE_BASE
,
2501 except ldb
.LdbError
as e
:
2502 (enum
, estr
) = e
.args
2503 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2507 self
.report("SID %s for %s conflicts with our current RID set in %s" % (sid
, res
[0].dn
, dn
))
2510 if self
.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2512 'fix_sid_rid_set_conflict'):
2513 self
.samdb
.transaction_start()
2515 # This will burn RIDs, which will move
2516 # past the conflict. We then check again
2517 # to see if the new RID conflicts, until
2518 # the end of the current pool. We don't
2519 # look at the next pool to avoid burning
2520 # all RIDs in one go in some strange
2524 allocated_rid
= self
.samdb
.allocate_rid()
2525 if allocated_rid
>= next_free_rid
:
2526 next_free_rid
= allocated_rid
+ 1
2529 self
.samdb
.transaction_cancel()
2532 self
.samdb
.transaction_commit()
2540 ################################################################
2541 # check special @ROOTDSE attributes
2542 def check_rootdse(self
):
2543 '''check the @ROOTDSE special object'''
2544 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
2546 self
.report("Checking object %s" % dn
)
2547 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
2549 self
.report("Object %s disappeared during check" % dn
)
2554 # check that the dsServiceName is in GUID form
2555 if 'dsServiceName' not in obj
:
2556 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
2557 return error_count
+ 1
2559 if not str(obj
['dsServiceName'][0]).startswith('<GUID='):
2560 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2562 if not self
.confirm('Change dsServiceName to GUID form?'):
2564 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0].decode('utf8')),
2565 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
2566 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
2569 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
2570 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
2571 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
2572 self
.report("Changed dsServiceName to GUID form")
2575 ###############################################
2576 # re-index the database
2578 def reindex_database(self
):
2579 '''re-index the whole database'''
2581 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
2582 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
2583 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
2584 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
2586 ###############################################
2588 def reset_modules(self
):
2589 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2591 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
2592 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
2593 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)