1 # Samba4 AD database checker
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from __future__
import print_function
24 from base64
import b64decode
25 from samba
import dsdb
26 from samba
import common
27 from samba
.dcerpc
import misc
28 from samba
.dcerpc
import drsuapi
29 from samba
.ndr
import ndr_unpack
, ndr_pack
30 from samba
.dcerpc
import drsblobs
31 from samba
.common
import dsdb_Dn
32 from samba
.dcerpc
import security
33 from samba
.descriptor
import get_wellknown_sds
, get_diff_sds
34 from samba
.auth
import system_session
, admin_session
35 from samba
.netcmd
import CommandError
36 from samba
.netcmd
.fsmo
import get_fsmo_roleowner
39 class dbcheck(object):
40 """check a SAM database for errors"""
42 def __init__(self
, samdb
, samdb_schema
=None, verbose
=False, fix
=False,
43 yes
=False, quiet
=False, in_transaction
=False,
44 reset_well_known_acls
=False):
46 self
.dict_oid_name
= None
47 self
.samdb_schema
= (samdb_schema
or samdb
)
48 self
.verbose
= verbose
52 self
.remove_all_unknown_attributes
= False
53 self
.remove_all_empty_attributes
= False
54 self
.fix_all_normalisation
= False
55 self
.fix_all_duplicates
= False
56 self
.fix_all_DN_GUIDs
= False
57 self
.fix_all_binary_dn
= False
58 self
.remove_implausible_deleted_DN_links
= False
59 self
.remove_plausible_deleted_DN_links
= False
60 self
.fix_all_string_dn_component_mismatch
= False
61 self
.fix_all_GUID_dn_component_mismatch
= False
62 self
.fix_all_SID_dn_component_mismatch
= False
63 self
.fix_all_old_dn_string_component_mismatch
= False
64 self
.fix_all_metadata
= False
65 self
.fix_time_metadata
= False
66 self
.fix_undead_linked_attributes
= False
67 self
.fix_all_missing_backlinks
= False
68 self
.fix_all_orphaned_backlinks
= False
69 self
.fix_all_missing_forward_links
= False
70 self
.duplicate_link_cache
= dict()
71 self
.recover_all_forward_links
= False
72 self
.fix_rmd_flags
= False
73 self
.fix_ntsecuritydescriptor
= False
74 self
.fix_ntsecuritydescriptor_owner_group
= False
75 self
.seize_fsmo_role
= False
76 self
.move_to_lost_and_found
= False
77 self
.fix_instancetype
= False
78 self
.fix_replmetadata_zero_invocationid
= False
79 self
.fix_replmetadata_duplicate_attid
= False
80 self
.fix_replmetadata_wrong_attid
= False
81 self
.fix_replmetadata_unsorted_attid
= False
82 self
.fix_deleted_deleted_objects
= False
83 self
.fix_incorrect_deleted_objects
= False
85 self
.fix_base64_userparameters
= False
86 self
.fix_utf8_userparameters
= False
87 self
.fix_doubled_userparameters
= False
88 self
.fix_sid_rid_set_conflict
= False
89 self
.reset_well_known_acls
= reset_well_known_acls
90 self
.reset_all_well_known_acls
= False
91 self
.in_transaction
= in_transaction
92 self
.infrastructure_dn
= ldb
.Dn(samdb
, "CN=Infrastructure," + samdb
.domain_dn())
93 self
.naming_dn
= ldb
.Dn(samdb
, "CN=Partitions,%s" % samdb
.get_config_basedn())
94 self
.schema_dn
= samdb
.get_schema_basedn()
95 self
.rid_dn
= ldb
.Dn(samdb
, "CN=RID Manager$,CN=System," + samdb
.domain_dn())
96 self
.ntds_dsa
= ldb
.Dn(samdb
, samdb
.get_dsServiceName())
97 self
.class_schemaIDGUID
= {}
98 self
.wellknown_sds
= get_wellknown_sds(self
.samdb
)
99 self
.fix_all_missing_objectclass
= False
100 self
.fix_missing_deleted_objects
= False
101 self
.fix_replica_locations
= False
102 self
.fix_missing_rid_set_master
= False
105 self
.link_id_cache
= {}
108 res
= samdb
.search(base
="CN=DnsAdmins,CN=Users,%s" % samdb
.domain_dn(), scope
=ldb
.SCOPE_BASE
,
110 dnsadmins_sid
= ndr_unpack(security
.dom_sid
, res
[0]["objectSid"][0])
111 self
.name_map
['DnsAdmins'] = str(dnsadmins_sid
)
112 except ldb
.LdbError
as e5
:
113 (enum
, estr
) = e5
.args
114 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
118 self
.system_session_info
= system_session()
119 self
.admin_session_info
= admin_session(None, samdb
.get_domain_sid())
121 res
= self
.samdb
.search(base
=self
.ntds_dsa
, scope
=ldb
.SCOPE_BASE
, attrs
=['msDS-hasMasterNCs', 'hasMasterNCs'])
122 if "msDS-hasMasterNCs" in res
[0]:
123 self
.write_ncs
= res
[0]["msDS-hasMasterNCs"]
125 # If the Forest Level is less than 2003 then there is no
126 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
127 # no need to merge as all the NCs that are in hasMasterNCs must
128 # also be in msDS-hasMasterNCs (but not the opposite)
129 if "hasMasterNCs" in res
[0]:
130 self
.write_ncs
= res
[0]["hasMasterNCs"]
132 self
.write_ncs
= None
134 res
= self
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['namingContexts'])
135 self
.deleted_objects_containers
= []
136 self
.ncs_lacking_deleted_containers
= []
137 self
.dns_partitions
= []
139 self
.ncs
= res
[0]["namingContexts"]
147 dn
= self
.samdb
.get_wellknown_dn(ldb
.Dn(self
.samdb
, nc
.decode('utf8')),
148 dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
149 self
.deleted_objects_containers
.append(dn
)
151 self
.ncs_lacking_deleted_containers
.append(ldb
.Dn(self
.samdb
, nc
.decode('utf8')))
153 domaindns_zone
= 'DC=DomainDnsZones,%s' % self
.samdb
.get_default_basedn()
154 forestdns_zone
= 'DC=ForestDnsZones,%s' % self
.samdb
.get_root_basedn()
155 domain
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
156 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
157 base
=self
.samdb
.get_partitions_dn(),
158 expression
="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone
)
160 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, forestdns_zone
), domain
[0]))
162 forest
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
163 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
164 base
=self
.samdb
.get_partitions_dn(),
165 expression
="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone
)
167 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, domaindns_zone
), forest
[0]))
169 fsmo_dn
= ldb
.Dn(self
.samdb
, "CN=RID Manager$,CN=System," + self
.samdb
.domain_dn())
170 rid_master
= get_fsmo_roleowner(self
.samdb
, fsmo_dn
, "rid")
171 if ldb
.Dn(self
.samdb
, self
.samdb
.get_dsServiceName()) == rid_master
:
172 self
.is_rid_master
= True
174 self
.is_rid_master
= False
176 # To get your rid set
178 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, self
.samdb
.get_serverName()),
179 scope
=ldb
.SCOPE_BASE
, attrs
=["serverReference"])
180 # 2. Get server reference
181 self
.server_ref_dn
= ldb
.Dn(self
.samdb
, res
[0]['serverReference'][0].decode('utf8'))
184 res
= self
.samdb
.search(base
=self
.server_ref_dn
,
185 scope
=ldb
.SCOPE_BASE
, attrs
=['rIDSetReferences'])
186 if "rIDSetReferences" in res
[0]:
187 self
.rid_set_dn
= ldb
.Dn(self
.samdb
, res
[0]['rIDSetReferences'][0].decode('utf8'))
189 self
.rid_set_dn
= None
191 self
.compatibleFeatures
= []
192 self
.requiredFeatures
= []
195 res
= self
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
197 attrs
=["compatibleFeatures",
199 if "compatibleFeatures" in res
[0]:
200 self
.compatibleFeatures
= res
[0]["compatibleFeatures"]
201 if "requiredFeatures" in res
[0]:
202 self
.requiredFeatures
= res
[0]["requiredFeatures"]
203 except ldb
.LdbError
as e6
:
204 (enum
, estr
) = e6
.args
205 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
209 def check_database(self
, DN
=None, scope
=ldb
.SCOPE_SUBTREE
, controls
=[], attrs
=['*']):
210 '''perform a database check, returning the number of errors found'''
211 res
= self
.samdb
.search(base
=DN
, scope
=scope
, attrs
=['dn'], controls
=controls
)
212 self
.report('Checking %u objects' % len(res
))
215 error_count
+= self
.check_deleted_objects_containers()
217 self
.attribute_or_class_ids
= set()
220 self
.dn_set
.add(str(object.dn
))
221 error_count
+= self
.check_object(object.dn
, attrs
=attrs
)
224 error_count
+= self
.check_rootdse()
226 if error_count
!= 0 and not self
.fix
:
227 self
.report("Please use --fix to fix these errors")
229 self
.report('Checked %u objects (%u errors)' % (len(res
), error_count
))
232 def check_deleted_objects_containers(self
):
233 """This function only fixes conflicts on the Deleted Objects
234 containers, not the attributes"""
236 for nc
in self
.ncs_lacking_deleted_containers
:
237 if nc
== self
.schema_dn
:
240 self
.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc
)
241 if not self
.confirm_all('Fix missing Deleted Objects container for %s?' % (nc
), 'fix_missing_deleted_objects'):
244 dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects")
249 # If something already exists here, add a conflict
250 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[],
251 controls
=["show_deleted:1", "extended_dn:1:1",
252 "show_recycled:1", "reveal_internals:0"])
254 guid
= res
[0].dn
.get_extended_component("GUID")
255 conflict_dn
= ldb
.Dn(self
.samdb
,
256 "CN=Deleted Objects\\0ACNF:%s" % str(misc
.GUID(guid
)))
257 conflict_dn
.add_base(nc
)
259 except ldb
.LdbError
as e2
:
260 (enum
, estr
) = e2
.args
261 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
264 self
.report("Couldn't check for conflicting Deleted Objects container: %s" % estr
)
267 if conflict_dn
is not None:
269 self
.samdb
.rename(dn
, conflict_dn
, ["show_deleted:1", "relax:0", "show_recycled:1"])
270 except ldb
.LdbError
as e1
:
271 (enum
, estr
) = e1
.args
272 self
.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn
, conflict_dn
, estr
))
275 # Refresh wellKnownObjects links
276 res
= self
.samdb
.search(base
=nc
, scope
=ldb
.SCOPE_BASE
,
277 attrs
=['wellKnownObjects'],
278 controls
=["show_deleted:1", "extended_dn:0",
279 "show_recycled:1", "reveal_internals:0"])
281 self
.report("wellKnownObjects was not found for NC %s" % nc
)
284 # Prevent duplicate deleted objects containers just in case
285 wko
= res
[0]["wellKnownObjects"]
287 proposed_objectguid
= None
289 dsdb_dn
= dsdb_Dn(self
.samdb
, o
.decode('utf8'), dsdb
.DSDB_SYNTAX_BINARY_DN
)
290 if self
.is_deleted_objects_dn(dsdb_dn
):
291 self
.report("wellKnownObjects had duplicate Deleted Objects value %s" % o
)
292 # We really want to put this back in the same spot
293 # as the original one, so that on replication we
294 # merge, rather than conflict.
295 proposed_objectguid
= dsdb_dn
.dn
.get_extended_component("GUID")
296 listwko
.append(str(o
))
298 if proposed_objectguid
is not None:
299 guid_suffix
= "\nobjectGUID: %s" % str(misc
.GUID(proposed_objectguid
))
301 wko_prefix
= "B:32:%s" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
302 listwko
.append('%s:%s' % (wko_prefix
, dn
))
305 # Insert a brand new Deleted Objects container
306 self
.samdb
.add_ldif("""dn: %s
308 objectClass: container
309 description: Container for deleted objects
311 isCriticalSystemObject: TRUE
312 showInAdvancedViewOnly: TRUE
313 systemFlags: -1946157056%s""" % (dn
, guid_suffix
),
314 controls
=["relax:0", "provision:0"])
316 delta
= ldb
.Message()
317 delta
.dn
= ldb
.Dn(self
.samdb
, str(res
[0]["dn"]))
318 delta
["wellKnownObjects"] = ldb
.MessageElement(listwko
,
319 ldb
.FLAG_MOD_REPLACE
,
322 # Insert the link to the brand new container
323 if self
.do_modify(delta
, ["relax:0"],
324 "NC %s lacks Deleted Objects WKGUID" % nc
,
326 self
.report("Added %s well known guid link" % dn
)
328 self
.deleted_objects_containers
.append(dn
)
332 def report(self
, msg
):
333 '''print a message unless quiet is set'''
337 def confirm(self
, msg
, allow_all
=False, forced
=False):
338 '''confirm a change'''
345 return common
.confirm(msg
, forced
=forced
, allow_all
=allow_all
)
347 ################################################################
348 # a local confirm function with support for 'all'
349 def confirm_all(self
, msg
, all_attr
):
350 '''confirm a change with support for "all" '''
353 if getattr(self
, all_attr
) == 'NONE':
355 if getattr(self
, all_attr
) == 'ALL':
361 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
363 setattr(self
, all_attr
, 'ALL')
366 setattr(self
, all_attr
, 'NONE')
370 def do_delete(self
, dn
, controls
, msg
):
371 '''delete dn with optional verbose output'''
373 self
.report("delete DN %s" % dn
)
375 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
376 self
.samdb
.delete(dn
, controls
=controls
)
377 except Exception as err
:
378 if self
.in_transaction
:
379 raise CommandError("%s : %s" % (msg
, err
))
380 self
.report("%s : %s" % (msg
, err
))
384 def do_modify(self
, m
, controls
, msg
, validate
=True):
385 '''perform a modify with optional verbose output'''
387 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
389 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
390 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
391 except Exception as err
:
392 if self
.in_transaction
:
393 raise CommandError("%s : %s" % (msg
, err
))
394 self
.report("%s : %s" % (msg
, err
))
398 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
399 '''perform a modify with optional verbose output'''
401 self
.report("""dn: %s
405 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
407 to_dn
= to_rdn
+ to_base
408 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
409 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
410 except Exception as err
:
411 if self
.in_transaction
:
412 raise CommandError("%s : %s" % (msg
, err
))
413 self
.report("%s : %s" % (msg
, err
))
417 def get_attr_linkID_and_reverse_name(self
, attrname
):
418 if attrname
in self
.link_id_cache
:
419 return self
.link_id_cache
[attrname
]
420 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
422 revname
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
425 self
.link_id_cache
[attrname
] = (linkID
, revname
)
426 return linkID
, revname
428 def err_empty_attribute(self
, dn
, attrname
):
429 '''fix empty attributes'''
430 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
431 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
432 self
.report("Not fixing empty attribute %s" % attrname
)
437 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
438 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
439 "Failed to remove empty attribute %s" % attrname
, validate
=False):
440 self
.report("Removed empty attribute %s" % attrname
)
442 def err_normalise_mismatch(self
, dn
, attrname
, values
):
443 '''fix attribute normalisation errors'''
444 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
447 normalised
= self
.samdb
.dsdb_normalise_attributes(
448 self
.samdb_schema
, attrname
, [val
])
449 if len(normalised
) != 1:
450 self
.report("Unable to normalise value '%s'" % val
)
451 mod_list
.append((val
, ''))
452 elif (normalised
[0] != val
):
453 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
454 mod_list
.append((val
, normalised
[0]))
455 if not self
.confirm_all('Fix normalisation for %s from %s?' % (attrname
, dn
), 'fix_all_normalisation'):
456 self
.report("Not fixing attribute %s" % attrname
)
461 for i
in range(0, len(mod_list
)):
462 (val
, nval
) = mod_list
[i
]
463 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
465 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
468 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
469 "Failed to normalise attribute %s" % attrname
,
471 self
.report("Normalised attribute %s" % attrname
)
473 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
474 '''fix attribute normalisation errors'''
475 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
476 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
477 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
478 if list(normalised
) == values
:
480 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
481 self
.report("Not fixing attribute '%s'" % attrname
)
486 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
488 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
489 "Failed to normalise attribute %s" % attrname
,
491 self
.report("Normalised attribute %s" % attrname
)
493 def err_duplicate_values(self
, dn
, attrname
, dup_values
, values
):
494 '''fix attribute normalisation errors'''
495 self
.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname
, dn
))
496 self
.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values
), ','.join(values
)))
497 if not self
.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_duplicates'):
498 self
.report("Not fixing attribute '%s'" % attrname
)
503 m
[attrname
] = ldb
.MessageElement(values
, ldb
.FLAG_MOD_REPLACE
, attrname
)
505 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
506 "Failed to remove duplicate value on attribute %s" % attrname
,
508 self
.report("Removed duplicate value on attribute %s" % attrname
)
510 def is_deleted_objects_dn(self
, dsdb_dn
):
511 '''see if a dsdb_Dn is the special Deleted Objects DN'''
512 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
514 def err_missing_objectclass(self
, dn
):
515 """handle object without objectclass"""
516 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
)))
517 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'):
518 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
520 if self
.do_delete(dn
, ["relax:0"],
521 "Failed to remove DN %s" % dn
):
522 self
.report("Removed DN %s" % dn
)
524 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, remove_plausible
=False):
525 """handle a DN pointing to a deleted object"""
526 if not remove_plausible
:
527 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
528 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
529 if not self
.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
530 self
.report("Not removing")
533 self
.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
534 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
535 if not self
.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
536 self
.report("Not removing")
541 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
542 if self
.do_modify(m
, ["show_recycled:1",
543 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
544 "Failed to remove deleted DN attribute %s" % attrname
):
545 self
.report("Removed deleted DN on attribute %s" % attrname
)
547 def err_missing_target_dn_or_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
548 """handle a missing target DN (if specified, GUID form can't be found,
549 and otherwise DN string form can't be found)"""
550 # check if its a backlink
551 linkID
, _
= self
.get_attr_linkID_and_reverse_name(attrname
)
552 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
554 linkID
, reverse_link_name \
555 = self
.get_attr_linkID_and_reverse_name(attrname
)
556 if reverse_link_name
is not None:
557 self
.report("WARNING: no target object found for GUID "
558 "component for one-way forward link "
560 "%s - %s" % (attrname
, dn
, val
))
561 self
.report("Not removing dangling forward link")
564 nc_root
= self
.samdb
.get_nc_root(dn
)
565 target_nc_root
= self
.samdb
.get_nc_root(dsdb_dn
.dn
)
566 if nc_root
!= target_nc_root
:
567 # We don't bump the error count as Samba produces these
568 # in normal operation
569 self
.report("WARNING: no target object found for GUID "
570 "component for cross-partition link "
572 "%s - %s" % (attrname
, dn
, val
))
573 self
.report("Not removing dangling one-way "
574 "cross-partition link "
575 "(we might be mid-replication)")
578 # Due to our link handling one-way links pointing to
579 # missing objects are plausible.
581 # We don't bump the error count as Samba produces these
582 # in normal operation
583 self
.report("WARNING: no target object found for GUID "
584 "component for DN value %s in object "
585 "%s - %s" % (attrname
, dn
, val
))
586 self
.err_deleted_dn(dn
, attrname
, val
,
587 dsdb_dn
, dsdb_dn
, True)
590 # We bump the error count here, as we should have deleted this
591 self
.report("ERROR: no target object found for GUID "
592 "component for link %s in object "
593 "%s - %s" % (attrname
, dn
, val
))
594 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
, False)
597 def err_missing_dn_GUID_component(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
598 """handle a missing GUID extended DN component"""
599 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
600 controls
= ["extended_dn:1:1", "show_recycled:1"]
602 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
603 attrs
=[], controls
=controls
)
604 except ldb
.LdbError
as e7
:
605 (enum
, estr
) = e7
.args
606 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
607 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
609 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
612 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
613 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
615 dsdb_dn
.dn
= res
[0].dn
617 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
618 self
.report("Not fixing %s" % errstr
)
622 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
623 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
625 if self
.do_modify(m
, ["show_recycled:1"],
626 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
627 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
629 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
630 """handle an incorrect binary DN component"""
631 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
632 controls
= ["extended_dn:1:1", "show_recycled:1"]
634 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
635 self
.report("Not fixing %s" % errstr
)
639 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
640 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
642 if self
.do_modify(m
, ["show_recycled:1"],
643 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
644 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
646 def err_dn_string_component_old(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
647 """handle a DN string being incorrect"""
648 self
.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
649 dsdb_dn
.dn
= correct_dn
651 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
652 'fix_all_old_dn_string_component_mismatch'):
653 self
.report("Not fixing old string component")
657 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
658 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
659 if self
.do_modify(m
, ["show_recycled:1",
660 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
],
661 "Failed to fix old DN string on attribute %s" % (attrname
)):
662 self
.report("Fixed old DN string on attribute %s" % (attrname
))
664 def err_dn_component_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, mismatch_type
):
665 """handle a DN string being incorrect"""
666 self
.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type
, attrname
, dn
, val
))
667 dsdb_dn
.dn
= correct_dn
669 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
670 'fix_all_%s_dn_component_mismatch' % mismatch_type
):
671 self
.report("Not fixing %s component mismatch" % mismatch_type
)
675 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
676 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
677 if self
.do_modify(m
, ["show_recycled:1"],
678 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type
, attrname
)):
679 self
.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type
, attrname
))
681 def err_unknown_attribute(self
, obj
, attrname
):
682 '''handle an unknown attribute error'''
683 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
684 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
685 self
.report("Not removing %s" % attrname
)
689 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
690 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
691 "Failed to remove unknown attribute %s" % attrname
):
692 self
.report("Removed unknown attribute %s" % (attrname
))
694 def err_undead_linked_attribute(self
, obj
, attrname
, val
):
695 '''handle a link that should not be there on a deleted object'''
696 self
.report("ERROR: linked attribute '%s' to '%s' is present on "
697 "deleted object %s" % (attrname
, val
, obj
.dn
))
698 if not self
.confirm_all('Remove linked attribute %s' % attrname
, 'fix_undead_linked_attributes'):
699 self
.report("Not removing linked attribute %s" % attrname
)
703 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
705 if self
.do_modify(m
, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
706 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
707 "Failed to delete forward link %s" % attrname
):
708 self
.report("Fixed undead forward link %s" % (attrname
))
710 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
711 '''handle a missing backlink value'''
712 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
713 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
714 self
.report("Not fixing missing backlink %s" % backlink_name
)
718 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, backlink_name
)
719 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
720 "Failed to fix missing backlink %s" % backlink_name
):
721 self
.report("Fixed missing backlink %s" % (backlink_name
))
723 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
724 '''handle a incorrect RMD_FLAGS value'''
725 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
726 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()))
727 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
728 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
732 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
733 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
734 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
735 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
737 def err_orphaned_backlink(self
, obj_dn
, backlink_attr
, backlink_val
,
738 target_dn
, forward_attr
, forward_syntax
,
739 check_duplicates
=True):
740 '''handle a orphaned backlink value'''
741 if check_duplicates
is True and self
.has_duplicate_links(target_dn
, forward_attr
, forward_syntax
):
742 self
.report("WARNING: Keep orphaned backlink attribute " +
743 "'%s' in '%s' for link '%s' in '%s'" % (
744 backlink_attr
, obj_dn
, forward_attr
, target_dn
))
746 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr
, obj_dn
, forward_attr
, target_dn
))
747 if not self
.confirm_all('Remove orphaned backlink %s' % backlink_attr
, 'fix_all_orphaned_backlinks'):
748 self
.report("Not removing orphaned backlink %s" % backlink_attr
)
752 m
['value'] = ldb
.MessageElement(backlink_val
, ldb
.FLAG_MOD_DELETE
, backlink_attr
)
753 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
754 "Failed to fix orphaned backlink %s" % backlink_attr
):
755 self
.report("Fixed orphaned backlink %s" % (backlink_attr
))
757 def err_recover_forward_links(self
, obj
, forward_attr
, forward_vals
):
758 '''handle a duplicate links value'''
760 self
.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr
, obj
.dn
))
762 if not self
.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr
, 'recover_all_forward_links'):
763 self
.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
764 forward_attr
, obj
.dn
))
768 m
['value'] = ldb
.MessageElement(forward_vals
, ldb
.FLAG_MOD_REPLACE
, forward_attr
)
769 if self
.do_modify(m
, ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS
],
770 "Failed to fix duplicate links in attribute '%s'" % forward_attr
):
771 self
.report("Fixed duplicate links in attribute '%s'" % (forward_attr
))
772 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
773 assert duplicate_cache_key
in self
.duplicate_link_cache
774 self
.duplicate_link_cache
[duplicate_cache_key
] = False
776 def err_no_fsmoRoleOwner(self
, obj
):
777 '''handle a missing fSMORoleOwner'''
778 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
779 res
= self
.samdb
.search("",
780 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
782 serviceName
= res
[0]["dsServiceName"][0]
783 if not self
.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
784 self
.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
788 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
789 if self
.do_modify(m
, [],
790 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
791 self
.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
793 def err_missing_parent(self
, obj
):
794 '''handle a missing parent'''
795 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
796 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
797 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
800 keep_transaction
= False
801 self
.samdb
.transaction_start()
803 nc_root
= self
.samdb
.get_nc_root(obj
.dn
)
804 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
805 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
806 new_dn
.remove_base_components(len(new_dn
) - 1)
807 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
808 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
809 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
813 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
815 if self
.do_modify(m
, [],
816 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
817 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
818 keep_transaction
= True
820 self
.samdb
.transaction_cancel()
824 self
.samdb
.transaction_commit()
826 self
.samdb
.transaction_cancel()
828 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
):
829 '''handle a wrong dn'''
831 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
832 new_rdn
.remove_base_components(len(new_rdn
) - 1)
833 new_parent
= new_dn
.parent()
836 if rdn_val
!= name_val
:
837 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
838 attributes
+= "name=%r" % (name_val
)
840 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
841 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
842 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
845 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, ["show_recycled:1", "relax:0"],
846 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
847 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
849 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
850 '''handle a wrong instanceType'''
851 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
852 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
853 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
858 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
859 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
860 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
861 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
863 def err_short_userParameters(self
, obj
, attrname
, value
):
864 # This is a truncated userParameters due to a pre 4.1 replication bug
865 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
)))
867 def err_base64_userParameters(self
, obj
, attrname
, value
):
868 '''handle a wrong userParameters'''
869 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
870 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
871 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
876 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
877 if self
.do_modify(m
, [],
878 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
879 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
881 def err_utf8_userParameters(self
, obj
, attrname
, value
):
882 '''handle a wrong userParameters'''
883 self
.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj
.dn
))
884 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
885 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
890 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
891 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
892 if self
.do_modify(m
, [],
893 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
894 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
896 def err_doubled_userParameters(self
, obj
, attrname
, value
):
897 '''handle a wrong userParameters'''
898 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
899 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
900 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
905 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
906 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
907 if self
.do_modify(m
, [],
908 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
909 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
911 def err_odd_userParameters(self
, obj
, attrname
):
912 # This is a truncated userParameters due to a pre 4.1 replication bug
913 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
)))
915 def find_revealed_link(self
, dn
, attrname
, guid
):
916 '''return a revealed link in an object'''
917 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
918 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
919 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
920 for val
in res
[0][attrname
]:
921 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
922 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
927 def check_duplicate_links(self
, obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
):
928 '''check a linked values for duplicate forward links'''
931 duplicate_dict
= dict()
934 # Only forward links can have this problem
935 if forward_linkID
& 1:
936 # If we got the reverse, skip it
937 return (error_count
, duplicate_dict
, unique_dict
)
939 if backlink_attr
is None:
940 return (error_count
, duplicate_dict
, unique_dict
)
942 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
943 if duplicate_cache_key
not in self
.duplicate_link_cache
:
944 self
.duplicate_link_cache
[duplicate_cache_key
] = False
946 for val
in obj
[forward_attr
]:
947 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), forward_syntax
)
949 # all DNs should have a GUID component
950 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
953 guidstr
= str(misc
.GUID(guid
))
954 keystr
= guidstr
+ dsdb_dn
.prefix
955 if keystr
not in unique_dict
:
956 unique_dict
[keystr
] = dsdb_dn
959 if keystr
not in duplicate_dict
:
960 duplicate_dict
[keystr
] = dict()
961 duplicate_dict
[keystr
]["keep"] = None
962 duplicate_dict
[keystr
]["delete"] = list()
964 # Now check for the highest RMD_VERSION
965 v1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_VERSION"))
966 v2
= int(dsdb_dn
.dn
.get_extended_component("RMD_VERSION"))
968 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
969 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
972 duplicate_dict
[keystr
]["keep"] = dsdb_dn
973 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
974 unique_dict
[keystr
] = dsdb_dn
976 # Fallback to the highest RMD_LOCAL_USN
977 u1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_LOCAL_USN"))
978 u2
= int(dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN"))
980 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
981 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
983 duplicate_dict
[keystr
]["keep"] = dsdb_dn
984 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
985 unique_dict
[keystr
] = dsdb_dn
988 self
.duplicate_link_cache
[duplicate_cache_key
] = True
990 return (error_count
, duplicate_dict
, unique_dict
)
992 def has_duplicate_links(self
, dn
, forward_attr
, forward_syntax
):
993 '''check a linked values for duplicate forward links'''
996 duplicate_cache_key
= "%s:%s" % (str(dn
), forward_attr
)
997 if duplicate_cache_key
in self
.duplicate_link_cache
:
998 return self
.duplicate_link_cache
[duplicate_cache_key
]
1000 forward_linkID
, backlink_attr
= self
.get_attr_linkID_and_reverse_name(forward_attr
)
1002 attrs
= [forward_attr
]
1003 controls
= ["extended_dn:1:1", "reveal_internals:0"]
1005 # check its the right GUID
1007 res
= self
.samdb
.search(base
=str(dn
), scope
=ldb
.SCOPE_BASE
,
1008 attrs
=attrs
, controls
=controls
)
1009 except ldb
.LdbError
as e8
:
1010 (enum
, estr
) = e8
.args
1011 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1017 error_count
, duplicate_dict
, unique_dict
= \
1018 self
.check_duplicate_links(obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
)
1020 if duplicate_cache_key
in self
.duplicate_link_cache
:
1021 return self
.duplicate_link_cache
[duplicate_cache_key
]
1025 def find_missing_forward_links_from_backlinks(self
, obj
,
1029 forward_unique_dict
):
1030 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1031 missing_forward_links
= []
1034 if backlink_attr
is None:
1035 return (missing_forward_links
, error_count
)
1037 if forward_syntax
!= ldb
.SYNTAX_DN
:
1038 self
.report("Not checking for missing forward links for syntax: %s",
1040 return (missing_forward_links
, error_count
)
1042 if "sortedLinks" in self
.compatibleFeatures
:
1043 self
.report("Not checking for missing forward links because the db " +
1044 "has the sortedLinks feature")
1045 return (missing_forward_links
, error_count
)
1048 obj_guid
= obj
['objectGUID'][0]
1049 obj_guid_str
= str(ndr_unpack(misc
.GUID
, obj_guid
))
1050 filter = "(%s=<GUID=%s>)" % (backlink_attr
, obj_guid_str
)
1052 res
= self
.samdb
.search(expression
=filter,
1053 scope
=ldb
.SCOPE_SUBTREE
, attrs
=["objectGUID"],
1054 controls
=["extended_dn:1:1",
1055 "search_options:1:2",
1056 "paged_results:1:1000"])
1057 except ldb
.LdbError
as e9
:
1058 (enum
, estr
) = e9
.args
1062 target_dn
= dsdb_Dn(self
.samdb
, r
.dn
.extended_str(), forward_syntax
)
1064 guid
= target_dn
.dn
.get_extended_component("GUID")
1065 guidstr
= str(misc
.GUID(guid
))
1066 if guidstr
in forward_unique_dict
:
1069 # A valid forward link looks like this:
1071 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1072 # <RMD_ADDTIME=131607546230000000>;
1073 # <RMD_CHANGETIME=131607546230000000>;
1075 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1076 # <RMD_LOCAL_USN=3765>;
1077 # <RMD_ORIGINATING_USN=3765>;
1079 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1080 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1082 # Note that versions older than Samba 4.8 create
1083 # links with RMD_VERSION=0.
1085 # Try to get the local_usn and time from objectClass
1086 # if possible and fallback to any other one.
1087 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1088 obj
['replPropertyMetadata'][0])
1089 for o
in repl
.ctr
.array
:
1090 local_usn
= o
.local_usn
1091 t
= o
.originating_change_time
1092 if o
.attid
== drsuapi
.DRSUAPI_ATTID_objectClass
:
1095 # We use a magic invocationID for restoring missing
1096 # forward links to recover from bug #13228.
1097 # This should allow some more future magic to fix the
1100 # It also means it looses the conflict resolution
1101 # against almost every real invocation, if the
1102 # version is also 0.
1103 originating_invocid
= misc
.GUID("ffffffff-4700-4700-4700-000000b13228")
1109 rmd_invocid
= originating_invocid
1110 rmd_originating_usn
= originating_usn
1111 rmd_local_usn
= local_usn
1114 target_dn
.dn
.set_extended_component("RMD_ADDTIME", str(rmd_addtime
))
1115 target_dn
.dn
.set_extended_component("RMD_CHANGETIME", str(rmd_changetime
))
1116 target_dn
.dn
.set_extended_component("RMD_FLAGS", str(rmd_flags
))
1117 target_dn
.dn
.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid
))
1118 target_dn
.dn
.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn
))
1119 target_dn
.dn
.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn
))
1120 target_dn
.dn
.set_extended_component("RMD_VERSION", str(rmd_version
))
1123 missing_forward_links
.append(target_dn
)
1125 return (missing_forward_links
, error_count
)
1127 def check_dn(self
, obj
, attrname
, syntax_oid
):
1128 '''check a DN attribute for correctness'''
1130 obj_guid
= obj
['objectGUID'][0]
1132 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
1133 if reverse_link_name
is not None:
1134 reverse_syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(reverse_link_name
)
1136 reverse_syntax_oid
= None
1138 error_count
, duplicate_dict
, unique_dict
= \
1139 self
.check_duplicate_links(obj
, attrname
, syntax_oid
, linkID
, reverse_link_name
)
1141 if len(duplicate_dict
) != 0:
1143 missing_forward_links
, missing_error_count
= \
1144 self
.find_missing_forward_links_from_backlinks(obj
,
1145 attrname
, syntax_oid
,
1148 error_count
+= missing_error_count
1150 forward_links
= [dn
for dn
in unique_dict
.values()]
1152 if missing_error_count
!= 0:
1153 self
.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1156 self
.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname
, obj
.dn
))
1157 for m
in missing_forward_links
:
1158 self
.report("Missing link '%s'" % (m
))
1159 if not self
.confirm_all("Schedule readding missing forward link for attribute %s" % attrname
,
1160 'fix_all_missing_forward_links'):
1161 self
.err_orphaned_backlink(m
.dn
, reverse_link_name
,
1162 obj
.dn
.extended_str(), obj
.dn
,
1163 attrname
, syntax_oid
,
1164 check_duplicates
=False)
1166 forward_links
+= [m
]
1167 for keystr
in duplicate_dict
.keys():
1168 d
= duplicate_dict
[keystr
]
1169 for dd
in d
["delete"]:
1170 self
.report("Duplicate link '%s'" % dd
)
1171 self
.report("Correct link '%s'" % d
["keep"])
1173 # We now construct the sorted dn values.
1174 # They're sorted by the objectGUID of the target
1175 # See dsdb_Dn.__cmp__()
1176 vals
= [str(dn
) for dn
in sorted(forward_links
)]
1177 self
.err_recover_forward_links(obj
, attrname
, vals
)
1178 # We should continue with the fixed values
1179 obj
[attrname
] = ldb
.MessageElement(vals
, 0, attrname
)
1181 for val
in obj
[attrname
]:
1182 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
1184 # all DNs should have a GUID component
1185 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1188 self
.err_missing_dn_GUID_component(obj
.dn
, attrname
, val
, dsdb_dn
,
1192 guidstr
= str(misc
.GUID(guid
))
1193 attrs
= ['isDeleted', 'replPropertyMetaData']
1195 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
1196 fixing_msDS_HasInstantiatedNCs
= True
1197 attrs
.append("instanceType")
1199 fixing_msDS_HasInstantiatedNCs
= False
1201 if reverse_link_name
is not None:
1202 attrs
.append(reverse_link_name
)
1204 # check its the right GUID
1206 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
1207 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1",
1208 "reveal_internals:0"
1210 except ldb
.LdbError
as e3
:
1211 (enum
, estr
) = e3
.args
1212 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1215 # We don't always want to
1216 error_count
+= self
.err_missing_target_dn_or_GUID(obj
.dn
,
1222 if fixing_msDS_HasInstantiatedNCs
:
1223 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
1224 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
1226 if str(dsdb_dn
) != val
:
1228 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
1231 # now we have two cases - the source object might or might not be deleted
1232 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
1233 target_is_deleted
= 'isDeleted' in res
[0] and res
[0]['isDeleted'][0].upper() == 'TRUE'
1235 if is_deleted
and obj
.dn
not in self
.deleted_objects_containers
and linkID
:
1236 # A fully deleted object should not have any linked
1237 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1238 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1240 self
.err_undead_linked_attribute(obj
, attrname
, val
)
1243 elif target_is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
) and linkID
:
1244 # the target DN is not allowed to be deleted, unless the target DN is the
1245 # special Deleted Objects container
1247 local_usn
= dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN")
1249 if 'replPropertyMetaData' in res
[0]:
1250 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1251 res
[0]['replPropertyMetadata'][0])
1253 for o
in repl
.ctr
.array
:
1254 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1255 deleted_usn
= o
.local_usn
1256 if deleted_usn
>= int(local_usn
):
1257 # If the object was deleted after the link
1258 # was last modified then, clean it up here
1263 self
.err_deleted_dn(obj
.dn
, attrname
,
1264 val
, dsdb_dn
, res
[0].dn
, True)
1267 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
, False)
1270 # We should not check for incorrect
1271 # components on deleted links, as these are allowed to
1272 # go stale (we just need the GUID, not the name)
1273 rmd_blob
= dsdb_dn
.dn
.get_extended_component("RMD_FLAGS")
1275 if rmd_blob
is not None:
1276 rmd_flags
= int(rmd_blob
)
1278 # assert the DN matches in string form, where a reverse
1279 # link exists, otherwise (below) offer to fix it as a non-error.
1280 # The string form is essentially only kept for forensics,
1281 # as we always re-resolve by GUID in normal operations.
1282 if not rmd_flags
& 1 and reverse_link_name
is not None:
1283 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1285 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1286 res
[0].dn
, "string")
1289 if res
[0].dn
.get_extended_component("GUID") != dsdb_dn
.dn
.get_extended_component("GUID"):
1291 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1295 if res
[0].dn
.get_extended_component("SID") != dsdb_dn
.dn
.get_extended_component("SID"):
1297 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1301 # Only for non-links, not even forward-only links
1302 # (otherwise this breaks repl_meta_data):
1304 # Now we have checked the GUID and SID, offer to fix old
1305 # DN strings as a non-error (DNs, not links so no
1306 # backlink). Samba does not maintain this string
1307 # otherwise, so we don't increment error_count.
1308 if reverse_link_name
is None:
1309 if linkID
== 0 and str(res
[0].dn
) != str(dsdb_dn
.dn
):
1310 # Pass in the old/bad DN without the <GUID=...> part,
1311 # otherwise the LDB code will correct it on the way through
1312 # (Note: we still want to preserve the DSDB DN prefix in the
1313 # case of binary DNs)
1314 bad_dn
= dsdb_dn
.prefix
+ dsdb_dn
.dn
.get_linearized()
1315 self
.err_dn_string_component_old(obj
.dn
, attrname
, bad_dn
,
1319 # check the reverse_link is correct if there should be one
1321 if reverse_link_name
in res
[0]:
1322 for v
in res
[0][reverse_link_name
]:
1323 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1324 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1325 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1327 if v_blob
is not None:
1328 v_rmd_flags
= int(v_blob
)
1331 if v_guid
== obj_guid
:
1334 if match_count
!= 1:
1335 if syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
or reverse_syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
:
1337 # Forward binary multi-valued linked attribute
1339 for w
in obj
[attrname
]:
1340 w_guid
= dsdb_Dn(self
.samdb
, w
.decode('utf8')).dn
.get_extended_component("GUID")
1344 if match_count
== forward_count
:
1347 for v
in obj
[attrname
]:
1348 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1349 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1350 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1352 if v_blob
is not None:
1353 v_rmd_flags
= int(v_blob
)
1359 if match_count
== expected_count
:
1362 diff_count
= expected_count
- match_count
1365 # If there's a backward link on binary multi-valued linked attribute,
1366 # let the check on the forward link remedy the value.
1367 # UNLESS, there is no forward link detected.
1368 if match_count
== 0:
1370 self
.err_orphaned_backlink(obj
.dn
, attrname
,
1375 # Only warn here and let the forward link logic fix it.
1376 self
.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1377 attrname
, expected_count
, str(obj
.dn
),
1378 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1381 assert not target_is_deleted
1383 self
.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1384 attrname
, expected_count
, str(obj
.dn
),
1385 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1387 # Loop until the difference between the forward and
1388 # the backward links is resolved.
1389 while diff_count
!= 0:
1392 if match_count
> 0 or diff_count
> 1:
1393 # TODO no method to fix these right now
1394 self
.report("ERROR: Can't fix missing "
1395 "multi-valued backlinks on %s" % str(dsdb_dn
.dn
))
1397 self
.err_missing_backlink(obj
, attrname
,
1398 obj
.dn
.extended_str(),
1403 self
.err_orphaned_backlink(res
[0].dn
, reverse_link_name
,
1404 obj
.dn
.extended_str(), obj
.dn
,
1405 attrname
, syntax_oid
)
1410 def get_originating_time(self
, val
, attid
):
1411 '''Read metadata properties and return the originating time for
1412 a given attributeId.
1414 :return: the originating time or 0 if not found
1417 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1420 for o
in repl
.ctr
.array
:
1421 if o
.attid
== attid
:
1422 return o
.originating_change_time
1426 def process_metadata(self
, dn
, val
):
1427 '''Read metadata properties and list attributes in it.
1428 raises KeyError if the attid is unknown.'''
1431 wrong_attids
= set()
1433 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1435 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1438 for o
in repl
.ctr
.array
:
1439 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1440 set_att
.add(att
.lower())
1441 list_attid
.append(o
.attid
)
1442 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
,
1443 is_schema_nc
=in_schema_nc
)
1444 if correct_attid
!= o
.attid
:
1445 wrong_attids
.add(o
.attid
)
1447 return (set_att
, list_attid
, wrong_attids
)
1449 def fix_metadata(self
, obj
, attr
):
1450 '''re-write replPropertyMetaData elements for a single attribute for a
1451 object. This is used to fix missing replPropertyMetaData elements'''
1452 guid_str
= str(ndr_unpack(misc
.GUID
, obj
['objectGUID'][0]))
1453 dn
= ldb
.Dn(self
.samdb
, "<GUID=%s>" % guid_str
)
1454 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attr
],
1455 controls
=["search_options:1:2",
1458 nmsg
= ldb
.Message()
1460 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
1461 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
1462 "Failed to fix metadata for attribute %s" % attr
):
1463 self
.report("Fixed metadata for attribute %s" % attr
)
1465 def ace_get_effective_inherited_type(self
, ace
):
1466 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
1470 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
1472 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
1474 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
1476 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
1482 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
1485 return str(ace
.object.inherited_type
)
1487 def lookup_class_schemaIDGUID(self
, cls
):
1488 if cls
in self
.class_schemaIDGUID
:
1489 return self
.class_schemaIDGUID
[cls
]
1491 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1492 res
= self
.samdb
.search(base
=self
.schema_dn
,
1494 attrs
=["schemaIDGUID"])
1495 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
1497 self
.class_schemaIDGUID
[cls
] = t
1500 def process_sd(self
, dn
, obj
):
1501 sd_attr
= "nTSecurityDescriptor"
1502 sd_val
= obj
[sd_attr
]
1504 sd
= ndr_unpack(security
.descriptor
, sd_val
[0])
1506 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
1508 # we don't fix deleted objects
1511 sd_clean
= security
.descriptor()
1512 sd_clean
.owner_sid
= sd
.owner_sid
1513 sd_clean
.group_sid
= sd
.group_sid
1514 sd_clean
.type = sd
.type
1515 sd_clean
.revision
= sd
.revision
1518 last_inherited_type
= None
1521 if sd
.sacl
is not None:
1523 for i
in range(0, len(aces
)):
1526 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1527 sd_clean
.sacl_add(ace
)
1530 t
= self
.ace_get_effective_inherited_type(ace
)
1534 if last_inherited_type
is not None:
1535 if t
!= last_inherited_type
:
1536 # if it inherited from more than
1537 # one type it's very likely to be broken
1539 # If not the recalculation will calculate
1544 last_inherited_type
= t
1547 if sd
.dacl
is not None:
1549 for i
in range(0, len(aces
)):
1552 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1553 sd_clean
.dacl_add(ace
)
1556 t
= self
.ace_get_effective_inherited_type(ace
)
1560 if last_inherited_type
is not None:
1561 if t
!= last_inherited_type
:
1562 # if it inherited from more than
1563 # one type it's very likely to be broken
1565 # If not the recalculation will calculate
1570 last_inherited_type
= t
1573 return (sd_clean
, sd
)
1575 if last_inherited_type
is None:
1581 cls
= obj
["objectClass"][-1]
1582 except KeyError as e
:
1586 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1587 attrs
=["isDeleted", "objectClass"],
1588 controls
=["show_recycled:1"])
1590 is_deleted
= 'isDeleted' in o
and o
['isDeleted'][0].upper() == 'TRUE'
1592 # we don't fix deleted objects
1594 cls
= o
["objectClass"][-1]
1596 t
= self
.lookup_class_schemaIDGUID(cls
)
1598 if t
!= last_inherited_type
:
1600 return (sd_clean
, sd
)
1605 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
1606 '''re-write the SD due to incorrect inherited ACEs'''
1607 sd_attr
= "nTSecurityDescriptor"
1608 sd_val
= ndr_pack(sd
)
1609 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1611 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
1612 self
.report('Not fixing %s on %s\n' % (sd_attr
, dn
))
1615 nmsg
= ldb
.Message()
1617 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1618 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1619 "Failed to fix attribute %s" % sd_attr
):
1620 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1622 def err_wrong_default_sd(self
, dn
, sd
, sd_old
, diff
):
1623 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1624 sd_attr
= "nTSecurityDescriptor"
1625 sd_val
= ndr_pack(sd
)
1626 sd_old_val
= ndr_pack(sd_old
)
1627 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1628 if sd
.owner_sid
is not None:
1629 sd_flags |
= security
.SECINFO_OWNER
1630 if sd
.group_sid
is not None:
1631 sd_flags |
= security
.SECINFO_GROUP
1633 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
1634 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
1639 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1640 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
1641 "Failed to reset attribute %s" % sd_attr
):
1642 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1644 def err_missing_sd_owner(self
, dn
, sd
):
1645 '''re-write the SD due to a missing owner or group'''
1646 sd_attr
= "nTSecurityDescriptor"
1647 sd_val
= ndr_pack(sd
)
1648 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
1650 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
1651 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
1654 nmsg
= ldb
.Message()
1656 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1658 # By setting the session_info to admin_session_info and
1659 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1660 # flags we cause the descriptor module to set the correct
1661 # owner and group on the SD, replacing the None/NULL values
1662 # for owner_sid and group_sid currently present.
1664 # The admin_session_info matches that used in provision, and
1665 # is the best guess we can make for an existing object that
1666 # hasn't had something specifically set.
1668 # This is important for the dns related naming contexts.
1669 self
.samdb
.set_session_info(self
.admin_session_info
)
1670 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1671 "Failed to fix metadata for attribute %s" % sd_attr
):
1672 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1673 self
.samdb
.set_session_info(self
.system_session_info
)
1675 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
1676 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1681 # Search for a zero invocationID
1682 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1686 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1687 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1688 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1689 % (dn
, o
.attid
, o
.version
,
1690 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
1691 self
.samdb
.get_invocation_id()))
1695 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
1696 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1699 now
= samba
.unix2nttime(int(time
.time()))
1702 # Search for a zero invocationID
1703 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1707 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
1708 o
.version
= o
.version
+ 1
1709 o
.originating_change_time
= now
1710 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
1711 o
.originating_usn
= seq
1715 replBlob
= ndr_pack(repl
)
1719 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1720 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1721 self
.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr
, dn
))
1724 nmsg
= ldb
.Message()
1726 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1727 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1728 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1729 "Failed to fix attribute %s" % attr
):
1730 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1732 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
1733 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1737 # Search for an invalid attid
1739 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1741 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
1744 def err_replmetadata_incorrect_attid(self
, dn
, attr
, repl_meta_data
, wrong_attids
):
1745 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1750 remove_attid
= set()
1753 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1756 # Sort the array, except for the last element. This strange
1757 # construction, creating a new list, due to bugs in samba's
1758 # array handling in IDL generated objects.
1759 ctr
.array
= sorted(ctr
.array
[:], key
=lambda o
: o
.attid
)
1760 # Now walk it in reverse, so we see the low (and so incorrect,
1761 # the correct values are above 0x80000000) values first and
1762 # remove the 'second' value we see.
1763 for o
in reversed(ctr
.array
):
1764 print("%s: 0x%08x" % (dn
, o
.attid
))
1765 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1766 if att
.lower() in set_att
:
1767 self
.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att
, attr
, dn
))
1768 if not self
.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1769 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
),
1770 'fix_replmetadata_duplicate_attid'):
1771 self
.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1772 % (o
.attid
, att
, attr
, dn
))
1775 remove_attid
.add(o
.attid
)
1776 # We want to set the metadata for the most recent
1777 # update to have been applied locally, that is the metadata
1778 # matching the (eg string) value in the attribute
1779 if o
.local_usn
> hash_att
[att
].local_usn
:
1780 # This is always what we would have sent over DRS,
1781 # because the DRS server will have sent the
1782 # msDS-IntID, but with the values from both
1783 # attribute entries.
1784 hash_att
[att
].version
= o
.version
1785 hash_att
[att
].originating_change_time
= o
.originating_change_time
1786 hash_att
[att
].originating_invocation_id
= o
.originating_invocation_id
1787 hash_att
[att
].originating_usn
= o
.originating_usn
1788 hash_att
[att
].local_usn
= o
.local_usn
1790 # Do not re-add the value to the set or overwrite the hash value
1794 set_att
.add(att
.lower())
1796 # Generate a real list we can sort on properly
1797 new_list
= [o
for o
in ctr
.array
if o
.attid
not in remove_attid
]
1799 if (len(wrong_attids
) > 0):
1801 if o
.attid
in wrong_attids
:
1802 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1803 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
, is_schema_nc
=in_schema_nc
)
1804 self
.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr
, dn
))
1805 if not self
.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1806 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
), 'fix_replmetadata_wrong_attid'):
1807 self
.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1808 % (o
.attid
, correct_attid
, att
, attr
, dn
))
1811 o
.attid
= correct_attid
1813 # Sort the array, (we changed the value so must re-sort)
1814 new_list
[:] = sorted(new_list
[:], key
=lambda o
: o
.attid
)
1816 # If we did not already need to fix it, then ask about sorting
1818 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
1819 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
1820 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
1821 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
1824 # The actual sort done is done at the top of the function
1826 ctr
.count
= len(new_list
)
1827 ctr
.array
= new_list
1828 replBlob
= ndr_pack(repl
)
1830 nmsg
= ldb
.Message()
1832 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1833 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1834 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1835 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1836 "Failed to fix attribute %s" % attr
):
1837 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1839 def is_deleted_deleted_objects(self
, obj
):
1841 if "description" not in obj
:
1842 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
1844 if "showInAdvancedViewOnly" not in obj
or obj
['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1845 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
1847 if "objectCategory" not in obj
:
1848 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
1850 if "isCriticalSystemObject" not in obj
or obj
['isCriticalSystemObject'][0].upper() == 'FALSE':
1851 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
1853 if "isRecycled" in obj
:
1854 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
1856 if "isDeleted" in obj
and obj
['isDeleted'][0].upper() == 'FALSE':
1857 self
.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj
.dn
)
1859 if "objectClass" not in obj
or (len(obj
['objectClass']) != 2 or
1860 obj
['objectClass'][0] != 'top' or
1861 obj
['objectClass'][1] != 'container'):
1862 self
.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj
.dn
)
1864 if "systemFlags" not in obj
or obj
['systemFlags'][0] != '-1946157056':
1865 self
.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj
.dn
)
1869 def err_deleted_deleted_objects(self
, obj
):
1870 nmsg
= ldb
.Message()
1871 nmsg
.dn
= dn
= obj
.dn
1873 if "description" not in obj
:
1874 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
1875 if "showInAdvancedViewOnly" not in obj
:
1876 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
1877 if "objectCategory" not in obj
:
1878 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
1879 if "isCriticalSystemObject" not in obj
:
1880 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
1881 if "isRecycled" in obj
:
1882 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
1884 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
1885 nmsg
["systemFlags"] = ldb
.MessageElement("-1946157056", ldb
.FLAG_MOD_REPLACE
, "systemFlags")
1886 nmsg
["objectClass"] = ldb
.MessageElement(["top", "container"], ldb
.FLAG_MOD_REPLACE
, "objectClass")
1888 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1889 % (dn
), 'fix_deleted_deleted_objects'):
1890 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
1893 if self
.do_modify(nmsg
, ["relax:0"],
1894 "Failed to fix Deleted Objects container %s" % dn
):
1895 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
1897 def err_replica_locations(self
, obj
, cross_ref
, attr
):
1898 nmsg
= ldb
.Message()
1900 target
= self
.samdb
.get_dsServiceName()
1902 if self
.samdb
.am_rodc():
1903 self
.report('Not fixing %s for the RODC' % (attr
, obj
.dn
))
1906 if not self
.confirm_all('Add yourself to the replica locations for %s?'
1907 % (obj
.dn
), 'fix_replica_locations'):
1908 self
.report('Not fixing missing/incorrect attributes on %s\n' % (obj
.dn
))
1911 nmsg
[attr
] = ldb
.MessageElement(target
, ldb
.FLAG_MOD_ADD
, attr
)
1912 if self
.do_modify(nmsg
, [], "Failed to add %s for %s" % (attr
, obj
.dn
)):
1913 self
.report("Fixed %s for %s" % (attr
, obj
.dn
))
1915 def is_fsmo_role(self
, dn
):
1916 if dn
== self
.samdb
.domain_dn
:
1918 if dn
== self
.infrastructure_dn
:
1920 if dn
== self
.naming_dn
:
1922 if dn
== self
.schema_dn
:
1924 if dn
== self
.rid_dn
:
1929 def calculate_instancetype(self
, dn
):
1931 nc_root
= self
.samdb
.get_nc_root(dn
)
1933 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
1935 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
1936 except ldb
.LdbError
as e4
:
1937 (enum
, estr
) = e4
.args
1938 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1941 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
1942 if self
.write_ncs
is not None and str(nc_root
) in [str(x
) for x
in self
.write_ncs
]:
1943 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
1947 def get_wellknown_sd(self
, dn
):
1948 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
1950 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
1951 return ndr_unpack(security
.descriptor
,
1952 descriptor_fn(domain_sid
,
1953 name_map
=self
.name_map
))
1957 def check_object(self
, dn
, attrs
=['*']):
1958 '''check one object'''
1960 self
.report("Checking object %s" % dn
)
1962 # If we modify the pass-by-reference attrs variable, then we get a
1963 # replPropertyMetadata for every object that we check.
1965 if "dn" in map(str.lower
, attrs
):
1966 attrs
.append("name")
1967 if "distinguishedname" in map(str.lower
, attrs
):
1968 attrs
.append("name")
1969 if str(dn
.get_rdn_name()).lower() in map(str.lower
, attrs
):
1970 attrs
.append("name")
1971 if 'name' in map(str.lower
, attrs
):
1972 attrs
.append(dn
.get_rdn_name())
1973 attrs
.append("isDeleted")
1974 attrs
.append("systemFlags")
1975 need_replPropertyMetaData
= False
1977 need_replPropertyMetaData
= True
1980 linkID
, _
= self
.get_attr_linkID_and_reverse_name(a
)
1985 need_replPropertyMetaData
= True
1987 if need_replPropertyMetaData
:
1988 attrs
.append("replPropertyMetaData")
1989 attrs
.append("objectGUID")
1993 sd_flags |
= security
.SECINFO_OWNER
1994 sd_flags |
= security
.SECINFO_GROUP
1995 sd_flags |
= security
.SECINFO_DACL
1996 sd_flags |
= security
.SECINFO_SACL
1998 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
2003 "sd_flags:1:%d" % sd_flags
,
2004 "reveal_internals:0",
2007 except ldb
.LdbError
as e10
:
2008 (enum
, estr
) = e10
.args
2009 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2010 if self
.in_transaction
:
2011 self
.report("ERROR: Object %s disappeared during check" % dn
)
2016 self
.report("ERROR: Object %s failed to load during check" % dn
)
2020 set_attrs_from_md
= set()
2021 set_attrs_seen
= set()
2022 got_repl_property_meta_data
= False
2023 got_objectclass
= False
2025 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
2027 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
2028 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
2030 # We have no deleted objects DN for schema, and we check for this above for the other
2032 deleted_objects_dn
= None
2034 object_rdn_attr
= None
2035 object_rdn_val
= None
2040 for attrname
in obj
:
2041 if attrname
== 'dn' or attrname
== "distinguishedName":
2044 if str(attrname
).lower() == 'objectclass':
2045 got_objectclass
= True
2047 if str(attrname
).lower() == "name":
2048 if len(obj
[attrname
]) != 1:
2050 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2051 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2053 name_val
= obj
[attrname
][0]
2055 if str(attrname
).lower() == str(obj
.dn
.get_rdn_name()).lower():
2056 object_rdn_attr
= attrname
2057 if len(obj
[attrname
]) != 1:
2059 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2060 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2062 object_rdn_val
= str(obj
[attrname
][0])
2064 if str(attrname
).lower() == 'isdeleted':
2065 if str(obj
[attrname
][0]) != "FALSE":
2068 if str(attrname
).lower() == 'systemflags':
2069 systemFlags
= int(obj
[attrname
][0])
2071 if str(attrname
).lower() == 'replpropertymetadata':
2072 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
][0]):
2074 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
][0])
2075 # We don't continue, as we may also have other fixes for this attribute
2076 # based on what other attributes we see.
2079 (set_attrs_from_md
, list_attid_from_md
, wrong_attids
) \
2080 = self
.process_metadata(dn
, obj
[attrname
][0])
2083 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
2086 if len(set_attrs_from_md
) < len(list_attid_from_md
) \
2087 or len(wrong_attids
) > 0 \
2088 or sorted(list_attid_from_md
) != list_attid_from_md
:
2090 self
.err_replmetadata_incorrect_attid(dn
, attrname
, obj
[attrname
][0], wrong_attids
)
2093 # Here we check that the first attid is 0
2095 if list_attid_from_md
[0] != 0:
2097 self
.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2098 (attrname
, str(dn
)))
2100 got_repl_property_meta_data
= True
2103 if str(attrname
).lower() == 'ntsecuritydescriptor':
2104 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
2105 if sd_broken
is not None:
2106 self
.err_wrong_sd(dn
, sd
, sd_broken
)
2110 if sd
.owner_sid
is None or sd
.group_sid
is None:
2111 self
.err_missing_sd_owner(dn
, sd
)
2115 if self
.reset_well_known_acls
:
2117 well_known_sd
= self
.get_wellknown_sd(dn
)
2121 current_sd
= ndr_unpack(security
.descriptor
,
2124 diff
= get_diff_sds(well_known_sd
, current_sd
, security
.dom_sid(self
.samdb
.get_domain_sid()))
2126 self
.err_wrong_default_sd(dn
, well_known_sd
, current_sd
, diff
)
2131 if str(attrname
).lower() == 'objectclass':
2132 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
2133 # Do not consider the attribute incorrect if:
2134 # - The sorted (alphabetically) list is the same, inclding case
2135 # - The first and last elements are the same
2137 # This avoids triggering an error due to
2138 # non-determinism in the sort routine in (at least)
2139 # 4.3 and earlier, and the fact that any AUX classes
2140 # in these attributes are also not sorted when
2141 # imported from Windows (they are just in the reverse
2142 # order of last set)
2143 if sorted(normalised
) != sorted(obj
[attrname
]) \
2144 or normalised
[0] != obj
[attrname
][0] \
2145 or normalised
[-1] != obj
[attrname
][-1]:
2146 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
2150 if str(attrname
).lower() == 'userparameters':
2151 if len(obj
[attrname
][0]) == 1 and obj
[attrname
][0][0] == '\x20':
2153 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
2156 elif obj
[attrname
][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2157 # This is the correct, normal prefix
2160 elif obj
[attrname
][0][:20] == 'IAAgACAAIAAgACAAIAAg':
2161 # this is the typical prefix from a windows migration
2163 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
2166 elif obj
[attrname
][0][1] != '\x00' and obj
[attrname
][0][3] != '\x00' and obj
[attrname
][0][5] != '\x00' and obj
[attrname
][0][7] != '\x00' and obj
[attrname
][0][9] != '\x00':
2167 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2169 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
2172 elif len(obj
[attrname
][0]) % 2 != 0:
2173 # This is a value that isn't even in length
2175 self
.err_odd_userParameters(obj
, attrname
, obj
[attrname
])
2178 elif obj
[attrname
][0][1] == '\x00' and obj
[attrname
][0][2] == '\x00' and obj
[attrname
][0][3] == '\x00' and obj
[attrname
][0][4] != '\x00' and obj
[attrname
][0][5] == '\x00':
2179 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2181 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
2184 if attrname
.lower() == 'attributeid' or attrname
.lower() == 'governsid':
2185 if obj
[attrname
][0] in self
.attribute_or_class_ids
:
2187 self
.report('Error: %s %s on %s already exists as an attributeId or governsId'
2188 % (attrname
, obj
.dn
, obj
[attrname
][0]))
2190 self
.attribute_or_class_ids
.add(obj
[attrname
][0])
2192 # check for empty attributes
2193 for val
in obj
[attrname
]:
2195 self
.err_empty_attribute(dn
, attrname
)
2199 # get the syntax oid for the attribute, so we can can have
2200 # special handling for some specific attribute types
2202 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
2203 except Exception as msg
:
2204 self
.err_unknown_attribute(obj
, attrname
)
2208 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
2210 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
2211 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
2212 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
2214 set_attrs_seen
.add(str(attrname
).lower())
2216 if syntax_oid
in [dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
2217 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
2218 # it's some form of DN, do specialised checking on those
2219 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
2223 # check for incorrectly normalised attributes
2224 for val
in obj
[attrname
]:
2227 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
2228 if len(normalised
) != 1 or normalised
[0] != val
:
2229 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
2233 if len(obj
[attrname
]) != len(values
):
2234 self
.err_duplicate_values(dn
, attrname
, obj
[attrname
], list(values
))
2238 if str(attrname
).lower() == "instancetype":
2239 calculated_instancetype
= self
.calculate_instancetype(dn
)
2240 if len(obj
["instanceType"]) != 1 or int(obj
["instanceType"][0]) != calculated_instancetype
:
2242 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
2244 if not got_objectclass
and ("*" in attrs
or "objectclass" in map(str.lower
, attrs
)):
2246 self
.err_missing_objectclass(dn
)
2248 if ("*" in attrs
or "name" in map(str.lower
, attrs
)):
2249 if name_val
is None:
2251 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
2252 if object_rdn_attr
is None:
2254 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
2256 if name_val
is not None:
2259 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
2260 parent_dn
= deleted_objects_dn
2261 if parent_dn
is None:
2262 parent_dn
= obj
.dn
.parent()
2263 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
2264 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
2266 if obj
.dn
== deleted_objects_dn
:
2267 expected_dn
= obj
.dn
2269 if expected_dn
!= obj
.dn
:
2271 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
, object_rdn_val
, name_val
)
2272 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
2274 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
, object_rdn_val
, str(obj
.dn
)))
2277 if got_repl_property_meta_data
:
2278 if obj
.dn
== deleted_objects_dn
:
2279 isDeletedAttId
= 131120
2280 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2282 expectedTimeDo
= 2650466015990000000
2283 originating
= self
.get_originating_time(obj
["replPropertyMetaData"][0], isDeletedAttId
)
2284 if originating
!= expectedTimeDo
:
2285 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
2286 nmsg
= ldb
.Message()
2288 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2290 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
2293 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
2295 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
2297 self
.report("On object %s" % dn
)
2300 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
2301 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
2302 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
2304 self
.fix_metadata(obj
, att
)
2306 if self
.is_fsmo_role(dn
):
2307 if "fSMORoleOwner" not in obj
and ("*" in attrs
or "fsmoroleowner" in map(str.lower
, attrs
)):
2308 self
.err_no_fsmoRoleOwner(obj
)
2312 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
2313 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
2314 controls
=["show_recycled:1", "show_deleted:1"])
2315 except ldb
.LdbError
as e11
:
2316 (enum
, estr
) = e11
.args
2317 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2318 self
.err_missing_parent(obj
)
2323 if dn
in self
.deleted_objects_containers
and '*' in attrs
:
2324 if self
.is_deleted_deleted_objects(obj
):
2325 self
.err_deleted_deleted_objects(obj
)
2328 for (dns_part
, msg
) in self
.dns_partitions
:
2329 if dn
== dns_part
and 'repsFrom' in obj
:
2330 location
= "msDS-NC-Replica-Locations"
2331 if self
.samdb
.am_rodc():
2332 location
= "msDS-NC-RO-Replica-Locations"
2334 if location
not in msg
:
2335 # There are no replica locations!
2336 self
.err_replica_locations(obj
, msg
.dn
, location
)
2341 for loc
in msg
[location
]:
2342 if loc
== self
.samdb
.get_dsServiceName():
2345 # This DC is not in the replica locations
2346 self
.err_replica_locations(obj
, msg
.dn
, location
)
2349 if dn
== self
.server_ref_dn
:
2350 # Check we have a valid RID Set
2351 if "*" in attrs
or "rIDSetReferences" in attrs
:
2352 if "rIDSetReferences" not in obj
:
2353 # NO RID SET reference
2354 # We are RID master, allocate it.
2357 if self
.is_rid_master
:
2358 # Allocate a RID Set
2359 if self
.confirm_all('Allocate the missing RID set for RID master?',
2360 'fix_missing_rid_set_master'):
2362 # We don't have auto-transaction logic on
2363 # extended operations, so we have to do it
2366 self
.samdb
.transaction_start()
2369 self
.samdb
.create_own_rid_set()
2372 self
.samdb
.transaction_cancel()
2375 self
.samdb
.transaction_commit()
2377 elif not self
.samdb
.am_rodc():
2378 self
.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn
)
2380 # Check some details of our own RID Set
2381 if dn
== self
.rid_set_dn
:
2382 res
= self
.samdb
.search(base
=self
.rid_set_dn
, scope
=ldb
.SCOPE_BASE
,
2383 attrs
=["rIDAllocationPool",
2384 "rIDPreviousAllocationPool",
2387 if "rIDAllocationPool" not in res
[0]:
2388 self
.report("No rIDAllocationPool found in %s" % dn
)
2391 next_pool
= int(res
[0]["rIDAllocationPool"][0])
2393 high
= (0xFFFFFFFF00000000 & next_pool
) >> 32
2394 low
= 0x00000000FFFFFFFF & next_pool
2397 self
.report("Invalid RID set %d-%s, %d > %d!" % (low
, high
, low
, high
))
2400 if "rIDNextRID" in res
[0]:
2401 next_free_rid
= int(res
[0]["rIDNextRID"][0])
2405 if next_free_rid
== 0:
2410 # Check the remainder of this pool for conflicts. If
2411 # ridalloc_allocate_rid() moves to a new pool, this
2412 # will be above high, so we will stop.
2413 while next_free_rid
<= high
:
2414 sid
= "%s-%d" % (self
.samdb
.get_domain_sid(), next_free_rid
)
2416 res
= self
.samdb
.search(base
="<SID=%s>" % sid
, scope
=ldb
.SCOPE_BASE
,
2418 except ldb
.LdbError
as e
:
2419 (enum
, estr
) = e
.args
2420 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2424 self
.report("SID %s for %s conflicts with our current RID set in %s" % (sid
, res
[0].dn
, dn
))
2427 if self
.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2429 'fix_sid_rid_set_conflict'):
2430 self
.samdb
.transaction_start()
2432 # This will burn RIDs, which will move
2433 # past the conflict. We then check again
2434 # to see if the new RID conflicts, until
2435 # the end of the current pool. We don't
2436 # look at the next pool to avoid burning
2437 # all RIDs in one go in some strange
2441 allocated_rid
= self
.samdb
.allocate_rid()
2442 if allocated_rid
>= next_free_rid
:
2443 next_free_rid
= allocated_rid
+ 1
2446 self
.samdb
.transaction_cancel()
2449 self
.samdb
.transaction_commit()
2457 ################################################################
2458 # check special @ROOTDSE attributes
2459 def check_rootdse(self
):
2460 '''check the @ROOTDSE special object'''
2461 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
2463 self
.report("Checking object %s" % dn
)
2464 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
2466 self
.report("Object %s disappeared during check" % dn
)
2471 # check that the dsServiceName is in GUID form
2472 if 'dsServiceName' not in obj
:
2473 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
2474 return error_count
+ 1
2476 if not obj
['dsServiceName'][0].startswith('<GUID='):
2477 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2479 if not self
.confirm('Change dsServiceName to GUID form?'):
2481 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0].decode('utf8')),
2482 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
2483 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
2486 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
2487 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
2488 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
2489 self
.report("Changed dsServiceName to GUID form")
2492 ###############################################
2493 # re-index the database
2495 def reindex_database(self
):
2496 '''re-index the whole database'''
2498 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
2499 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
2500 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
2501 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
2503 ###############################################
2505 def reset_modules(self
):
2506 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2508 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
2509 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
2510 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)