1 # Samba4 AD database checker
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 from base64
import b64decode
24 from samba
import dsdb
25 from samba
import common
26 from samba
.dcerpc
import misc
27 from samba
.dcerpc
import drsuapi
28 from samba
.ndr
import ndr_unpack
, ndr_pack
29 from samba
.dcerpc
import drsblobs
30 from samba
.common
import dsdb_Dn
31 from samba
.dcerpc
import security
32 from samba
.descriptor
import get_wellknown_sds
, get_diff_sds
33 from samba
.auth
import system_session
, admin_session
34 from samba
.netcmd
import CommandError
35 from samba
.netcmd
.fsmo
import get_fsmo_roleowner
38 class dbcheck(object):
39 """check a SAM database for errors"""
41 def __init__(self
, samdb
, samdb_schema
=None, verbose
=False, fix
=False,
42 yes
=False, quiet
=False, in_transaction
=False,
43 reset_well_known_acls
=False):
45 self
.dict_oid_name
= None
46 self
.samdb_schema
= (samdb_schema
or samdb
)
47 self
.verbose
= verbose
51 self
.remove_all_unknown_attributes
= False
52 self
.remove_all_empty_attributes
= False
53 self
.fix_all_normalisation
= False
54 self
.fix_all_duplicates
= False
55 self
.fix_all_DN_GUIDs
= False
56 self
.fix_all_binary_dn
= False
57 self
.remove_implausible_deleted_DN_links
= False
58 self
.remove_plausible_deleted_DN_links
= False
59 self
.fix_all_string_dn_component_mismatch
= False
60 self
.fix_all_GUID_dn_component_mismatch
= False
61 self
.fix_all_SID_dn_component_mismatch
= False
62 self
.fix_all_old_dn_string_component_mismatch
= False
63 self
.fix_all_metadata
= False
64 self
.fix_time_metadata
= False
65 self
.fix_undead_linked_attributes
= False
66 self
.fix_all_missing_backlinks
= False
67 self
.fix_all_orphaned_backlinks
= False
68 self
.fix_all_missing_forward_links
= False
69 self
.duplicate_link_cache
= dict()
70 self
.recover_all_forward_links
= False
71 self
.fix_rmd_flags
= False
72 self
.fix_ntsecuritydescriptor
= False
73 self
.fix_ntsecuritydescriptor_owner_group
= False
74 self
.seize_fsmo_role
= False
75 self
.move_to_lost_and_found
= False
76 self
.fix_instancetype
= False
77 self
.fix_replmetadata_zero_invocationid
= False
78 self
.fix_replmetadata_duplicate_attid
= False
79 self
.fix_replmetadata_wrong_attid
= False
80 self
.fix_replmetadata_unsorted_attid
= False
81 self
.fix_deleted_deleted_objects
= False
82 self
.fix_incorrect_deleted_objects
= False
84 self
.fix_base64_userparameters
= False
85 self
.fix_utf8_userparameters
= False
86 self
.fix_doubled_userparameters
= False
87 self
.fix_sid_rid_set_conflict
= False
88 self
.reset_well_known_acls
= reset_well_known_acls
89 self
.reset_all_well_known_acls
= False
90 self
.in_transaction
= in_transaction
91 self
.infrastructure_dn
= ldb
.Dn(samdb
, "CN=Infrastructure," + samdb
.domain_dn())
92 self
.naming_dn
= ldb
.Dn(samdb
, "CN=Partitions,%s" % samdb
.get_config_basedn())
93 self
.schema_dn
= samdb
.get_schema_basedn()
94 self
.rid_dn
= ldb
.Dn(samdb
, "CN=RID Manager$,CN=System," + samdb
.domain_dn())
95 self
.ntds_dsa
= ldb
.Dn(samdb
, samdb
.get_dsServiceName())
96 self
.class_schemaIDGUID
= {}
97 self
.wellknown_sds
= get_wellknown_sds(self
.samdb
)
98 self
.fix_all_missing_objectclass
= False
99 self
.fix_missing_deleted_objects
= False
100 self
.fix_replica_locations
= False
101 self
.fix_missing_rid_set_master
= False
104 self
.link_id_cache
= {}
107 res
= samdb
.search(base
="CN=DnsAdmins,CN=Users,%s" % samdb
.domain_dn(), scope
=ldb
.SCOPE_BASE
,
109 dnsadmins_sid
= ndr_unpack(security
.dom_sid
, res
[0]["objectSid"][0])
110 self
.name_map
['DnsAdmins'] = str(dnsadmins_sid
)
111 except ldb
.LdbError
, (enum
, estr
):
112 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
116 self
.system_session_info
= system_session()
117 self
.admin_session_info
= admin_session(None, samdb
.get_domain_sid())
119 res
= self
.samdb
.search(base
=self
.ntds_dsa
, scope
=ldb
.SCOPE_BASE
, attrs
=['msDS-hasMasterNCs', 'hasMasterNCs'])
120 if "msDS-hasMasterNCs" in res
[0]:
121 self
.write_ncs
= res
[0]["msDS-hasMasterNCs"]
123 # If the Forest Level is less than 2003 then there is no
124 # msDS-hasMasterNCs, so we fall back to hasMasterNCs
125 # no need to merge as all the NCs that are in hasMasterNCs must
126 # also be in msDS-hasMasterNCs (but not the opposite)
127 if "hasMasterNCs" in res
[0]:
128 self
.write_ncs
= res
[0]["hasMasterNCs"]
130 self
.write_ncs
= None
132 res
= self
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['namingContexts'])
133 self
.deleted_objects_containers
= []
134 self
.ncs_lacking_deleted_containers
= []
135 self
.dns_partitions
= []
137 self
.ncs
= res
[0]["namingContexts"]
145 dn
= self
.samdb
.get_wellknown_dn(ldb
.Dn(self
.samdb
, nc
),
146 dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
147 self
.deleted_objects_containers
.append(dn
)
149 self
.ncs_lacking_deleted_containers
.append(ldb
.Dn(self
.samdb
, nc
))
151 domaindns_zone
= 'DC=DomainDnsZones,%s' % self
.samdb
.get_default_basedn()
152 forestdns_zone
= 'DC=ForestDnsZones,%s' % self
.samdb
.get_root_basedn()
153 domain
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
154 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
155 base
=self
.samdb
.get_partitions_dn(),
156 expression
="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone
)
158 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, forestdns_zone
), domain
[0]))
160 forest
= self
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
161 attrs
=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
162 base
=self
.samdb
.get_partitions_dn(),
163 expression
="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone
)
165 self
.dns_partitions
.append((ldb
.Dn(self
.samdb
, domaindns_zone
), forest
[0]))
167 fsmo_dn
= ldb
.Dn(self
.samdb
, "CN=RID Manager$,CN=System," + self
.samdb
.domain_dn())
168 rid_master
= get_fsmo_roleowner(self
.samdb
, fsmo_dn
, "rid")
169 if ldb
.Dn(self
.samdb
, self
.samdb
.get_dsServiceName()) == rid_master
:
170 self
.is_rid_master
= True
172 self
.is_rid_master
= False
174 # To get your rid set
176 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, self
.samdb
.get_serverName()),
177 scope
=ldb
.SCOPE_BASE
, attrs
=["serverReference"])
178 # 2. Get server reference
179 self
.server_ref_dn
= ldb
.Dn(self
.samdb
, res
[0]['serverReference'][0])
182 res
= self
.samdb
.search(base
=self
.server_ref_dn
,
183 scope
=ldb
.SCOPE_BASE
, attrs
=['rIDSetReferences'])
184 if "rIDSetReferences" in res
[0]:
185 self
.rid_set_dn
= ldb
.Dn(self
.samdb
, res
[0]['rIDSetReferences'][0])
187 self
.rid_set_dn
= None
189 self
.compatibleFeatures
= []
190 self
.requiredFeatures
= []
193 res
= self
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
195 attrs
=["compatibleFeatures",
197 if "compatibleFeatures" in res
[0]:
198 self
.compatibleFeatures
= res
[0]["compatibleFeatures"]
199 if "requiredFeatures" in res
[0]:
200 self
.requiredFeatures
= res
[0]["requiredFeatures"]
201 except ldb
.LdbError
as (enum
, estr
):
202 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
206 def check_database(self
, DN
=None, scope
=ldb
.SCOPE_SUBTREE
, controls
=[], attrs
=['*']):
207 '''perform a database check, returning the number of errors found'''
208 res
= self
.samdb
.search(base
=DN
, scope
=scope
, attrs
=['dn'], controls
=controls
)
209 self
.report('Checking %u objects' % len(res
))
212 error_count
+= self
.check_deleted_objects_containers()
214 self
.attribute_or_class_ids
= set()
217 self
.dn_set
.add(str(object.dn
))
218 error_count
+= self
.check_object(object.dn
, attrs
=attrs
)
221 error_count
+= self
.check_rootdse()
223 if error_count
!= 0 and not self
.fix
:
224 self
.report("Please use --fix to fix these errors")
226 self
.report('Checked %u objects (%u errors)' % (len(res
), error_count
))
229 def check_deleted_objects_containers(self
):
230 """This function only fixes conflicts on the Deleted Objects
231 containers, not the attributes"""
233 for nc
in self
.ncs_lacking_deleted_containers
:
234 if nc
== self
.schema_dn
:
237 self
.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc
)
238 if not self
.confirm_all('Fix missing Deleted Objects container for %s?' % (nc
), 'fix_missing_deleted_objects'):
241 dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects")
246 # If something already exists here, add a conflict
247 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[],
248 controls
=["show_deleted:1", "extended_dn:1:1",
249 "show_recycled:1", "reveal_internals:0"])
251 guid
= res
[0].dn
.get_extended_component("GUID")
252 conflict_dn
= ldb
.Dn(self
.samdb
,
253 "CN=Deleted Objects\\0ACNF:%s" % str(misc
.GUID(guid
)))
254 conflict_dn
.add_base(nc
)
256 except ldb
.LdbError
, (enum
, estr
):
257 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
260 self
.report("Couldn't check for conflicting Deleted Objects container: %s" % estr
)
263 if conflict_dn
is not None:
265 self
.samdb
.rename(dn
, conflict_dn
, ["show_deleted:1", "relax:0", "show_recycled:1"])
266 except ldb
.LdbError
, (enum
, estr
):
267 self
.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn
, conflict_dn
, estr
))
270 # Refresh wellKnownObjects links
271 res
= self
.samdb
.search(base
=nc
, scope
=ldb
.SCOPE_BASE
,
272 attrs
=['wellKnownObjects'],
273 controls
=["show_deleted:1", "extended_dn:0",
274 "show_recycled:1", "reveal_internals:0"])
276 self
.report("wellKnownObjects was not found for NC %s" % nc
)
279 # Prevent duplicate deleted objects containers just in case
280 wko
= res
[0]["wellKnownObjects"]
282 proposed_objectguid
= None
284 dsdb_dn
= dsdb_Dn(self
.samdb
, o
, dsdb
.DSDB_SYNTAX_BINARY_DN
)
285 if self
.is_deleted_objects_dn(dsdb_dn
):
286 self
.report("wellKnownObjects had duplicate Deleted Objects value %s" % o
)
287 # We really want to put this back in the same spot
288 # as the original one, so that on replication we
289 # merge, rather than conflict.
290 proposed_objectguid
= dsdb_dn
.dn
.get_extended_component("GUID")
293 if proposed_objectguid
is not None:
294 guid_suffix
= "\nobjectGUID: %s" % str(misc
.GUID(proposed_objectguid
))
296 wko_prefix
= "B:32:%s" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
297 listwko
.append('%s:%s' % (wko_prefix
, dn
))
300 # Insert a brand new Deleted Objects container
301 self
.samdb
.add_ldif("""dn: %s
303 objectClass: container
304 description: Container for deleted objects
306 isCriticalSystemObject: TRUE
307 showInAdvancedViewOnly: TRUE
308 systemFlags: -1946157056%s""" % (dn
, guid_suffix
),
309 controls
=["relax:0", "provision:0"])
311 delta
= ldb
.Message()
312 delta
.dn
= ldb
.Dn(self
.samdb
, str(res
[0]["dn"]))
313 delta
["wellKnownObjects"] = ldb
.MessageElement(listwko
,
314 ldb
.FLAG_MOD_REPLACE
,
317 # Insert the link to the brand new container
318 if self
.do_modify(delta
, ["relax:0"],
319 "NC %s lacks Deleted Objects WKGUID" % nc
,
321 self
.report("Added %s well known guid link" % dn
)
323 self
.deleted_objects_containers
.append(dn
)
327 def report(self
, msg
):
328 '''print a message unless quiet is set'''
332 def confirm(self
, msg
, allow_all
=False, forced
=False):
333 '''confirm a change'''
340 return common
.confirm(msg
, forced
=forced
, allow_all
=allow_all
)
342 ################################################################
343 # a local confirm function with support for 'all'
344 def confirm_all(self
, msg
, all_attr
):
345 '''confirm a change with support for "all" '''
348 if getattr(self
, all_attr
) == 'NONE':
350 if getattr(self
, all_attr
) == 'ALL':
356 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
358 setattr(self
, all_attr
, 'ALL')
361 setattr(self
, all_attr
, 'NONE')
365 def do_delete(self
, dn
, controls
, msg
):
366 '''delete dn with optional verbose output'''
368 self
.report("delete DN %s" % dn
)
370 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
371 self
.samdb
.delete(dn
, controls
=controls
)
372 except Exception, err
:
373 if self
.in_transaction
:
374 raise CommandError("%s : %s" % (msg
, err
))
375 self
.report("%s : %s" % (msg
, err
))
379 def do_modify(self
, m
, controls
, msg
, validate
=True):
380 '''perform a modify with optional verbose output'''
381 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
383 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
384 self
.report("controls: %r" % controls
)
386 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
387 except Exception, err
:
388 if self
.in_transaction
:
389 raise CommandError("%s : %s" % (msg
, err
))
390 self
.report("%s : %s" % (msg
, err
))
394 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
395 '''perform a modify with optional verbose output'''
397 self
.report("""dn: %s
401 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
403 to_dn
= to_rdn
+ to_base
404 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
405 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
406 except Exception, err
:
407 if self
.in_transaction
:
408 raise CommandError("%s : %s" % (msg
, err
))
409 self
.report("%s : %s" % (msg
, err
))
413 def get_attr_linkID_and_reverse_name(self
, attrname
):
414 if attrname
in self
.link_id_cache
:
415 return self
.link_id_cache
[attrname
]
416 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
418 revname
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
421 self
.link_id_cache
[attrname
] = (linkID
, revname
)
422 return linkID
, revname
424 def err_empty_attribute(self
, dn
, attrname
):
425 '''fix empty attributes'''
426 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
427 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
428 self
.report("Not fixing empty attribute %s" % attrname
)
433 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
434 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
435 "Failed to remove empty attribute %s" % attrname
, validate
=False):
436 self
.report("Removed empty attribute %s" % attrname
)
438 def err_normalise_mismatch(self
, dn
, attrname
, values
):
439 '''fix attribute normalisation errors'''
440 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
443 normalised
= self
.samdb
.dsdb_normalise_attributes(
444 self
.samdb_schema
, attrname
, [val
])
445 if len(normalised
) != 1:
446 self
.report("Unable to normalise value '%s'" % val
)
447 mod_list
.append((val
, ''))
448 elif (normalised
[0] != val
):
449 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
450 mod_list
.append((val
, normalised
[0]))
451 if not self
.confirm_all('Fix normalisation for %s from %s?' % (attrname
, dn
), 'fix_all_normalisation'):
452 self
.report("Not fixing attribute %s" % attrname
)
457 for i
in range(0, len(mod_list
)):
458 (val
, nval
) = mod_list
[i
]
459 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
461 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
464 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
465 "Failed to normalise attribute %s" % attrname
,
467 self
.report("Normalised attribute %s" % attrname
)
469 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
470 '''fix attribute normalisation errors'''
471 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
472 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
473 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
474 if list(normalised
) == values
:
476 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
477 self
.report("Not fixing attribute '%s'" % attrname
)
482 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
484 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
485 "Failed to normalise attribute %s" % attrname
,
487 self
.report("Normalised attribute %s" % attrname
)
489 def err_duplicate_values(self
, dn
, attrname
, dup_values
, values
):
490 '''fix attribute normalisation errors'''
491 self
.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname
, dn
))
492 self
.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values
), ','.join(values
)))
493 if not self
.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_duplicates'):
494 self
.report("Not fixing attribute '%s'" % attrname
)
499 m
[attrname
] = ldb
.MessageElement(values
, ldb
.FLAG_MOD_REPLACE
, attrname
)
501 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
502 "Failed to remove duplicate value on attribute %s" % attrname
,
504 self
.report("Removed duplicate value on attribute %s" % attrname
)
506 def is_deleted_objects_dn(self
, dsdb_dn
):
507 '''see if a dsdb_Dn is the special Deleted Objects DN'''
508 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
510 def err_missing_objectclass(self
, dn
):
511 """handle object without objectclass"""
512 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
)))
513 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'):
514 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
516 if self
.do_delete(dn
, ["relax:0"],
517 "Failed to remove DN %s" % dn
):
518 self
.report("Removed DN %s" % dn
)
520 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, remove_plausible
=False):
521 """handle a DN pointing to a deleted object"""
522 if not remove_plausible
:
523 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
524 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
525 if not self
.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
526 self
.report("Not removing")
529 self
.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
530 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
531 if not self
.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
532 self
.report("Not removing")
537 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
538 if self
.do_modify(m
, ["show_recycled:1",
539 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
540 "Failed to remove deleted DN attribute %s" % attrname
):
541 self
.report("Removed deleted DN on attribute %s" % attrname
)
543 def err_missing_target_dn_or_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
544 """handle a missing target DN (if specified, GUID form can't be found,
545 and otherwise DN string form can't be found)"""
546 # check if its a backlink
547 linkID
, _
= self
.get_attr_linkID_and_reverse_name(attrname
)
548 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
550 linkID
, reverse_link_name \
551 = self
.get_attr_linkID_and_reverse_name(attrname
)
552 if reverse_link_name
is not None:
553 self
.report("WARNING: no target object found for GUID "
554 "component for one-way forward link "
556 "%s - %s" % (attrname
, dn
, val
))
557 self
.report("Not removing dangling forward link")
560 nc_root
= self
.samdb
.get_nc_root(dn
)
561 target_nc_root
= self
.samdb
.get_nc_root(dsdb_dn
.dn
)
562 if nc_root
!= target_nc_root
:
563 # We don't bump the error count as Samba produces these
564 # in normal operation
565 self
.report("WARNING: no target object found for GUID "
566 "component for cross-partition link "
568 "%s - %s" % (attrname
, dn
, val
))
569 self
.report("Not removing dangling one-way "
570 "cross-partition link "
571 "(we might be mid-replication)")
574 # Due to our link handling one-way links pointing to
575 # missing objects are plausible.
577 # We don't bump the error count as Samba produces these
578 # in normal operation
579 self
.report("WARNING: no target object found for GUID "
580 "component for DN value %s in object "
581 "%s - %s" % (attrname
, dn
, val
))
582 self
.err_deleted_dn(dn
, attrname
, val
,
583 dsdb_dn
, dsdb_dn
, True)
586 # We bump the error count here, as we should have deleted this
587 self
.report("ERROR: no target object found for GUID "
588 "component for link %s in object "
589 "%s - %s" % (attrname
, dn
, val
))
590 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
, False)
593 def err_missing_dn_GUID_component(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
594 """handle a missing GUID extended DN component"""
595 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
596 controls
=["extended_dn:1:1", "show_recycled:1"]
598 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
599 attrs
=[], controls
=controls
)
600 except ldb
.LdbError
, (enum
, estr
):
601 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
602 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
604 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
607 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
608 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
610 dsdb_dn
.dn
= res
[0].dn
612 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
613 self
.report("Not fixing %s" % errstr
)
617 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
618 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
620 if self
.do_modify(m
, ["show_recycled:1"],
621 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
622 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
624 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
625 """handle an incorrect binary DN component"""
626 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
627 controls
=["extended_dn:1:1", "show_recycled:1"]
629 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
630 self
.report("Not fixing %s" % errstr
)
634 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
635 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
637 if self
.do_modify(m
, ["show_recycled:1"],
638 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
639 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
641 def err_dn_string_component_old(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
642 """handle a DN string being incorrect"""
643 self
.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
644 dsdb_dn
.dn
= correct_dn
646 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
647 'fix_all_old_dn_string_component_mismatch'):
648 self
.report("Not fixing old string component")
652 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
653 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
654 if self
.do_modify(m
, ["show_recycled:1",
655 "local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME
],
656 "Failed to fix old DN string on attribute %s" % (attrname
)):
657 self
.report("Fixed old DN string on attribute %s" % (attrname
))
659 def err_dn_component_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, mismatch_type
):
660 """handle a DN string being incorrect"""
661 self
.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type
, attrname
, dn
, val
))
662 dsdb_dn
.dn
= correct_dn
664 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
665 'fix_all_%s_dn_component_mismatch' % mismatch_type
):
666 self
.report("Not fixing %s component mismatch" % mismatch_type
)
670 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
671 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
672 if self
.do_modify(m
, ["show_recycled:1"],
673 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type
, attrname
)):
674 self
.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type
, attrname
))
676 def err_unknown_attribute(self
, obj
, attrname
):
677 '''handle an unknown attribute error'''
678 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
679 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
680 self
.report("Not removing %s" % attrname
)
684 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
685 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
686 "Failed to remove unknown attribute %s" % attrname
):
687 self
.report("Removed unknown attribute %s" % (attrname
))
689 def err_undead_linked_attribute(self
, obj
, attrname
, val
):
690 '''handle a link that should not be there on a deleted object'''
691 self
.report("ERROR: linked attribute '%s' to '%s' is present on "
692 "deleted object %s" % (attrname
, val
, obj
.dn
))
693 if not self
.confirm_all('Remove linked attribute %s' % attrname
, 'fix_undead_linked_attributes'):
694 self
.report("Not removing linked attribute %s" % attrname
)
698 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
700 if self
.do_modify(m
, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
701 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
702 "Failed to delete forward link %s" % attrname
):
703 self
.report("Fixed undead forward link %s" % (attrname
))
705 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
706 '''handle a missing backlink value'''
707 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
708 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
709 self
.report("Not fixing missing backlink %s" % backlink_name
)
713 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, backlink_name
)
714 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
715 "Failed to fix missing backlink %s" % backlink_name
):
716 self
.report("Fixed missing backlink %s" % (backlink_name
))
718 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
719 '''handle a incorrect RMD_FLAGS value'''
720 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
721 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()))
722 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
723 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
727 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
728 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
729 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
730 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
732 def err_orphaned_backlink(self
, obj_dn
, backlink_attr
, backlink_val
,
733 target_dn
, forward_attr
, forward_syntax
,
734 check_duplicates
=True):
735 '''handle a orphaned backlink value'''
736 if check_duplicates
is True and self
.has_duplicate_links(target_dn
, forward_attr
, forward_syntax
):
737 self
.report("WARNING: Keep orphaned backlink attribute " + \
738 "'%s' in '%s' for link '%s' in '%s'" % (
739 backlink_attr
, obj_dn
, forward_attr
, target_dn
))
741 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr
, obj_dn
, forward_attr
, target_dn
))
742 if not self
.confirm_all('Remove orphaned backlink %s' % backlink_attr
, 'fix_all_orphaned_backlinks'):
743 self
.report("Not removing orphaned backlink %s" % backlink_attr
)
747 m
['value'] = ldb
.MessageElement(backlink_val
, ldb
.FLAG_MOD_DELETE
, backlink_attr
)
748 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
749 "Failed to fix orphaned backlink %s" % backlink_attr
):
750 self
.report("Fixed orphaned backlink %s" % (backlink_attr
))
752 def err_recover_forward_links(self
, obj
, forward_attr
, forward_vals
):
753 '''handle a duplicate links value'''
755 self
.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr
, obj
.dn
))
757 if not self
.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr
, 'recover_all_forward_links'):
758 self
.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
759 forward_attr
, obj
.dn
))
763 m
['value'] = ldb
.MessageElement(forward_vals
, ldb
.FLAG_MOD_REPLACE
, forward_attr
)
764 if self
.do_modify(m
, ["local_oid:%s:1" % dsdb
.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS
],
765 "Failed to fix duplicate links in attribute '%s'" % forward_attr
):
766 self
.report("Fixed duplicate links in attribute '%s'" % (forward_attr
))
767 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
768 assert duplicate_cache_key
in self
.duplicate_link_cache
769 self
.duplicate_link_cache
[duplicate_cache_key
] = False
771 def err_no_fsmoRoleOwner(self
, obj
):
772 '''handle a missing fSMORoleOwner'''
773 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
774 res
= self
.samdb
.search("",
775 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
777 serviceName
= res
[0]["dsServiceName"][0]
778 if not self
.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
779 self
.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
783 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
784 if self
.do_modify(m
, [],
785 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
786 self
.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
788 def err_missing_parent(self
, obj
):
789 '''handle a missing parent'''
790 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
791 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
792 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
795 keep_transaction
= False
796 self
.samdb
.transaction_start()
798 nc_root
= self
.samdb
.get_nc_root(obj
.dn
);
799 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
800 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
801 new_dn
.remove_base_components(len(new_dn
) - 1)
802 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
803 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
804 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
808 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
810 if self
.do_modify(m
, [],
811 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
812 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
813 keep_transaction
= True
815 self
.samdb
.transaction_cancel()
819 self
.samdb
.transaction_commit()
821 self
.samdb
.transaction_cancel()
823 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
):
824 '''handle a wrong dn'''
826 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
827 new_rdn
.remove_base_components(len(new_rdn
) - 1)
828 new_parent
= new_dn
.parent()
831 if rdn_val
!= name_val
:
832 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
833 attributes
+= "name=%r" % (name_val
)
835 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
836 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
837 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
840 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, ["show_recycled:1", "relax:0"],
841 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
842 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
844 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
845 '''handle a wrong instanceType'''
846 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
847 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
848 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
853 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
854 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
855 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
856 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
858 def err_short_userParameters(self
, obj
, attrname
, value
):
859 # This is a truncated userParameters due to a pre 4.1 replication bug
860 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
)))
862 def err_base64_userParameters(self
, obj
, attrname
, value
):
863 '''handle a wrong userParameters'''
864 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
865 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
866 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
871 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
872 if self
.do_modify(m
, [],
873 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
874 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
876 def err_utf8_userParameters(self
, obj
, attrname
, value
):
877 '''handle a wrong userParameters'''
878 self
.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj
.dn
))
879 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
880 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
885 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
886 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
887 if self
.do_modify(m
, [],
888 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
889 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
891 def err_doubled_userParameters(self
, obj
, attrname
, value
):
892 '''handle a wrong userParameters'''
893 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
894 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
895 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
900 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
901 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
902 if self
.do_modify(m
, [],
903 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
904 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
906 def err_odd_userParameters(self
, obj
, attrname
):
907 # This is a truncated userParameters due to a pre 4.1 replication bug
908 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
)))
910 def find_revealed_link(self
, dn
, attrname
, guid
):
911 '''return a revealed link in an object'''
912 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
913 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
914 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
915 for val
in res
[0][attrname
]:
916 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, syntax_oid
)
917 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
922 def check_duplicate_links(self
, obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
):
923 '''check a linked values for duplicate forward links'''
926 duplicate_dict
= dict()
929 # Only forward links can have this problem
930 if forward_linkID
& 1:
931 # If we got the reverse, skip it
932 return (error_count
, duplicate_dict
, unique_dict
)
934 if backlink_attr
is None:
935 return (error_count
, duplicate_dict
, unique_dict
)
937 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
938 if duplicate_cache_key
not in self
.duplicate_link_cache
:
939 self
.duplicate_link_cache
[duplicate_cache_key
] = False
941 for val
in obj
[forward_attr
]:
942 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, forward_syntax
)
944 # all DNs should have a GUID component
945 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
948 guidstr
= str(misc
.GUID(guid
))
949 keystr
= guidstr
+ dsdb_dn
.prefix
950 if keystr
not in unique_dict
:
951 unique_dict
[keystr
] = dsdb_dn
954 if keystr
not in duplicate_dict
:
955 duplicate_dict
[keystr
] = dict()
956 duplicate_dict
[keystr
]["keep"] = None
957 duplicate_dict
[keystr
]["delete"] = list()
959 # Now check for the highest RMD_VERSION
960 v1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_VERSION"))
961 v2
= int(dsdb_dn
.dn
.get_extended_component("RMD_VERSION"))
963 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
964 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
967 duplicate_dict
[keystr
]["keep"] = dsdb_dn
968 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
969 unique_dict
[keystr
] = dsdb_dn
971 # Fallback to the highest RMD_LOCAL_USN
972 u1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_LOCAL_USN"))
973 u2
= int(dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN"))
975 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
976 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
978 duplicate_dict
[keystr
]["keep"] = dsdb_dn
979 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
980 unique_dict
[keystr
] = dsdb_dn
983 self
.duplicate_link_cache
[duplicate_cache_key
] = True
985 return (error_count
, duplicate_dict
, unique_dict
)
987 def has_duplicate_links(self
, dn
, forward_attr
, forward_syntax
):
988 '''check a linked values for duplicate forward links'''
991 duplicate_cache_key
= "%s:%s" % (str(dn
), forward_attr
)
992 if duplicate_cache_key
in self
.duplicate_link_cache
:
993 return self
.duplicate_link_cache
[duplicate_cache_key
]
995 forward_linkID
, backlink_attr
= self
.get_attr_linkID_and_reverse_name(forward_attr
)
997 attrs
= [forward_attr
]
998 controls
= ["extended_dn:1:1", "reveal_internals:0"]
1000 # check its the right GUID
1002 res
= self
.samdb
.search(base
=str(dn
), scope
=ldb
.SCOPE_BASE
,
1003 attrs
=attrs
, controls
=controls
)
1004 except ldb
.LdbError
, (enum
, estr
):
1005 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1011 error_count
, duplicate_dict
, unique_dict
= \
1012 self
.check_duplicate_links(obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
)
1014 if duplicate_cache_key
in self
.duplicate_link_cache
:
1015 return self
.duplicate_link_cache
[duplicate_cache_key
]
1019 def find_missing_forward_links_from_backlinks(self
, obj
,
1023 forward_unique_dict
):
1024 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1025 missing_forward_links
= []
1028 if backlink_attr
is None:
1029 return (missing_forward_links
, error_count
)
1031 if forward_syntax
!= ldb
.SYNTAX_DN
:
1032 self
.report("Not checking for missing forward links for syntax: %s",
1034 return (missing_forward_links
, error_count
)
1036 if "sortedLinks" in self
.compatibleFeatures
:
1037 self
.report("Not checking for missing forward links because the db " + \
1038 "has the sortedLinks feature")
1039 return (missing_forward_links
, error_count
)
1042 obj_guid
= obj
['objectGUID'][0]
1043 obj_guid_str
= str(ndr_unpack(misc
.GUID
, obj_guid
))
1044 filter = "(%s=<GUID=%s>)" % (backlink_attr
, obj_guid_str
)
1046 res
= self
.samdb
.search(expression
=filter,
1047 scope
=ldb
.SCOPE_SUBTREE
, attrs
=["objectGUID"],
1048 controls
=["extended_dn:1:1",
1049 "search_options:1:2",
1050 "paged_results:1:1000"])
1051 except ldb
.LdbError
, (enum
, estr
):
1055 target_dn
= dsdb_Dn(self
.samdb
, r
.dn
.extended_str(), forward_syntax
)
1057 guid
= target_dn
.dn
.get_extended_component("GUID")
1058 guidstr
= str(misc
.GUID(guid
))
1059 if guidstr
in forward_unique_dict
:
1062 # A valid forward link looks like this:
1064 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1065 # <RMD_ADDTIME=131607546230000000>;
1066 # <RMD_CHANGETIME=131607546230000000>;
1068 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1069 # <RMD_LOCAL_USN=3765>;
1070 # <RMD_ORIGINATING_USN=3765>;
1072 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1073 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1075 # Note that versions older than Samba 4.8 create
1076 # links with RMD_VERSION=0.
1078 # Try to get the local_usn and time from objectClass
1079 # if possible and fallback to any other one.
1080 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1081 obj
['replPropertyMetadata'][0])
1082 for o
in repl
.ctr
.array
:
1083 local_usn
= o
.local_usn
1084 t
= o
.originating_change_time
1085 if o
.attid
== drsuapi
.DRSUAPI_ATTID_objectClass
:
1088 # We use a magic invocationID for restoring missing
1089 # forward links to recover from bug #13228.
1090 # This should allow some more future magic to fix the
1093 # It also means it looses the conflict resolution
1094 # against almost every real invocation, if the
1095 # version is also 0.
1096 originating_invocid
= misc
.GUID("ffffffff-4700-4700-4700-000000b13228")
1102 rmd_invocid
= originating_invocid
1103 rmd_originating_usn
= originating_usn
1104 rmd_local_usn
= local_usn
1107 target_dn
.dn
.set_extended_component("RMD_ADDTIME", str(rmd_addtime
))
1108 target_dn
.dn
.set_extended_component("RMD_CHANGETIME", str(rmd_changetime
))
1109 target_dn
.dn
.set_extended_component("RMD_FLAGS", str(rmd_flags
))
1110 target_dn
.dn
.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid
))
1111 target_dn
.dn
.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn
))
1112 target_dn
.dn
.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn
))
1113 target_dn
.dn
.set_extended_component("RMD_VERSION", str(rmd_version
))
1116 missing_forward_links
.append(target_dn
)
1118 return (missing_forward_links
, error_count
)
1120 def check_dn(self
, obj
, attrname
, syntax_oid
):
1121 '''check a DN attribute for correctness'''
1123 obj_guid
= obj
['objectGUID'][0]
1125 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
1126 if reverse_link_name
is not None:
1127 reverse_syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(reverse_link_name
)
1129 reverse_syntax_oid
= None
1131 error_count
, duplicate_dict
, unique_dict
= \
1132 self
.check_duplicate_links(obj
, attrname
, syntax_oid
, linkID
, reverse_link_name
)
1134 if len(duplicate_dict
) != 0:
1136 missing_forward_links
, missing_error_count
= \
1137 self
.find_missing_forward_links_from_backlinks(obj
,
1138 attrname
, syntax_oid
,
1141 error_count
+= missing_error_count
1143 forward_links
= [dn
for dn
in unique_dict
.values()]
1145 if missing_error_count
!= 0:
1146 self
.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1149 self
.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname
, obj
.dn
))
1150 for m
in missing_forward_links
:
1151 self
.report("Missing link '%s'" % (m
))
1152 if not self
.confirm_all("Schedule readding missing forward link for attribute %s" % attrname
,
1153 'fix_all_missing_forward_links'):
1154 self
.err_orphaned_backlink(m
.dn
, reverse_link_name
,
1155 obj
.dn
.extended_str(), obj
.dn
,
1156 attrname
, syntax_oid
,
1157 check_duplicates
=False)
1159 forward_links
+= [m
]
1160 for keystr
in duplicate_dict
.keys():
1161 d
= duplicate_dict
[keystr
]
1162 for dd
in d
["delete"]:
1163 self
.report("Duplicate link '%s'" % dd
)
1164 self
.report("Correct link '%s'" % d
["keep"])
1166 # We now construct the sorted dn values.
1167 # They're sorted by the objectGUID of the target
1168 # See dsdb_Dn.__cmp__()
1169 vals
= [str(dn
) for dn
in sorted(forward_links
)]
1170 self
.err_recover_forward_links(obj
, attrname
, vals
)
1171 # We should continue with the fixed values
1172 obj
[attrname
] = ldb
.MessageElement(vals
, 0, attrname
)
1174 for val
in obj
[attrname
]:
1175 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, syntax_oid
)
1177 # all DNs should have a GUID component
1178 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1181 self
.err_missing_dn_GUID_component(obj
.dn
, attrname
, val
, dsdb_dn
,
1185 guidstr
= str(misc
.GUID(guid
))
1186 attrs
= ['isDeleted', 'replPropertyMetaData']
1188 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
1189 fixing_msDS_HasInstantiatedNCs
= True
1190 attrs
.append("instanceType")
1192 fixing_msDS_HasInstantiatedNCs
= False
1194 if reverse_link_name
is not None:
1195 attrs
.append(reverse_link_name
)
1197 # check its the right GUID
1199 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
1200 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1",
1201 "reveal_internals:0"
1203 except ldb
.LdbError
, (enum
, estr
):
1204 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1207 # We don't always want to
1208 error_count
+= self
.err_missing_target_dn_or_GUID(obj
.dn
,
1214 if fixing_msDS_HasInstantiatedNCs
:
1215 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
1216 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
1218 if str(dsdb_dn
) != val
:
1220 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
1223 # now we have two cases - the source object might or might not be deleted
1224 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
1225 target_is_deleted
= 'isDeleted' in res
[0] and res
[0]['isDeleted'][0].upper() == 'TRUE'
1228 if is_deleted
and not obj
.dn
in self
.deleted_objects_containers
and linkID
:
1229 # A fully deleted object should not have any linked
1230 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1231 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1233 self
.err_undead_linked_attribute(obj
, attrname
, val
)
1236 elif target_is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
) and linkID
:
1237 # the target DN is not allowed to be deleted, unless the target DN is the
1238 # special Deleted Objects container
1240 local_usn
= dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN")
1242 if 'replPropertyMetaData' in res
[0]:
1243 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1244 str(res
[0]['replPropertyMetadata']))
1246 for o
in repl
.ctr
.array
:
1247 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1248 deleted_usn
= o
.local_usn
1249 if deleted_usn
>= int(local_usn
):
1250 # If the object was deleted after the link
1251 # was last modified then, clean it up here
1256 self
.err_deleted_dn(obj
.dn
, attrname
,
1257 val
, dsdb_dn
, res
[0].dn
, True)
1260 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
, False)
1263 # We should not check for incorrect
1264 # components on deleted links, as these are allowed to
1265 # go stale (we just need the GUID, not the name)
1266 rmd_blob
= dsdb_dn
.dn
.get_extended_component("RMD_FLAGS")
1268 if rmd_blob
is not None:
1269 rmd_flags
= int(rmd_blob
)
1271 # assert the DN matches in string form, where a reverse
1272 # link exists, otherwise (below) offer to fix it as a non-error.
1273 # The string form is essentially only kept for forensics,
1274 # as we always re-resolve by GUID in normal operations.
1275 if not rmd_flags
& 1 and reverse_link_name
is not None:
1276 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1278 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1279 res
[0].dn
, "string")
1282 if res
[0].dn
.get_extended_component("GUID") != dsdb_dn
.dn
.get_extended_component("GUID"):
1284 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1288 if res
[0].dn
.get_extended_component("SID") != dsdb_dn
.dn
.get_extended_component("SID"):
1290 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1294 # Only for non-links, not even forward-only links
1295 # (otherwise this breaks repl_meta_data):
1297 # Now we have checked the GUID and SID, offer to fix old
1298 # DN strings as a non-error (DNs, not links so no
1299 # backlink). Samba does not maintain this string
1300 # otherwise, so we don't increment error_count.
1301 if reverse_link_name
is None:
1302 if linkID
== 0 and str(res
[0].dn
) != str(dsdb_dn
.dn
):
1303 # Pass in the old/bad DN without the <GUID=...> part,
1304 # otherwise the LDB code will correct it on the way through
1305 # (Note: we still want to preserve the DSDB DN prefix in the
1306 # case of binary DNs)
1307 bad_dn
= dsdb_dn
.prefix
+ dsdb_dn
.dn
.get_linearized()
1308 self
.err_dn_string_component_old(obj
.dn
, attrname
, bad_dn
,
1312 # check the reverse_link is correct if there should be one
1314 if reverse_link_name
in res
[0]:
1315 for v
in res
[0][reverse_link_name
]:
1316 v_dn
= dsdb_Dn(self
.samdb
, v
)
1317 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1318 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1320 if v_blob
is not None:
1321 v_rmd_flags
= int(v_blob
)
1324 if v_guid
== obj_guid
:
1327 if match_count
!= 1:
1328 if syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
or reverse_syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
:
1330 # Forward binary multi-valued linked attribute
1332 for w
in obj
[attrname
]:
1333 w_guid
= dsdb_Dn(self
.samdb
, w
).dn
.get_extended_component("GUID")
1337 if match_count
== forward_count
:
1340 for v
in obj
[attrname
]:
1341 v_dn
= dsdb_Dn(self
.samdb
, v
)
1342 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1343 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1345 if v_blob
is not None:
1346 v_rmd_flags
= int(v_blob
)
1352 if match_count
== expected_count
:
1355 diff_count
= expected_count
- match_count
1358 # If there's a backward link on binary multi-valued linked attribute,
1359 # let the check on the forward link remedy the value.
1360 # UNLESS, there is no forward link detected.
1361 if match_count
== 0:
1363 self
.err_orphaned_backlink(obj
.dn
, attrname
,
1368 # Only warn here and let the forward link logic fix it.
1369 self
.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1370 attrname
, expected_count
, str(obj
.dn
),
1371 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1374 assert not target_is_deleted
1376 self
.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1377 attrname
, expected_count
, str(obj
.dn
),
1378 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1380 # Loop until the difference between the forward and
1381 # the backward links is resolved.
1382 while diff_count
!= 0:
1385 if match_count
> 0 or diff_count
> 1:
1386 # TODO no method to fix these right now
1387 self
.report("ERROR: Can't fix missing "
1388 "multi-valued backlinks on %s" % str(dsdb_dn
.dn
))
1390 self
.err_missing_backlink(obj
, attrname
,
1391 obj
.dn
.extended_str(),
1396 self
.err_orphaned_backlink(res
[0].dn
, reverse_link_name
,
1397 obj
.dn
.extended_str(), obj
.dn
,
1398 attrname
, syntax_oid
)
1405 def get_originating_time(self
, val
, attid
):
1406 '''Read metadata properties and return the originating time for
1407 a given attributeId.
1409 :return: the originating time or 0 if not found
1412 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
1415 for o
in repl
.ctr
.array
:
1416 if o
.attid
== attid
:
1417 return o
.originating_change_time
1421 def process_metadata(self
, dn
, val
):
1422 '''Read metadata properties and list attributes in it.
1423 raises KeyError if the attid is unknown.'''
1426 wrong_attids
= set()
1428 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1430 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
1433 for o
in repl
.ctr
.array
:
1434 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1435 set_att
.add(att
.lower())
1436 list_attid
.append(o
.attid
)
1437 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
,
1438 is_schema_nc
=in_schema_nc
)
1439 if correct_attid
!= o
.attid
:
1440 wrong_attids
.add(o
.attid
)
1442 return (set_att
, list_attid
, wrong_attids
)
1445 def fix_metadata(self
, obj
, attr
):
1446 '''re-write replPropertyMetaData elements for a single attribute for a
1447 object. This is used to fix missing replPropertyMetaData elements'''
1448 guid_str
= str(ndr_unpack(misc
.GUID
, obj
['objectGUID'][0]))
1449 dn
= ldb
.Dn(self
.samdb
, "<GUID=%s>" % guid_str
)
1450 res
= self
.samdb
.search(base
= dn
, scope
=ldb
.SCOPE_BASE
, attrs
= [attr
],
1451 controls
= ["search_options:1:2",
1454 nmsg
= ldb
.Message()
1456 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
1457 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
1458 "Failed to fix metadata for attribute %s" % attr
):
1459 self
.report("Fixed metadata for attribute %s" % attr
)
1461 def ace_get_effective_inherited_type(self
, ace
):
1462 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
1466 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
1468 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
1470 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
1472 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
1478 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
1481 return str(ace
.object.inherited_type
)
1483 def lookup_class_schemaIDGUID(self
, cls
):
1484 if cls
in self
.class_schemaIDGUID
:
1485 return self
.class_schemaIDGUID
[cls
]
1487 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1488 res
= self
.samdb
.search(base
=self
.schema_dn
,
1490 attrs
=["schemaIDGUID"])
1491 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
1493 self
.class_schemaIDGUID
[cls
] = t
1496 def process_sd(self
, dn
, obj
):
1497 sd_attr
= "nTSecurityDescriptor"
1498 sd_val
= obj
[sd_attr
]
1500 sd
= ndr_unpack(security
.descriptor
, str(sd_val
))
1502 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
1504 # we don't fix deleted objects
1507 sd_clean
= security
.descriptor()
1508 sd_clean
.owner_sid
= sd
.owner_sid
1509 sd_clean
.group_sid
= sd
.group_sid
1510 sd_clean
.type = sd
.type
1511 sd_clean
.revision
= sd
.revision
1514 last_inherited_type
= None
1517 if sd
.sacl
is not None:
1519 for i
in range(0, len(aces
)):
1522 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1523 sd_clean
.sacl_add(ace
)
1526 t
= self
.ace_get_effective_inherited_type(ace
)
1530 if last_inherited_type
is not None:
1531 if t
!= last_inherited_type
:
1532 # if it inherited from more than
1533 # one type it's very likely to be broken
1535 # If not the recalculation will calculate
1540 last_inherited_type
= t
1543 if sd
.dacl
is not None:
1545 for i
in range(0, len(aces
)):
1548 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1549 sd_clean
.dacl_add(ace
)
1552 t
= self
.ace_get_effective_inherited_type(ace
)
1556 if last_inherited_type
is not None:
1557 if t
!= last_inherited_type
:
1558 # if it inherited from more than
1559 # one type it's very likely to be broken
1561 # If not the recalculation will calculate
1566 last_inherited_type
= t
1569 return (sd_clean
, sd
)
1571 if last_inherited_type
is None:
1577 cls
= obj
["objectClass"][-1]
1582 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1583 attrs
=["isDeleted", "objectClass"],
1584 controls
=["show_recycled:1"])
1586 is_deleted
= 'isDeleted' in o
and o
['isDeleted'][0].upper() == 'TRUE'
1588 # we don't fix deleted objects
1590 cls
= o
["objectClass"][-1]
1592 t
= self
.lookup_class_schemaIDGUID(cls
)
1594 if t
!= last_inherited_type
:
1596 return (sd_clean
, sd
)
1601 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
1602 '''re-write the SD due to incorrect inherited ACEs'''
1603 sd_attr
= "nTSecurityDescriptor"
1604 sd_val
= ndr_pack(sd
)
1605 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1607 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
1608 self
.report('Not fixing %s on %s\n' % (sd_attr
, dn
))
1611 nmsg
= ldb
.Message()
1613 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1614 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1615 "Failed to fix attribute %s" % sd_attr
):
1616 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1618 def err_wrong_default_sd(self
, dn
, sd
, sd_old
, diff
):
1619 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1620 sd_attr
= "nTSecurityDescriptor"
1621 sd_val
= ndr_pack(sd
)
1622 sd_old_val
= ndr_pack(sd_old
)
1623 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1624 if sd
.owner_sid
is not None:
1625 sd_flags |
= security
.SECINFO_OWNER
1626 if sd
.group_sid
is not None:
1627 sd_flags |
= security
.SECINFO_GROUP
1629 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
1630 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
1635 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1636 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
1637 "Failed to reset attribute %s" % sd_attr
):
1638 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1640 def err_missing_sd_owner(self
, dn
, sd
):
1641 '''re-write the SD due to a missing owner or group'''
1642 sd_attr
= "nTSecurityDescriptor"
1643 sd_val
= ndr_pack(sd
)
1644 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
1646 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
1647 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
1650 nmsg
= ldb
.Message()
1652 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1654 # By setting the session_info to admin_session_info and
1655 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1656 # flags we cause the descriptor module to set the correct
1657 # owner and group on the SD, replacing the None/NULL values
1658 # for owner_sid and group_sid currently present.
1660 # The admin_session_info matches that used in provision, and
1661 # is the best guess we can make for an existing object that
1662 # hasn't had something specifically set.
1664 # This is important for the dns related naming contexts.
1665 self
.samdb
.set_session_info(self
.admin_session_info
)
1666 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1667 "Failed to fix metadata for attribute %s" % sd_attr
):
1668 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1669 self
.samdb
.set_session_info(self
.system_session_info
)
1672 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
1673 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1674 str(repl_meta_data
))
1678 # Search for a zero invocationID
1679 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1683 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1684 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1685 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1686 % (dn
, o
.attid
, o
.version
,
1687 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
1688 self
.samdb
.get_invocation_id()))
1693 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
1694 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1695 str(repl_meta_data
))
1697 now
= samba
.unix2nttime(int(time
.time()))
1700 # Search for a zero invocationID
1701 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1705 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
1706 o
.version
= o
.version
+ 1
1707 o
.originating_change_time
= now
1708 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
1709 o
.originating_usn
= seq
1713 replBlob
= ndr_pack(repl
)
1717 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1718 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1719 self
.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr
, dn
))
1722 nmsg
= ldb
.Message()
1724 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1725 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1726 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1727 "Failed to fix attribute %s" % attr
):
1728 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1731 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
1732 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1733 str(repl_meta_data
))
1736 # Search for an invalid attid
1738 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1740 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
1744 def err_replmetadata_incorrect_attid(self
, dn
, attr
, repl_meta_data
, wrong_attids
):
1745 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1746 str(repl_meta_data
))
1750 remove_attid
= set()
1753 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1756 # Sort the array, except for the last element. This strange
1757 # construction, creating a new list, due to bugs in samba's
1758 # array handling in IDL generated objects.
1759 ctr
.array
= sorted(ctr
.array
[:], key
=lambda o
: o
.attid
)
1760 # Now walk it in reverse, so we see the low (and so incorrect,
1761 # the correct values are above 0x80000000) values first and
1762 # remove the 'second' value we see.
1763 for o
in reversed(ctr
.array
):
1764 print "%s: 0x%08x" % (dn
, o
.attid
)
1765 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1766 if att
.lower() in set_att
:
1767 self
.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att
, attr
, dn
))
1768 if not self
.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1769 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
),
1770 'fix_replmetadata_duplicate_attid'):
1771 self
.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1772 % (o
.attid
, att
, attr
, dn
))
1775 remove_attid
.add(o
.attid
)
1776 # We want to set the metadata for the most recent
1777 # update to have been applied locally, that is the metadata
1778 # matching the (eg string) value in the attribute
1779 if o
.local_usn
> hash_att
[att
].local_usn
:
1780 # This is always what we would have sent over DRS,
1781 # because the DRS server will have sent the
1782 # msDS-IntID, but with the values from both
1783 # attribute entries.
1784 hash_att
[att
].version
= o
.version
1785 hash_att
[att
].originating_change_time
= o
.originating_change_time
1786 hash_att
[att
].originating_invocation_id
= o
.originating_invocation_id
1787 hash_att
[att
].originating_usn
= o
.originating_usn
1788 hash_att
[att
].local_usn
= o
.local_usn
1790 # Do not re-add the value to the set or overwrite the hash value
1794 set_att
.add(att
.lower())
1796 # Generate a real list we can sort on properly
1797 new_list
= [o
for o
in ctr
.array
if o
.attid
not in remove_attid
]
1799 if (len(wrong_attids
) > 0):
1801 if o
.attid
in wrong_attids
:
1802 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1803 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
, is_schema_nc
=in_schema_nc
)
1804 self
.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr
, dn
))
1805 if not self
.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1806 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
), 'fix_replmetadata_wrong_attid'):
1807 self
.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1808 % (o
.attid
, correct_attid
, att
, attr
, dn
))
1811 o
.attid
= correct_attid
1813 # Sort the array, (we changed the value so must re-sort)
1814 new_list
[:] = sorted(new_list
[:], key
=lambda o
: o
.attid
)
1816 # If we did not already need to fix it, then ask about sorting
1818 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
1819 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
1820 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
1821 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
1824 # The actual sort done is done at the top of the function
1826 ctr
.count
= len(new_list
)
1827 ctr
.array
= new_list
1828 replBlob
= ndr_pack(repl
)
1830 nmsg
= ldb
.Message()
1832 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1833 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1834 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1835 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1836 "Failed to fix attribute %s" % attr
):
1837 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1840 def is_deleted_deleted_objects(self
, obj
):
1842 if "description" not in obj
:
1843 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
1845 if "showInAdvancedViewOnly" not in obj
or obj
['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1846 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
1848 if "objectCategory" not in obj
:
1849 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
1851 if "isCriticalSystemObject" not in obj
or obj
['isCriticalSystemObject'][0].upper() == 'FALSE':
1852 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
1854 if "isRecycled" in obj
:
1855 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
1857 if "isDeleted" in obj
and obj
['isDeleted'][0].upper() == 'FALSE':
1858 self
.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj
.dn
)
1860 if "objectClass" not in obj
or (len(obj
['objectClass']) != 2 or
1861 obj
['objectClass'][0] != 'top' or
1862 obj
['objectClass'][1] != 'container'):
1863 self
.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj
.dn
)
1865 if "systemFlags" not in obj
or obj
['systemFlags'][0] != '-1946157056':
1866 self
.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj
.dn
)
1870 def err_deleted_deleted_objects(self
, obj
):
1871 nmsg
= ldb
.Message()
1872 nmsg
.dn
= dn
= obj
.dn
1874 if "description" not in obj
:
1875 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
1876 if "showInAdvancedViewOnly" not in obj
:
1877 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
1878 if "objectCategory" not in obj
:
1879 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
1880 if "isCriticalSystemObject" not in obj
:
1881 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
1882 if "isRecycled" in obj
:
1883 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
1885 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
1886 nmsg
["systemFlags"] = ldb
.MessageElement("-1946157056", ldb
.FLAG_MOD_REPLACE
, "systemFlags")
1887 nmsg
["objectClass"] = ldb
.MessageElement(["top", "container"], ldb
.FLAG_MOD_REPLACE
, "objectClass")
1889 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1890 % (dn
), 'fix_deleted_deleted_objects'):
1891 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
1894 if self
.do_modify(nmsg
, ["relax:0"],
1895 "Failed to fix Deleted Objects container %s" % dn
):
1896 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
1898 def err_replica_locations(self
, obj
, cross_ref
, attr
):
1899 nmsg
= ldb
.Message()
1901 target
= self
.samdb
.get_dsServiceName()
1903 if self
.samdb
.am_rodc():
1904 self
.report('Not fixing %s for the RODC' % (attr
, obj
.dn
))
1907 if not self
.confirm_all('Add yourself to the replica locations for %s?'
1908 % (obj
.dn
), 'fix_replica_locations'):
1909 self
.report('Not fixing missing/incorrect attributes on %s\n' % (obj
.dn
))
1912 nmsg
[attr
] = ldb
.MessageElement(target
, ldb
.FLAG_MOD_ADD
, attr
)
1913 if self
.do_modify(nmsg
, [], "Failed to add %s for %s" % (attr
, obj
.dn
)):
1914 self
.report("Fixed %s for %s" % (attr
, obj
.dn
))
1916 def is_fsmo_role(self
, dn
):
1917 if dn
== self
.samdb
.domain_dn
:
1919 if dn
== self
.infrastructure_dn
:
1921 if dn
== self
.naming_dn
:
1923 if dn
== self
.schema_dn
:
1925 if dn
== self
.rid_dn
:
1930 def calculate_instancetype(self
, dn
):
1932 nc_root
= self
.samdb
.get_nc_root(dn
)
1934 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
1936 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
1937 except ldb
.LdbError
, (enum
, estr
):
1938 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1941 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
1943 if self
.write_ncs
is not None and str(nc_root
) in self
.write_ncs
:
1944 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
1948 def get_wellknown_sd(self
, dn
):
1949 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
1951 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
1952 return ndr_unpack(security
.descriptor
,
1953 descriptor_fn(domain_sid
,
1954 name_map
=self
.name_map
))
1958 def check_object(self
, dn
, attrs
=['*']):
1959 '''check one object'''
1961 self
.report("Checking object %s" % dn
)
1963 # If we modify the pass-by-reference attrs variable, then we get a
1964 # replPropertyMetadata for every object that we check.
1966 if "dn" in map(str.lower
, attrs
):
1967 attrs
.append("name")
1968 if "distinguishedname" in map(str.lower
, attrs
):
1969 attrs
.append("name")
1970 if str(dn
.get_rdn_name()).lower() in map(str.lower
, attrs
):
1971 attrs
.append("name")
1972 if 'name' in map(str.lower
, attrs
):
1973 attrs
.append(dn
.get_rdn_name())
1974 attrs
.append("isDeleted")
1975 attrs
.append("systemFlags")
1976 need_replPropertyMetaData
= False
1978 need_replPropertyMetaData
= True
1981 linkID
, _
= self
.get_attr_linkID_and_reverse_name(a
)
1986 need_replPropertyMetaData
= True
1988 if need_replPropertyMetaData
:
1989 attrs
.append("replPropertyMetaData")
1990 attrs
.append("objectGUID")
1994 sd_flags |
= security
.SECINFO_OWNER
1995 sd_flags |
= security
.SECINFO_GROUP
1996 sd_flags |
= security
.SECINFO_DACL
1997 sd_flags |
= security
.SECINFO_SACL
1999 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
2004 "sd_flags:1:%d" % sd_flags
,
2005 "reveal_internals:0",
2008 except ldb
.LdbError
, (enum
, estr
):
2009 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2010 if self
.in_transaction
:
2011 self
.report("ERROR: Object %s disappeared during check" % dn
)
2016 self
.report("ERROR: Object %s failed to load during check" % dn
)
2020 set_attrs_from_md
= set()
2021 set_attrs_seen
= set()
2022 got_repl_property_meta_data
= False
2023 got_objectclass
= False
2025 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
2027 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
2028 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
2030 # We have no deleted objects DN for schema, and we check for this above for the other
2032 deleted_objects_dn
= None
2035 object_rdn_attr
= None
2036 object_rdn_val
= None
2041 for attrname
in obj
:
2042 if attrname
== 'dn' or attrname
== "distinguishedName":
2045 if str(attrname
).lower() == 'objectclass':
2046 got_objectclass
= True
2048 if str(attrname
).lower() == "name":
2049 if len(obj
[attrname
]) != 1:
2051 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2052 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2054 name_val
= obj
[attrname
][0]
2056 if str(attrname
).lower() == str(obj
.dn
.get_rdn_name()).lower():
2057 object_rdn_attr
= attrname
2058 if len(obj
[attrname
]) != 1:
2060 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2061 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2063 object_rdn_val
= obj
[attrname
][0]
2065 if str(attrname
).lower() == 'isdeleted':
2066 if obj
[attrname
][0] != "FALSE":
2069 if str(attrname
).lower() == 'systemflags':
2070 systemFlags
= int(obj
[attrname
][0])
2072 if str(attrname
).lower() == 'replpropertymetadata':
2073 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
]):
2075 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
])
2076 # We don't continue, as we may also have other fixes for this attribute
2077 # based on what other attributes we see.
2080 (set_attrs_from_md
, list_attid_from_md
, wrong_attids
) \
2081 = self
.process_metadata(dn
, obj
[attrname
])
2084 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
2087 if len(set_attrs_from_md
) < len(list_attid_from_md
) \
2088 or len(wrong_attids
) > 0 \
2089 or sorted(list_attid_from_md
) != list_attid_from_md
:
2091 self
.err_replmetadata_incorrect_attid(dn
, attrname
, obj
[attrname
], wrong_attids
)
2094 # Here we check that the first attid is 0
2096 if list_attid_from_md
[0] != 0:
2098 self
.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2099 (attrname
, str(dn
)))
2101 got_repl_property_meta_data
= True
2104 if str(attrname
).lower() == 'ntsecuritydescriptor':
2105 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
2106 if sd_broken
is not None:
2107 self
.err_wrong_sd(dn
, sd
, sd_broken
)
2111 if sd
.owner_sid
is None or sd
.group_sid
is None:
2112 self
.err_missing_sd_owner(dn
, sd
)
2116 if self
.reset_well_known_acls
:
2118 well_known_sd
= self
.get_wellknown_sd(dn
)
2122 current_sd
= ndr_unpack(security
.descriptor
,
2123 str(obj
[attrname
][0]))
2125 diff
= get_diff_sds(well_known_sd
, current_sd
, security
.dom_sid(self
.samdb
.get_domain_sid()))
2127 self
.err_wrong_default_sd(dn
, well_known_sd
, current_sd
, diff
)
2132 if str(attrname
).lower() == 'objectclass':
2133 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
2134 # Do not consider the attribute incorrect if:
2135 # - The sorted (alphabetically) list is the same, inclding case
2136 # - The first and last elements are the same
2138 # This avoids triggering an error due to
2139 # non-determinism in the sort routine in (at least)
2140 # 4.3 and earlier, and the fact that any AUX classes
2141 # in these attributes are also not sorted when
2142 # imported from Windows (they are just in the reverse
2143 # order of last set)
2144 if sorted(normalised
) != sorted(obj
[attrname
]) \
2145 or normalised
[0] != obj
[attrname
][0] \
2146 or normalised
[-1] != obj
[attrname
][-1]:
2147 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
2151 if str(attrname
).lower() == 'userparameters':
2152 if len(obj
[attrname
][0]) == 1 and obj
[attrname
][0][0] == '\x20':
2154 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
2157 elif obj
[attrname
][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2158 # This is the correct, normal prefix
2161 elif obj
[attrname
][0][:20] == 'IAAgACAAIAAgACAAIAAg':
2162 # this is the typical prefix from a windows migration
2164 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
2167 elif obj
[attrname
][0][1] != '\x00' and obj
[attrname
][0][3] != '\x00' and obj
[attrname
][0][5] != '\x00' and obj
[attrname
][0][7] != '\x00' and obj
[attrname
][0][9] != '\x00':
2168 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2170 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
2173 elif len(obj
[attrname
][0]) % 2 != 0:
2174 # This is a value that isn't even in length
2176 self
.err_odd_userParameters(obj
, attrname
, obj
[attrname
])
2179 elif obj
[attrname
][0][1] == '\x00' and obj
[attrname
][0][2] == '\x00' and obj
[attrname
][0][3] == '\x00' and obj
[attrname
][0][4] != '\x00' and obj
[attrname
][0][5] == '\x00':
2180 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2182 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
2185 if attrname
.lower() == 'attributeid' or attrname
.lower() == 'governsid':
2186 if obj
[attrname
][0] in self
.attribute_or_class_ids
:
2188 self
.report('Error: %s %s on %s already exists as an attributeId or governsId'
2189 % (attrname
, obj
.dn
, obj
[attrname
][0]))
2191 self
.attribute_or_class_ids
.add(obj
[attrname
][0])
2193 # check for empty attributes
2194 for val
in obj
[attrname
]:
2196 self
.err_empty_attribute(dn
, attrname
)
2200 # get the syntax oid for the attribute, so we can can have
2201 # special handling for some specific attribute types
2203 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
2204 except Exception, msg
:
2205 self
.err_unknown_attribute(obj
, attrname
)
2209 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
2211 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
2212 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
2213 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
2215 set_attrs_seen
.add(str(attrname
).lower())
2217 if syntax_oid
in [ dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
2218 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
2219 # it's some form of DN, do specialised checking on those
2220 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
2224 # check for incorrectly normalised attributes
2225 for val
in obj
[attrname
]:
2226 values
.add(str(val
))
2228 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
2229 if len(normalised
) != 1 or normalised
[0] != val
:
2230 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
2234 if len(obj
[attrname
]) != len(values
):
2235 self
.err_duplicate_values(dn
, attrname
, obj
[attrname
], list(values
))
2239 if str(attrname
).lower() == "instancetype":
2240 calculated_instancetype
= self
.calculate_instancetype(dn
)
2241 if len(obj
["instanceType"]) != 1 or obj
["instanceType"][0] != str(calculated_instancetype
):
2243 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
2245 if not got_objectclass
and ("*" in attrs
or "objectclass" in map(str.lower
, attrs
)):
2247 self
.err_missing_objectclass(dn
)
2249 if ("*" in attrs
or "name" in map(str.lower
, attrs
)):
2250 if name_val
is None:
2252 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
2253 if object_rdn_attr
is None:
2255 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
2257 if name_val
is not None:
2260 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
2261 parent_dn
= deleted_objects_dn
2262 if parent_dn
is None:
2263 parent_dn
= obj
.dn
.parent()
2264 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
2265 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
2267 if obj
.dn
== deleted_objects_dn
:
2268 expected_dn
= obj
.dn
2270 if expected_dn
!= obj
.dn
:
2272 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
, object_rdn_val
, name_val
)
2273 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
2275 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
, object_rdn_val
, str(obj
.dn
)))
2278 if got_repl_property_meta_data
:
2279 if obj
.dn
== deleted_objects_dn
:
2280 isDeletedAttId
= 131120
2281 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2283 expectedTimeDo
= 2650466015990000000
2284 originating
= self
.get_originating_time(obj
["replPropertyMetaData"], isDeletedAttId
)
2285 if originating
!= expectedTimeDo
:
2286 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
2287 nmsg
= ldb
.Message()
2289 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2291 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
2294 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
2296 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
2298 self
.report("On object %s" % dn
)
2301 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
2302 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
2303 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
2305 self
.fix_metadata(obj
, att
)
2307 if self
.is_fsmo_role(dn
):
2308 if "fSMORoleOwner" not in obj
and ("*" in attrs
or "fsmoroleowner" in map(str.lower
, attrs
)):
2309 self
.err_no_fsmoRoleOwner(obj
)
2313 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
2314 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
2315 controls
=["show_recycled:1", "show_deleted:1"])
2316 except ldb
.LdbError
, (enum
, estr
):
2317 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2318 self
.err_missing_parent(obj
)
2323 if dn
in self
.deleted_objects_containers
and '*' in attrs
:
2324 if self
.is_deleted_deleted_objects(obj
):
2325 self
.err_deleted_deleted_objects(obj
)
2328 for (dns_part
, msg
) in self
.dns_partitions
:
2329 if dn
== dns_part
and 'repsFrom' in obj
:
2330 location
= "msDS-NC-Replica-Locations"
2331 if self
.samdb
.am_rodc():
2332 location
= "msDS-NC-RO-Replica-Locations"
2334 if location
not in msg
:
2335 # There are no replica locations!
2336 self
.err_replica_locations(obj
, msg
.dn
, location
)
2341 for loc
in msg
[location
]:
2342 if loc
== self
.samdb
.get_dsServiceName():
2345 # This DC is not in the replica locations
2346 self
.err_replica_locations(obj
, msg
.dn
, location
)
2349 if dn
== self
.server_ref_dn
:
2350 # Check we have a valid RID Set
2351 if "*" in attrs
or "rIDSetReferences" in attrs
:
2352 if "rIDSetReferences" not in obj
:
2353 # NO RID SET reference
2354 # We are RID master, allocate it.
2357 if self
.is_rid_master
:
2358 # Allocate a RID Set
2359 if self
.confirm_all('Allocate the missing RID set for RID master?',
2360 'fix_missing_rid_set_master'):
2362 # We don't have auto-transaction logic on
2363 # extended operations, so we have to do it
2366 self
.samdb
.transaction_start()
2369 self
.samdb
.create_own_rid_set()
2372 self
.samdb
.transaction_cancel()
2375 self
.samdb
.transaction_commit()
2378 elif not self
.samdb
.am_rodc():
2379 self
.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn
)
2382 # Check some details of our own RID Set
2383 if dn
== self
.rid_set_dn
:
2384 res
= self
.samdb
.search(base
=self
.rid_set_dn
, scope
=ldb
.SCOPE_BASE
,
2385 attrs
=["rIDAllocationPool",
2386 "rIDPreviousAllocationPool",
2389 if "rIDAllocationPool" not in res
[0]:
2390 self
.report("No rIDAllocationPool found in %s" % dn
)
2393 next_pool
= int(res
[0]["rIDAllocationPool"][0])
2395 high
= (0xFFFFFFFF00000000 & next_pool
) >> 32
2396 low
= 0x00000000FFFFFFFF & next_pool
2399 self
.report("Invalid RID set %d-%s, %d > %d!" % (low
, high
, low
, high
))
2402 if "rIDNextRID" in res
[0]:
2403 next_free_rid
= int(res
[0]["rIDNextRID"][0])
2407 if next_free_rid
== 0:
2412 # Check the remainder of this pool for conflicts. If
2413 # ridalloc_allocate_rid() moves to a new pool, this
2414 # will be above high, so we will stop.
2415 while next_free_rid
<= high
:
2416 sid
= "%s-%d" % (self
.samdb
.get_domain_sid(), next_free_rid
)
2418 res
= self
.samdb
.search(base
="<SID=%s>" % sid
, scope
=ldb
.SCOPE_BASE
,
2420 except ldb
.LdbError
, (enum
, estr
):
2421 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2425 self
.report("SID %s for %s conflicts with our current RID set in %s" % (sid
, res
[0].dn
, dn
))
2428 if self
.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2430 'fix_sid_rid_set_conflict'):
2431 self
.samdb
.transaction_start()
2433 # This will burn RIDs, which will move
2434 # past the conflict. We then check again
2435 # to see if the new RID conflicts, until
2436 # the end of the current pool. We don't
2437 # look at the next pool to avoid burning
2438 # all RIDs in one go in some strange
2442 allocated_rid
= self
.samdb
.allocate_rid()
2443 if allocated_rid
>= next_free_rid
:
2444 next_free_rid
= allocated_rid
+ 1
2447 self
.samdb
.transaction_cancel()
2450 self
.samdb
.transaction_commit()
2459 ################################################################
2460 # check special @ROOTDSE attributes
2461 def check_rootdse(self
):
2462 '''check the @ROOTDSE special object'''
2463 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
2465 self
.report("Checking object %s" % dn
)
2466 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
2468 self
.report("Object %s disappeared during check" % dn
)
2473 # check that the dsServiceName is in GUID form
2474 if not 'dsServiceName' in obj
:
2475 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
2476 return error_count
+1
2478 if not obj
['dsServiceName'][0].startswith('<GUID='):
2479 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2481 if not self
.confirm('Change dsServiceName to GUID form?'):
2483 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0]),
2484 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
2485 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
2488 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
2489 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
2490 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
2491 self
.report("Changed dsServiceName to GUID form")
2495 ###############################################
2496 # re-index the database
2497 def reindex_database(self
):
2498 '''re-index the whole database'''
2500 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
2501 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
2502 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
2503 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
2505 ###############################################
2507 def reset_modules(self
):
2508 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2510 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
2511 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
2512 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)