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/>.
23 from base64
import b64decode
24 from samba
import dsdb
25 from samba
import common
26 from samba
.dcerpc
import misc
27 from samba
.dcerpc
import drsuapi
28 from samba
.ndr
import ndr_unpack
, ndr_pack
29 from samba
.dcerpc
import drsblobs
30 from samba
.samdb
import dsdb_Dn
31 from samba
.dcerpc
import security
32 from samba
.descriptor
import get_wellknown_sds
, get_diff_sds
33 from samba
.auth
import system_session
, admin_session
34 from samba
.netcmd
import CommandError
35 from samba
.netcmd
.fsmo
import get_fsmo_roleowner
36 from samba
.colour
import c_RED
, c_DARK_YELLOW
, c_DARK_CYAN
, c_DARK_GREEN
38 def dump_attr_values(vals
):
39 """Stringify a value list, using utf-8 if possible (which some tests
40 want), or the python bytes representation otherwise (with leading
41 'b' and escapes like b'\x00').
46 result
.append(value
.decode('utf-8'))
47 except UnicodeDecodeError:
48 result
.append(repr(value
))
49 return ','.join(result
)
52 class dbcheck(object):
53 """check a SAM database for errors"""
55 def __init__(self
, samdb
, samdb_schema
=None, verbose
=False, fix
=False,
56 yes
=False, quiet
=False, in_transaction
=False,
57 quick_membership_checks
=False,
58 reset_well_known_acls
=False,
59 check_expired_tombstones
=False,
62 self
.dict_oid_name
= None
63 self
.samdb_schema
= (samdb_schema
or samdb
)
64 self
.verbose
= verbose
69 self
.remove_all_unknown_attributes
= False
70 self
.remove_all_empty_attributes
= False
71 self
.fix_all_normalisation
= False
72 self
.fix_all_duplicates
= False
73 self
.fix_all_DN_GUIDs
= False
74 self
.fix_all_binary_dn
= False
75 self
.remove_implausible_deleted_DN_links
= False
76 self
.remove_plausible_deleted_DN_links
= False
77 self
.fix_all_string_dn_component_mismatch
= False
78 self
.fix_all_GUID_dn_component_mismatch
= False
79 self
.fix_all_SID_dn_component_mismatch
= False
80 self
.fix_all_SID_dn_component_missing
= False
81 self
.fix_all_old_dn_string_component_mismatch
= False
82 self
.fix_all_metadata
= False
83 self
.fix_time_metadata
= False
84 self
.fix_undead_linked_attributes
= False
85 self
.fix_all_missing_backlinks
= False
86 self
.fix_all_orphaned_backlinks
= False
87 self
.fix_all_missing_forward_links
= False
88 self
.duplicate_link_cache
= dict()
89 self
.recover_all_forward_links
= False
90 self
.fix_rmd_flags
= False
91 self
.fix_ntsecuritydescriptor
= False
92 self
.fix_ntsecuritydescriptor_owner_group
= False
93 self
.seize_fsmo_role
= False
94 self
.move_to_lost_and_found
= False
95 self
.fix_instancetype
= False
96 self
.fix_replmetadata_zero_invocationid
= False
97 self
.fix_replmetadata_duplicate_attid
= False
98 self
.fix_replmetadata_wrong_attid
= False
99 self
.fix_replmetadata_unsorted_attid
= False
100 self
.fix_deleted_deleted_objects
= False
102 self
.fix_base64_userparameters
= False
103 self
.fix_utf8_userparameters
= False
104 self
.fix_doubled_userparameters
= False
105 self
.fix_sid_rid_set_conflict
= False
106 self
.quick_membership_checks
= quick_membership_checks
107 self
.reset_well_known_acls
= reset_well_known_acls
108 self
.check_expired_tombstones
= check_expired_tombstones
109 self
.expired_tombstones
= 0
110 self
.reset_all_well_known_acls
= False
111 self
.in_transaction
= in_transaction
112 self
.infrastructure_dn
= ldb
.Dn(samdb
, "CN=Infrastructure," + samdb
.domain_dn())
113 self
.naming_dn
= ldb
.Dn(samdb
, "CN=Partitions,%s" % samdb
.get_config_basedn())
114 self
.schema_dn
= samdb
.get_schema_basedn()
115 self
.rid_dn
= ldb
.Dn(samdb
, "CN=RID Manager$,CN=System," + samdb
.domain_dn())
116 self
.ntds_dsa
= ldb
.Dn(samdb
, samdb
.get_dsServiceName())
117 self
.class_schemaIDGUID
= {}
118 self
.wellknown_sds
= get_wellknown_sds(self
.samdb
)
119 self
.fix_all_missing_objectclass
= False
120 self
.fix_missing_deleted_objects
= False
121 self
.fix_replica_locations
= False
122 self
.fix_missing_rid_set_master
= False
123 self
.fix_changes_after_deletion_bug
= False
126 self
.link_id_cache
= {}
129 base_dn
= "CN=DnsAdmins,%s" % samdb
.get_wellknown_dn(
130 samdb
.get_default_basedn(),
131 dsdb
.DS_GUID_USERS_CONTAINER
)
132 res
= samdb
.search(base
=base_dn
, scope
=ldb
.SCOPE_BASE
,
134 dnsadmins_sid
= ndr_unpack(security
.dom_sid
, res
[0]["objectSid"][0])
135 self
.name_map
['DnsAdmins'] = str(dnsadmins_sid
)
136 except ldb
.LdbError
as e5
:
137 (enum
, estr
) = e5
.args
138 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
141 self
.system_session_info
= system_session()
142 self
.admin_session_info
= admin_session(None, samdb
.get_domain_sid())
144 res
= self
.samdb
.search(base
=self
.ntds_dsa
, scope
=ldb
.SCOPE_BASE
, attrs
=['msDS-hasMasterNCs', 'hasMasterNCs'])
145 if "msDS-hasMasterNCs" in res
[0]:
146 self
.write_ncs
= res
[0]["msDS-hasMasterNCs"]
148 # If the Forest Level is less than 2003 then there is no
149 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
150 # no need to merge as all the NCs that are in hasMasterNCs must
151 # also be in msDS-hasMasterNCs (but not the opposite)
152 if "hasMasterNCs" in res
[0]:
153 self
.write_ncs
= res
[0]["hasMasterNCs"]
155 self
.write_ncs
= None
157 res
= self
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['namingContexts'])
158 self
.deleted_objects_containers
= []
159 self
.ncs_lacking_deleted_containers
= []
160 self
.dns_partitions
= []
162 self
.ncs
= res
[0]["namingContexts"]
170 dn
= self
.samdb
.get_wellknown_dn(ldb
.Dn(self
.samdb
, nc
.decode('utf8')),
171 dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
172 self
.deleted_objects_containers
.append(dn
)
174 self
.ncs_lacking_deleted_containers
.append(ldb
.Dn(self
.samdb
, nc
.decode('utf8')))
176 domaindns_zone
= 'DC=DomainDnsZones,%s' % self
.samdb
.get_default_basedn()
177 forestdns_zone
= 'DC=ForestDnsZones,%s' % self
.samdb
.get_root_basedn()
178 domain
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
179 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
180 base
=self
.samdb
.get_partitions_dn(),
181 expression
="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone
)
183 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, forestdns_zone
), domain
[0]))
185 forest
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
186 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
187 base
=self
.samdb
.get_partitions_dn(),
188 expression
="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone
)
190 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, domaindns_zone
), forest
[0]))
192 fsmo_dn
= ldb
.Dn(self
.samdb
, "CN=RID Manager$,CN=System," + self
.samdb
.domain_dn())
193 rid_master
= get_fsmo_roleowner(self
.samdb
, fsmo_dn
, "rid")
194 if ldb
.Dn(self
.samdb
, self
.samdb
.get_dsServiceName()) == rid_master
:
195 self
.is_rid_master
= True
197 self
.is_rid_master
= False
199 # To get your rid set
201 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, self
.samdb
.get_serverName()),
202 scope
=ldb
.SCOPE_BASE
, attrs
=["serverReference"])
203 # 2. Get server reference
204 self
.server_ref_dn
= ldb
.Dn(self
.samdb
, res
[0]['serverReference'][0].decode('utf8'))
207 res
= self
.samdb
.search(base
=self
.server_ref_dn
,
208 scope
=ldb
.SCOPE_BASE
, attrs
=['rIDSetReferences'])
209 if "rIDSetReferences" in res
[0]:
210 self
.rid_set_dn
= ldb
.Dn(self
.samdb
, res
[0]['rIDSetReferences'][0].decode('utf8'))
212 self
.rid_set_dn
= None
214 ntds_service_dn
= "CN=Directory Service,CN=Windows NT,CN=Services,%s" % \
215 self
.samdb
.get_config_basedn().get_linearized()
216 res
= samdb
.search(base
=ntds_service_dn
,
217 scope
=ldb
.SCOPE_BASE
,
218 expression
="(objectClass=nTDSService)",
219 attrs
=["tombstoneLifetime"])
220 if "tombstoneLifetime" in res
[0]:
221 self
.tombstoneLifetime
= int(res
[0]["tombstoneLifetime"][0])
223 self
.tombstoneLifetime
= 180
225 self
.compatibleFeatures
= []
226 self
.requiredFeatures
= []
229 res
= self
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
231 attrs
=["compatibleFeatures",
233 if "compatibleFeatures" in res
[0]:
234 self
.compatibleFeatures
= res
[0]["compatibleFeatures"]
235 if "requiredFeatures" in res
[0]:
236 self
.requiredFeatures
= res
[0]["requiredFeatures"]
237 except ldb
.LdbError
as e6
:
238 (enum
, estr
) = e6
.args
239 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
242 def check_database(self
, DN
=None, scope
=ldb
.SCOPE_SUBTREE
, controls
=None,
244 '''perform a database check, returning the number of errors found'''
245 res
= self
.samdb
.search(base
=DN
, scope
=scope
, attrs
=['dn'], controls
=controls
)
246 self
.report('Checking %u objects' % len(res
))
248 self
.unfixable_errors
= 0
250 error_count
+= self
.check_deleted_objects_containers()
252 self
.attribute_or_class_ids
= set()
255 self
.dn_set
.add(str(object.dn
))
256 error_count
+= self
.check_object(object.dn
, requested_attrs
=attrs
)
259 error_count
+= self
.check_rootdse()
261 if self
.expired_tombstones
> 0:
262 self
.report("NOTICE: found %d expired tombstones, "
263 "'samba' will remove them daily, "
264 "'samba-tool domain tombstones expunge' "
265 "would do that immediately." % (
266 self
.expired_tombstones
))
268 self
.report('Checked %u objects (%u errors)' %
269 (len(res
), error_count
+ self
.unfixable_errors
))
271 if self
.unfixable_errors
!= 0:
272 self
.report(f
"WARNING: {self.unfixable_errors} "
273 "of these errors cannot be automatically fixed.")
275 if error_count
!= 0 and not self
.fix
:
276 self
.report("Please use 'samba-tool dbcheck --fix' to fix "
277 f
"{error_count} errors")
281 def check_deleted_objects_containers(self
):
282 """This function only fixes conflicts on the Deleted Objects
283 containers, not the attributes"""
285 for nc
in self
.ncs_lacking_deleted_containers
:
286 if nc
== self
.schema_dn
:
289 self
.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc
)
290 if not self
.confirm_all('Fix missing Deleted Objects container for %s?' % (nc
), 'fix_missing_deleted_objects'):
293 dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects")
298 # If something already exists here, add a conflict
299 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[],
300 controls
=["show_deleted:1", "extended_dn:1:1",
301 "show_recycled:1", "reveal_internals:0"])
303 guid
= res
[0].dn
.get_extended_component("GUID")
304 conflict_dn
= ldb
.Dn(self
.samdb
,
305 "CN=Deleted Objects\\0ACNF:%s" % str(misc
.GUID(guid
)))
306 conflict_dn
.add_base(nc
)
308 except ldb
.LdbError
as e2
:
309 (enum
, estr
) = e2
.args
310 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
313 self
.report("Couldn't check for conflicting Deleted Objects container: %s" % estr
)
316 if conflict_dn
is not None:
318 self
.samdb
.rename(dn
, conflict_dn
, ["show_deleted:1", "relax:0", "show_recycled:1"])
319 except ldb
.LdbError
as e1
:
320 (enum
, estr
) = e1
.args
321 self
.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn
, conflict_dn
, estr
))
324 # Refresh wellKnownObjects links
325 res
= self
.samdb
.search(base
=nc
, scope
=ldb
.SCOPE_BASE
,
326 attrs
=['wellKnownObjects'],
327 controls
=["show_deleted:1", "extended_dn:0",
328 "show_recycled:1", "reveal_internals:0"])
330 self
.report("wellKnownObjects was not found for NC %s" % nc
)
333 # Prevent duplicate deleted objects containers just in case
334 wko
= res
[0]["wellKnownObjects"]
336 proposed_objectguid
= None
338 dsdb_dn
= dsdb_Dn(self
.samdb
, o
.decode('utf8'), dsdb
.DSDB_SYNTAX_BINARY_DN
)
339 if self
.is_deleted_objects_dn(dsdb_dn
):
340 self
.report("wellKnownObjects had duplicate Deleted Objects value %s" % o
)
341 # We really want to put this back in the same spot
342 # as the original one, so that on replication we
343 # merge, rather than conflict.
344 proposed_objectguid
= dsdb_dn
.dn
.get_extended_component("GUID")
345 listwko
.append(str(o
))
347 if proposed_objectguid
is not None:
348 guid_suffix
= "\nobjectGUID: %s" % str(misc
.GUID(proposed_objectguid
))
350 wko_prefix
= "B:32:%s" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
351 listwko
.append('%s:%s' % (wko_prefix
, dn
))
354 # Insert a brand new Deleted Objects container
355 self
.samdb
.add_ldif("""dn: %s
357 objectClass: container
358 description: Container for deleted objects
360 isCriticalSystemObject: TRUE
361 showInAdvancedViewOnly: TRUE
362 systemFlags: -1946157056%s""" % (dn
, guid_suffix
),
363 controls
=["relax:0", "provision:0"])
365 delta
= ldb
.Message()
366 delta
.dn
= ldb
.Dn(self
.samdb
, str(res
[0]["dn"]))
367 delta
["wellKnownObjects"] = ldb
.MessageElement(listwko
,
368 ldb
.FLAG_MOD_REPLACE
,
371 # Insert the link to the brand new container
372 if self
.do_modify(delta
, ["relax:0"],
373 "NC %s lacks Deleted Objects WKGUID" % nc
,
375 self
.report("Added %s well known guid link" % dn
)
377 self
.deleted_objects_containers
.append(dn
)
381 def report(self
, msg
):
382 '''print a message unless quiet is set'''
386 if msg
.startswith('ERROR'):
387 msg
= c_RED('ERROR') + msg
[5:]
388 elif msg
.startswith('WARNING'):
389 msg
= c_DARK_YELLOW('WARNING') + msg
[7:]
390 elif msg
.startswith('INFO'):
391 msg
= c_DARK_CYAN('INFO') + msg
[4:]
392 elif msg
.startswith('NOTICE'):
393 msg
= c_DARK_CYAN('NOTICE') + msg
[6:]
394 elif msg
.startswith('NOTE'):
395 msg
= c_DARK_CYAN('NOTE') + msg
[4:]
396 elif msg
.startswith('SKIPPING'):
397 msg
= c_DARK_GREEN('SKIPPING') + msg
[8:]
401 def confirm(self
, msg
, allow_all
=False, forced
=False):
402 '''confirm a change'''
409 return common
.confirm(msg
, forced
=forced
, allow_all
=allow_all
)
411 ################################################################
412 # a local confirm function with support for 'all'
413 def confirm_all(self
, msg
, all_attr
):
414 '''confirm a change with support for "all" '''
417 if getattr(self
, all_attr
) == 'NONE':
419 if getattr(self
, all_attr
) == 'ALL':
425 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
427 setattr(self
, all_attr
, 'ALL')
430 setattr(self
, all_attr
, 'NONE')
434 def do_delete(self
, dn
, controls
, msg
):
435 '''delete dn with optional verbose output'''
437 self
.report("delete DN %s" % dn
)
439 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
440 self
.samdb
.delete(dn
, controls
=controls
)
441 except Exception as err
:
442 if self
.in_transaction
:
443 raise CommandError("%s : %s" % (msg
, err
))
444 self
.report("%s : %s" % (msg
, err
))
448 def do_modify(self
, m
, controls
, msg
, validate
=True):
449 '''perform a modify with optional verbose output'''
450 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
452 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
453 self
.report("controls: %r" % controls
)
455 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
456 except Exception as err
:
457 if self
.in_transaction
:
458 raise CommandError("%s : %s" % (msg
, err
))
459 self
.report("%s : %s" % (msg
, err
))
463 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
464 '''perform a rename with optional verbose output'''
466 self
.report("""dn: %s
470 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
472 to_dn
= to_rdn
+ to_base
473 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
474 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
475 except Exception as err
:
476 if self
.in_transaction
:
477 raise CommandError("%s : %s" % (msg
, err
))
478 self
.report("%s : %s" % (msg
, err
))
482 def get_attr_linkID_and_reverse_name(self
, attrname
):
483 if attrname
in self
.link_id_cache
:
484 return self
.link_id_cache
[attrname
]
485 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
487 revname
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
490 self
.link_id_cache
[attrname
] = (linkID
, revname
)
491 return linkID
, revname
493 def err_empty_attribute(self
, dn
, attrname
):
494 '''fix empty attributes'''
495 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
496 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
497 self
.report("Not fixing empty attribute %s" % attrname
)
502 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
503 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
504 "Failed to remove empty attribute %s" % attrname
, validate
=False):
505 self
.report("Removed empty attribute %s" % attrname
)
507 def err_normalise_mismatch(self
, dn
, attrname
, values
):
508 '''fix attribute normalisation errors, without altering sort order'''
509 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
512 normalised
= self
.samdb
.dsdb_normalise_attributes(
513 self
.samdb_schema
, attrname
, [val
])
514 if len(normalised
) != 1:
515 self
.report("Unable to normalise value '%s'" % val
)
516 mod_list
.append((val
, ''))
517 elif (normalised
[0] != val
):
518 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
519 mod_list
.append((val
, normalised
[0]))
520 if not self
.confirm_all('Fix normalisation for %s from %s?' % (attrname
, dn
), 'fix_all_normalisation'):
521 self
.report("Not fixing attribute %s" % attrname
)
526 for i
in range(0, len(mod_list
)):
527 (val
, nval
) = mod_list
[i
]
528 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
530 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
533 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
534 "Failed to normalise attribute %s" % attrname
,
536 self
.report("Normalised attribute %s" % attrname
)
538 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
539 '''fix attribute normalisation and/or sort errors'''
540 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
541 if list(normalised
) == values
:
542 # how we got here is a mystery.
544 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
545 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
546 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
547 self
.report("Not fixing attribute '%s'" % attrname
)
552 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
554 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
555 "Failed to normalise attribute %s" % attrname
,
557 self
.report("Normalised attribute %s" % attrname
)
559 def err_duplicate_values(self
, dn
, attrname
, dup_values
, values
):
560 '''fix duplicate attribute values'''
561 self
.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname
, dn
))
562 self
.report("Values contain a duplicate: [%s]/[%s]!" %
563 (dump_attr_values(dup_values
), dump_attr_values(values
)))
564 if not self
.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_duplicates'):
565 self
.report("Not fixing attribute '%s'" % attrname
)
570 m
[attrname
] = ldb
.MessageElement(values
, ldb
.FLAG_MOD_REPLACE
, attrname
)
572 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
573 "Failed to remove duplicate value on attribute %s" % attrname
,
575 self
.report("Removed duplicate value on attribute %s" % attrname
)
577 def is_deleted_objects_dn(self
, dsdb_dn
):
578 '''see if a dsdb_Dn is the special Deleted Objects DN'''
579 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
581 def err_missing_objectclass(self
, dn
):
582 """handle object without objectclass"""
583 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
)))
584 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'):
585 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
587 if self
.do_delete(dn
, ["relax:0"],
588 "Failed to remove DN %s" % dn
):
589 self
.report("Removed DN %s" % dn
)
591 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, remove_plausible
=False):
592 """handle a DN pointing to a deleted object"""
593 if not remove_plausible
:
594 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
595 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
596 if not self
.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
597 self
.report("Not removing")
600 self
.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
601 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
602 if not self
.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
603 self
.report("Not removing")
608 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
609 if self
.do_modify(m
, ["show_recycled:1",
610 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
611 "Failed to remove deleted DN attribute %s" % attrname
):
612 self
.report("Removed deleted DN on attribute %s" % attrname
)
614 def err_missing_target_dn_or_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
615 """handle a missing target DN (if specified, GUID form can't be found,
616 and otherwise DN string form can't be found)"""
618 # Don't change anything if the object itself is deleted
619 if str(dn
).find('\\0ADEL') != -1:
620 # We don't bump the error count as Samba produces these
621 # in normal operation
622 self
.report("WARNING: no target object found for GUID "
623 "component link %s in deleted object "
624 "%s - %s" % (attrname
, dn
, val
))
625 self
.report("Not removing dangling one-way "
626 "link on deleted object "
627 "(tombstone garbage collection in progress?)")
630 # check if its a backlink
631 linkID
, _
= self
.get_attr_linkID_and_reverse_name(attrname
)
632 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
634 linkID
, reverse_link_name \
635 = self
.get_attr_linkID_and_reverse_name(attrname
)
636 if reverse_link_name
is not None:
637 self
.report("WARNING: no target object found for GUID "
638 "component for one-way forward link "
640 "%s - %s" % (attrname
, dn
, val
))
641 self
.report("Not removing dangling forward link")
644 nc_root
= self
.samdb
.get_nc_root(dn
)
646 target_nc_root
= self
.samdb
.get_nc_root(dsdb_dn
.dn
)
647 except ldb
.LdbError
as e
:
648 (enum
, estr
) = e
.args
649 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
651 target_nc_root
= None
653 if target_nc_root
is None:
654 # We don't bump the error count as Samba produces
655 # these in normal operation creating a lab domain (due
656 # to the way the rename is handled, links to
657 # now-expunged objects will never be fixed to stay
659 self
.report("WARNING: no target object found for GUID "
660 "component for link "
661 "%s in object to %s outside our NCs"
662 "%s - %s" % (attrname
, dsdb_dn
.dn
, dn
, val
))
663 self
.report("Not removing dangling one-way "
664 "left-over link outside our NCs "
665 "(we might be building a renamed/lab domain)")
668 if nc_root
!= target_nc_root
:
669 # We don't bump the error count as Samba produces these
670 # in normal operation
671 self
.report("WARNING: no target object found for GUID "
672 "component for cross-partition link "
674 "%s - %s" % (attrname
, dn
, val
))
675 self
.report("Not removing dangling one-way "
676 "cross-partition link "
677 "(we might be mid-replication)")
680 # Due to our link handling one-way links pointing to
681 # missing objects are plausible.
683 # We don't bump the error count as Samba produces these
684 # in normal operation
685 self
.report("WARNING: no target object found for GUID "
686 "component for DN value %s in object "
687 "%s - %s" % (attrname
, dn
, val
))
688 self
.err_deleted_dn(dn
, attrname
, val
,
689 dsdb_dn
, dsdb_dn
, True)
692 # We bump the error count here, as we should have deleted this
693 self
.report("ERROR: no target object found for GUID "
694 "component for link %s in object "
695 "%s - %s" % (attrname
, dn
, val
))
696 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
, False)
699 def err_missing_dn_GUID_component(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
700 """handle a missing GUID extended DN component"""
701 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
702 controls
= ["extended_dn:1:1", "show_recycled:1"]
704 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
705 attrs
=[], controls
=controls
)
706 except ldb
.LdbError
as e7
:
707 (enum
, estr
) = e7
.args
708 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
709 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
711 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
714 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
715 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
717 dsdb_dn
.dn
= res
[0].dn
719 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
720 self
.report("Not fixing %s" % errstr
)
724 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
725 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
727 if self
.do_modify(m
, ["show_recycled:1"],
728 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
729 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
731 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
732 """handle an incorrect binary DN component"""
733 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
735 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
736 self
.report("Not fixing %s" % errstr
)
740 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
741 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
743 if self
.do_modify(m
, ["show_recycled:1"],
744 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
745 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
747 def err_dn_string_component_old(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
748 """handle a DN string being incorrect due to a rename or delete"""
749 self
.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
750 dsdb_dn
.dn
= correct_dn
752 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
753 'fix_all_old_dn_string_component_mismatch'):
754 self
.report("Not fixing old string component")
758 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
759 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
760 if self
.do_modify(m
, ["show_recycled:1",
761 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
],
762 "Failed to fix old DN string on attribute %s" % (attrname
)):
763 self
.report("Fixed old DN string on attribute %s" % (attrname
))
765 def err_dn_component_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, mismatch_type
):
766 """handle a DN string being incorrect"""
767 self
.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type
, attrname
, dn
, val
))
768 dsdb_dn
.dn
= correct_dn
770 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
771 'fix_all_%s_dn_component_mismatch' % mismatch_type
):
772 self
.report("Not fixing %s component mismatch" % mismatch_type
)
776 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
777 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
778 if self
.do_modify(m
, ["show_recycled:1"],
779 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type
, attrname
)):
780 self
.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type
, attrname
))
782 def err_dn_component_missing_target_sid(self
, dn
, attrname
, val
, dsdb_dn
, target_sid_blob
):
783 """fix missing <SID=...> on linked attributes"""
784 self
.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname
, dn
, val
))
786 if len(dsdb_dn
.prefix
) != 0:
787 self
.report("Not fixing missing DN SID on DN+BINARY or DN+STRING")
790 correct_dn
= ldb
.Dn(self
.samdb
, dsdb_dn
.dn
.extended_str())
791 correct_dn
.set_extended_component("SID", target_sid_blob
)
793 if not self
.confirm_all('Change DN to %s?' % correct_dn
.extended_str(),
794 'fix_all_SID_dn_component_missing'):
795 self
.report("Not fixing missing DN SID component")
798 target_guid_blob
= correct_dn
.get_extended_component("GUID")
799 guid_sid_dn
= ldb
.Dn(self
.samdb
, "")
800 guid_sid_dn
.set_extended_component("GUID", target_guid_blob
)
801 guid_sid_dn
.set_extended_component("SID", target_sid_blob
)
805 m
['new_value'] = ldb
.MessageElement(guid_sid_dn
.extended_str(), ldb
.FLAG_MOD_ADD
, attrname
)
808 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID
810 if self
.do_modify(m
, controls
,
811 "Failed to ADD missing DN SID on attribute %s" % (attrname
)):
812 self
.report("Fixed missing DN SID on attribute %s" % (attrname
))
814 def err_unknown_attribute(self
, obj
, attrname
):
815 '''handle an unknown attribute error'''
816 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
817 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
818 self
.report("Not removing %s" % attrname
)
822 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
823 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
824 "Failed to remove unknown attribute %s" % attrname
):
825 self
.report("Removed unknown attribute %s" % (attrname
))
827 def err_undead_linked_attribute(self
, obj
, attrname
, val
):
828 '''handle a link that should not be there on a deleted object'''
829 self
.report("ERROR: linked attribute '%s' to '%s' is present on "
830 "deleted object %s" % (attrname
, val
, obj
.dn
))
831 if not self
.confirm_all('Remove linked attribute %s' % attrname
, 'fix_undead_linked_attributes'):
832 self
.report("Not removing linked attribute %s" % attrname
)
836 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
838 if self
.do_modify(m
, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
839 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
840 "Failed to delete forward link %s" % attrname
):
841 self
.report("Fixed undead forward link %s" % (attrname
))
843 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
844 '''handle a missing backlink value'''
845 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
846 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
847 self
.report("Not fixing missing backlink %s" % backlink_name
)
851 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, backlink_name
)
852 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
853 "Failed to fix missing backlink %s" % backlink_name
):
854 self
.report("Fixed missing backlink %s" % (backlink_name
))
856 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
857 '''handle a incorrect RMD_FLAGS value'''
858 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
859 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()))
860 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
861 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
865 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
866 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
867 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
868 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
870 def err_orphaned_backlink(self
, obj_dn
, backlink_attr
, backlink_val
,
871 target_dn
, forward_attr
, forward_syntax
,
872 check_duplicates
=True):
873 '''handle a orphaned backlink value'''
874 if check_duplicates
is True and self
.has_duplicate_links(target_dn
, forward_attr
, forward_syntax
):
875 self
.report("WARNING: Keep orphaned backlink attribute " +
876 "'%s' in '%s' for link '%s' in '%s'" % (
877 backlink_attr
, obj_dn
, forward_attr
, target_dn
))
879 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr
, obj_dn
, forward_attr
, target_dn
))
880 if not self
.confirm_all('Remove orphaned backlink %s' % backlink_attr
, 'fix_all_orphaned_backlinks'):
881 self
.report("Not removing orphaned backlink %s" % backlink_attr
)
885 m
['value'] = ldb
.MessageElement(backlink_val
, ldb
.FLAG_MOD_DELETE
, backlink_attr
)
886 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
887 "Failed to fix orphaned backlink %s" % backlink_attr
):
888 self
.report("Fixed orphaned backlink %s" % (backlink_attr
))
890 def err_recover_forward_links(self
, obj
, forward_attr
, forward_vals
):
891 '''handle a duplicate links value'''
893 self
.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr
, obj
.dn
))
895 if not self
.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr
, 'recover_all_forward_links'):
896 self
.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
897 forward_attr
, obj
.dn
))
901 m
['value'] = ldb
.MessageElement(forward_vals
, ldb
.FLAG_MOD_REPLACE
, forward_attr
)
902 if self
.do_modify(m
, ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS
],
903 "Failed to fix duplicate links in attribute '%s'" % forward_attr
):
904 self
.report("Fixed duplicate links in attribute '%s'" % (forward_attr
))
905 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
906 assert duplicate_cache_key
in self
.duplicate_link_cache
907 self
.duplicate_link_cache
[duplicate_cache_key
] = False
909 def err_no_fsmoRoleOwner(self
, obj
):
910 '''handle a missing fSMORoleOwner'''
911 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
912 res
= self
.samdb
.search("",
913 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
915 serviceName
= str(res
[0]["dsServiceName"][0])
916 if not self
.confirm_all('Seize role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
917 self
.report("Not Seizing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
921 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
922 if self
.do_modify(m
, [],
923 "Failed to seize role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
924 self
.report("Seized role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
926 def err_missing_parent(self
, obj
):
927 '''handle a missing parent'''
928 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
929 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
930 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
933 keep_transaction
= False
934 self
.samdb
.transaction_start()
936 nc_root
= self
.samdb
.get_nc_root(obj
.dn
)
937 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
938 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
939 new_dn
.remove_base_components(len(new_dn
) - 1)
940 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
941 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
942 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
946 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
948 if self
.do_modify(m
, [],
949 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
950 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
951 keep_transaction
= True
953 self
.samdb
.transaction_cancel()
957 self
.samdb
.transaction_commit()
959 self
.samdb
.transaction_cancel()
961 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
, controls
):
962 '''handle a wrong dn'''
964 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
965 new_rdn
.remove_base_components(len(new_rdn
) - 1)
966 new_parent
= new_dn
.parent()
969 if rdn_val
!= name_val
:
970 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
971 attributes
+= "name=%r" % (name_val
)
973 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
974 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
975 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
978 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, controls
,
979 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
980 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
982 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
983 '''handle a wrong instanceType'''
984 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
985 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
986 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
991 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
992 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
993 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
994 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
996 def err_short_userParameters(self
, obj
, attrname
, value
):
997 # This is a truncated userParameters due to a pre 4.1 replication bug
998 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
)))
1000 def err_base64_userParameters(self
, obj
, attrname
, value
):
1001 '''handle a userParameters that is wrongly base64 encoded'''
1002 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
1003 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
1004 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
1009 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
1010 if self
.do_modify(m
, [],
1011 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
1012 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
1014 def err_utf8_userParameters(self
, obj
, attrname
, value
):
1015 '''handle a userParameters that is wrongly utf-8 encoded'''
1016 self
.report("ERROR: wrongly formatted userParameters on %s, "
1017 "should not be pseudo-UTF8 encoded" % (obj
.dn
))
1018 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
1019 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
1024 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
1025 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
1026 if self
.do_modify(m
, [],
1027 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
1028 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
1030 def err_doubled_userParameters(self
, obj
, attrname
, value
):
1031 '''handle a userParameters that has been utf-16 encoded twice'''
1032 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
1033 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
1034 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
1039 # m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
1040 # hmm the above old python2 code doesn't make sense to me and cannot
1041 # work in python3 because a string doesn't have a decode method.
1042 # However in python2 for some unknown reason this double decode
1043 # followed by encode seems to result in what looks like utf8.
1044 # In python2 just .decode('utf-16-le').encode('utf-16-le') does nothing
1045 # but trigger the 'double UTF16 encoded' condition again :/
1047 # In python2 and python3 value.decode('utf-16-le').encode('utf8') seems
1048 # to do the trick and work as expected.
1049 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').encode('utf8'),
1050 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
1052 if self
.do_modify(m
, [],
1053 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
1054 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
1056 def err_odd_userParameters(self
, obj
, attrname
):
1057 """Fix a truncated userParameters due to a pre 4.1 replication bug"""
1058 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
)))
1060 def find_revealed_link(self
, dn
, attrname
, guid
):
1061 '''return a revealed link in an object'''
1062 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
1063 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
1064 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
1065 for val
in res
[0][attrname
]:
1066 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
1067 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
1072 def check_duplicate_links(self
, obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
):
1073 '''check a linked values for duplicate forward links'''
1076 duplicate_dict
= dict()
1077 unique_dict
= dict()
1079 # Only forward links can have this problem
1080 if forward_linkID
& 1:
1081 # If we got the reverse, skip it
1082 return (error_count
, duplicate_dict
, unique_dict
)
1084 if backlink_attr
is None:
1085 return (error_count
, duplicate_dict
, unique_dict
)
1087 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
1088 if duplicate_cache_key
not in self
.duplicate_link_cache
:
1089 self
.duplicate_link_cache
[duplicate_cache_key
] = False
1091 for val
in obj
[forward_attr
]:
1092 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), forward_syntax
)
1094 # all DNs should have a GUID component
1095 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1098 guidstr
= str(misc
.GUID(guid
))
1099 keystr
= guidstr
+ dsdb_dn
.prefix
1100 if keystr
not in unique_dict
:
1101 unique_dict
[keystr
] = dsdb_dn
1104 if keystr
not in duplicate_dict
:
1105 duplicate_dict
[keystr
] = dict()
1106 duplicate_dict
[keystr
]["keep"] = None
1107 duplicate_dict
[keystr
]["delete"] = list()
1109 # Now check for the highest RMD_VERSION
1110 v1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_VERSION"))
1111 v2
= int(dsdb_dn
.dn
.get_extended_component("RMD_VERSION"))
1113 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1114 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
1117 duplicate_dict
[keystr
]["keep"] = dsdb_dn
1118 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
1119 unique_dict
[keystr
] = dsdb_dn
1121 # Fallback to the highest RMD_LOCAL_USN
1122 u1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_LOCAL_USN"))
1123 u2
= int(dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN"))
1125 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1126 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
1128 duplicate_dict
[keystr
]["keep"] = dsdb_dn
1129 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
1130 unique_dict
[keystr
] = dsdb_dn
1132 if error_count
!= 0:
1133 self
.duplicate_link_cache
[duplicate_cache_key
] = True
1135 return (error_count
, duplicate_dict
, unique_dict
)
1137 def has_duplicate_links(self
, dn
, forward_attr
, forward_syntax
):
1138 '''check a linked values for duplicate forward links'''
1141 duplicate_cache_key
= "%s:%s" % (str(dn
), forward_attr
)
1142 if duplicate_cache_key
in self
.duplicate_link_cache
:
1143 return self
.duplicate_link_cache
[duplicate_cache_key
]
1145 forward_linkID
, backlink_attr
= self
.get_attr_linkID_and_reverse_name(forward_attr
)
1147 attrs
= [forward_attr
]
1148 controls
= ["extended_dn:1:1", "reveal_internals:0"]
1150 # check its the right GUID
1152 res
= self
.samdb
.search(base
=str(dn
), scope
=ldb
.SCOPE_BASE
,
1153 attrs
=attrs
, controls
=controls
)
1154 except ldb
.LdbError
as e8
:
1155 (enum
, estr
) = e8
.args
1156 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1162 error_count
, duplicate_dict
, unique_dict
= \
1163 self
.check_duplicate_links(obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
)
1165 if duplicate_cache_key
in self
.duplicate_link_cache
:
1166 return self
.duplicate_link_cache
[duplicate_cache_key
]
1170 def find_missing_forward_links_from_backlinks(self
, obj
,
1174 forward_unique_dict
):
1175 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1176 missing_forward_links
= []
1179 if backlink_attr
is None:
1180 return (missing_forward_links
, error_count
)
1182 if forward_syntax
!= ldb
.SYNTAX_DN
:
1183 self
.report("Not checking for missing forward links for syntax: %s" %
1185 return (missing_forward_links
, error_count
)
1187 if "sortedLinks" in self
.compatibleFeatures
:
1188 self
.report("Not checking for missing forward links because the db " +
1189 "has the sortedLinks feature")
1190 return (missing_forward_links
, error_count
)
1193 obj_guid
= obj
['objectGUID'][0]
1194 obj_guid_str
= str(ndr_unpack(misc
.GUID
, obj_guid
))
1195 filter = "(%s=<GUID=%s>)" % (backlink_attr
, obj_guid_str
)
1197 res
= self
.samdb
.search(expression
=filter,
1198 scope
=ldb
.SCOPE_SUBTREE
, attrs
=["objectGUID"],
1199 controls
=["extended_dn:1:1",
1200 "search_options:1:2",
1201 "paged_results:1:1000"])
1202 except ldb
.LdbError
as e9
:
1203 (enum
, estr
) = e9
.args
1207 target_dn
= dsdb_Dn(self
.samdb
, r
.dn
.extended_str(), forward_syntax
)
1209 guid
= target_dn
.dn
.get_extended_component("GUID")
1210 guidstr
= str(misc
.GUID(guid
))
1211 if guidstr
in forward_unique_dict
:
1214 # A valid forward link looks like this:
1216 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1217 # <RMD_ADDTIME=131607546230000000>;
1218 # <RMD_CHANGETIME=131607546230000000>;
1220 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1221 # <RMD_LOCAL_USN=3765>;
1222 # <RMD_ORIGINATING_USN=3765>;
1224 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1225 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1227 # Note that versions older than Samba 4.8 create
1228 # links with RMD_VERSION=0.
1230 # Try to get the local_usn and time from objectClass
1231 # if possible and fallback to any other one.
1232 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1233 obj
['replPropertyMetadata'][0])
1234 for o
in repl
.ctr
.array
:
1235 local_usn
= o
.local_usn
1236 t
= o
.originating_change_time
1237 if o
.attid
== drsuapi
.DRSUAPI_ATTID_objectClass
:
1240 # We use a magic invocationID for restoring missing
1241 # forward links to recover from bug #13228.
1242 # This should allow some more future magic to fix the
1245 # It also means it looses the conflict resolution
1246 # against almost every real invocation, if the
1247 # version is also 0.
1248 originating_invocid
= misc
.GUID("ffffffff-4700-4700-4700-000000b13228")
1254 rmd_invocid
= originating_invocid
1255 rmd_originating_usn
= originating_usn
1256 rmd_local_usn
= local_usn
1259 target_dn
.dn
.set_extended_component("RMD_ADDTIME", str(rmd_addtime
))
1260 target_dn
.dn
.set_extended_component("RMD_CHANGETIME", str(rmd_changetime
))
1261 target_dn
.dn
.set_extended_component("RMD_FLAGS", str(rmd_flags
))
1262 target_dn
.dn
.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid
))
1263 target_dn
.dn
.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn
))
1264 target_dn
.dn
.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn
))
1265 target_dn
.dn
.set_extended_component("RMD_VERSION", str(rmd_version
))
1268 missing_forward_links
.append(target_dn
)
1270 return (missing_forward_links
, error_count
)
1272 def check_dn(self
, obj
, attrname
, syntax_oid
):
1273 '''check a DN attribute for correctness'''
1275 obj_guid
= obj
['objectGUID'][0]
1277 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
1278 if reverse_link_name
is not None:
1279 reverse_syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(reverse_link_name
)
1281 reverse_syntax_oid
= None
1283 is_member_link
= attrname
in ("member", "memberOf")
1284 if is_member_link
and self
.quick_membership_checks
:
1287 error_count
, duplicate_dict
, unique_dict
= \
1288 self
.check_duplicate_links(obj
, attrname
, syntax_oid
,
1289 linkID
, reverse_link_name
)
1291 if len(duplicate_dict
) != 0:
1293 missing_forward_links
, missing_error_count
= \
1294 self
.find_missing_forward_links_from_backlinks(obj
,
1295 attrname
, syntax_oid
,
1298 error_count
+= missing_error_count
1300 forward_links
= [dn
for dn
in unique_dict
.values()]
1302 if missing_error_count
!= 0:
1303 self
.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1306 self
.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname
, obj
.dn
))
1307 for m
in missing_forward_links
:
1308 self
.report("Missing link '%s'" % (m
))
1309 if not self
.confirm_all("Schedule readding missing forward link for attribute %s" % attrname
,
1310 'fix_all_missing_forward_links'):
1311 self
.err_orphaned_backlink(m
.dn
, reverse_link_name
,
1312 obj
.dn
.extended_str(), obj
.dn
,
1313 attrname
, syntax_oid
,
1314 check_duplicates
=False)
1316 forward_links
+= [m
]
1317 for keystr
in duplicate_dict
.keys():
1318 d
= duplicate_dict
[keystr
]
1319 for dd
in d
["delete"]:
1320 self
.report("Duplicate link '%s'" % dd
)
1321 self
.report("Correct link '%s'" % d
["keep"])
1323 # We now construct the sorted dn values.
1324 # They're sorted by the objectGUID of the target
1325 # See dsdb_Dn.__cmp__()
1326 vals
= [str(dn
) for dn
in sorted(forward_links
)]
1327 self
.err_recover_forward_links(obj
, attrname
, vals
)
1328 # We should continue with the fixed values
1329 obj
[attrname
] = ldb
.MessageElement(vals
, 0, attrname
)
1331 for val
in obj
[attrname
]:
1332 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
1334 # all DNs should have a GUID component
1335 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1338 self
.err_missing_dn_GUID_component(obj
.dn
, attrname
, val
, dsdb_dn
,
1342 guidstr
= str(misc
.GUID(guid
))
1343 attrs
= ['isDeleted', 'replPropertyMetaData']
1345 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
1346 fixing_msDS_HasInstantiatedNCs
= True
1347 attrs
.append("instanceType")
1349 fixing_msDS_HasInstantiatedNCs
= False
1351 if reverse_link_name
is not None:
1352 attrs
.append(reverse_link_name
)
1354 # check its the right GUID
1356 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
1357 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1",
1358 "reveal_internals:0"
1360 except ldb
.LdbError
as e3
:
1361 (enum
, estr
) = e3
.args
1362 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1365 # We don't always want to
1366 error_count
+= self
.err_missing_target_dn_or_GUID(obj
.dn
,
1372 if fixing_msDS_HasInstantiatedNCs
:
1373 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
1374 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
1376 if str(dsdb_dn
) != str(val
):
1378 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
1381 # now we have two cases - the source object might or might not be deleted
1382 is_deleted
= 'isDeleted' in obj
and str(obj
['isDeleted'][0]).upper() == 'TRUE'
1383 target_is_deleted
= 'isDeleted' in res
[0] and str(res
[0]['isDeleted'][0]).upper() == 'TRUE'
1385 if is_deleted
and obj
.dn
not in self
.deleted_objects_containers
and linkID
:
1386 # A fully deleted object should not have any linked
1387 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1388 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1390 self
.err_undead_linked_attribute(obj
, attrname
, val
)
1393 elif target_is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
) and linkID
:
1394 # the target DN is not allowed to be deleted, unless the target DN is the
1395 # special Deleted Objects container
1397 local_usn
= dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN")
1399 if 'replPropertyMetaData' in res
[0]:
1400 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1401 res
[0]['replPropertyMetadata'][0])
1403 for o
in repl
.ctr
.array
:
1404 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1405 deleted_usn
= o
.local_usn
1406 if deleted_usn
>= int(local_usn
):
1407 # If the object was deleted after the link
1408 # was last modified then, clean it up here
1413 self
.err_deleted_dn(obj
.dn
, attrname
,
1414 val
, dsdb_dn
, res
[0].dn
, True)
1417 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
, False)
1420 # We should not check for incorrect
1421 # components on deleted links, as these are allowed to
1422 # go stale (we just need the GUID, not the name)
1423 rmd_blob
= dsdb_dn
.dn
.get_extended_component("RMD_FLAGS")
1425 if rmd_blob
is not None:
1426 rmd_flags
= int(rmd_blob
)
1428 # assert the DN matches in string form, where a reverse
1429 # link exists, otherwise (below) offer to fix it as a non-error.
1430 # The string form is essentially only kept for forensics,
1431 # as we always re-resolve by GUID in normal operations.
1432 if not rmd_flags
& 1 and reverse_link_name
is not None:
1433 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1435 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1436 res
[0].dn
, "string")
1439 if res
[0].dn
.get_extended_component("GUID") != dsdb_dn
.dn
.get_extended_component("GUID"):
1441 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1445 target_sid
= res
[0].dn
.get_extended_component("SID")
1446 link_sid
= dsdb_dn
.dn
.get_extended_component("SID")
1447 if link_sid
is None and target_sid
is not None:
1449 self
.err_dn_component_missing_target_sid(obj
.dn
, attrname
, val
,
1450 dsdb_dn
, target_sid
)
1452 if link_sid
!= target_sid
:
1454 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1458 # Only for non-links, not even forward-only links
1459 # (otherwise this breaks repl_meta_data):
1461 # Now we have checked the GUID and SID, offer to fix old
1462 # DN strings as a non-error (DNs, not links so no
1463 # backlink). Samba does not maintain this string
1464 # otherwise, so we don't increment error_count.
1465 if reverse_link_name
is None:
1466 if linkID
== 0 and str(res
[0].dn
) != str(dsdb_dn
.dn
):
1467 # Pass in the old/bad DN without the <GUID=...> part,
1468 # otherwise the LDB code will correct it on the way through
1469 # (Note: we still want to preserve the DSDB DN prefix in the
1470 # case of binary DNs)
1471 bad_dn
= dsdb_dn
.prefix
+ dsdb_dn
.dn
.get_linearized()
1472 self
.err_dn_string_component_old(obj
.dn
, attrname
, bad_dn
,
1476 if is_member_link
and self
.quick_membership_checks
:
1479 # check the reverse_link is correct if there should be one
1481 if reverse_link_name
in res
[0]:
1482 for v
in res
[0][reverse_link_name
]:
1483 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1484 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1485 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1487 if v_blob
is not None:
1488 v_rmd_flags
= int(v_blob
)
1491 if v_guid
== obj_guid
:
1494 if match_count
!= 1:
1495 if syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
or reverse_syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
:
1497 # Forward binary multi-valued linked attribute
1499 for w
in obj
[attrname
]:
1500 w_guid
= dsdb_Dn(self
.samdb
, w
.decode('utf8')).dn
.get_extended_component("GUID")
1504 if match_count
== forward_count
:
1507 for v
in obj
[attrname
]:
1508 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1509 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1510 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1512 if v_blob
is not None:
1513 v_rmd_flags
= int(v_blob
)
1519 if match_count
== expected_count
:
1522 diff_count
= expected_count
- match_count
1525 # If there's a backward link on binary multi-valued linked attribute,
1526 # let the check on the forward link remedy the value.
1527 # UNLESS, there is no forward link detected.
1528 if match_count
== 0:
1530 self
.err_orphaned_backlink(obj
.dn
, attrname
,
1535 # Only warn here and let the forward link logic fix it.
1536 self
.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1537 attrname
, expected_count
, str(obj
.dn
),
1538 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1541 assert not target_is_deleted
1543 self
.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1544 attrname
, expected_count
, str(obj
.dn
),
1545 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1547 # Loop until the difference between the forward and
1548 # the backward links is resolved.
1549 while diff_count
!= 0:
1552 if match_count
> 0 or diff_count
> 1:
1553 # TODO no method to fix these right now
1554 self
.report("ERROR: Can't fix missing "
1555 "multi-valued backlinks on %s" % str(dsdb_dn
.dn
))
1557 self
.err_missing_backlink(obj
, attrname
,
1558 obj
.dn
.extended_str(),
1563 self
.err_orphaned_backlink(res
[0].dn
, reverse_link_name
,
1564 obj
.dn
.extended_str(), obj
.dn
,
1565 attrname
, syntax_oid
)
1570 def find_repl_attid(self
, repl
, attid
):
1571 for o
in repl
.ctr
.array
:
1572 if o
.attid
== attid
:
1577 def get_originating_time(self
, val
, attid
):
1578 '''Read metadata properties and return the originating time for
1579 a given attributeId.
1581 :return: the originating time or 0 if not found
1584 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1585 o
= self
.find_repl_attid(repl
, attid
)
1587 return o
.originating_change_time
1590 def process_metadata(self
, dn
, val
):
1591 '''Read metadata properties and list attributes in it.
1592 raises KeyError if the attid is unknown.'''
1595 wrong_attids
= set()
1597 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1599 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1601 for o
in repl
.ctr
.array
:
1602 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1603 set_att
.add(att
.lower())
1604 list_attid
.append(o
.attid
)
1605 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
,
1606 is_schema_nc
=in_schema_nc
)
1607 if correct_attid
!= o
.attid
:
1608 wrong_attids
.add(o
.attid
)
1610 return (set_att
, list_attid
, wrong_attids
)
1612 def fix_metadata(self
, obj
, attr
):
1613 '''re-write replPropertyMetaData elements for a single attribute for a
1614 object. This is used to fix missing replPropertyMetaData elements'''
1615 guid_str
= str(ndr_unpack(misc
.GUID
, obj
['objectGUID'][0]))
1616 dn
= ldb
.Dn(self
.samdb
, "<GUID=%s>" % guid_str
)
1617 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attr
],
1618 controls
=["search_options:1:2",
1621 nmsg
= ldb
.Message()
1623 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
1624 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
1625 "Failed to fix metadata for attribute %s" % attr
):
1626 self
.report("Fixed metadata for attribute %s" % attr
)
1628 def ace_get_effective_inherited_type(self
, ace
):
1629 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
1633 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
1635 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
1637 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
1639 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
1645 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
1648 return str(ace
.object.inherited_type
)
1650 def lookup_class_schemaIDGUID(self
, cls
):
1651 if cls
in self
.class_schemaIDGUID
:
1652 return self
.class_schemaIDGUID
[cls
]
1654 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1655 res
= self
.samdb
.search(base
=self
.schema_dn
,
1657 attrs
=["schemaIDGUID"])
1658 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
1660 self
.class_schemaIDGUID
[cls
] = t
1663 def process_sd(self
, dn
, obj
):
1664 sd_attr
= "nTSecurityDescriptor"
1665 sd_val
= obj
[sd_attr
]
1667 sd
= ndr_unpack(security
.descriptor
, sd_val
[0])
1669 is_deleted
= 'isDeleted' in obj
and str(obj
['isDeleted'][0]).upper() == 'TRUE'
1671 # we don't fix deleted objects
1674 sd_clean
= security
.descriptor()
1675 sd_clean
.owner_sid
= sd
.owner_sid
1676 sd_clean
.group_sid
= sd
.group_sid
1677 sd_clean
.type = sd
.type
1678 sd_clean
.revision
= sd
.revision
1681 last_inherited_type
= None
1684 if sd
.sacl
is not None:
1686 for i
in range(0, len(aces
)):
1689 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1690 sd_clean
.sacl_add(ace
)
1693 t
= self
.ace_get_effective_inherited_type(ace
)
1697 if last_inherited_type
is not None:
1698 if t
!= last_inherited_type
:
1699 # if it inherited from more than
1700 # one type it's very likely to be broken
1702 # If not the recalculation will calculate
1707 last_inherited_type
= t
1710 if sd
.dacl
is not None:
1712 for i
in range(0, len(aces
)):
1715 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1716 sd_clean
.dacl_add(ace
)
1719 t
= self
.ace_get_effective_inherited_type(ace
)
1723 if last_inherited_type
is not None:
1724 if t
!= last_inherited_type
:
1725 # if it inherited from more than
1726 # one type it's very likely to be broken
1728 # If not the recalculation will calculate
1733 last_inherited_type
= t
1736 return (sd_clean
, sd
)
1738 if last_inherited_type
is None:
1744 cls
= obj
["objectClass"][-1]
1745 except KeyError as e
:
1749 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1750 attrs
=["isDeleted", "objectClass"],
1751 controls
=["show_recycled:1"])
1753 is_deleted
= 'isDeleted' in o
and str(o
['isDeleted'][0]).upper() == 'TRUE'
1755 # we don't fix deleted objects
1757 cls
= o
["objectClass"][-1]
1759 t
= self
.lookup_class_schemaIDGUID(cls
)
1761 if t
!= last_inherited_type
:
1763 return (sd_clean
, sd
)
1768 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
1769 '''re-write the SD due to incorrect inherited ACEs'''
1770 sd_attr
= "nTSecurityDescriptor"
1771 sd_val
= ndr_pack(sd
)
1772 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1774 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
1775 self
.report('Not fixing %s on %s\n' % (sd_attr
, dn
))
1778 nmsg
= ldb
.Message()
1780 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1781 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1782 "Failed to fix attribute %s" % sd_attr
):
1783 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1785 def err_wrong_default_sd(self
, dn
, sd
, diff
):
1786 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1787 sd_attr
= "nTSecurityDescriptor"
1788 sd_val
= ndr_pack(sd
)
1789 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1790 if sd
.owner_sid
is not None:
1791 sd_flags |
= security
.SECINFO_OWNER
1792 if sd
.group_sid
is not None:
1793 sd_flags |
= security
.SECINFO_GROUP
1795 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
1796 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
1801 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1802 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
1803 "Failed to reset attribute %s" % sd_attr
):
1804 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1806 def err_missing_sd_owner(self
, dn
, sd
):
1807 '''re-write the SD due to a missing owner or group'''
1808 sd_attr
= "nTSecurityDescriptor"
1809 sd_val
= ndr_pack(sd
)
1810 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
1812 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
1813 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
1816 nmsg
= ldb
.Message()
1818 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1820 # By setting the session_info to admin_session_info and
1821 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1822 # flags we cause the descriptor module to set the correct
1823 # owner and group on the SD, replacing the None/NULL values
1824 # for owner_sid and group_sid currently present.
1826 # The admin_session_info matches that used in provision, and
1827 # is the best guess we can make for an existing object that
1828 # hasn't had something specifically set.
1830 # This is important for the dns related naming contexts.
1831 self
.samdb
.set_session_info(self
.admin_session_info
)
1832 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1833 "Failed to fix metadata for attribute %s" % sd_attr
):
1834 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1835 self
.samdb
.set_session_info(self
.system_session_info
)
1837 def is_expired_tombstone(self
, dn
, repl_val
):
1838 if self
.check_expired_tombstones
:
1839 # This is not the default, it's just
1840 # used to keep dbcheck tests work with
1841 # old static provision dumps
1844 if dn
in self
.deleted_objects_containers
:
1845 # The Deleted Objects container will look like an expired
1849 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, repl_val
)
1851 isDeleted
= self
.find_repl_attid(repl
, drsuapi
.DRSUAPI_ATTID_isDeleted
)
1853 delete_time
= samba
.nttime2unix(isDeleted
.originating_change_time
)
1854 current_time
= time
.time()
1856 tombstone_delta
= self
.tombstoneLifetime
* (24 * 60 * 60)
1858 delta
= current_time
- delete_time
1859 if delta
<= tombstone_delta
:
1862 expunge_time
= delete_time
+ tombstone_delta
1864 delta_days
= delta
/ (24 * 60 * 60)
1867 self
.report("SKIPPING additional checks on object "
1868 "%s which very recently "
1869 "became an expired tombstone (normal)" % dn
)
1870 self
.report("INFO: it is expected this will be expunged "
1871 "by the next daily task some time after %s, "
1873 % (time
.ctime(expunge_time
), delta
// (60 * 60)))
1875 self
.report("SKIPPING: object %s is an expired tombstone" % dn
)
1876 self
.report("INFO: it was expected this object would have "
1877 "been expunged soon after"
1879 % (time
.ctime(expunge_time
), delta_days
))
1881 self
.report("isDeleted: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1884 isDeleted
.originating_invocation_id
,
1885 isDeleted
.originating_usn
,
1886 isDeleted
.local_usn
,
1887 time
.ctime(samba
.nttime2unix(isDeleted
.originating_change_time
))))
1888 self
.expired_tombstones
+= 1
1891 def find_changes_after_deletion(self
, repl_val
):
1892 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, repl_val
)
1894 isDeleted
= self
.find_repl_attid(repl
, drsuapi
.DRSUAPI_ATTID_isDeleted
)
1896 delete_time
= samba
.nttime2unix(isDeleted
.originating_change_time
)
1898 tombstone_delta
= self
.tombstoneLifetime
* (24 * 60 * 60)
1901 for o
in repl
.ctr
.array
:
1902 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1905 if o
.local_usn
<= isDeleted
.local_usn
:
1908 if o
.originating_change_time
<= isDeleted
.originating_change_time
:
1911 change_time
= samba
.nttime2unix(o
.originating_change_time
)
1913 delta
= change_time
- delete_time
1914 if delta
<= tombstone_delta
:
1917 # If the modification happened after the tombstone lifetime
1918 # has passed, we have a bug as the object might be deleted
1919 # already on other DCs and won't be able to replicate
1923 return found
, isDeleted
1925 def has_changes_after_deletion(self
, dn
, repl_val
):
1926 found
, isDeleted
= self
.find_changes_after_deletion(repl_val
)
1930 def report_attid(o
):
1932 attname
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1934 attname
= "<unknown:0x%x08x>" % o
.attid
1936 self
.report("%s: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1937 attname
, o
.attid
, o
.version
,
1938 o
.originating_invocation_id
,
1941 time
.ctime(samba
.nttime2unix(o
.originating_change_time
))))
1943 self
.report("ERROR: object %s, has changes after deletion" % dn
)
1944 report_attid(isDeleted
)
1950 def err_changes_after_deletion(self
, dn
, repl_val
):
1951 found
, isDeleted
= self
.find_changes_after_deletion(repl_val
)
1953 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1954 rdn_attr
= dn
.get_rdn_name()
1955 rdn_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(rdn_attr
,
1956 is_schema_nc
=in_schema_nc
)
1960 if o
.attid
== rdn_attid
:
1962 if o
.attid
== drsuapi
.DRSUAPI_ATTID_name
:
1964 if o
.attid
== drsuapi
.DRSUAPI_ATTID_lastKnownParent
:
1967 attname
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1969 attname
= "<unknown:0x%x08x>" % o
.attid
1970 unexpected
.append(attname
)
1972 if len(unexpected
) > 0:
1973 self
.report('Unexpeted attributes: %s' % ",".join(unexpected
))
1974 self
.report('Not fixing changes after deletion bug')
1977 if not self
.confirm_all('Delete broken tombstone object %s deleted %s days ago?' % (
1978 dn
, self
.tombstoneLifetime
), 'fix_changes_after_deletion_bug'):
1979 self
.report('Not fixing changes after deletion bug')
1982 if self
.do_delete(dn
, ["relax:0"],
1983 "Failed to remove DN %s" % dn
):
1984 self
.report("Removed DN %s" % dn
)
1986 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
1987 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1992 # Search for a zero invocationID
1993 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1997 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1998 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1999 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
2000 % (dn
, o
.attid
, o
.version
,
2001 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
2002 self
.samdb
.get_invocation_id()))
2006 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
2007 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
2010 now
= samba
.unix2nttime(int(time
.time()))
2013 # Search for a zero invocationID
2014 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
2018 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
2019 o
.version
= o
.version
+ 1
2020 o
.originating_change_time
= now
2021 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
2022 o
.originating_usn
= seq
2026 replBlob
= ndr_pack(repl
)
2030 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
2031 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
2032 self
.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr
, dn
))
2035 nmsg
= ldb
.Message()
2037 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
2038 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
2039 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
2040 "Failed to fix attribute %s" % attr
):
2041 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
2043 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
2044 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
2048 # Search for an invalid attid
2050 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
2052 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
2055 def err_replmetadata_incorrect_attid(self
, dn
, attr
, repl_meta_data
, wrong_attids
):
2056 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
2061 remove_attid
= set()
2064 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
2067 # Sort the array, except for the last element. This strange
2068 # construction, creating a new list, due to bugs in samba's
2069 # array handling in IDL generated objects.
2070 ctr
.array
= sorted(ctr
.array
[:], key
=lambda o
: o
.attid
)
2071 # Now walk it in reverse, so we see the low (and so incorrect,
2072 # the correct values are above 0x80000000) values first and
2073 # remove the 'second' value we see.
2074 for o
in reversed(ctr
.array
):
2075 print("%s: 0x%08x" % (dn
, o
.attid
))
2076 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
2077 if att
.lower() in set_att
:
2078 self
.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att
, attr
, dn
))
2079 if not self
.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
2080 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
),
2081 'fix_replmetadata_duplicate_attid'):
2082 self
.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
2083 % (o
.attid
, att
, attr
, dn
))
2086 remove_attid
.add(o
.attid
)
2087 # We want to set the metadata for the most recent
2088 # update to have been applied locally, that is the metadata
2089 # matching the (eg string) value in the attribute
2090 if o
.local_usn
> hash_att
[att
].local_usn
:
2091 # This is always what we would have sent over DRS,
2092 # because the DRS server will have sent the
2093 # msDS-IntID, but with the values from both
2094 # attribute entries.
2095 hash_att
[att
].version
= o
.version
2096 hash_att
[att
].originating_change_time
= o
.originating_change_time
2097 hash_att
[att
].originating_invocation_id
= o
.originating_invocation_id
2098 hash_att
[att
].originating_usn
= o
.originating_usn
2099 hash_att
[att
].local_usn
= o
.local_usn
2101 # Do not re-add the value to the set or overwrite the hash value
2105 set_att
.add(att
.lower())
2107 # Generate a real list we can sort on properly
2108 new_list
= [o
for o
in ctr
.array
if o
.attid
not in remove_attid
]
2110 if (len(wrong_attids
) > 0):
2112 if o
.attid
in wrong_attids
:
2113 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
2114 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
, is_schema_nc
=in_schema_nc
)
2115 self
.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr
, dn
))
2116 if not self
.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
2117 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
), 'fix_replmetadata_wrong_attid'):
2118 self
.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
2119 % (o
.attid
, correct_attid
, att
, attr
, dn
))
2122 o
.attid
= correct_attid
2124 # Sort the array, (we changed the value so must re-sort)
2125 new_list
[:] = sorted(new_list
[:], key
=lambda o
: o
.attid
)
2127 # If we did not already need to fix it, then ask about sorting
2129 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
2130 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
2131 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
2132 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
2135 # The actual sort done is done at the top of the function
2137 ctr
.count
= len(new_list
)
2138 ctr
.array
= new_list
2139 replBlob
= ndr_pack(repl
)
2141 nmsg
= ldb
.Message()
2143 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
2144 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
2145 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
2146 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
2147 "Failed to fix attribute %s" % attr
):
2148 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
2150 def is_deleted_deleted_objects(self
, obj
):
2152 if "description" not in obj
:
2153 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
2155 if "showInAdvancedViewOnly" not in obj
or str(obj
['showInAdvancedViewOnly'][0]).upper() == 'FALSE':
2156 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
2158 if "objectCategory" not in obj
:
2159 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
2161 if "isCriticalSystemObject" not in obj
or str(obj
['isCriticalSystemObject'][0]).upper() == 'FALSE':
2162 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
2164 if "isRecycled" in obj
:
2165 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
2167 if "isDeleted" in obj
and str(obj
['isDeleted'][0]).upper() == 'FALSE':
2168 self
.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj
.dn
)
2170 if "objectClass" not in obj
or (len(obj
['objectClass']) != 2 or
2171 str(obj
['objectClass'][0]) != 'top' or
2172 str(obj
['objectClass'][1]) != 'container'):
2173 self
.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj
.dn
)
2175 if "systemFlags" not in obj
or str(obj
['systemFlags'][0]) != '-1946157056':
2176 self
.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj
.dn
)
2180 def err_deleted_deleted_objects(self
, obj
):
2181 nmsg
= ldb
.Message()
2182 nmsg
.dn
= dn
= obj
.dn
2184 if "description" not in obj
:
2185 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
2186 if "showInAdvancedViewOnly" not in obj
:
2187 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
2188 if "objectCategory" not in obj
:
2189 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
2190 if "isCriticalSystemObject" not in obj
:
2191 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
2192 if "isRecycled" in obj
:
2193 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
2195 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2196 nmsg
["systemFlags"] = ldb
.MessageElement("-1946157056", ldb
.FLAG_MOD_REPLACE
, "systemFlags")
2197 nmsg
["objectClass"] = ldb
.MessageElement(["top", "container"], ldb
.FLAG_MOD_REPLACE
, "objectClass")
2199 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
2200 % (dn
), 'fix_deleted_deleted_objects'):
2201 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
2204 if self
.do_modify(nmsg
, ["relax:0"],
2205 "Failed to fix Deleted Objects container %s" % dn
):
2206 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
2208 def err_replica_locations(self
, obj
, cross_ref
, attr
):
2209 nmsg
= ldb
.Message()
2211 target
= self
.samdb
.get_dsServiceName()
2213 if self
.samdb
.am_rodc():
2214 self
.report('Not fixing %s %s for the RODC' % (attr
, obj
.dn
))
2217 if not self
.confirm_all('Add yourself to the replica locations for %s?'
2218 % (obj
.dn
), 'fix_replica_locations'):
2219 self
.report('Not fixing missing/incorrect attributes on %s\n' % (obj
.dn
))
2222 nmsg
[attr
] = ldb
.MessageElement(target
, ldb
.FLAG_MOD_ADD
, attr
)
2223 if self
.do_modify(nmsg
, [], "Failed to add %s for %s" % (attr
, obj
.dn
)):
2224 self
.report("Fixed %s for %s" % (attr
, obj
.dn
))
2226 def is_fsmo_role(self
, dn
):
2227 if dn
== self
.samdb
.domain_dn
:
2229 if dn
== self
.infrastructure_dn
:
2231 if dn
== self
.naming_dn
:
2233 if dn
== self
.schema_dn
:
2235 if dn
== self
.rid_dn
:
2240 def calculate_instancetype(self
, dn
):
2242 nc_root
= self
.samdb
.get_nc_root(dn
)
2244 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
2246 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
2247 except ldb
.LdbError
as e4
:
2248 (enum
, estr
) = e4
.args
2249 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2252 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
2253 if self
.write_ncs
is not None and str(nc_root
) in [str(x
) for x
in self
.write_ncs
]:
2254 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
2258 def get_wellknown_sd(self
, dn
):
2259 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
2261 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
2262 return ndr_unpack(security
.descriptor
,
2263 descriptor_fn(domain_sid
,
2264 name_map
=self
.name_map
))
2268 def find_checkable_attrs(self
, dn
, requested_attrs
):
2269 """A helper function for check_object() that calculates the list of
2270 attributes that need to be checked, and returns that as a list
2271 in the original case, and a set normalised to lowercase (for
2272 easy existence checks).
2274 if requested_attrs
is None:
2277 attrs
= list(requested_attrs
)
2279 lc_attrs
= set(x
.lower() for x
in attrs
)
2282 if a
.lower() not in lc_attrs
:
2284 lc_attrs
.add(a
.lower())
2286 if ("dn" in lc_attrs
or
2287 "distinguishedname" in lc_attrs
or
2288 dn
.get_rdn_name().lower() in lc_attrs
):
2289 attrs
.append("name")
2290 lc_attrs
.add('name')
2292 if 'name' in lc_attrs
:
2293 for a
in (dn
.get_rdn_name(),
2298 need_replPropertyMetaData
= False
2300 need_replPropertyMetaData
= True
2303 linkID
, _
= self
.get_attr_linkID_and_reverse_name(a
)
2308 need_replPropertyMetaData
= True
2310 if need_replPropertyMetaData
:
2311 add_attr("replPropertyMetaData")
2313 add_attr("objectGUID")
2315 return attrs
, lc_attrs
2317 def check_object(self
, dn
, requested_attrs
=None):
2318 '''check one object'''
2320 self
.report("Checking object %s" % dn
)
2322 # search attrs are used to find the attributes, lc_attrs are
2323 # used for existence checks
2324 search_attrs
, lc_attrs
= self
.find_checkable_attrs(dn
, requested_attrs
)
2328 sd_flags |
= security
.SECINFO_OWNER
2329 sd_flags |
= security
.SECINFO_GROUP
2330 sd_flags |
= security
.SECINFO_DACL
2331 sd_flags |
= security
.SECINFO_SACL
2333 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
2338 "sd_flags:1:%d" % sd_flags
,
2339 "reveal_internals:0",
2342 except ldb
.LdbError
as e10
:
2343 (enum
, estr
) = e10
.args
2344 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2345 if self
.in_transaction
:
2346 self
.report("ERROR: Object %s disappeared during check" % dn
)
2351 self
.report("ERROR: Object %s failed to load during check" % dn
)
2355 set_attrs_from_md
= set()
2356 set_attrs_seen
= set()
2357 got_objectclass
= False
2359 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
2361 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
2362 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
2364 # We have no deleted objects DN for schema, and we check for this above for the other
2366 deleted_objects_dn
= None
2368 object_rdn_attr
= None
2369 object_rdn_val
= None
2373 repl_meta_data_val
= None
2375 for attrname
in obj
:
2376 if attrname
.lower() == 'isdeleted':
2377 if str(obj
[attrname
][0]) != "FALSE":
2380 if attrname
.lower() == 'systemflags':
2381 systemFlags
= int(obj
[attrname
][0])
2383 if attrname
.lower() == 'replpropertymetadata':
2384 repl_meta_data_val
= obj
[attrname
][0]
2386 if isDeleted
and repl_meta_data_val
:
2387 if self
.has_changes_after_deletion(dn
, repl_meta_data_val
):
2389 self
.err_changes_after_deletion(dn
, repl_meta_data_val
)
2391 if self
.is_expired_tombstone(dn
, repl_meta_data_val
):
2394 for attrname
in obj
:
2395 if attrname
== 'dn' or attrname
== "distinguishedName":
2398 if attrname
.lower() == 'objectclass':
2399 got_objectclass
= True
2401 if attrname
.lower() == "name":
2402 if len(obj
[attrname
]) != 1:
2403 self
.unfixable_errors
+= 1
2404 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2405 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2407 name_val
= str(obj
[attrname
][0])
2409 if attrname
.lower() == str(obj
.dn
.get_rdn_name()).lower():
2410 object_rdn_attr
= attrname
2411 if len(obj
[attrname
]) != 1:
2412 self
.unfixable_errors
+= 1
2413 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2414 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2416 object_rdn_val
= str(obj
[attrname
][0])
2418 if attrname
.lower() == 'replpropertymetadata':
2419 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
][0]):
2421 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
][0])
2422 # We don't continue, as we may also have other fixes for this attribute
2423 # based on what other attributes we see.
2426 (set_attrs_from_md
, list_attid_from_md
, wrong_attids
) \
2427 = self
.process_metadata(dn
, obj
[attrname
][0])
2430 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
2433 if len(set_attrs_from_md
) < len(list_attid_from_md
) \
2434 or len(wrong_attids
) > 0 \
2435 or sorted(list_attid_from_md
) != list_attid_from_md
:
2437 self
.err_replmetadata_incorrect_attid(dn
, attrname
, obj
[attrname
][0], wrong_attids
)
2440 # Here we check that the first attid is 0
2442 if list_attid_from_md
[0] != 0:
2443 self
.unfixable_errors
+= 1
2444 self
.report("ERROR: Not fixing incorrect initial attributeID in '%s' on '%s', it should be objectClass" %
2445 (attrname
, str(dn
)))
2449 if attrname
.lower() == 'ntsecuritydescriptor':
2450 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
2451 if sd_broken
is not None:
2452 self
.err_wrong_sd(dn
, sd
, sd_broken
)
2456 if sd
.owner_sid
is None or sd
.group_sid
is None:
2457 self
.err_missing_sd_owner(dn
, sd
)
2461 if self
.reset_well_known_acls
:
2463 well_known_sd
= self
.get_wellknown_sd(dn
)
2467 current_sd
= ndr_unpack(security
.descriptor
,
2470 diff
= get_diff_sds(well_known_sd
, current_sd
, security
.dom_sid(self
.samdb
.get_domain_sid()))
2472 self
.err_wrong_default_sd(dn
, well_known_sd
, diff
)
2477 if attrname
.lower() == 'objectclass':
2478 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
2479 # Do not consider the attribute incorrect if:
2480 # - The sorted (alphabetically) list is the same, including case
2481 # - The first and last elements are the same
2483 # This avoids triggering an error due to
2484 # non-determinism in the sort routine in (at least)
2485 # 4.3 and earlier, and the fact that any AUX classes
2486 # in these attributes are also not sorted when
2487 # imported from Windows (they are just in the reverse
2488 # order of last set)
2489 if sorted(normalised
) != sorted(obj
[attrname
]) \
2490 or normalised
[0] != obj
[attrname
][0] \
2491 or normalised
[-1] != obj
[attrname
][-1]:
2492 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
2496 if attrname
.lower() == 'userparameters':
2497 userparams
= obj
[attrname
][0]
2498 if userparams
== b
' ':
2500 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
2503 elif userparams
[:16] == b
'\x20\x00' * 8:
2504 # This is the correct, normal prefix
2507 elif userparams
[:20] == b
'IAAgACAAIAAgACAAIAAg':
2508 # this is the typical prefix from a windows migration
2510 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
2513 #43:00:00:00:74:00:00:00:78
2514 elif (userparams
[1] != 0 and
2515 userparams
[3] != 0 and
2516 userparams
[5] != 0 and
2517 userparams
[7] != 0 and
2518 userparams
[9] != 0):
2519 # This is a prefix that is not in UTF-16 format
2520 # for the space or munged dialback prefix
2522 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
2525 elif len(userparams
) % 2 != 0:
2526 # This is a value that isn't even in length
2528 self
.err_odd_userParameters(obj
, attrname
)
2531 elif (userparams
[1] == 0 and
2532 userparams
[2] == 0 and
2533 userparams
[3] == 0 and
2534 userparams
[4] != 0 and
2535 userparams
[5] == 0):
2536 # This is a prefix that would happen if a
2537 # SAMR-written value was replicated from a Samba
2538 # 4.1 server to a working server
2540 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
2543 if attrname
.lower() == 'attributeid' or attrname
.lower() == 'governsid':
2544 if obj
[attrname
][0] in self
.attribute_or_class_ids
:
2545 self
.unfixable_errors
+= 1
2546 self
.report('Error: %s %s on %s already exists as an attributeId or governsId'
2547 % (attrname
, obj
.dn
, obj
[attrname
][0]))
2549 self
.attribute_or_class_ids
.add(obj
[attrname
][0])
2551 # check for empty attributes
2552 for val
in obj
[attrname
]:
2554 self
.err_empty_attribute(dn
, attrname
)
2558 # get the syntax oid for the attribute, so we can can have
2559 # special handling for some specific attribute types
2561 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
2562 except Exception as msg
:
2563 self
.err_unknown_attribute(obj
, attrname
)
2567 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
2569 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
2570 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
2571 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
2573 set_attrs_seen
.add(attrname
.lower())
2575 if syntax_oid
in [dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
2576 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
2577 # it's some form of DN, do specialised checking on those
2578 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
2582 # check for incorrectly normalised attributes
2583 for val
in obj
[attrname
]:
2586 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
2587 if len(normalised
) != 1 or normalised
[0] != val
:
2588 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
2592 if len(obj
[attrname
]) != len(values
):
2593 self
.err_duplicate_values(dn
, attrname
, obj
[attrname
], list(values
))
2597 if attrname
.lower() == "instancetype":
2598 calculated_instancetype
= self
.calculate_instancetype(dn
)
2599 if len(obj
["instanceType"]) != 1 or int(obj
["instanceType"][0]) != calculated_instancetype
:
2601 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
2603 if not got_objectclass
and ("*" in lc_attrs
or "objectclass" in lc_attrs
):
2605 self
.err_missing_objectclass(dn
)
2607 if ("*" in lc_attrs
or "name" in lc_attrs
):
2608 if name_val
is None:
2609 self
.unfixable_errors
+= 1
2610 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
2611 if object_rdn_attr
is None:
2612 self
.unfixable_errors
+= 1
2613 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
2615 if name_val
is not None:
2617 controls
= ["show_recycled:1", "relax:0"]
2619 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
2620 parent_dn
= deleted_objects_dn
2621 controls
+= ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
]
2622 if parent_dn
is None:
2623 parent_dn
= obj
.dn
.parent()
2626 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
2627 except ValueError as e
:
2628 self
.unfixable_errors
+= 1
2629 self
.report(f
"ERROR: could not handle parent DN '{parent_dn}': "
2630 "skipping RDN checks")
2632 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
2634 if obj
.dn
== deleted_objects_dn
:
2635 expected_dn
= obj
.dn
2637 if expected_dn
!= obj
.dn
:
2639 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
,
2640 object_rdn_val
, name_val
, controls
)
2641 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
2642 self
.unfixable_errors
+= 1
2643 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
,
2648 if repl_meta_data_val
:
2649 if obj
.dn
== deleted_objects_dn
:
2650 isDeletedAttId
= 131120
2651 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2653 expectedTimeDo
= 2650466015990000000
2654 originating
= self
.get_originating_time(repl_meta_data_val
, isDeletedAttId
)
2655 if originating
!= expectedTimeDo
:
2656 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
2657 nmsg
= ldb
.Message()
2659 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2661 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
2664 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
2666 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
2668 self
.report("On object %s" % dn
)
2671 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
2672 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
2673 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
2675 self
.fix_metadata(obj
, att
)
2677 if self
.is_fsmo_role(dn
):
2678 if "fSMORoleOwner" not in obj
and ("*" in lc_attrs
or "fsmoroleowner" in lc_attrs
):
2679 self
.err_no_fsmoRoleOwner(obj
)
2683 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
2684 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
2685 controls
=["show_recycled:1", "show_deleted:1"])
2686 except ldb
.LdbError
as e11
:
2687 (enum
, estr
) = e11
.args
2688 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2690 self
.report("WARNING: parent object not found for %s" % (obj
.dn
))
2691 self
.report("Not moving to LostAndFound "
2692 "(tombstone garbage collection in progress?)")
2694 self
.err_missing_parent(obj
)
2699 if dn
in self
.deleted_objects_containers
and '*' in lc_attrs
:
2700 if self
.is_deleted_deleted_objects(obj
):
2701 self
.err_deleted_deleted_objects(obj
)
2704 for (dns_part
, msg
) in self
.dns_partitions
:
2705 if dn
== dns_part
and 'repsFrom' in obj
:
2706 location
= "msDS-NC-Replica-Locations"
2707 if self
.samdb
.am_rodc():
2708 location
= "msDS-NC-RO-Replica-Locations"
2710 if location
not in msg
:
2711 # There are no replica locations!
2712 self
.err_replica_locations(obj
, msg
.dn
, location
)
2717 for loc
in msg
[location
]:
2718 if str(loc
) == self
.samdb
.get_dsServiceName():
2721 # This DC is not in the replica locations
2722 self
.err_replica_locations(obj
, msg
.dn
, location
)
2725 if dn
== self
.server_ref_dn
:
2726 # Check we have a valid RID Set
2727 if "*" in lc_attrs
or "ridsetreferences" in lc_attrs
:
2728 if "rIDSetReferences" not in obj
:
2729 # NO RID SET reference
2730 # We are RID master, allocate it.
2733 if self
.is_rid_master
:
2734 # Allocate a RID Set
2735 if self
.confirm_all('Allocate the missing RID set for '
2737 'fix_missing_rid_set_master'):
2739 # We don't have auto-transaction logic on
2740 # extended operations, so we have to do it
2743 self
.samdb
.transaction_start()
2746 self
.samdb
.create_own_rid_set()
2749 self
.samdb
.transaction_cancel()
2752 self
.samdb
.transaction_commit()
2754 elif not self
.samdb
.am_rodc():
2755 self
.report("No RID Set found for this server: %s, "
2756 "and we are not the RID Master (so can "
2757 "not self-allocate)" % dn
)
2759 # Check some details of our own RID Set
2761 # Note that the attributes have very bad names. From ridalloc.c:
2763 # Note: the RID allocation attributes in AD are very badly named.
2764 # Here is what we think they really do:
2766 # in RID Set object:
2767 # - rIDPreviousAllocationPool: the pool which a DC is currently
2768 # pulling RIDs from. Managed by client DC
2770 # - rIDAllocationPool: the pool that the DC will switch to next,
2771 # when rIDPreviousAllocationPool is exhausted. Managed by RID
2774 # - rIDNextRID: the last RID allocated by this DC. Managed by
2777 # in RID Manager object:
2778 # - rIDAvailablePool: the pool where the RID Manager gets new rID
2779 # pools from when it gets a EXOP_RID_ALLOC getncchanges call
2780 # (or locally when the DC is the RID Manager)
2782 if dn
== self
.rid_set_dn
:
2783 pool_attrs
= ["rIDAllocationPool", "rIDPreviousAllocationPool"]
2785 res
= self
.samdb
.search(base
=self
.rid_set_dn
, scope
=ldb
.SCOPE_BASE
,
2788 for pool_attr
in pool_attrs
:
2789 if pool_attr
not in res
[0]:
2792 pool
= int(res
[0][pool_attr
][0])
2795 low
= 0xFFFFFFFF & pool
2797 if pool
!= 0 and low
>= high
:
2798 self
.report("Invalid RID pool %d-%d, %d >= %d!" %
2799 (low
, high
, low
, high
))
2800 self
.unfixable_errors
+= 1
2802 if "rIDAllocationPool" not in res
[0]:
2803 self
.report("No rIDAllocationPool found in %s" % dn
)
2804 self
.unfixable_errors
+= 1
2807 next_free_rid
, high
= self
.samdb
.free_rid_bounds()
2808 except ldb
.LdbError
as err
:
2809 enum
, estr
= err
.args
2810 self
.report("Couldn't get available RIDs: %s" % estr
)
2811 self
.unfixable_errors
+= 1
2813 # Check the remainder of this pool for conflicts. If
2814 # ridalloc_allocate_rid() moves to a new pool, this
2815 # will be above high, so we will stop.
2816 domain_sid
= self
.samdb
.get_domain_sid()
2817 while next_free_rid
<= high
:
2818 sid
= "%s-%d" % (domain_sid
, next_free_rid
)
2820 res
= self
.samdb
.search(base
="<SID=%s>" % sid
,
2821 scope
=ldb
.SCOPE_BASE
,
2823 except ldb
.LdbError
as e
:
2824 (enum
, estr
) = e
.args
2825 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2829 self
.report("SID %s for %s conflicts with our current "
2830 "RID set in %s" % (sid
, res
[0].dn
, dn
))
2833 if self
.confirm_all('Fix conflict between SID %s and '
2834 'RID pool in %s by allocating a '
2837 'fix_sid_rid_set_conflict'):
2838 self
.samdb
.transaction_start()
2840 # This will burn RIDs, which will move
2841 # past the conflict. We then check again
2842 # to see if the new RID conflicts, until
2843 # the end of the current pool. We don't
2844 # look at the next pool to avoid burning
2845 # all RIDs in one go in some strange
2849 allocated_rid
= self
.samdb
.allocate_rid()
2850 if allocated_rid
>= next_free_rid
:
2851 next_free_rid
= allocated_rid
+ 1
2854 self
.samdb
.transaction_cancel()
2857 self
.samdb
.transaction_commit()
2865 ################################################################
2866 # check special @ROOTDSE attributes
2867 def check_rootdse(self
):
2868 '''check the @ROOTDSE special object'''
2869 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
2871 self
.report("Checking object %s" % dn
)
2872 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
2874 self
.report("Object %s disappeared during check" % dn
)
2879 # check that the dsServiceName is in GUID form
2880 if 'dsServiceName' not in obj
:
2881 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
2882 return error_count
+ 1
2884 if not str(obj
['dsServiceName'][0]).startswith('<GUID='):
2885 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2887 if not self
.confirm('Change dsServiceName to GUID form?'):
2889 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0].decode('utf8')),
2890 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
2891 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
2894 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
2895 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
2896 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
2897 self
.report("Changed dsServiceName to GUID form")
2900 ###############################################
2901 # re-index the database
2903 def reindex_database(self
):
2904 '''re-index the whole database'''
2906 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
2907 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
2908 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
2909 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
2911 ###############################################
2913 def reset_modules(self
):
2914 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2916 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
2917 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
2918 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)