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
, b64encode
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 (
34 get_deletedobjects_descriptor
,
37 from samba
.auth
import system_session
, admin_session
38 from samba
.netcmd
import CommandError
39 from samba
.netcmd
.fsmo
import get_fsmo_roleowner
40 from samba
.colour
import c_RED
, c_DARK_YELLOW
, c_DARK_CYAN
, c_DARK_GREEN
42 def dump_attr_values(vals
):
43 """Stringify a value list, using utf-8 if possible (which some tests
44 want), or the python bytes representation otherwise (with leading
45 'b' and escapes like b'\x00').
50 result
.append(value
.decode('utf-8'))
51 except UnicodeDecodeError:
52 result
.append(repr(value
))
53 return ','.join(result
)
56 class dbcheck(object):
57 """check a SAM database for errors"""
59 def __init__(self
, samdb
, samdb_schema
=None, verbose
=False, fix
=False,
60 yes
=False, quiet
=False, in_transaction
=False,
61 quick_membership_checks
=False,
62 reset_well_known_acls
=False,
63 check_expired_tombstones
=False,
66 self
.dict_oid_name
= None
67 self
.samdb_schema
= (samdb_schema
or samdb
)
68 self
.verbose
= verbose
73 self
.remove_all_unknown_attributes
= False
74 self
.remove_all_empty_attributes
= False
75 self
.fix_all_normalisation
= False
76 self
.fix_all_duplicates
= False
77 self
.fix_all_DN_GUIDs
= False
78 self
.fix_all_binary_dn
= False
79 self
.remove_implausible_deleted_DN_links
= False
80 self
.remove_plausible_deleted_DN_links
= False
81 self
.fix_all_string_dn_component_mismatch
= False
82 self
.fix_all_GUID_dn_component_mismatch
= False
83 self
.fix_all_SID_dn_component_mismatch
= False
84 self
.fix_all_SID_dn_component_missing
= False
85 self
.fix_all_old_dn_string_component_mismatch
= False
86 self
.fix_all_metadata
= False
87 self
.fix_time_metadata
= False
88 self
.fix_undead_linked_attributes
= False
89 self
.fix_all_missing_backlinks
= False
90 self
.fix_all_orphaned_backlinks
= False
91 self
.fix_all_missing_forward_links
= False
92 self
.duplicate_link_cache
= dict()
93 self
.recover_all_forward_links
= False
94 self
.fix_rmd_flags
= False
95 self
.fix_ntsecuritydescriptor
= False
96 self
.fix_ntsecuritydescriptor_owner_group
= False
97 self
.seize_fsmo_role
= False
98 self
.move_to_lost_and_found
= False
99 self
.fix_instancetype
= False
100 self
.fix_replmetadata_zero_invocationid
= False
101 self
.fix_replmetadata_duplicate_attid
= False
102 self
.fix_replmetadata_wrong_attid
= False
103 self
.fix_replmetadata_unsorted_attid
= False
104 self
.fix_deleted_deleted_objects
= False
106 self
.fix_base64_userparameters
= False
107 self
.fix_utf8_userparameters
= False
108 self
.fix_doubled_userparameters
= False
109 self
.fix_sid_rid_set_conflict
= False
110 self
.quick_membership_checks
= quick_membership_checks
111 self
.reset_well_known_acls
= reset_well_known_acls
112 self
.check_expired_tombstones
= check_expired_tombstones
113 self
.expired_tombstones
= 0
114 self
.reset_all_well_known_acls
= False
115 self
.in_transaction
= in_transaction
116 self
.infrastructure_dn
= ldb
.Dn(samdb
, "CN=Infrastructure," + samdb
.domain_dn())
117 self
.naming_dn
= ldb
.Dn(samdb
, "CN=Partitions,%s" % samdb
.get_config_basedn())
118 self
.schema_dn
= samdb
.get_schema_basedn()
119 self
.rid_dn
= ldb
.Dn(samdb
, "CN=RID Manager$,CN=System," + samdb
.domain_dn())
120 self
.ntds_dsa
= ldb
.Dn(samdb
, samdb
.get_dsServiceName())
121 self
.class_schemaIDGUID
= {}
122 self
.wellknown_sds
= get_wellknown_sds(self
.samdb
)
123 self
.fix_all_missing_objectclass
= False
124 self
.fix_missing_deleted_objects
= False
125 self
.fix_replica_locations
= False
126 self
.fix_missing_rid_set_master
= False
127 self
.fix_changes_after_deletion_bug
= False
130 self
.link_id_cache
= {}
133 base_dn
= "CN=DnsAdmins,%s" % samdb
.get_wellknown_dn(
134 samdb
.get_default_basedn(),
135 dsdb
.DS_GUID_USERS_CONTAINER
)
136 res
= samdb
.search(base
=base_dn
, scope
=ldb
.SCOPE_BASE
,
138 dnsadmins_sid
= ndr_unpack(security
.dom_sid
, res
[0]["objectSid"][0])
139 self
.name_map
['DnsAdmins'] = str(dnsadmins_sid
)
140 except ldb
.LdbError
as e5
:
141 (enum
, estr
) = e5
.args
142 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
145 self
.system_session_info
= system_session()
146 self
.admin_session_info
= admin_session(None, samdb
.get_domain_sid())
148 res
= self
.samdb
.search(base
=self
.ntds_dsa
, scope
=ldb
.SCOPE_BASE
, attrs
=['msDS-hasMasterNCs', 'hasMasterNCs'])
149 if "msDS-hasMasterNCs" in res
[0]:
150 self
.write_ncs
= res
[0]["msDS-hasMasterNCs"]
152 # If the Forest Level is less than 2003 then there is no
153 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
154 # no need to merge as all the NCs that are in hasMasterNCs must
155 # also be in msDS-hasMasterNCs (but not the opposite)
156 if "hasMasterNCs" in res
[0]:
157 self
.write_ncs
= res
[0]["hasMasterNCs"]
159 self
.write_ncs
= None
161 res
= self
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['namingContexts'])
162 self
.deleted_objects_containers
= []
163 self
.ncs_lacking_deleted_containers
= []
164 self
.dns_partitions
= []
166 self
.ncs
= res
[0]["namingContexts"]
174 dn
= self
.samdb
.get_wellknown_dn(ldb
.Dn(self
.samdb
, nc
.decode('utf8')),
175 dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
176 self
.deleted_objects_containers
.append(dn
)
178 self
.ncs_lacking_deleted_containers
.append(ldb
.Dn(self
.samdb
, nc
.decode('utf8')))
180 domaindns_zone
= 'DC=DomainDnsZones,%s' % self
.samdb
.get_default_basedn()
181 forestdns_zone
= 'DC=ForestDnsZones,%s' % self
.samdb
.get_root_basedn()
182 domain
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
183 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
184 base
=self
.samdb
.get_partitions_dn(),
185 expression
="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone
)
187 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, forestdns_zone
), domain
[0]))
189 forest
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
190 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
191 base
=self
.samdb
.get_partitions_dn(),
192 expression
="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone
)
194 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, domaindns_zone
), forest
[0]))
196 fsmo_dn
= ldb
.Dn(self
.samdb
, "CN=RID Manager$,CN=System," + self
.samdb
.domain_dn())
197 rid_master
= get_fsmo_roleowner(self
.samdb
, fsmo_dn
, "rid")
198 if ldb
.Dn(self
.samdb
, self
.samdb
.get_dsServiceName()) == rid_master
:
199 self
.is_rid_master
= True
201 self
.is_rid_master
= False
203 # To get your rid set
205 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, self
.samdb
.get_serverName()),
206 scope
=ldb
.SCOPE_BASE
, attrs
=["serverReference"])
207 # 2. Get server reference
208 self
.server_ref_dn
= ldb
.Dn(self
.samdb
, res
[0]['serverReference'][0].decode('utf8'))
211 res
= self
.samdb
.search(base
=self
.server_ref_dn
,
212 scope
=ldb
.SCOPE_BASE
, attrs
=['rIDSetReferences'])
213 if "rIDSetReferences" in res
[0]:
214 self
.rid_set_dn
= ldb
.Dn(self
.samdb
, res
[0]['rIDSetReferences'][0].decode('utf8'))
216 self
.rid_set_dn
= None
218 ntds_service_dn
= "CN=Directory Service,CN=Windows NT,CN=Services,%s" % \
219 self
.samdb
.get_config_basedn().get_linearized()
220 res
= samdb
.search(base
=ntds_service_dn
,
221 scope
=ldb
.SCOPE_BASE
,
222 expression
="(objectClass=nTDSService)",
223 attrs
=["tombstoneLifetime"])
224 if "tombstoneLifetime" in res
[0]:
225 self
.tombstoneLifetime
= int(res
[0]["tombstoneLifetime"][0])
227 self
.tombstoneLifetime
= 180
229 self
.compatibleFeatures
= []
230 self
.requiredFeatures
= []
233 res
= self
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
235 attrs
=["compatibleFeatures",
237 if "compatibleFeatures" in res
[0]:
238 self
.compatibleFeatures
= res
[0]["compatibleFeatures"]
239 if "requiredFeatures" in res
[0]:
240 self
.requiredFeatures
= res
[0]["requiredFeatures"]
241 except ldb
.LdbError
as e6
:
242 (enum
, estr
) = e6
.args
243 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
246 def check_database(self
, DN
=None, scope
=ldb
.SCOPE_SUBTREE
, controls
=None,
248 '''perform a database check, returning the number of errors found'''
249 res
= self
.samdb
.search(base
=DN
, scope
=scope
, attrs
=['dn'], controls
=controls
)
250 self
.report('Checking %u objects' % len(res
))
252 self
.unfixable_errors
= 0
254 error_count
+= self
.check_deleted_objects_containers()
256 self
.attribute_or_class_ids
= set()
259 self
.dn_set
.add(str(object.dn
))
260 error_count
+= self
.check_object(object.dn
, requested_attrs
=attrs
)
263 error_count
+= self
.check_rootdse()
265 if self
.expired_tombstones
> 0:
266 self
.report("NOTICE: found %d expired tombstones, "
267 "'samba' will remove them daily, "
268 "'samba-tool domain tombstones expunge' "
269 "would do that immediately." % (
270 self
.expired_tombstones
))
272 self
.report('Checked %u objects (%u errors)' %
273 (len(res
), error_count
+ self
.unfixable_errors
))
275 if self
.unfixable_errors
!= 0:
276 self
.report(f
"WARNING: {self.unfixable_errors} "
277 "of these errors cannot be automatically fixed.")
279 if error_count
!= 0 and not self
.fix
:
280 self
.report("Please use 'samba-tool dbcheck --fix' to fix "
281 f
"{error_count} errors")
285 def check_deleted_objects_containers(self
):
286 """This function only fixes conflicts on the Deleted Objects
287 containers, not the attributes"""
289 for nc
in self
.ncs_lacking_deleted_containers
:
290 if nc
== self
.schema_dn
:
293 self
.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc
)
294 if not self
.confirm_all('Fix missing Deleted Objects container for %s?' % (nc
), 'fix_missing_deleted_objects'):
297 dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects")
302 # If something already exists here, add a conflict
303 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[],
304 controls
=["show_deleted:1", "extended_dn:1:1",
305 "show_recycled:1", "reveal_internals:0"])
307 guid
= res
[0].dn
.get_extended_component("GUID")
308 conflict_dn
= ldb
.Dn(self
.samdb
,
309 "CN=Deleted Objects\\0ACNF:%s" % str(misc
.GUID(guid
)))
310 conflict_dn
.add_base(nc
)
312 except ldb
.LdbError
as e2
:
313 (enum
, estr
) = e2
.args
314 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
317 self
.report("Couldn't check for conflicting Deleted Objects container: %s" % estr
)
320 if conflict_dn
is not None:
322 self
.samdb
.rename(dn
, conflict_dn
, ["show_deleted:1", "relax:0", "show_recycled:1"])
323 except ldb
.LdbError
as e1
:
324 (enum
, estr
) = e1
.args
325 self
.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn
, conflict_dn
, estr
))
328 # Refresh wellKnownObjects links
329 res
= self
.samdb
.search(base
=nc
, scope
=ldb
.SCOPE_BASE
,
330 attrs
=['wellKnownObjects'],
331 controls
=["show_deleted:1", "extended_dn:0",
332 "show_recycled:1", "reveal_internals:0"])
334 self
.report("wellKnownObjects was not found for NC %s" % nc
)
337 # Prevent duplicate deleted objects containers just in case
338 wko
= res
[0]["wellKnownObjects"]
340 proposed_objectguid
= None
342 dsdb_dn
= dsdb_Dn(self
.samdb
, o
.decode('utf8'), dsdb
.DSDB_SYNTAX_BINARY_DN
)
343 if self
.is_deleted_objects_dn(dsdb_dn
):
344 self
.report("wellKnownObjects had duplicate Deleted Objects value %s" % o
)
345 # We really want to put this back in the same spot
346 # as the original one, so that on replication we
347 # merge, rather than conflict.
348 proposed_objectguid
= dsdb_dn
.dn
.get_extended_component("GUID")
349 listwko
.append(str(o
))
351 if proposed_objectguid
is not None:
352 guid_suffix
= "\nobjectGUID: %s" % str(misc
.GUID(proposed_objectguid
))
354 wko_prefix
= "B:32:%s" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
355 listwko
.append('%s:%s' % (wko_prefix
, dn
))
359 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
360 sec_desc
= get_deletedobjects_descriptor(domain_sid
,
361 name_map
=self
.name_map
)
362 sec_desc_b64
= b64encode(sec_desc
).decode('utf8')
364 # Insert a brand new Deleted Objects container
365 self
.samdb
.add_ldif("""dn: %s
367 objectClass: container
368 description: Container for deleted objects
370 isCriticalSystemObject: TRUE
371 showInAdvancedViewOnly: TRUE
372 nTSecurityDescriptor:: %s
373 systemFlags: -1946157056%s""" % (dn
, sec_desc_b64
, guid_suffix
),
374 controls
=["relax:0", "provision:0"])
376 delta
= ldb
.Message()
377 delta
.dn
= ldb
.Dn(self
.samdb
, str(res
[0]["dn"]))
378 delta
["wellKnownObjects"] = ldb
.MessageElement(listwko
,
379 ldb
.FLAG_MOD_REPLACE
,
382 # Insert the link to the brand new container
383 if self
.do_modify(delta
, ["relax:0"],
384 "NC %s lacks Deleted Objects WKGUID" % nc
,
386 self
.report("Added %s well known guid link" % dn
)
388 self
.deleted_objects_containers
.append(dn
)
392 def report(self
, msg
):
393 '''print a message unless quiet is set'''
397 if msg
.startswith('ERROR'):
398 msg
= c_RED('ERROR') + msg
[5:]
399 elif msg
.startswith('WARNING'):
400 msg
= c_DARK_YELLOW('WARNING') + msg
[7:]
401 elif msg
.startswith('INFO'):
402 msg
= c_DARK_CYAN('INFO') + msg
[4:]
403 elif msg
.startswith('NOTICE'):
404 msg
= c_DARK_CYAN('NOTICE') + msg
[6:]
405 elif msg
.startswith('NOTE'):
406 msg
= c_DARK_CYAN('NOTE') + msg
[4:]
407 elif msg
.startswith('SKIPPING'):
408 msg
= c_DARK_GREEN('SKIPPING') + msg
[8:]
412 def confirm(self
, msg
, allow_all
=False, forced
=False):
413 '''confirm a change'''
420 return common
.confirm(msg
, forced
=forced
, allow_all
=allow_all
)
422 ################################################################
423 # a local confirm function with support for 'all'
424 def confirm_all(self
, msg
, all_attr
):
425 '''confirm a change with support for "all" '''
428 if getattr(self
, all_attr
) == 'NONE':
430 if getattr(self
, all_attr
) == 'ALL':
436 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
438 setattr(self
, all_attr
, 'ALL')
441 setattr(self
, all_attr
, 'NONE')
445 def do_delete(self
, dn
, controls
, msg
):
446 '''delete dn with optional verbose output'''
448 self
.report("delete DN %s" % dn
)
450 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
451 self
.samdb
.delete(dn
, controls
=controls
)
452 except Exception as err
:
453 if self
.in_transaction
:
454 raise CommandError("%s : %s" % (msg
, err
))
455 self
.report("%s : %s" % (msg
, err
))
459 def do_modify(self
, m
, controls
, msg
, validate
=True):
460 '''perform a modify with optional verbose output'''
461 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
463 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
464 self
.report("controls: %r" % controls
)
466 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
467 except Exception as err
:
468 if self
.in_transaction
:
469 raise CommandError("%s : %s" % (msg
, err
))
470 self
.report("%s : %s" % (msg
, err
))
474 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
475 '''perform a rename with optional verbose output'''
477 self
.report("""dn: %s
481 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
483 to_dn
= to_rdn
+ to_base
484 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
485 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
486 except Exception as err
:
487 if self
.in_transaction
:
488 raise CommandError("%s : %s" % (msg
, err
))
489 self
.report("%s : %s" % (msg
, err
))
493 def get_attr_linkID_and_reverse_name(self
, attrname
):
494 if attrname
in self
.link_id_cache
:
495 return self
.link_id_cache
[attrname
]
496 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
498 revname
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
501 self
.link_id_cache
[attrname
] = (linkID
, revname
)
502 return linkID
, revname
504 def err_empty_attribute(self
, dn
, attrname
):
505 '''fix empty attributes'''
506 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
507 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
508 self
.report("Not fixing empty attribute %s" % attrname
)
513 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
514 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
515 "Failed to remove empty attribute %s" % attrname
, validate
=False):
516 self
.report("Removed empty attribute %s" % attrname
)
518 def err_normalise_mismatch(self
, dn
, attrname
, values
):
519 '''fix attribute normalisation errors, without altering sort order'''
520 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
523 normalised
= self
.samdb
.dsdb_normalise_attributes(
524 self
.samdb_schema
, attrname
, [val
])
525 if len(normalised
) != 1:
526 self
.report("Unable to normalise value '%s'" % val
)
527 mod_list
.append((val
, ''))
528 elif (normalised
[0] != val
):
529 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
530 mod_list
.append((val
, normalised
[0]))
531 if not self
.confirm_all('Fix normalisation for %s from %s?' % (attrname
, dn
), 'fix_all_normalisation'):
532 self
.report("Not fixing attribute %s" % attrname
)
537 for i
in range(0, len(mod_list
)):
538 (val
, nval
) = mod_list
[i
]
539 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
541 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
544 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
545 "Failed to normalise attribute %s" % attrname
,
547 self
.report("Normalised attribute %s" % attrname
)
549 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
550 '''fix attribute normalisation and/or sort errors'''
551 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
552 if list(normalised
) == values
:
553 # how we got here is a mystery.
555 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
556 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
557 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
558 self
.report("Not fixing attribute '%s'" % attrname
)
563 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
565 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
566 "Failed to normalise attribute %s" % attrname
,
568 self
.report("Normalised attribute %s" % attrname
)
570 def err_duplicate_values(self
, dn
, attrname
, dup_values
, values
):
571 '''fix duplicate attribute values'''
572 self
.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname
, dn
))
573 self
.report("Values contain a duplicate: [%s]/[%s]!" %
574 (dump_attr_values(dup_values
), dump_attr_values(values
)))
575 if not self
.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_duplicates'):
576 self
.report("Not fixing attribute '%s'" % attrname
)
581 m
[attrname
] = ldb
.MessageElement(values
, ldb
.FLAG_MOD_REPLACE
, attrname
)
583 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
584 "Failed to remove duplicate value on attribute %s" % attrname
,
586 self
.report("Removed duplicate value on attribute %s" % attrname
)
588 def is_deleted_objects_dn(self
, dsdb_dn
):
589 '''see if a dsdb_Dn is the special Deleted Objects DN'''
590 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
592 def err_missing_objectclass(self
, dn
):
593 """handle object without objectclass"""
594 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
)))
595 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'):
596 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
598 if self
.do_delete(dn
, ["relax:0"],
599 "Failed to remove DN %s" % dn
):
600 self
.report("Removed DN %s" % dn
)
602 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, remove_plausible
=False):
603 """handle a DN pointing to a deleted object"""
604 if not remove_plausible
:
605 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
606 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
607 if not self
.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
608 self
.report("Not removing")
611 self
.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
612 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
613 if not self
.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
614 self
.report("Not removing")
619 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
620 if self
.do_modify(m
, ["show_recycled:1",
621 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
622 "Failed to remove deleted DN attribute %s" % attrname
):
623 self
.report("Removed deleted DN on attribute %s" % attrname
)
625 def err_missing_target_dn_or_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
626 """handle a missing target DN (if specified, GUID form can't be found,
627 and otherwise DN string form can't be found)"""
629 # Don't change anything if the object itself is deleted
630 if str(dn
).find('\\0ADEL') != -1:
631 # We don't bump the error count as Samba produces these
632 # in normal operation
633 self
.report("WARNING: no target object found for GUID "
634 "component link %s in deleted object "
635 "%s - %s" % (attrname
, dn
, val
))
636 self
.report("Not removing dangling one-way "
637 "link on deleted object "
638 "(tombstone garbage collection in progress?)")
641 # check if its a backlink
642 linkID
, _
= self
.get_attr_linkID_and_reverse_name(attrname
)
643 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
645 linkID
, reverse_link_name \
646 = self
.get_attr_linkID_and_reverse_name(attrname
)
647 if reverse_link_name
is not None:
648 self
.report("WARNING: no target object found for GUID "
649 "component for one-way forward link "
651 "%s - %s" % (attrname
, dn
, val
))
652 self
.report("Not removing dangling forward link")
655 nc_root
= self
.samdb
.get_nc_root(dn
)
657 target_nc_root
= self
.samdb
.get_nc_root(dsdb_dn
.dn
)
658 except ldb
.LdbError
as e
:
659 (enum
, estr
) = e
.args
660 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
662 target_nc_root
= None
664 if target_nc_root
is None:
665 # We don't bump the error count as Samba produces
666 # these in normal operation creating a lab domain (due
667 # to the way the rename is handled, links to
668 # now-expunged objects will never be fixed to stay
670 self
.report("WARNING: no target object found for GUID "
671 "component for link "
672 "%s in object to %s outside our NCs"
673 "%s - %s" % (attrname
, dsdb_dn
.dn
, dn
, val
))
674 self
.report("Not removing dangling one-way "
675 "left-over link outside our NCs "
676 "(we might be building a renamed/lab domain)")
679 if nc_root
!= target_nc_root
:
680 # We don't bump the error count as Samba produces these
681 # in normal operation
682 self
.report("WARNING: no target object found for GUID "
683 "component for cross-partition link "
685 "%s - %s" % (attrname
, dn
, val
))
686 self
.report("Not removing dangling one-way "
687 "cross-partition link "
688 "(we might be mid-replication)")
691 # Due to our link handling one-way links pointing to
692 # missing objects are plausible.
694 # We don't bump the error count as Samba produces these
695 # in normal operation
696 self
.report("WARNING: no target object found for GUID "
697 "component for DN value %s in object "
698 "%s - %s" % (attrname
, dn
, val
))
699 self
.err_deleted_dn(dn
, attrname
, val
,
700 dsdb_dn
, dsdb_dn
, True)
703 # We bump the error count here, as we should have deleted this
704 self
.report("ERROR: no target object found for GUID "
705 "component for link %s in object "
706 "%s - %s" % (attrname
, dn
, val
))
707 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
, False)
710 def err_missing_dn_GUID_component(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
711 """handle a missing GUID extended DN component"""
712 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
713 controls
= ["extended_dn:1:1", "show_recycled:1"]
715 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
716 attrs
=[], controls
=controls
)
717 except ldb
.LdbError
as e7
:
718 (enum
, estr
) = e7
.args
719 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
720 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
722 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
725 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
726 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
728 dsdb_dn
.dn
= res
[0].dn
730 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
731 self
.report("Not fixing %s" % errstr
)
735 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
736 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
738 if self
.do_modify(m
, ["show_recycled:1"],
739 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
740 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
742 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
743 """handle an incorrect binary DN component"""
744 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
746 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
747 self
.report("Not fixing %s" % errstr
)
751 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
752 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
754 if self
.do_modify(m
, ["show_recycled:1"],
755 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
756 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
758 def err_dn_string_component_old(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
759 """handle a DN string being incorrect due to a rename or delete"""
760 self
.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
761 dsdb_dn
.dn
= correct_dn
763 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
764 'fix_all_old_dn_string_component_mismatch'):
765 self
.report("Not fixing old string component")
769 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
770 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
771 if self
.do_modify(m
, ["show_recycled:1",
772 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
],
773 "Failed to fix old DN string on attribute %s" % (attrname
)):
774 self
.report("Fixed old DN string on attribute %s" % (attrname
))
776 def err_dn_component_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, mismatch_type
):
777 """handle a DN string being incorrect"""
778 self
.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type
, attrname
, dn
, val
))
779 dsdb_dn
.dn
= correct_dn
781 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
782 'fix_all_%s_dn_component_mismatch' % mismatch_type
):
783 self
.report("Not fixing %s component mismatch" % mismatch_type
)
787 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
788 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
789 if self
.do_modify(m
, ["show_recycled:1"],
790 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type
, attrname
)):
791 self
.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type
, attrname
))
793 def err_dn_component_missing_target_sid(self
, dn
, attrname
, val
, dsdb_dn
, target_sid_blob
):
794 """fix missing <SID=...> on linked attributes"""
795 self
.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname
, dn
, val
))
797 if len(dsdb_dn
.prefix
) != 0:
798 self
.report("Not fixing missing DN SID on DN+BINARY or DN+STRING")
801 correct_dn
= ldb
.Dn(self
.samdb
, dsdb_dn
.dn
.extended_str())
802 correct_dn
.set_extended_component("SID", target_sid_blob
)
804 if not self
.confirm_all('Change DN to %s?' % correct_dn
.extended_str(),
805 'fix_all_SID_dn_component_missing'):
806 self
.report("Not fixing missing DN SID component")
809 target_guid_blob
= correct_dn
.get_extended_component("GUID")
810 guid_sid_dn
= ldb
.Dn(self
.samdb
, "")
811 guid_sid_dn
.set_extended_component("GUID", target_guid_blob
)
812 guid_sid_dn
.set_extended_component("SID", target_sid_blob
)
816 m
['new_value'] = ldb
.MessageElement(guid_sid_dn
.extended_str(), ldb
.FLAG_MOD_ADD
, attrname
)
819 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID
821 if self
.do_modify(m
, controls
,
822 "Failed to ADD missing DN SID on attribute %s" % (attrname
)):
823 self
.report("Fixed missing DN SID on attribute %s" % (attrname
))
825 def err_unknown_attribute(self
, obj
, attrname
):
826 '''handle an unknown attribute error'''
827 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
828 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
829 self
.report("Not removing %s" % attrname
)
833 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
834 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
835 "Failed to remove unknown attribute %s" % attrname
):
836 self
.report("Removed unknown attribute %s" % (attrname
))
838 def err_undead_linked_attribute(self
, obj
, attrname
, val
):
839 '''handle a link that should not be there on a deleted object'''
840 self
.report("ERROR: linked attribute '%s' to '%s' is present on "
841 "deleted object %s" % (attrname
, val
, obj
.dn
))
842 if not self
.confirm_all('Remove linked attribute %s' % attrname
, 'fix_undead_linked_attributes'):
843 self
.report("Not removing linked attribute %s" % attrname
)
847 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
849 if self
.do_modify(m
, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
850 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
851 "Failed to delete forward link %s" % attrname
):
852 self
.report("Fixed undead forward link %s" % (attrname
))
854 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
855 '''handle a missing backlink value'''
856 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
857 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
858 self
.report("Not fixing missing backlink %s" % backlink_name
)
862 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, backlink_name
)
863 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
864 "Failed to fix missing backlink %s" % backlink_name
):
865 self
.report("Fixed missing backlink %s" % (backlink_name
))
867 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
868 '''handle a incorrect RMD_FLAGS value'''
869 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
870 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()))
871 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
872 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
876 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
877 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
878 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
879 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
881 def err_orphaned_backlink(self
, obj_dn
, backlink_attr
, backlink_val
,
882 target_dn
, forward_attr
, forward_syntax
,
883 check_duplicates
=True):
884 '''handle a orphaned backlink value'''
885 if check_duplicates
is True and self
.has_duplicate_links(target_dn
, forward_attr
, forward_syntax
):
886 self
.report("WARNING: Keep orphaned backlink attribute " +
887 "'%s' in '%s' for link '%s' in '%s'" % (
888 backlink_attr
, obj_dn
, forward_attr
, target_dn
))
890 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr
, obj_dn
, forward_attr
, target_dn
))
891 if not self
.confirm_all('Remove orphaned backlink %s' % backlink_attr
, 'fix_all_orphaned_backlinks'):
892 self
.report("Not removing orphaned backlink %s" % backlink_attr
)
896 m
['value'] = ldb
.MessageElement(backlink_val
, ldb
.FLAG_MOD_DELETE
, backlink_attr
)
897 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
898 "Failed to fix orphaned backlink %s" % backlink_attr
):
899 self
.report("Fixed orphaned backlink %s" % (backlink_attr
))
901 def err_recover_forward_links(self
, obj
, forward_attr
, forward_vals
):
902 '''handle a duplicate links value'''
904 self
.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr
, obj
.dn
))
906 if not self
.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr
, 'recover_all_forward_links'):
907 self
.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
908 forward_attr
, obj
.dn
))
912 m
['value'] = ldb
.MessageElement(forward_vals
, ldb
.FLAG_MOD_REPLACE
, forward_attr
)
913 if self
.do_modify(m
, ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS
],
914 "Failed to fix duplicate links in attribute '%s'" % forward_attr
):
915 self
.report("Fixed duplicate links in attribute '%s'" % (forward_attr
))
916 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
917 assert duplicate_cache_key
in self
.duplicate_link_cache
918 self
.duplicate_link_cache
[duplicate_cache_key
] = False
920 def err_no_fsmoRoleOwner(self
, obj
):
921 '''handle a missing fSMORoleOwner'''
922 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
923 res
= self
.samdb
.search("",
924 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
926 serviceName
= str(res
[0]["dsServiceName"][0])
927 if not self
.confirm_all('Seize role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
928 self
.report("Not Seizing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
932 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
933 if self
.do_modify(m
, [],
934 "Failed to seize role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
935 self
.report("Seized role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
937 def err_missing_parent(self
, obj
):
938 '''handle a missing parent'''
939 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
940 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
941 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
944 keep_transaction
= False
945 self
.samdb
.transaction_start()
947 nc_root
= self
.samdb
.get_nc_root(obj
.dn
)
948 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
949 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
950 new_dn
.remove_base_components(len(new_dn
) - 1)
951 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
952 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
953 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
957 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
959 if self
.do_modify(m
, [],
960 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
961 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
962 keep_transaction
= True
964 self
.samdb
.transaction_cancel()
968 self
.samdb
.transaction_commit()
970 self
.samdb
.transaction_cancel()
972 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
, controls
):
973 '''handle a wrong dn'''
975 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
976 new_rdn
.remove_base_components(len(new_rdn
) - 1)
977 new_parent
= new_dn
.parent()
980 if rdn_val
!= name_val
:
981 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
982 attributes
+= "name=%r" % (name_val
)
984 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
985 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
986 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
989 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, controls
,
990 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
991 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
993 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
994 '''handle a wrong instanceType'''
995 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
996 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
997 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
1002 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
1003 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
1004 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
1005 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
1007 def err_short_userParameters(self
, obj
, attrname
, value
):
1008 # This is a truncated userParameters due to a pre 4.1 replication bug
1009 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
)))
1011 def err_base64_userParameters(self
, obj
, attrname
, value
):
1012 '''handle a userParameters that is wrongly base64 encoded'''
1013 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
1014 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
1015 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
1020 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
1021 if self
.do_modify(m
, [],
1022 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
1023 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
1025 def err_utf8_userParameters(self
, obj
, attrname
, value
):
1026 '''handle a userParameters that is wrongly utf-8 encoded'''
1027 self
.report("ERROR: wrongly formatted userParameters on %s, "
1028 "should not be pseudo-UTF8 encoded" % (obj
.dn
))
1029 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
1030 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
1035 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
1036 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
1037 if self
.do_modify(m
, [],
1038 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
1039 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
1041 def err_doubled_userParameters(self
, obj
, attrname
, value
):
1042 '''handle a userParameters that has been utf-16 encoded twice'''
1043 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
1044 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
1045 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
1050 # m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
1051 # hmm the above old python2 code doesn't make sense to me and cannot
1052 # work in python3 because a string doesn't have a decode method.
1053 # However in python2 for some unknown reason this double decode
1054 # followed by encode seems to result in what looks like utf8.
1055 # In python2 just .decode('utf-16-le').encode('utf-16-le') does nothing
1056 # but trigger the 'double UTF16 encoded' condition again :/
1058 # In python2 and python3 value.decode('utf-16-le').encode('utf8') seems
1059 # to do the trick and work as expected.
1060 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').encode('utf8'),
1061 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
1063 if self
.do_modify(m
, [],
1064 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
1065 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
1067 def err_odd_userParameters(self
, obj
, attrname
):
1068 """Fix a truncated userParameters due to a pre 4.1 replication bug"""
1069 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
)))
1071 def find_revealed_link(self
, dn
, attrname
, guid
):
1072 '''return a revealed link in an object'''
1073 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
1074 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
1075 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
1076 for val
in res
[0][attrname
]:
1077 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
1078 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
1083 def check_duplicate_links(self
, obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
):
1084 '''check a linked values for duplicate forward links'''
1087 duplicate_dict
= dict()
1088 unique_dict
= dict()
1090 # Only forward links can have this problem
1091 if forward_linkID
& 1:
1092 # If we got the reverse, skip it
1093 return (error_count
, duplicate_dict
, unique_dict
)
1095 if backlink_attr
is None:
1096 return (error_count
, duplicate_dict
, unique_dict
)
1098 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
1099 if duplicate_cache_key
not in self
.duplicate_link_cache
:
1100 self
.duplicate_link_cache
[duplicate_cache_key
] = False
1102 for val
in obj
[forward_attr
]:
1103 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), forward_syntax
)
1105 # all DNs should have a GUID component
1106 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1109 guidstr
= str(misc
.GUID(guid
))
1110 keystr
= guidstr
+ dsdb_dn
.prefix
1111 if keystr
not in unique_dict
:
1112 unique_dict
[keystr
] = dsdb_dn
1115 if keystr
not in duplicate_dict
:
1116 duplicate_dict
[keystr
] = dict()
1117 duplicate_dict
[keystr
]["keep"] = None
1118 duplicate_dict
[keystr
]["delete"] = list()
1120 # Now check for the highest RMD_VERSION
1121 v1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_VERSION"))
1122 v2
= int(dsdb_dn
.dn
.get_extended_component("RMD_VERSION"))
1124 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1125 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 # Fallback to the highest RMD_LOCAL_USN
1133 u1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_LOCAL_USN"))
1134 u2
= int(dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN"))
1136 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
1137 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
1139 duplicate_dict
[keystr
]["keep"] = dsdb_dn
1140 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
1141 unique_dict
[keystr
] = dsdb_dn
1143 if error_count
!= 0:
1144 self
.duplicate_link_cache
[duplicate_cache_key
] = True
1146 return (error_count
, duplicate_dict
, unique_dict
)
1148 def has_duplicate_links(self
, dn
, forward_attr
, forward_syntax
):
1149 '''check a linked values for duplicate forward links'''
1152 duplicate_cache_key
= "%s:%s" % (str(dn
), forward_attr
)
1153 if duplicate_cache_key
in self
.duplicate_link_cache
:
1154 return self
.duplicate_link_cache
[duplicate_cache_key
]
1156 forward_linkID
, backlink_attr
= self
.get_attr_linkID_and_reverse_name(forward_attr
)
1158 attrs
= [forward_attr
]
1159 controls
= ["extended_dn:1:1", "reveal_internals:0"]
1161 # check its the right GUID
1163 res
= self
.samdb
.search(base
=str(dn
), scope
=ldb
.SCOPE_BASE
,
1164 attrs
=attrs
, controls
=controls
)
1165 except ldb
.LdbError
as e8
:
1166 (enum
, estr
) = e8
.args
1167 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1173 error_count
, duplicate_dict
, unique_dict
= \
1174 self
.check_duplicate_links(obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
)
1176 if duplicate_cache_key
in self
.duplicate_link_cache
:
1177 return self
.duplicate_link_cache
[duplicate_cache_key
]
1181 def find_missing_forward_links_from_backlinks(self
, obj
,
1185 forward_unique_dict
):
1186 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1187 missing_forward_links
= []
1190 if backlink_attr
is None:
1191 return (missing_forward_links
, error_count
)
1193 if forward_syntax
!= ldb
.SYNTAX_DN
:
1194 self
.report("Not checking for missing forward links for syntax: %s" %
1196 return (missing_forward_links
, error_count
)
1198 if "sortedLinks" in self
.compatibleFeatures
:
1199 self
.report("Not checking for missing forward links because the db " +
1200 "has the sortedLinks feature")
1201 return (missing_forward_links
, error_count
)
1204 obj_guid
= obj
['objectGUID'][0]
1205 obj_guid_str
= str(ndr_unpack(misc
.GUID
, obj_guid
))
1206 filter = "(%s=<GUID=%s>)" % (backlink_attr
, obj_guid_str
)
1208 res
= self
.samdb
.search(expression
=filter,
1209 scope
=ldb
.SCOPE_SUBTREE
, attrs
=["objectGUID"],
1210 controls
=["extended_dn:1:1",
1211 "search_options:1:2",
1212 "paged_results:1:1000"])
1213 except ldb
.LdbError
as e9
:
1214 (enum
, estr
) = e9
.args
1218 target_dn
= dsdb_Dn(self
.samdb
, r
.dn
.extended_str(), forward_syntax
)
1220 guid
= target_dn
.dn
.get_extended_component("GUID")
1221 guidstr
= str(misc
.GUID(guid
))
1222 if guidstr
in forward_unique_dict
:
1225 # A valid forward link looks like this:
1227 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1228 # <RMD_ADDTIME=131607546230000000>;
1229 # <RMD_CHANGETIME=131607546230000000>;
1231 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1232 # <RMD_LOCAL_USN=3765>;
1233 # <RMD_ORIGINATING_USN=3765>;
1235 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1236 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1238 # Note that versions older than Samba 4.8 create
1239 # links with RMD_VERSION=0.
1241 # Try to get the local_usn and time from objectClass
1242 # if possible and fallback to any other one.
1243 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1244 obj
['replPropertyMetadata'][0])
1245 for o
in repl
.ctr
.array
:
1246 local_usn
= o
.local_usn
1247 t
= o
.originating_change_time
1248 if o
.attid
== drsuapi
.DRSUAPI_ATTID_objectClass
:
1251 # We use a magic invocationID for restoring missing
1252 # forward links to recover from bug #13228.
1253 # This should allow some more future magic to fix the
1256 # It also means it looses the conflict resolution
1257 # against almost every real invocation, if the
1258 # version is also 0.
1259 originating_invocid
= misc
.GUID("ffffffff-4700-4700-4700-000000b13228")
1265 rmd_invocid
= originating_invocid
1266 rmd_originating_usn
= originating_usn
1267 rmd_local_usn
= local_usn
1270 target_dn
.dn
.set_extended_component("RMD_ADDTIME", str(rmd_addtime
))
1271 target_dn
.dn
.set_extended_component("RMD_CHANGETIME", str(rmd_changetime
))
1272 target_dn
.dn
.set_extended_component("RMD_FLAGS", str(rmd_flags
))
1273 target_dn
.dn
.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid
))
1274 target_dn
.dn
.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn
))
1275 target_dn
.dn
.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn
))
1276 target_dn
.dn
.set_extended_component("RMD_VERSION", str(rmd_version
))
1279 missing_forward_links
.append(target_dn
)
1281 return (missing_forward_links
, error_count
)
1283 def check_dn(self
, obj
, attrname
, syntax_oid
):
1284 '''check a DN attribute for correctness'''
1286 obj_guid
= obj
['objectGUID'][0]
1288 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
1289 if reverse_link_name
is not None:
1290 reverse_syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(reverse_link_name
)
1292 reverse_syntax_oid
= None
1294 is_member_link
= attrname
in ("member", "memberOf")
1295 if is_member_link
and self
.quick_membership_checks
:
1298 error_count
, duplicate_dict
, unique_dict
= \
1299 self
.check_duplicate_links(obj
, attrname
, syntax_oid
,
1300 linkID
, reverse_link_name
)
1302 if len(duplicate_dict
) != 0:
1304 missing_forward_links
, missing_error_count
= \
1305 self
.find_missing_forward_links_from_backlinks(obj
,
1306 attrname
, syntax_oid
,
1309 error_count
+= missing_error_count
1311 forward_links
= [dn
for dn
in unique_dict
.values()]
1313 if missing_error_count
!= 0:
1314 self
.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1317 self
.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname
, obj
.dn
))
1318 for m
in missing_forward_links
:
1319 self
.report("Missing link '%s'" % (m
))
1320 if not self
.confirm_all("Schedule re-adding missing forward link for attribute %s" % attrname
,
1321 'fix_all_missing_forward_links'):
1322 self
.err_orphaned_backlink(m
.dn
, reverse_link_name
,
1323 obj
.dn
.extended_str(), obj
.dn
,
1324 attrname
, syntax_oid
,
1325 check_duplicates
=False)
1327 forward_links
+= [m
]
1328 for keystr
in duplicate_dict
.keys():
1329 d
= duplicate_dict
[keystr
]
1330 for dd
in d
["delete"]:
1331 self
.report("Duplicate link '%s'" % dd
)
1332 self
.report("Correct link '%s'" % d
["keep"])
1334 # We now construct the sorted dn values.
1335 # They're sorted by the objectGUID of the target
1336 # See dsdb_Dn.__cmp__()
1337 vals
= [str(dn
) for dn
in sorted(forward_links
)]
1338 self
.err_recover_forward_links(obj
, attrname
, vals
)
1339 # We should continue with the fixed values
1340 obj
[attrname
] = ldb
.MessageElement(vals
, 0, attrname
)
1342 for val
in obj
[attrname
]:
1343 dsdb_dn
= dsdb_Dn(self
.samdb
, val
.decode('utf8'), syntax_oid
)
1345 # all DNs should have a GUID component
1346 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1349 self
.err_missing_dn_GUID_component(obj
.dn
, attrname
, val
, dsdb_dn
,
1353 guidstr
= str(misc
.GUID(guid
))
1354 attrs
= ['isDeleted', 'replPropertyMetaData']
1356 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
1357 fixing_msDS_HasInstantiatedNCs
= True
1358 attrs
.append("instanceType")
1360 fixing_msDS_HasInstantiatedNCs
= False
1362 if reverse_link_name
is not None:
1363 attrs
.append(reverse_link_name
)
1365 # check its the right GUID
1367 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
1368 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1",
1369 "reveal_internals:0"
1371 except ldb
.LdbError
as e3
:
1372 (enum
, estr
) = e3
.args
1373 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1376 # We don't always want to
1377 error_count
+= self
.err_missing_target_dn_or_GUID(obj
.dn
,
1383 if fixing_msDS_HasInstantiatedNCs
:
1384 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
1385 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
1387 if str(dsdb_dn
) != str(val
):
1389 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
1392 # now we have two cases - the source object might or might not be deleted
1393 is_deleted
= 'isDeleted' in obj
and str(obj
['isDeleted'][0]).upper() == 'TRUE'
1394 target_is_deleted
= 'isDeleted' in res
[0] and str(res
[0]['isDeleted'][0]).upper() == 'TRUE'
1396 if is_deleted
and obj
.dn
not in self
.deleted_objects_containers
and linkID
:
1397 # A fully deleted object should not have any linked
1398 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1399 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1401 self
.err_undead_linked_attribute(obj
, attrname
, val
)
1404 elif target_is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
) and linkID
:
1405 # the target DN is not allowed to be deleted, unless the target DN is the
1406 # special Deleted Objects container
1408 local_usn
= dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN")
1410 if 'replPropertyMetaData' in res
[0]:
1411 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1412 res
[0]['replPropertyMetadata'][0])
1414 for o
in repl
.ctr
.array
:
1415 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1416 deleted_usn
= o
.local_usn
1417 if deleted_usn
>= int(local_usn
):
1418 # If the object was deleted after the link
1419 # was last modified then, clean it up here
1424 self
.err_deleted_dn(obj
.dn
, attrname
,
1425 val
, dsdb_dn
, res
[0].dn
, True)
1428 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
, False)
1431 # We should not check for incorrect
1432 # components on deleted links, as these are allowed to
1433 # go stale (we just need the GUID, not the name)
1434 rmd_blob
= dsdb_dn
.dn
.get_extended_component("RMD_FLAGS")
1436 if rmd_blob
is not None:
1437 rmd_flags
= int(rmd_blob
)
1439 # assert the DN matches in string form, where a reverse
1440 # link exists, otherwise (below) offer to fix it as a non-error.
1441 # The string form is essentially only kept for forensics,
1442 # as we always re-resolve by GUID in normal operations.
1443 if not rmd_flags
& 1 and reverse_link_name
is not None:
1444 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1446 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1447 res
[0].dn
, "string")
1450 if res
[0].dn
.get_extended_component("GUID") != dsdb_dn
.dn
.get_extended_component("GUID"):
1452 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1456 target_sid
= res
[0].dn
.get_extended_component("SID")
1457 link_sid
= dsdb_dn
.dn
.get_extended_component("SID")
1458 if link_sid
is None and target_sid
is not None:
1460 self
.err_dn_component_missing_target_sid(obj
.dn
, attrname
, val
,
1461 dsdb_dn
, target_sid
)
1463 if link_sid
!= target_sid
:
1465 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1469 # Only for non-links, not even forward-only links
1470 # (otherwise this breaks repl_meta_data):
1472 # Now we have checked the GUID and SID, offer to fix old
1473 # DN strings as a non-error (DNs, not links so no
1474 # backlink). Samba does not maintain this string
1475 # otherwise, so we don't increment error_count.
1476 if reverse_link_name
is None:
1477 if linkID
== 0 and str(res
[0].dn
) != str(dsdb_dn
.dn
):
1478 # Pass in the old/bad DN without the <GUID=...> part,
1479 # otherwise the LDB code will correct it on the way through
1480 # (Note: we still want to preserve the DSDB DN prefix in the
1481 # case of binary DNs)
1482 bad_dn
= dsdb_dn
.prefix
+ dsdb_dn
.dn
.get_linearized()
1483 self
.err_dn_string_component_old(obj
.dn
, attrname
, bad_dn
,
1487 if is_member_link
and self
.quick_membership_checks
:
1490 # check the reverse_link is correct if there should be one
1492 if reverse_link_name
in res
[0]:
1493 for v
in res
[0][reverse_link_name
]:
1494 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1495 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1496 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1498 if v_blob
is not None:
1499 v_rmd_flags
= int(v_blob
)
1502 if v_guid
== obj_guid
:
1505 if match_count
!= 1:
1506 if syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
or reverse_syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
:
1508 # Forward binary multi-valued linked attribute
1510 for w
in obj
[attrname
]:
1511 w_guid
= dsdb_Dn(self
.samdb
, w
.decode('utf8')).dn
.get_extended_component("GUID")
1515 if match_count
== forward_count
:
1518 for v
in obj
[attrname
]:
1519 v_dn
= dsdb_Dn(self
.samdb
, v
.decode('utf8'))
1520 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1521 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1523 if v_blob
is not None:
1524 v_rmd_flags
= int(v_blob
)
1530 if match_count
== expected_count
:
1533 diff_count
= expected_count
- match_count
1536 # If there's a backward link on binary multi-valued linked attribute,
1537 # let the check on the forward link remedy the value.
1538 # UNLESS, there is no forward link detected.
1539 if match_count
== 0:
1541 self
.err_orphaned_backlink(obj
.dn
, attrname
,
1546 # Only warn here and let the forward link logic fix it.
1547 self
.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1548 attrname
, expected_count
, str(obj
.dn
),
1549 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1552 assert not target_is_deleted
1554 self
.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1555 attrname
, expected_count
, str(obj
.dn
),
1556 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1558 # Loop until the difference between the forward and
1559 # the backward links is resolved.
1560 while diff_count
!= 0:
1563 if match_count
> 0 or diff_count
> 1:
1564 # TODO no method to fix these right now
1565 self
.report("ERROR: Can't fix missing "
1566 "multi-valued backlinks on %s" % str(dsdb_dn
.dn
))
1568 self
.err_missing_backlink(obj
, attrname
,
1569 obj
.dn
.extended_str(),
1574 self
.err_orphaned_backlink(res
[0].dn
, reverse_link_name
,
1575 obj
.dn
.extended_str(), obj
.dn
,
1576 attrname
, syntax_oid
)
1581 def find_repl_attid(self
, repl
, attid
):
1582 for o
in repl
.ctr
.array
:
1583 if o
.attid
== attid
:
1588 def get_originating_time(self
, val
, attid
):
1589 '''Read metadata properties and return the originating time for
1590 a given attributeId.
1592 :return: the originating time or 0 if not found
1595 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1596 o
= self
.find_repl_attid(repl
, attid
)
1598 return o
.originating_change_time
1601 def process_metadata(self
, dn
, val
):
1602 '''Read metadata properties and list attributes in it.
1603 raises KeyError if the attid is unknown.'''
1606 wrong_attids
= set()
1608 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1610 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, val
)
1612 for o
in repl
.ctr
.array
:
1613 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1614 set_att
.add(att
.lower())
1615 list_attid
.append(o
.attid
)
1616 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
,
1617 is_schema_nc
=in_schema_nc
)
1618 if correct_attid
!= o
.attid
:
1619 wrong_attids
.add(o
.attid
)
1621 return (set_att
, list_attid
, wrong_attids
)
1623 def fix_metadata(self
, obj
, attr
):
1624 '''re-write replPropertyMetaData elements for a single attribute for a
1625 object. This is used to fix missing replPropertyMetaData elements'''
1626 guid_str
= str(ndr_unpack(misc
.GUID
, obj
['objectGUID'][0]))
1627 dn
= ldb
.Dn(self
.samdb
, "<GUID=%s>" % guid_str
)
1628 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attr
],
1629 controls
=["search_options:1:2",
1632 nmsg
= ldb
.Message()
1634 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
1635 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
1636 "Failed to fix metadata for attribute %s" % attr
):
1637 self
.report("Fixed metadata for attribute %s" % attr
)
1639 def ace_get_effective_inherited_type(self
, ace
):
1640 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
1644 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
1646 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
1648 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
1650 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
1656 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
1659 return str(ace
.object.inherited_type
)
1661 def lookup_class_schemaIDGUID(self
, cls
):
1662 if cls
in self
.class_schemaIDGUID
:
1663 return self
.class_schemaIDGUID
[cls
]
1665 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1666 res
= self
.samdb
.search(base
=self
.schema_dn
,
1668 attrs
=["schemaIDGUID"])
1669 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
1671 self
.class_schemaIDGUID
[cls
] = t
1674 def process_sd(self
, dn
, obj
):
1675 sd_attr
= "nTSecurityDescriptor"
1676 sd_val
= obj
[sd_attr
]
1678 sd
= ndr_unpack(security
.descriptor
, sd_val
[0])
1680 is_deleted
= 'isDeleted' in obj
and str(obj
['isDeleted'][0]).upper() == 'TRUE'
1682 # we don't fix deleted objects
1685 sd_clean
= security
.descriptor()
1686 sd_clean
.owner_sid
= sd
.owner_sid
1687 sd_clean
.group_sid
= sd
.group_sid
1688 sd_clean
.type = sd
.type
1689 sd_clean
.revision
= sd
.revision
1692 last_inherited_type
= None
1695 if sd
.sacl
is not None:
1697 for i
in range(0, len(aces
)):
1700 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1701 sd_clean
.sacl_add(ace
)
1704 t
= self
.ace_get_effective_inherited_type(ace
)
1708 if last_inherited_type
is not None:
1709 if t
!= last_inherited_type
:
1710 # if it inherited from more than
1711 # one type it's very likely to be broken
1713 # If not the recalculation will calculate
1718 last_inherited_type
= t
1721 if sd
.dacl
is not None:
1723 for i
in range(0, len(aces
)):
1726 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1727 sd_clean
.dacl_add(ace
)
1730 t
= self
.ace_get_effective_inherited_type(ace
)
1734 if last_inherited_type
is not None:
1735 if t
!= last_inherited_type
:
1736 # if it inherited from more than
1737 # one type it's very likely to be broken
1739 # If not the recalculation will calculate
1744 last_inherited_type
= t
1747 return (sd_clean
, sd
)
1749 if last_inherited_type
is None:
1755 cls
= obj
["objectClass"][-1]
1756 except KeyError as e
:
1760 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1761 attrs
=["isDeleted", "objectClass"],
1762 controls
=["show_recycled:1"])
1764 is_deleted
= 'isDeleted' in o
and str(o
['isDeleted'][0]).upper() == 'TRUE'
1766 # we don't fix deleted objects
1768 cls
= o
["objectClass"][-1]
1770 t
= self
.lookup_class_schemaIDGUID(cls
)
1772 if t
!= last_inherited_type
:
1774 return (sd_clean
, sd
)
1779 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
1780 '''re-write the SD due to incorrect inherited ACEs'''
1781 sd_attr
= "nTSecurityDescriptor"
1782 sd_val
= ndr_pack(sd
)
1783 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1785 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
1786 self
.report('Not fixing %s on %s\n' % (sd_attr
, dn
))
1789 nmsg
= ldb
.Message()
1791 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1792 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1793 "Failed to fix attribute %s" % sd_attr
):
1794 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1796 def err_wrong_default_sd(self
, dn
, sd
, diff
):
1797 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1798 sd_attr
= "nTSecurityDescriptor"
1799 sd_val
= ndr_pack(sd
)
1800 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1801 if sd
.owner_sid
is not None:
1802 sd_flags |
= security
.SECINFO_OWNER
1803 if sd
.group_sid
is not None:
1804 sd_flags |
= security
.SECINFO_GROUP
1806 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
1807 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
1812 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1813 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
1814 "Failed to reset attribute %s" % sd_attr
):
1815 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1817 def err_missing_sd_owner(self
, dn
, sd
):
1818 '''re-write the SD due to a missing owner or group'''
1819 sd_attr
= "nTSecurityDescriptor"
1820 sd_val
= ndr_pack(sd
)
1821 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
1823 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
1824 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
1827 nmsg
= ldb
.Message()
1829 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1831 # By setting the session_info to admin_session_info and
1832 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1833 # flags we cause the descriptor module to set the correct
1834 # owner and group on the SD, replacing the None/NULL values
1835 # for owner_sid and group_sid currently present.
1837 # The admin_session_info matches that used in provision, and
1838 # is the best guess we can make for an existing object that
1839 # hasn't had something specifically set.
1841 # This is important for the dns related naming contexts.
1842 self
.samdb
.set_session_info(self
.admin_session_info
)
1843 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1844 "Failed to fix metadata for attribute %s" % sd_attr
):
1845 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1846 self
.samdb
.set_session_info(self
.system_session_info
)
1848 def is_expired_tombstone(self
, dn
, repl_val
):
1849 if self
.check_expired_tombstones
:
1850 # This is not the default, it's just
1851 # used to keep dbcheck tests work with
1852 # old static provision dumps
1855 if dn
in self
.deleted_objects_containers
:
1856 # The Deleted Objects container will look like an expired
1860 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, repl_val
)
1862 isDeleted
= self
.find_repl_attid(repl
, drsuapi
.DRSUAPI_ATTID_isDeleted
)
1864 delete_time
= samba
.nttime2unix(isDeleted
.originating_change_time
)
1865 current_time
= time
.time()
1867 tombstone_delta
= self
.tombstoneLifetime
* (24 * 60 * 60)
1869 delta
= current_time
- delete_time
1870 if delta
<= tombstone_delta
:
1873 expunge_time
= delete_time
+ tombstone_delta
1875 delta_days
= delta
/ (24 * 60 * 60)
1878 self
.report("SKIPPING additional checks on object "
1879 "%s which very recently "
1880 "became an expired tombstone (normal)" % dn
)
1881 self
.report("INFO: it is expected this will be expunged "
1882 "by the next daily task some time after %s, "
1884 % (time
.ctime(expunge_time
), delta
// (60 * 60)))
1886 self
.report("SKIPPING: object %s is an expired tombstone" % dn
)
1887 self
.report("INFO: it was expected this object would have "
1888 "been expunged soon after"
1890 % (time
.ctime(expunge_time
), delta_days
))
1892 self
.report("isDeleted: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1895 isDeleted
.originating_invocation_id
,
1896 isDeleted
.originating_usn
,
1897 isDeleted
.local_usn
,
1898 time
.ctime(samba
.nttime2unix(isDeleted
.originating_change_time
))))
1899 self
.expired_tombstones
+= 1
1902 def find_changes_after_deletion(self
, repl_val
):
1903 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, repl_val
)
1905 isDeleted
= self
.find_repl_attid(repl
, drsuapi
.DRSUAPI_ATTID_isDeleted
)
1907 delete_time
= samba
.nttime2unix(isDeleted
.originating_change_time
)
1909 tombstone_delta
= self
.tombstoneLifetime
* (24 * 60 * 60)
1912 for o
in repl
.ctr
.array
:
1913 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1916 if o
.local_usn
<= isDeleted
.local_usn
:
1919 if o
.originating_change_time
<= isDeleted
.originating_change_time
:
1922 change_time
= samba
.nttime2unix(o
.originating_change_time
)
1924 delta
= change_time
- delete_time
1925 if delta
<= tombstone_delta
:
1928 # If the modification happened after the tombstone lifetime
1929 # has passed, we have a bug as the object might be deleted
1930 # already on other DCs and won't be able to replicate
1934 return found
, isDeleted
1936 def has_changes_after_deletion(self
, dn
, repl_val
):
1937 found
, isDeleted
= self
.find_changes_after_deletion(repl_val
)
1941 def report_attid(o
):
1943 attname
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1945 attname
= "<unknown:0x%x08x>" % o
.attid
1947 self
.report("%s: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1948 attname
, o
.attid
, o
.version
,
1949 o
.originating_invocation_id
,
1952 time
.ctime(samba
.nttime2unix(o
.originating_change_time
))))
1954 self
.report("ERROR: object %s, has changes after deletion" % dn
)
1955 report_attid(isDeleted
)
1961 def err_changes_after_deletion(self
, dn
, repl_val
):
1962 found
, isDeleted
= self
.find_changes_after_deletion(repl_val
)
1964 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1965 rdn_attr
= dn
.get_rdn_name()
1966 rdn_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(rdn_attr
,
1967 is_schema_nc
=in_schema_nc
)
1971 if o
.attid
== rdn_attid
:
1973 if o
.attid
== drsuapi
.DRSUAPI_ATTID_name
:
1975 if o
.attid
== drsuapi
.DRSUAPI_ATTID_lastKnownParent
:
1978 attname
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1980 attname
= "<unknown:0x%x08x>" % o
.attid
1981 unexpected
.append(attname
)
1983 if len(unexpected
) > 0:
1984 self
.report('Unexpeted attributes: %s' % ",".join(unexpected
))
1985 self
.report('Not fixing changes after deletion bug')
1988 if not self
.confirm_all('Delete broken tombstone object %s deleted %s days ago?' % (
1989 dn
, self
.tombstoneLifetime
), 'fix_changes_after_deletion_bug'):
1990 self
.report('Not fixing changes after deletion bug')
1993 if self
.do_delete(dn
, ["relax:0"],
1994 "Failed to remove DN %s" % dn
):
1995 self
.report("Removed DN %s" % dn
)
1997 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
1998 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
2003 # Search for a zero invocationID
2004 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
2008 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
2009 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
2010 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
2011 % (dn
, o
.attid
, o
.version
,
2012 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
2013 self
.samdb
.get_invocation_id()))
2017 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
2018 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
2021 now
= samba
.unix2nttime(int(time
.time()))
2024 # Search for a zero invocationID
2025 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
2029 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
2030 o
.version
= o
.version
+ 1
2031 o
.originating_change_time
= now
2032 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
2033 o
.originating_usn
= seq
2037 replBlob
= ndr_pack(repl
)
2041 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
2042 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
2043 self
.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr
, dn
))
2046 nmsg
= ldb
.Message()
2048 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
2049 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
2050 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
2051 "Failed to fix attribute %s" % attr
):
2052 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
2054 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
2055 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
2059 # Search for an invalid attid
2061 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
2063 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
2066 def err_replmetadata_incorrect_attid(self
, dn
, attr
, repl_meta_data
, wrong_attids
):
2067 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
2072 remove_attid
= set()
2075 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
2078 # Sort the array, except for the last element. This strange
2079 # construction, creating a new list, due to bugs in samba's
2080 # array handling in IDL generated objects.
2081 ctr
.array
= sorted(ctr
.array
[:], key
=lambda o
: o
.attid
)
2082 # Now walk it in reverse, so we see the low (and so incorrect,
2083 # the correct values are above 0x80000000) values first and
2084 # remove the 'second' value we see.
2085 for o
in reversed(ctr
.array
):
2086 print("%s: 0x%08x" % (dn
, o
.attid
))
2087 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
2088 if att
.lower() in set_att
:
2089 self
.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att
, attr
, dn
))
2090 if not self
.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
2091 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
),
2092 'fix_replmetadata_duplicate_attid'):
2093 self
.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
2094 % (o
.attid
, att
, attr
, dn
))
2097 remove_attid
.add(o
.attid
)
2098 # We want to set the metadata for the most recent
2099 # update to have been applied locally, that is the metadata
2100 # matching the (eg string) value in the attribute
2101 if o
.local_usn
> hash_att
[att
].local_usn
:
2102 # This is always what we would have sent over DRS,
2103 # because the DRS server will have sent the
2104 # msDS-IntID, but with the values from both
2105 # attribute entries.
2106 hash_att
[att
].version
= o
.version
2107 hash_att
[att
].originating_change_time
= o
.originating_change_time
2108 hash_att
[att
].originating_invocation_id
= o
.originating_invocation_id
2109 hash_att
[att
].originating_usn
= o
.originating_usn
2110 hash_att
[att
].local_usn
= o
.local_usn
2112 # Do not re-add the value to the set or overwrite the hash value
2116 set_att
.add(att
.lower())
2118 # Generate a real list we can sort on properly
2119 new_list
= [o
for o
in ctr
.array
if o
.attid
not in remove_attid
]
2121 if (len(wrong_attids
) > 0):
2123 if o
.attid
in wrong_attids
:
2124 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
2125 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
, is_schema_nc
=in_schema_nc
)
2126 self
.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr
, dn
))
2127 if not self
.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
2128 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
), 'fix_replmetadata_wrong_attid'):
2129 self
.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
2130 % (o
.attid
, correct_attid
, att
, attr
, dn
))
2133 o
.attid
= correct_attid
2135 # Sort the array, (we changed the value so must re-sort)
2136 new_list
[:] = sorted(new_list
[:], key
=lambda o
: o
.attid
)
2138 # If we did not already need to fix it, then ask about sorting
2140 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
2141 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
2142 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
2143 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
2146 # The actual sort done is done at the top of the function
2148 ctr
.count
= len(new_list
)
2149 ctr
.array
= new_list
2150 replBlob
= ndr_pack(repl
)
2152 nmsg
= ldb
.Message()
2154 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
2155 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
2156 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
2157 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
2158 "Failed to fix attribute %s" % attr
):
2159 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
2161 def is_deleted_deleted_objects(self
, obj
):
2163 if "description" not in obj
:
2164 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
2166 if "showInAdvancedViewOnly" not in obj
or str(obj
['showInAdvancedViewOnly'][0]).upper() == 'FALSE':
2167 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
2169 if "objectCategory" not in obj
:
2170 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
2172 if "isCriticalSystemObject" not in obj
or str(obj
['isCriticalSystemObject'][0]).upper() == 'FALSE':
2173 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
2175 if "isRecycled" in obj
:
2176 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
2178 if "isDeleted" in obj
and str(obj
['isDeleted'][0]).upper() == 'FALSE':
2179 self
.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj
.dn
)
2181 if "objectClass" not in obj
or (len(obj
['objectClass']) != 2 or
2182 str(obj
['objectClass'][0]) != 'top' or
2183 str(obj
['objectClass'][1]) != 'container'):
2184 self
.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj
.dn
)
2186 if "systemFlags" not in obj
or str(obj
['systemFlags'][0]) != '-1946157056':
2187 self
.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj
.dn
)
2191 def err_deleted_deleted_objects(self
, obj
):
2192 nmsg
= ldb
.Message()
2193 nmsg
.dn
= dn
= obj
.dn
2195 if "description" not in obj
:
2196 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
2197 if "showInAdvancedViewOnly" not in obj
:
2198 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
2199 if "objectCategory" not in obj
:
2200 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
2201 if "isCriticalSystemObject" not in obj
:
2202 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
2203 if "isRecycled" in obj
:
2204 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
2206 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2207 nmsg
["systemFlags"] = ldb
.MessageElement("-1946157056", ldb
.FLAG_MOD_REPLACE
, "systemFlags")
2208 nmsg
["objectClass"] = ldb
.MessageElement(["top", "container"], ldb
.FLAG_MOD_REPLACE
, "objectClass")
2210 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
2211 % (dn
), 'fix_deleted_deleted_objects'):
2212 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
2215 if self
.do_modify(nmsg
, ["relax:0"],
2216 "Failed to fix Deleted Objects container %s" % dn
):
2217 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
2219 def err_replica_locations(self
, obj
, cross_ref
, attr
):
2220 nmsg
= ldb
.Message()
2222 target
= self
.samdb
.get_dsServiceName()
2224 if self
.samdb
.am_rodc():
2225 self
.report('Not fixing %s %s for the RODC' % (attr
, obj
.dn
))
2228 if not self
.confirm_all('Add yourself to the replica locations for %s?'
2229 % (obj
.dn
), 'fix_replica_locations'):
2230 self
.report('Not fixing missing/incorrect attributes on %s\n' % (obj
.dn
))
2233 nmsg
[attr
] = ldb
.MessageElement(target
, ldb
.FLAG_MOD_ADD
, attr
)
2234 if self
.do_modify(nmsg
, [], "Failed to add %s for %s" % (attr
, obj
.dn
)):
2235 self
.report("Fixed %s for %s" % (attr
, obj
.dn
))
2237 def is_fsmo_role(self
, dn
):
2238 if dn
== self
.samdb
.domain_dn
:
2240 if dn
== self
.infrastructure_dn
:
2242 if dn
== self
.naming_dn
:
2244 if dn
== self
.schema_dn
:
2246 if dn
== self
.rid_dn
:
2251 def calculate_instancetype(self
, dn
):
2253 nc_root
= self
.samdb
.get_nc_root(dn
)
2255 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
2257 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
2258 except ldb
.LdbError
as e4
:
2259 (enum
, estr
) = e4
.args
2260 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2263 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
2264 if self
.write_ncs
is not None and str(nc_root
) in [str(x
) for x
in self
.write_ncs
]:
2265 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
2269 def get_wellknown_sd(self
, dn
):
2270 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
2272 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
2273 return ndr_unpack(security
.descriptor
,
2274 descriptor_fn(domain_sid
,
2275 name_map
=self
.name_map
))
2279 def find_checkable_attrs(self
, dn
, requested_attrs
):
2280 """A helper function for check_object() that calculates the list of
2281 attributes that need to be checked, and returns that as a list
2282 in the original case, and a set normalised to lowercase (for
2283 easy existence checks).
2285 if requested_attrs
is None:
2288 attrs
= list(requested_attrs
)
2290 lc_attrs
= set(x
.lower() for x
in attrs
)
2293 if a
.lower() not in lc_attrs
:
2295 lc_attrs
.add(a
.lower())
2297 if ("dn" in lc_attrs
or
2298 "distinguishedname" in lc_attrs
or
2299 dn
.get_rdn_name().lower() in lc_attrs
):
2300 attrs
.append("name")
2301 lc_attrs
.add('name')
2303 if 'name' in lc_attrs
:
2304 for a
in (dn
.get_rdn_name(),
2309 need_replPropertyMetaData
= False
2311 need_replPropertyMetaData
= True
2314 linkID
, _
= self
.get_attr_linkID_and_reverse_name(a
)
2319 need_replPropertyMetaData
= True
2321 if need_replPropertyMetaData
:
2322 add_attr("replPropertyMetaData")
2324 add_attr("objectGUID")
2326 return attrs
, lc_attrs
2328 def check_object(self
, dn
, requested_attrs
=None):
2329 '''check one object'''
2331 self
.report("Checking object %s" % dn
)
2333 # search attrs are used to find the attributes, lc_attrs are
2334 # used for existence checks
2335 search_attrs
, lc_attrs
= self
.find_checkable_attrs(dn
, requested_attrs
)
2339 sd_flags |
= security
.SECINFO_OWNER
2340 sd_flags |
= security
.SECINFO_GROUP
2341 sd_flags |
= security
.SECINFO_DACL
2342 sd_flags |
= security
.SECINFO_SACL
2344 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
2349 "sd_flags:1:%d" % sd_flags
,
2350 "reveal_internals:0",
2353 except ldb
.LdbError
as e10
:
2354 (enum
, estr
) = e10
.args
2355 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2356 if self
.in_transaction
:
2357 self
.report("ERROR: Object %s disappeared during check" % dn
)
2362 self
.report("ERROR: Object %s failed to load during check" % dn
)
2366 set_attrs_from_md
= set()
2367 set_attrs_seen
= set()
2368 got_objectclass
= False
2370 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
2372 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
2373 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
2375 # We have no deleted objects DN for schema, and we check for this above for the other
2377 deleted_objects_dn
= None
2379 object_rdn_attr
= None
2380 object_rdn_val
= None
2384 repl_meta_data_val
= None
2386 for attrname
in obj
:
2387 if attrname
.lower() == 'isdeleted':
2388 if str(obj
[attrname
][0]) != "FALSE":
2391 if attrname
.lower() == 'systemflags':
2392 systemFlags
= int(obj
[attrname
][0])
2394 if attrname
.lower() == 'replpropertymetadata':
2395 repl_meta_data_val
= obj
[attrname
][0]
2397 if isDeleted
and repl_meta_data_val
:
2398 if self
.has_changes_after_deletion(dn
, repl_meta_data_val
):
2400 self
.err_changes_after_deletion(dn
, repl_meta_data_val
)
2402 if self
.is_expired_tombstone(dn
, repl_meta_data_val
):
2405 for attrname
in obj
:
2406 if attrname
== 'dn' or attrname
== "distinguishedName":
2409 if attrname
.lower() == 'objectclass':
2410 got_objectclass
= True
2412 if attrname
.lower() == "name":
2413 if len(obj
[attrname
]) != 1:
2414 self
.unfixable_errors
+= 1
2415 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2416 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2418 name_val
= str(obj
[attrname
][0])
2420 if attrname
.lower() == str(obj
.dn
.get_rdn_name()).lower():
2421 object_rdn_attr
= attrname
2422 if len(obj
[attrname
]) != 1:
2423 self
.unfixable_errors
+= 1
2424 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2425 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2427 object_rdn_val
= str(obj
[attrname
][0])
2429 if attrname
.lower() == 'replpropertymetadata':
2430 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
][0]):
2432 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
][0])
2433 # We don't continue, as we may also have other fixes for this attribute
2434 # based on what other attributes we see.
2437 (set_attrs_from_md
, list_attid_from_md
, wrong_attids
) \
2438 = self
.process_metadata(dn
, obj
[attrname
][0])
2441 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
2444 if len(set_attrs_from_md
) < len(list_attid_from_md
) \
2445 or len(wrong_attids
) > 0 \
2446 or sorted(list_attid_from_md
) != list_attid_from_md
:
2448 self
.err_replmetadata_incorrect_attid(dn
, attrname
, obj
[attrname
][0], wrong_attids
)
2451 # Here we check that the first attid is 0
2453 if list_attid_from_md
[0] != 0:
2454 self
.unfixable_errors
+= 1
2455 self
.report("ERROR: Not fixing incorrect initial attributeID in '%s' on '%s', it should be objectClass" %
2456 (attrname
, str(dn
)))
2460 if attrname
.lower() == 'ntsecuritydescriptor':
2461 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
2462 if sd_broken
is not None:
2463 self
.err_wrong_sd(dn
, sd
, sd_broken
)
2467 if sd
.owner_sid
is None or sd
.group_sid
is None:
2468 self
.err_missing_sd_owner(dn
, sd
)
2472 if dn
== deleted_objects_dn
or self
.reset_well_known_acls
:
2474 well_known_sd
= self
.get_wellknown_sd(dn
)
2478 current_sd
= ndr_unpack(security
.descriptor
,
2481 ignoreAdditionalACEs
= False
2482 if not self
.reset_well_known_acls
:
2483 ignoreAdditionalACEs
= True
2485 diff
= get_diff_sds(well_known_sd
, current_sd
,
2486 security
.dom_sid(self
.samdb
.get_domain_sid()),
2487 ignoreAdditionalACEs
=ignoreAdditionalACEs
)
2489 self
.err_wrong_default_sd(dn
, well_known_sd
, diff
)
2494 if attrname
.lower() == 'objectclass':
2495 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
2496 # Do not consider the attribute incorrect if:
2497 # - The sorted (alphabetically) list is the same, including case
2498 # - The first and last elements are the same
2500 # This avoids triggering an error due to
2501 # non-determinism in the sort routine in (at least)
2502 # 4.3 and earlier, and the fact that any AUX classes
2503 # in these attributes are also not sorted when
2504 # imported from Windows (they are just in the reverse
2505 # order of last set)
2506 if sorted(normalised
) != sorted(obj
[attrname
]) \
2507 or normalised
[0] != obj
[attrname
][0] \
2508 or normalised
[-1] != obj
[attrname
][-1]:
2509 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
2513 if attrname
.lower() == 'userparameters':
2514 userparams
= obj
[attrname
][0]
2515 if userparams
== b
' ':
2517 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
2520 elif userparams
[:16] == b
'\x20\x00' * 8:
2521 # This is the correct, normal prefix
2524 elif userparams
[:20] == b
'IAAgACAAIAAgACAAIAAg':
2525 # this is the typical prefix from a windows migration
2527 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
2530 #43:00:00:00:74:00:00:00:78
2531 elif (userparams
[1] != 0 and
2532 userparams
[3] != 0 and
2533 userparams
[5] != 0 and
2534 userparams
[7] != 0 and
2535 userparams
[9] != 0):
2536 # This is a prefix that is not in UTF-16 format
2537 # for the space or munged dialback prefix
2539 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
2542 elif len(userparams
) % 2 != 0:
2543 # This is a value that isn't even in length
2545 self
.err_odd_userParameters(obj
, attrname
)
2548 elif (userparams
[1] == 0 and
2549 userparams
[2] == 0 and
2550 userparams
[3] == 0 and
2551 userparams
[4] != 0 and
2552 userparams
[5] == 0):
2553 # This is a prefix that would happen if a
2554 # SAMR-written value was replicated from a Samba
2555 # 4.1 server to a working server
2557 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
2560 if attrname
.lower() == 'attributeid' or attrname
.lower() == 'governsid':
2561 if obj
[attrname
][0] in self
.attribute_or_class_ids
:
2562 self
.unfixable_errors
+= 1
2563 self
.report('Error: %s %s on %s already exists as an attributeId or governsId'
2564 % (attrname
, obj
.dn
, obj
[attrname
][0]))
2566 self
.attribute_or_class_ids
.add(obj
[attrname
][0])
2568 # check for empty attributes
2569 for val
in obj
[attrname
]:
2571 self
.err_empty_attribute(dn
, attrname
)
2575 # get the syntax oid for the attribute, so we can can have
2576 # special handling for some specific attribute types
2578 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
2579 except Exception as msg
:
2580 self
.err_unknown_attribute(obj
, attrname
)
2584 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
2586 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
2587 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
2588 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
2590 set_attrs_seen
.add(attrname
.lower())
2592 if syntax_oid
in [dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
2593 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
2594 # it's some form of DN, do specialised checking on those
2595 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
2599 # check for incorrectly normalised attributes
2600 for val
in obj
[attrname
]:
2603 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
2604 if len(normalised
) != 1 or normalised
[0] != val
:
2605 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
2609 if len(obj
[attrname
]) != len(values
):
2610 self
.err_duplicate_values(dn
, attrname
, obj
[attrname
], list(values
))
2614 if attrname
.lower() == "instancetype":
2615 calculated_instancetype
= self
.calculate_instancetype(dn
)
2616 if len(obj
["instanceType"]) != 1 or int(obj
["instanceType"][0]) != calculated_instancetype
:
2618 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
2620 if not got_objectclass
and ("*" in lc_attrs
or "objectclass" in lc_attrs
):
2622 self
.err_missing_objectclass(dn
)
2624 if ("*" in lc_attrs
or "name" in lc_attrs
):
2625 if name_val
is None:
2626 self
.unfixable_errors
+= 1
2627 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
2628 if object_rdn_attr
is None:
2629 self
.unfixable_errors
+= 1
2630 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
2632 if name_val
is not None:
2634 controls
= ["show_recycled:1", "relax:0"]
2636 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
2637 parent_dn
= deleted_objects_dn
2638 controls
+= ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
]
2639 if parent_dn
is None:
2640 parent_dn
= obj
.dn
.parent()
2643 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
2644 except ValueError as e
:
2645 self
.unfixable_errors
+= 1
2646 self
.report(f
"ERROR: could not handle parent DN '{parent_dn}': "
2647 "skipping RDN checks")
2649 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
2651 if obj
.dn
== deleted_objects_dn
:
2652 expected_dn
= obj
.dn
2654 if expected_dn
!= obj
.dn
:
2656 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
,
2657 object_rdn_val
, name_val
, controls
)
2658 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
2659 self
.unfixable_errors
+= 1
2660 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
,
2665 if repl_meta_data_val
:
2666 if obj
.dn
== deleted_objects_dn
:
2667 isDeletedAttId
= 131120
2668 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2670 expectedTimeDo
= 2650466015990000000
2671 originating
= self
.get_originating_time(repl_meta_data_val
, isDeletedAttId
)
2672 if originating
!= expectedTimeDo
:
2673 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
2674 nmsg
= ldb
.Message()
2676 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2678 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
2681 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
2683 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
2685 self
.report("On object %s" % dn
)
2688 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
2689 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
2690 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
2692 self
.fix_metadata(obj
, att
)
2694 if self
.is_fsmo_role(dn
):
2695 if "fSMORoleOwner" not in obj
and ("*" in lc_attrs
or "fsmoroleowner" in lc_attrs
):
2696 self
.err_no_fsmoRoleOwner(obj
)
2700 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
2701 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
2702 controls
=["show_recycled:1", "show_deleted:1"])
2703 except ldb
.LdbError
as e11
:
2704 (enum
, estr
) = e11
.args
2705 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2707 self
.report("WARNING: parent object not found for %s" % (obj
.dn
))
2708 self
.report("Not moving to LostAndFound "
2709 "(tombstone garbage collection in progress?)")
2711 self
.err_missing_parent(obj
)
2716 if dn
in self
.deleted_objects_containers
and '*' in lc_attrs
:
2717 if self
.is_deleted_deleted_objects(obj
):
2718 self
.err_deleted_deleted_objects(obj
)
2721 for (dns_part
, msg
) in self
.dns_partitions
:
2722 if dn
== dns_part
and 'repsFrom' in obj
:
2723 location
= "msDS-NC-Replica-Locations"
2724 if self
.samdb
.am_rodc():
2725 location
= "msDS-NC-RO-Replica-Locations"
2727 if location
not in msg
:
2728 # There are no replica locations!
2729 self
.err_replica_locations(obj
, msg
.dn
, location
)
2734 for loc
in msg
[location
]:
2735 if str(loc
) == self
.samdb
.get_dsServiceName():
2738 # This DC is not in the replica locations
2739 self
.err_replica_locations(obj
, msg
.dn
, location
)
2742 if dn
== self
.server_ref_dn
:
2743 # Check we have a valid RID Set
2744 if "*" in lc_attrs
or "ridsetreferences" in lc_attrs
:
2745 if "rIDSetReferences" not in obj
:
2746 # NO RID SET reference
2747 # We are RID master, allocate it.
2750 if self
.is_rid_master
:
2751 # Allocate a RID Set
2752 if self
.confirm_all('Allocate the missing RID set for '
2754 'fix_missing_rid_set_master'):
2756 # We don't have auto-transaction logic on
2757 # extended operations, so we have to do it
2760 self
.samdb
.transaction_start()
2763 self
.samdb
.create_own_rid_set()
2766 self
.samdb
.transaction_cancel()
2769 self
.samdb
.transaction_commit()
2771 elif not self
.samdb
.am_rodc():
2772 self
.report("No RID Set found for this server: %s, "
2773 "and we are not the RID Master (so can "
2774 "not self-allocate)" % dn
)
2776 # Check some details of our own RID Set
2778 # Note that the attributes have very bad names. From ridalloc.c:
2780 # Note: the RID allocation attributes in AD are very badly named.
2781 # Here is what we think they really do:
2783 # in RID Set object:
2784 # - rIDPreviousAllocationPool: the pool which a DC is currently
2785 # pulling RIDs from. Managed by client DC
2787 # - rIDAllocationPool: the pool that the DC will switch to next,
2788 # when rIDPreviousAllocationPool is exhausted. Managed by RID
2791 # - rIDNextRID: the last RID allocated by this DC. Managed by
2794 # in RID Manager object:
2795 # - rIDAvailablePool: the pool where the RID Manager gets new rID
2796 # pools from when it gets a EXOP_RID_ALLOC getncchanges call
2797 # (or locally when the DC is the RID Manager)
2799 if dn
== self
.rid_set_dn
:
2800 pool_attrs
= ["rIDAllocationPool", "rIDPreviousAllocationPool"]
2802 res
= self
.samdb
.search(base
=self
.rid_set_dn
, scope
=ldb
.SCOPE_BASE
,
2805 for pool_attr
in pool_attrs
:
2806 if pool_attr
not in res
[0]:
2809 pool
= int(res
[0][pool_attr
][0])
2812 low
= 0xFFFFFFFF & pool
2814 if pool
!= 0 and low
>= high
:
2815 self
.report("Invalid RID pool %d-%d, %d >= %d!" %
2816 (low
, high
, low
, high
))
2817 self
.unfixable_errors
+= 1
2819 if "rIDAllocationPool" not in res
[0]:
2820 self
.report("No rIDAllocationPool found in %s" % dn
)
2821 self
.unfixable_errors
+= 1
2824 next_free_rid
, high
= self
.samdb
.free_rid_bounds()
2825 except ldb
.LdbError
as err
:
2826 enum
, estr
= err
.args
2827 self
.report("Couldn't get available RIDs: %s" % estr
)
2828 self
.unfixable_errors
+= 1
2830 # Check the remainder of this pool for conflicts. If
2831 # ridalloc_allocate_rid() moves to a new pool, this
2832 # will be above high, so we will stop.
2833 domain_sid
= self
.samdb
.get_domain_sid()
2834 while next_free_rid
<= high
:
2835 sid
= "%s-%d" % (domain_sid
, next_free_rid
)
2837 res
= self
.samdb
.search(base
="<SID=%s>" % sid
,
2838 scope
=ldb
.SCOPE_BASE
,
2840 except ldb
.LdbError
as e
:
2841 (enum
, estr
) = e
.args
2842 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2846 self
.report("SID %s for %s conflicts with our current "
2847 "RID set in %s" % (sid
, res
[0].dn
, dn
))
2850 if self
.confirm_all('Fix conflict between SID %s and '
2851 'RID pool in %s by allocating a '
2854 'fix_sid_rid_set_conflict'):
2855 self
.samdb
.transaction_start()
2857 # This will burn RIDs, which will move
2858 # past the conflict. We then check again
2859 # to see if the new RID conflicts, until
2860 # the end of the current pool. We don't
2861 # look at the next pool to avoid burning
2862 # all RIDs in one go in some strange
2866 allocated_rid
= self
.samdb
.allocate_rid()
2867 if allocated_rid
>= next_free_rid
:
2868 next_free_rid
= allocated_rid
+ 1
2871 self
.samdb
.transaction_cancel()
2874 self
.samdb
.transaction_commit()
2882 ################################################################
2883 # check special @ROOTDSE attributes
2884 def check_rootdse(self
):
2885 '''check the @ROOTDSE special object'''
2886 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
2888 self
.report("Checking object %s" % dn
)
2889 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
2891 self
.report("Object %s disappeared during check" % dn
)
2896 # check that the dsServiceName is in GUID form
2897 if 'dsServiceName' not in obj
:
2898 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
2899 return error_count
+ 1
2901 if not str(obj
['dsServiceName'][0]).startswith('<GUID='):
2902 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2904 if not self
.confirm('Change dsServiceName to GUID form?'):
2906 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0].decode('utf8')),
2907 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
2908 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
2911 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
2912 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
2913 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
2914 self
.report("Changed dsServiceName to GUID form")
2917 ###############################################
2918 # re-index the database
2920 def reindex_database(self
):
2921 '''re-index the whole database'''
2923 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
2924 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
2925 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
2926 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
2928 ###############################################
2930 def reset_modules(self
):
2931 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2933 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
2934 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
2935 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)