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 as 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'''
382 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
384 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
385 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
386 except Exception as err
:
387 if self
.in_transaction
:
388 raise CommandError("%s : %s" % (msg
, err
))
389 self
.report("%s : %s" % (msg
, err
))
393 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
394 '''perform a modify with optional verbose output'''
396 self
.report("""dn: %s
400 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
402 to_dn
= to_rdn
+ to_base
403 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
404 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
405 except Exception as err
:
406 if self
.in_transaction
:
407 raise CommandError("%s : %s" % (msg
, err
))
408 self
.report("%s : %s" % (msg
, err
))
412 def get_attr_linkID_and_reverse_name(self
, attrname
):
413 if attrname
in self
.link_id_cache
:
414 return self
.link_id_cache
[attrname
]
415 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
417 revname
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
420 self
.link_id_cache
[attrname
] = (linkID
, revname
)
421 return linkID
, revname
423 def err_empty_attribute(self
, dn
, attrname
):
424 '''fix empty attributes'''
425 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
426 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
427 self
.report("Not fixing empty attribute %s" % attrname
)
432 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
433 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
434 "Failed to remove empty attribute %s" % attrname
, validate
=False):
435 self
.report("Removed empty attribute %s" % attrname
)
437 def err_normalise_mismatch(self
, dn
, attrname
, values
):
438 '''fix attribute normalisation errors'''
439 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
442 normalised
= self
.samdb
.dsdb_normalise_attributes(
443 self
.samdb_schema
, attrname
, [val
])
444 if len(normalised
) != 1:
445 self
.report("Unable to normalise value '%s'" % val
)
446 mod_list
.append((val
, ''))
447 elif (normalised
[0] != val
):
448 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
449 mod_list
.append((val
, normalised
[0]))
450 if not self
.confirm_all('Fix normalisation for %s from %s?' % (attrname
, dn
), 'fix_all_normalisation'):
451 self
.report("Not fixing attribute %s" % attrname
)
456 for i
in range(0, len(mod_list
)):
457 (val
, nval
) = mod_list
[i
]
458 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
460 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
463 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
464 "Failed to normalise attribute %s" % attrname
,
466 self
.report("Normalised attribute %s" % attrname
)
468 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
469 '''fix attribute normalisation errors'''
470 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
471 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
472 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
473 if list(normalised
) == values
:
475 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
476 self
.report("Not fixing attribute '%s'" % attrname
)
481 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
483 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
484 "Failed to normalise attribute %s" % attrname
,
486 self
.report("Normalised attribute %s" % attrname
)
488 def err_duplicate_values(self
, dn
, attrname
, dup_values
, values
):
489 '''fix attribute normalisation errors'''
490 self
.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname
, dn
))
491 self
.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values
), ','.join(values
)))
492 if not self
.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_duplicates'):
493 self
.report("Not fixing attribute '%s'" % attrname
)
498 m
[attrname
] = ldb
.MessageElement(values
, ldb
.FLAG_MOD_REPLACE
, attrname
)
500 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
501 "Failed to remove duplicate value on attribute %s" % attrname
,
503 self
.report("Removed duplicate value on attribute %s" % attrname
)
505 def is_deleted_objects_dn(self
, dsdb_dn
):
506 '''see if a dsdb_Dn is the special Deleted Objects DN'''
507 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
509 def err_missing_objectclass(self
, dn
):
510 """handle object without objectclass"""
511 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
)))
512 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'):
513 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
515 if self
.do_delete(dn
, ["relax:0"],
516 "Failed to remove DN %s" % dn
):
517 self
.report("Removed DN %s" % dn
)
519 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, remove_plausible
=False):
520 """handle a DN pointing to a deleted object"""
521 if not remove_plausible
:
522 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
523 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
524 if not self
.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
525 self
.report("Not removing")
528 self
.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
529 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
530 if not self
.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
531 self
.report("Not removing")
536 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
537 if self
.do_modify(m
, ["show_recycled:1",
538 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
539 "Failed to remove deleted DN attribute %s" % attrname
):
540 self
.report("Removed deleted DN on attribute %s" % attrname
)
542 def err_missing_target_dn_or_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
543 """handle a missing target DN (if specified, GUID form can't be found,
544 and otherwise DN string form can't be found)"""
545 # check if its a backlink
546 linkID
, _
= self
.get_attr_linkID_and_reverse_name(attrname
)
547 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
549 linkID
, reverse_link_name \
550 = self
.get_attr_linkID_and_reverse_name(attrname
)
551 if reverse_link_name
is not None:
552 self
.report("WARNING: no target object found for GUID "
553 "component for one-way forward link "
555 "%s - %s" % (attrname
, dn
, val
))
556 self
.report("Not removing dangling forward link")
559 nc_root
= self
.samdb
.get_nc_root(dn
)
560 target_nc_root
= self
.samdb
.get_nc_root(dsdb_dn
.dn
)
561 if nc_root
!= target_nc_root
:
562 # We don't bump the error count as Samba produces these
563 # in normal operation
564 self
.report("WARNING: no target object found for GUID "
565 "component for cross-partition link "
567 "%s - %s" % (attrname
, dn
, val
))
568 self
.report("Not removing dangling one-way "
569 "cross-partition link "
570 "(we might be mid-replication)")
573 # Due to our link handling one-way links pointing to
574 # missing objects are plausible.
576 # We don't bump the error count as Samba produces these
577 # in normal operation
578 self
.report("WARNING: no target object found for GUID "
579 "component for DN value %s in object "
580 "%s - %s" % (attrname
, dn
, val
))
581 self
.err_deleted_dn(dn
, attrname
, val
,
582 dsdb_dn
, dsdb_dn
, True)
585 # We bump the error count here, as we should have deleted this
586 self
.report("ERROR: no target object found for GUID "
587 "component for link %s in object "
588 "%s - %s" % (attrname
, dn
, val
))
589 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
, False)
592 def err_missing_dn_GUID_component(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
593 """handle a missing GUID extended DN component"""
594 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
595 controls
=["extended_dn:1:1", "show_recycled:1"]
597 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
598 attrs
=[], controls
=controls
)
599 except ldb
.LdbError
, (enum
, estr
):
600 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
601 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
603 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
606 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
607 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
609 dsdb_dn
.dn
= res
[0].dn
611 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
612 self
.report("Not fixing %s" % errstr
)
616 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
617 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
619 if self
.do_modify(m
, ["show_recycled:1"],
620 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
621 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
623 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
624 """handle an incorrect binary DN component"""
625 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
626 controls
=["extended_dn:1:1", "show_recycled:1"]
628 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
629 self
.report("Not fixing %s" % errstr
)
633 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
634 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
636 if self
.do_modify(m
, ["show_recycled:1"],
637 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
638 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
640 def err_dn_string_component_old(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
641 """handle a DN string being incorrect"""
642 self
.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
643 dsdb_dn
.dn
= correct_dn
645 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
646 'fix_all_old_dn_string_component_mismatch'):
647 self
.report("Not fixing old string component")
651 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
652 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
653 if self
.do_modify(m
, ["show_recycled:1"],
654 "Failed to fix old DN string on attribute %s" % (attrname
)):
655 self
.report("Fixed old DN string on attribute %s" % (attrname
))
657 def err_dn_component_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, mismatch_type
):
658 """handle a DN string being incorrect"""
659 self
.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type
, attrname
, dn
, val
))
660 dsdb_dn
.dn
= correct_dn
662 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
663 'fix_all_%s_dn_component_mismatch' % mismatch_type
):
664 self
.report("Not fixing %s component mismatch" % mismatch_type
)
668 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
669 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
670 if self
.do_modify(m
, ["show_recycled:1"],
671 "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type
, attrname
)):
672 self
.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type
, attrname
))
674 def err_unknown_attribute(self
, obj
, attrname
):
675 '''handle an unknown attribute error'''
676 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
677 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
678 self
.report("Not removing %s" % attrname
)
682 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
683 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
684 "Failed to remove unknown attribute %s" % attrname
):
685 self
.report("Removed unknown attribute %s" % (attrname
))
687 def err_undead_linked_attribute(self
, obj
, attrname
, val
):
688 '''handle a link that should not be there on a deleted object'''
689 self
.report("ERROR: linked attribute '%s' to '%s' is present on "
690 "deleted object %s" % (attrname
, val
, obj
.dn
))
691 if not self
.confirm_all('Remove linked attribute %s' % attrname
, 'fix_undead_linked_attributes'):
692 self
.report("Not removing linked attribute %s" % attrname
)
696 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
698 if self
.do_modify(m
, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
699 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
700 "Failed to delete forward link %s" % attrname
):
701 self
.report("Fixed undead forward link %s" % (attrname
))
703 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
704 '''handle a missing backlink value'''
705 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
706 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
707 self
.report("Not fixing missing backlink %s" % backlink_name
)
711 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, backlink_name
)
712 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
713 "Failed to fix missing backlink %s" % backlink_name
):
714 self
.report("Fixed missing backlink %s" % (backlink_name
))
716 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
717 '''handle a incorrect RMD_FLAGS value'''
718 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
719 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()))
720 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
721 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
725 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
726 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
727 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
728 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
730 def err_orphaned_backlink(self
, obj_dn
, backlink_attr
, backlink_val
,
731 target_dn
, forward_attr
, forward_syntax
,
732 check_duplicates
=True):
733 '''handle a orphaned backlink value'''
734 if check_duplicates
is True and self
.has_duplicate_links(target_dn
, forward_attr
, forward_syntax
):
735 self
.report("WARNING: Keep orphaned backlink attribute " + \
736 "'%s' in '%s' for link '%s' in '%s'" % (
737 backlink_attr
, obj_dn
, forward_attr
, target_dn
))
739 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr
, obj_dn
, forward_attr
, target_dn
))
740 if not self
.confirm_all('Remove orphaned backlink %s' % backlink_attr
, 'fix_all_orphaned_backlinks'):
741 self
.report("Not removing orphaned backlink %s" % backlink_attr
)
745 m
['value'] = ldb
.MessageElement(backlink_val
, ldb
.FLAG_MOD_DELETE
, backlink_attr
)
746 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
747 "Failed to fix orphaned backlink %s" % backlink_attr
):
748 self
.report("Fixed orphaned backlink %s" % (backlink_attr
))
750 def err_recover_forward_links(self
, obj
, forward_attr
, forward_vals
):
751 '''handle a duplicate links value'''
753 self
.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr
, obj
.dn
))
755 if not self
.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr
, 'recover_all_forward_links'):
756 self
.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
757 forward_attr
, obj
.dn
))
761 m
['value'] = ldb
.MessageElement(forward_vals
, ldb
.FLAG_MOD_REPLACE
, forward_attr
)
762 if self
.do_modify(m
, ["local_oid:1.3.6.1.4.1.7165.4.3.19.2:1"],
763 "Failed to fix duplicate links in attribute '%s'" % forward_attr
):
764 self
.report("Fixed duplicate links in attribute '%s'" % (forward_attr
))
765 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
766 assert duplicate_cache_key
in self
.duplicate_link_cache
767 self
.duplicate_link_cache
[duplicate_cache_key
] = False
769 def err_no_fsmoRoleOwner(self
, obj
):
770 '''handle a missing fSMORoleOwner'''
771 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
772 res
= self
.samdb
.search("",
773 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
775 serviceName
= res
[0]["dsServiceName"][0]
776 if not self
.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
777 self
.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
781 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
782 if self
.do_modify(m
, [],
783 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
784 self
.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
786 def err_missing_parent(self
, obj
):
787 '''handle a missing parent'''
788 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
789 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
790 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
793 keep_transaction
= False
794 self
.samdb
.transaction_start()
796 nc_root
= self
.samdb
.get_nc_root(obj
.dn
);
797 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
798 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
799 new_dn
.remove_base_components(len(new_dn
) - 1)
800 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
801 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
802 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
806 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
808 if self
.do_modify(m
, [],
809 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
810 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
811 keep_transaction
= True
813 self
.samdb
.transaction_cancel()
817 self
.samdb
.transaction_commit()
819 self
.samdb
.transaction_cancel()
821 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
):
822 '''handle a wrong dn'''
824 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
825 new_rdn
.remove_base_components(len(new_rdn
) - 1)
826 new_parent
= new_dn
.parent()
829 if rdn_val
!= name_val
:
830 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
831 attributes
+= "name=%r" % (name_val
)
833 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
834 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
835 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
838 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, ["show_recycled:1", "relax:0"],
839 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
840 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
842 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
843 '''handle a wrong instanceType'''
844 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
845 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
846 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
851 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
852 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
853 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
854 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
856 def err_short_userParameters(self
, obj
, attrname
, value
):
857 # This is a truncated userParameters due to a pre 4.1 replication bug
858 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
)))
860 def err_base64_userParameters(self
, obj
, attrname
, value
):
861 '''handle a wrong userParameters'''
862 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
863 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
864 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
869 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
870 if self
.do_modify(m
, [],
871 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
872 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
874 def err_utf8_userParameters(self
, obj
, attrname
, value
):
875 '''handle a wrong userParameters'''
876 self
.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj
.dn
))
877 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
878 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
883 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
884 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
885 if self
.do_modify(m
, [],
886 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
887 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
889 def err_doubled_userParameters(self
, obj
, attrname
, value
):
890 '''handle a wrong userParameters'''
891 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
892 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
893 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
898 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
899 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
900 if self
.do_modify(m
, [],
901 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
902 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
904 def err_odd_userParameters(self
, obj
, attrname
):
905 # This is a truncated userParameters due to a pre 4.1 replication bug
906 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
)))
908 def find_revealed_link(self
, dn
, attrname
, guid
):
909 '''return a revealed link in an object'''
910 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
911 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
912 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
913 for val
in res
[0][attrname
]:
914 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, syntax_oid
)
915 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
920 def check_duplicate_links(self
, obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
):
921 '''check a linked values for duplicate forward links'''
924 duplicate_dict
= dict()
927 # Only forward links can have this problem
928 if forward_linkID
& 1:
929 # If we got the reverse, skip it
930 return (error_count
, duplicate_dict
, unique_dict
)
932 if backlink_attr
is None:
933 return (error_count
, duplicate_dict
, unique_dict
)
935 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
936 if duplicate_cache_key
not in self
.duplicate_link_cache
:
937 self
.duplicate_link_cache
[duplicate_cache_key
] = False
939 for val
in obj
[forward_attr
]:
940 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, forward_syntax
)
942 # all DNs should have a GUID component
943 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
946 guidstr
= str(misc
.GUID(guid
))
947 keystr
= guidstr
+ dsdb_dn
.prefix
948 if keystr
not in unique_dict
:
949 unique_dict
[keystr
] = dsdb_dn
952 if keystr
not in duplicate_dict
:
953 duplicate_dict
[keystr
] = dict()
954 duplicate_dict
[keystr
]["keep"] = None
955 duplicate_dict
[keystr
]["delete"] = list()
957 # Now check for the highest RMD_VERSION
958 v1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_VERSION"))
959 v2
= int(dsdb_dn
.dn
.get_extended_component("RMD_VERSION"))
961 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
962 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
965 duplicate_dict
[keystr
]["keep"] = dsdb_dn
966 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
967 unique_dict
[keystr
] = dsdb_dn
969 # Fallback to the highest RMD_LOCAL_USN
970 u1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_LOCAL_USN"))
971 u2
= int(dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN"))
973 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
974 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
976 duplicate_dict
[keystr
]["keep"] = dsdb_dn
977 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
978 unique_dict
[keystr
] = dsdb_dn
981 self
.duplicate_link_cache
[duplicate_cache_key
] = True
983 return (error_count
, duplicate_dict
, unique_dict
)
985 def has_duplicate_links(self
, dn
, forward_attr
, forward_syntax
):
986 '''check a linked values for duplicate forward links'''
989 duplicate_cache_key
= "%s:%s" % (str(dn
), forward_attr
)
990 if duplicate_cache_key
in self
.duplicate_link_cache
:
991 return self
.duplicate_link_cache
[duplicate_cache_key
]
993 forward_linkID
, backlink_attr
= self
.get_attr_linkID_and_reverse_name(forward_attr
)
995 attrs
= [forward_attr
]
996 controls
= ["extended_dn:1:1", "reveal_internals:0"]
998 # check its the right GUID
1000 res
= self
.samdb
.search(base
=str(dn
), scope
=ldb
.SCOPE_BASE
,
1001 attrs
=attrs
, controls
=controls
)
1002 except ldb
.LdbError
, (enum
, estr
):
1003 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1009 error_count
, duplicate_dict
, unique_dict
= \
1010 self
.check_duplicate_links(obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
)
1012 if duplicate_cache_key
in self
.duplicate_link_cache
:
1013 return self
.duplicate_link_cache
[duplicate_cache_key
]
1017 def find_missing_forward_links_from_backlinks(self
, obj
,
1021 forward_unique_dict
):
1022 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1023 missing_forward_links
= []
1026 if backlink_attr
is None:
1027 return (missing_forward_links
, error_count
)
1029 if forward_syntax
!= ldb
.SYNTAX_DN
:
1030 self
.report("Not checking for missing forward links for syntax: %s",
1032 return (missing_forward_links
, error_count
)
1034 if "sortedLinks" in self
.compatibleFeatures
:
1035 self
.report("Not checking for missing forward links because the db " + \
1036 "has the sortedLinks feature")
1037 return (missing_forward_links
, error_count
)
1040 obj_guid
= obj
['objectGUID'][0]
1041 obj_guid_str
= str(ndr_unpack(misc
.GUID
, obj_guid
))
1042 filter = "(%s=<GUID=%s>)" % (backlink_attr
, obj_guid_str
)
1044 res
= self
.samdb
.search(expression
=filter,
1045 scope
=ldb
.SCOPE_SUBTREE
, attrs
=["objectGUID"],
1046 controls
=["extended_dn:1:1",
1047 "search_options:1:2",
1048 "paged_results:1:1000"])
1049 except ldb
.LdbError
, (enum
, estr
):
1053 target_dn
= dsdb_Dn(self
.samdb
, r
.dn
.extended_str(), forward_syntax
)
1055 guid
= target_dn
.dn
.get_extended_component("GUID")
1056 guidstr
= str(misc
.GUID(guid
))
1057 if guidstr
in forward_unique_dict
:
1060 # A valid forward link looks like this:
1062 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1063 # <RMD_ADDTIME=131607546230000000>;
1064 # <RMD_CHANGETIME=131607546230000000>;
1066 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1067 # <RMD_LOCAL_USN=3765>;
1068 # <RMD_ORIGINATING_USN=3765>;
1070 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1071 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1073 # Note that versions older than Samba 4.8 create
1074 # links with RMD_VERSION=0.
1076 # Try to get the local_usn and time from objectClass
1077 # if possible and fallback to any other one.
1078 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1079 obj
['replPropertyMetadata'][0])
1080 for o
in repl
.ctr
.array
:
1081 local_usn
= o
.local_usn
1082 t
= o
.originating_change_time
1083 if o
.attid
== drsuapi
.DRSUAPI_ATTID_objectClass
:
1086 # We use a magic invocationID for restoring missing
1087 # forward links to recover from bug #13228.
1088 # This should allow some more future magic to fix the
1091 # It also means it looses the conflict resolution
1092 # against almost every real invocation, if the
1093 # version is also 0.
1094 originating_invocid
= misc
.GUID("ffffffff-4700-4700-4700-000000b13228")
1100 rmd_invocid
= originating_invocid
1101 rmd_originating_usn
= originating_usn
1102 rmd_local_usn
= local_usn
1105 target_dn
.dn
.set_extended_component("RMD_ADDTIME", str(rmd_addtime
))
1106 target_dn
.dn
.set_extended_component("RMD_CHANGETIME", str(rmd_changetime
))
1107 target_dn
.dn
.set_extended_component("RMD_FLAGS", str(rmd_flags
))
1108 target_dn
.dn
.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid
))
1109 target_dn
.dn
.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn
))
1110 target_dn
.dn
.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn
))
1111 target_dn
.dn
.set_extended_component("RMD_VERSION", str(rmd_version
))
1114 missing_forward_links
.append(target_dn
)
1116 return (missing_forward_links
, error_count
)
1118 def check_dn(self
, obj
, attrname
, syntax_oid
):
1119 '''check a DN attribute for correctness'''
1121 obj_guid
= obj
['objectGUID'][0]
1123 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
1124 if reverse_link_name
is not None:
1125 reverse_syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(reverse_link_name
)
1127 reverse_syntax_oid
= None
1129 error_count
, duplicate_dict
, unique_dict
= \
1130 self
.check_duplicate_links(obj
, attrname
, syntax_oid
, linkID
, reverse_link_name
)
1132 if len(duplicate_dict
) != 0:
1134 missing_forward_links
, missing_error_count
= \
1135 self
.find_missing_forward_links_from_backlinks(obj
,
1136 attrname
, syntax_oid
,
1139 error_count
+= missing_error_count
1141 forward_links
= [dn
for dn
in unique_dict
.values()]
1143 if missing_error_count
!= 0:
1144 self
.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1147 self
.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname
, obj
.dn
))
1148 for m
in missing_forward_links
:
1149 self
.report("Missing link '%s'" % (m
))
1150 if not self
.confirm_all("Schedule readding missing forward link for attribute %s" % attrname
,
1151 'fix_all_missing_forward_links'):
1152 self
.err_orphaned_backlink(m
.dn
, reverse_link_name
,
1153 obj
.dn
.extended_str(), obj
.dn
,
1154 attrname
, syntax_oid
,
1155 check_duplicates
=False)
1157 forward_links
+= [m
]
1158 for keystr
in duplicate_dict
.keys():
1159 d
= duplicate_dict
[keystr
]
1160 for dd
in d
["delete"]:
1161 self
.report("Duplicate link '%s'" % dd
)
1162 self
.report("Correct link '%s'" % d
["keep"])
1164 # We now construct the sorted dn values.
1165 # They're sorted by the objectGUID of the target
1166 # See dsdb_Dn.__cmp__()
1167 vals
= [str(dn
) for dn
in sorted(forward_links
)]
1168 self
.err_recover_forward_links(obj
, attrname
, vals
)
1169 # We should continue with the fixed values
1170 obj
[attrname
] = ldb
.MessageElement(vals
, 0, attrname
)
1172 for val
in obj
[attrname
]:
1173 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, syntax_oid
)
1175 # all DNs should have a GUID component
1176 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1179 self
.err_missing_dn_GUID_component(obj
.dn
, attrname
, val
, dsdb_dn
,
1183 guidstr
= str(misc
.GUID(guid
))
1184 attrs
= ['isDeleted', 'replPropertyMetaData']
1186 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
1187 fixing_msDS_HasInstantiatedNCs
= True
1188 attrs
.append("instanceType")
1190 fixing_msDS_HasInstantiatedNCs
= False
1192 if reverse_link_name
is not None:
1193 attrs
.append(reverse_link_name
)
1195 # check its the right GUID
1197 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
1198 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1",
1199 "reveal_internals:0"
1201 except ldb
.LdbError
, (enum
, estr
):
1202 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1205 # We don't always want to
1206 error_count
+= self
.err_missing_target_dn_or_GUID(obj
.dn
,
1212 if fixing_msDS_HasInstantiatedNCs
:
1213 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
1214 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
1216 if str(dsdb_dn
) != val
:
1218 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
1221 # now we have two cases - the source object might or might not be deleted
1222 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
1223 target_is_deleted
= 'isDeleted' in res
[0] and res
[0]['isDeleted'][0].upper() == 'TRUE'
1226 if is_deleted
and not obj
.dn
in self
.deleted_objects_containers
and linkID
:
1227 # A fully deleted object should not have any linked
1228 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1229 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1231 self
.err_undead_linked_attribute(obj
, attrname
, val
)
1234 elif target_is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
) and linkID
:
1235 # the target DN is not allowed to be deleted, unless the target DN is the
1236 # special Deleted Objects container
1238 local_usn
= dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN")
1240 if 'replPropertyMetaData' in res
[0]:
1241 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1242 str(res
[0]['replPropertyMetadata']))
1244 for o
in repl
.ctr
.array
:
1245 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1246 deleted_usn
= o
.local_usn
1247 if deleted_usn
>= int(local_usn
):
1248 # If the object was deleted after the link
1249 # was last modified then, clean it up here
1254 self
.err_deleted_dn(obj
.dn
, attrname
,
1255 val
, dsdb_dn
, res
[0].dn
, True)
1258 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
, False)
1261 # We should not check for incorrect
1262 # components on deleted links, as these are allowed to
1263 # go stale (we just need the GUID, not the name)
1264 rmd_blob
= dsdb_dn
.dn
.get_extended_component("RMD_FLAGS")
1266 if rmd_blob
is not None:
1267 rmd_flags
= int(rmd_blob
)
1269 # assert the DN matches in string form, where a reverse
1270 # link exists, otherwise (below) offer to fix it as a non-error.
1271 # The string form is essentially only kept for forensics,
1272 # as we always re-resolve by GUID in normal operations.
1273 if not rmd_flags
& 1 and reverse_link_name
is not None:
1274 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1276 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1277 res
[0].dn
, "string")
1280 if res
[0].dn
.get_extended_component("GUID") != dsdb_dn
.dn
.get_extended_component("GUID"):
1282 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1286 if res
[0].dn
.get_extended_component("SID") != dsdb_dn
.dn
.get_extended_component("SID"):
1288 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1292 # Now we have checked the GUID and SID, offer to fix old
1293 # DN strings as a non-error (for forward links with no
1294 # backlink). Samba does not maintain this string
1295 # otherwise, so we don't increment error_count.
1296 if reverse_link_name
is None:
1297 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1298 self
.err_dn_string_component_old(obj
.dn
, attrname
, val
, dsdb_dn
,
1302 # check the reverse_link is correct if there should be one
1304 if reverse_link_name
in res
[0]:
1305 for v
in res
[0][reverse_link_name
]:
1306 v_dn
= dsdb_Dn(self
.samdb
, v
)
1307 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1308 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1310 if v_blob
is not None:
1311 v_rmd_flags
= int(v_blob
)
1314 if v_guid
== obj_guid
:
1317 if match_count
!= 1:
1318 if syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
or reverse_syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
:
1320 # Forward binary multi-valued linked attribute
1322 for w
in obj
[attrname
]:
1323 w_guid
= dsdb_Dn(self
.samdb
, w
).dn
.get_extended_component("GUID")
1327 if match_count
== forward_count
:
1330 for v
in obj
[attrname
]:
1331 v_dn
= dsdb_Dn(self
.samdb
, v
)
1332 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1333 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1335 if v_blob
is not None:
1336 v_rmd_flags
= int(v_blob
)
1342 if match_count
== expected_count
:
1345 diff_count
= expected_count
- match_count
1348 # If there's a backward link on binary multi-valued linked attribute,
1349 # let the check on the forward link remedy the value.
1350 # UNLESS, there is no forward link detected.
1351 if match_count
== 0:
1353 self
.err_orphaned_backlink(obj
.dn
, attrname
,
1358 # Only warn here and let the forward link logic fix it.
1359 self
.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1360 attrname
, expected_count
, str(obj
.dn
),
1361 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1364 assert not target_is_deleted
1366 self
.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1367 attrname
, expected_count
, str(obj
.dn
),
1368 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1370 # Loop until the difference between the forward and
1371 # the backward links is resolved.
1372 while diff_count
!= 0:
1375 if match_count
> 0 or diff_count
> 1:
1376 # TODO no method to fix these right now
1377 self
.report("ERROR: Can't fix missing "
1378 "multi-valued backlinks on %s" % str(dsdb_dn
.dn
))
1380 self
.err_missing_backlink(obj
, attrname
,
1381 obj
.dn
.extended_str(),
1386 self
.err_orphaned_backlink(res
[0].dn
, reverse_link_name
,
1387 obj
.dn
.extended_str(), obj
.dn
,
1388 attrname
, syntax_oid
)
1395 def get_originating_time(self
, val
, attid
):
1396 '''Read metadata properties and return the originating time for
1397 a given attributeId.
1399 :return: the originating time or 0 if not found
1402 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
1405 for o
in repl
.ctr
.array
:
1406 if o
.attid
== attid
:
1407 return o
.originating_change_time
1411 def process_metadata(self
, dn
, val
):
1412 '''Read metadata properties and list attributes in it.
1413 raises KeyError if the attid is unknown.'''
1416 wrong_attids
= set()
1418 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1420 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
1423 for o
in repl
.ctr
.array
:
1424 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1425 set_att
.add(att
.lower())
1426 list_attid
.append(o
.attid
)
1427 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
,
1428 is_schema_nc
=in_schema_nc
)
1429 if correct_attid
!= o
.attid
:
1430 wrong_attids
.add(o
.attid
)
1432 return (set_att
, list_attid
, wrong_attids
)
1435 def fix_metadata(self
, obj
, attr
):
1436 '''re-write replPropertyMetaData elements for a single attribute for a
1437 object. This is used to fix missing replPropertyMetaData elements'''
1438 guid_str
= str(ndr_unpack(misc
.GUID
, obj
['objectGUID'][0]))
1439 dn
= ldb
.Dn(self
.samdb
, "<GUID=%s>" % guid_str
)
1440 res
= self
.samdb
.search(base
= dn
, scope
=ldb
.SCOPE_BASE
, attrs
= [attr
],
1441 controls
= ["search_options:1:2",
1444 nmsg
= ldb
.Message()
1446 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
1447 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
1448 "Failed to fix metadata for attribute %s" % attr
):
1449 self
.report("Fixed metadata for attribute %s" % attr
)
1451 def ace_get_effective_inherited_type(self
, ace
):
1452 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
1456 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
1458 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
1460 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
1462 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
1468 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
1471 return str(ace
.object.inherited_type
)
1473 def lookup_class_schemaIDGUID(self
, cls
):
1474 if cls
in self
.class_schemaIDGUID
:
1475 return self
.class_schemaIDGUID
[cls
]
1477 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1478 res
= self
.samdb
.search(base
=self
.schema_dn
,
1480 attrs
=["schemaIDGUID"])
1481 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
1483 self
.class_schemaIDGUID
[cls
] = t
1486 def process_sd(self
, dn
, obj
):
1487 sd_attr
= "nTSecurityDescriptor"
1488 sd_val
= obj
[sd_attr
]
1490 sd
= ndr_unpack(security
.descriptor
, str(sd_val
))
1492 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
1494 # we don't fix deleted objects
1497 sd_clean
= security
.descriptor()
1498 sd_clean
.owner_sid
= sd
.owner_sid
1499 sd_clean
.group_sid
= sd
.group_sid
1500 sd_clean
.type = sd
.type
1501 sd_clean
.revision
= sd
.revision
1504 last_inherited_type
= None
1507 if sd
.sacl
is not None:
1509 for i
in range(0, len(aces
)):
1512 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1513 sd_clean
.sacl_add(ace
)
1516 t
= self
.ace_get_effective_inherited_type(ace
)
1520 if last_inherited_type
is not None:
1521 if t
!= last_inherited_type
:
1522 # if it inherited from more than
1523 # one type it's very likely to be broken
1525 # If not the recalculation will calculate
1530 last_inherited_type
= t
1533 if sd
.dacl
is not None:
1535 for i
in range(0, len(aces
)):
1538 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1539 sd_clean
.dacl_add(ace
)
1542 t
= self
.ace_get_effective_inherited_type(ace
)
1546 if last_inherited_type
is not None:
1547 if t
!= last_inherited_type
:
1548 # if it inherited from more than
1549 # one type it's very likely to be broken
1551 # If not the recalculation will calculate
1556 last_inherited_type
= t
1559 return (sd_clean
, sd
)
1561 if last_inherited_type
is None:
1567 cls
= obj
["objectClass"][-1]
1568 except KeyError as e
:
1572 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1573 attrs
=["isDeleted", "objectClass"],
1574 controls
=["show_recycled:1"])
1576 is_deleted
= 'isDeleted' in o
and o
['isDeleted'][0].upper() == 'TRUE'
1578 # we don't fix deleted objects
1580 cls
= o
["objectClass"][-1]
1582 t
= self
.lookup_class_schemaIDGUID(cls
)
1584 if t
!= last_inherited_type
:
1586 return (sd_clean
, sd
)
1591 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
1592 '''re-write the SD due to incorrect inherited ACEs'''
1593 sd_attr
= "nTSecurityDescriptor"
1594 sd_val
= ndr_pack(sd
)
1595 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1597 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
1598 self
.report('Not fixing %s on %s\n' % (sd_attr
, dn
))
1601 nmsg
= ldb
.Message()
1603 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1604 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1605 "Failed to fix attribute %s" % sd_attr
):
1606 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1608 def err_wrong_default_sd(self
, dn
, sd
, sd_old
, diff
):
1609 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1610 sd_attr
= "nTSecurityDescriptor"
1611 sd_val
= ndr_pack(sd
)
1612 sd_old_val
= ndr_pack(sd_old
)
1613 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1614 if sd
.owner_sid
is not None:
1615 sd_flags |
= security
.SECINFO_OWNER
1616 if sd
.group_sid
is not None:
1617 sd_flags |
= security
.SECINFO_GROUP
1619 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
1620 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
1625 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1626 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
1627 "Failed to reset attribute %s" % sd_attr
):
1628 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1630 def err_missing_sd_owner(self
, dn
, sd
):
1631 '''re-write the SD due to a missing owner or group'''
1632 sd_attr
= "nTSecurityDescriptor"
1633 sd_val
= ndr_pack(sd
)
1634 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
1636 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
1637 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
1640 nmsg
= ldb
.Message()
1642 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1644 # By setting the session_info to admin_session_info and
1645 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1646 # flags we cause the descriptor module to set the correct
1647 # owner and group on the SD, replacing the None/NULL values
1648 # for owner_sid and group_sid currently present.
1650 # The admin_session_info matches that used in provision, and
1651 # is the best guess we can make for an existing object that
1652 # hasn't had something specifically set.
1654 # This is important for the dns related naming contexts.
1655 self
.samdb
.set_session_info(self
.admin_session_info
)
1656 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1657 "Failed to fix metadata for attribute %s" % sd_attr
):
1658 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1659 self
.samdb
.set_session_info(self
.system_session_info
)
1662 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
1663 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1664 str(repl_meta_data
))
1668 # Search for a zero invocationID
1669 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1673 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1674 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1675 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1676 % (dn
, o
.attid
, o
.version
,
1677 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
1678 self
.samdb
.get_invocation_id()))
1683 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
1684 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1685 str(repl_meta_data
))
1687 now
= samba
.unix2nttime(int(time
.time()))
1690 # Search for a zero invocationID
1691 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1695 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
1696 o
.version
= o
.version
+ 1
1697 o
.originating_change_time
= now
1698 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
1699 o
.originating_usn
= seq
1703 replBlob
= ndr_pack(repl
)
1707 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1708 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1709 self
.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr
, dn
))
1712 nmsg
= ldb
.Message()
1714 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1715 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1716 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1717 "Failed to fix attribute %s" % attr
):
1718 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1721 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
1722 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1723 str(repl_meta_data
))
1726 # Search for an invalid attid
1728 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1730 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
1734 def err_replmetadata_incorrect_attid(self
, dn
, attr
, repl_meta_data
, wrong_attids
):
1735 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1736 str(repl_meta_data
))
1740 remove_attid
= set()
1743 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1746 # Sort the array, except for the last element. This strange
1747 # construction, creating a new list, due to bugs in samba's
1748 # array handling in IDL generated objects.
1749 ctr
.array
= sorted(ctr
.array
[:], key
=lambda o
: o
.attid
)
1750 # Now walk it in reverse, so we see the low (and so incorrect,
1751 # the correct values are above 0x80000000) values first and
1752 # remove the 'second' value we see.
1753 for o
in reversed(ctr
.array
):
1754 print "%s: 0x%08x" % (dn
, o
.attid
)
1755 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1756 if att
.lower() in set_att
:
1757 self
.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att
, attr
, dn
))
1758 if not self
.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1759 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
),
1760 'fix_replmetadata_duplicate_attid'):
1761 self
.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1762 % (o
.attid
, att
, attr
, dn
))
1765 remove_attid
.add(o
.attid
)
1766 # We want to set the metadata for the most recent
1767 # update to have been applied locally, that is the metadata
1768 # matching the (eg string) value in the attribute
1769 if o
.local_usn
> hash_att
[att
].local_usn
:
1770 # This is always what we would have sent over DRS,
1771 # because the DRS server will have sent the
1772 # msDS-IntID, but with the values from both
1773 # attribute entries.
1774 hash_att
[att
].version
= o
.version
1775 hash_att
[att
].originating_change_time
= o
.originating_change_time
1776 hash_att
[att
].originating_invocation_id
= o
.originating_invocation_id
1777 hash_att
[att
].originating_usn
= o
.originating_usn
1778 hash_att
[att
].local_usn
= o
.local_usn
1780 # Do not re-add the value to the set or overwrite the hash value
1784 set_att
.add(att
.lower())
1786 # Generate a real list we can sort on properly
1787 new_list
= [o
for o
in ctr
.array
if o
.attid
not in remove_attid
]
1789 if (len(wrong_attids
) > 0):
1791 if o
.attid
in wrong_attids
:
1792 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1793 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
, is_schema_nc
=in_schema_nc
)
1794 self
.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr
, dn
))
1795 if not self
.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1796 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
), 'fix_replmetadata_wrong_attid'):
1797 self
.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1798 % (o
.attid
, correct_attid
, att
, attr
, dn
))
1801 o
.attid
= correct_attid
1803 # Sort the array, (we changed the value so must re-sort)
1804 new_list
[:] = sorted(new_list
[:], key
=lambda o
: o
.attid
)
1806 # If we did not already need to fix it, then ask about sorting
1808 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
1809 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
1810 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
1811 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
1814 # The actual sort done is done at the top of the function
1816 ctr
.count
= len(new_list
)
1817 ctr
.array
= new_list
1818 replBlob
= ndr_pack(repl
)
1820 nmsg
= ldb
.Message()
1822 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1823 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1824 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1825 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1826 "Failed to fix attribute %s" % attr
):
1827 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1830 def is_deleted_deleted_objects(self
, obj
):
1832 if "description" not in obj
:
1833 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
1835 if "showInAdvancedViewOnly" not in obj
or obj
['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1836 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
1838 if "objectCategory" not in obj
:
1839 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
1841 if "isCriticalSystemObject" not in obj
or obj
['isCriticalSystemObject'][0].upper() == 'FALSE':
1842 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
1844 if "isRecycled" in obj
:
1845 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
1847 if "isDeleted" in obj
and obj
['isDeleted'][0].upper() == 'FALSE':
1848 self
.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj
.dn
)
1850 if "objectClass" not in obj
or (len(obj
['objectClass']) != 2 or
1851 obj
['objectClass'][0] != 'top' or
1852 obj
['objectClass'][1] != 'container'):
1853 self
.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj
.dn
)
1855 if "systemFlags" not in obj
or obj
['systemFlags'][0] != '-1946157056':
1856 self
.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj
.dn
)
1860 def err_deleted_deleted_objects(self
, obj
):
1861 nmsg
= ldb
.Message()
1862 nmsg
.dn
= dn
= obj
.dn
1864 if "description" not in obj
:
1865 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
1866 if "showInAdvancedViewOnly" not in obj
:
1867 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
1868 if "objectCategory" not in obj
:
1869 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
1870 if "isCriticalSystemObject" not in obj
:
1871 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
1872 if "isRecycled" in obj
:
1873 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
1875 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
1876 nmsg
["systemFlags"] = ldb
.MessageElement("-1946157056", ldb
.FLAG_MOD_REPLACE
, "systemFlags")
1877 nmsg
["objectClass"] = ldb
.MessageElement(["top", "container"], ldb
.FLAG_MOD_REPLACE
, "objectClass")
1879 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1880 % (dn
), 'fix_deleted_deleted_objects'):
1881 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
1884 if self
.do_modify(nmsg
, ["relax:0"],
1885 "Failed to fix Deleted Objects container %s" % dn
):
1886 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
1888 def err_replica_locations(self
, obj
, cross_ref
, attr
):
1889 nmsg
= ldb
.Message()
1891 target
= self
.samdb
.get_dsServiceName()
1893 if self
.samdb
.am_rodc():
1894 self
.report('Not fixing %s for the RODC' % (attr
, obj
.dn
))
1897 if not self
.confirm_all('Add yourself to the replica locations for %s?'
1898 % (obj
.dn
), 'fix_replica_locations'):
1899 self
.report('Not fixing missing/incorrect attributes on %s\n' % (obj
.dn
))
1902 nmsg
[attr
] = ldb
.MessageElement(target
, ldb
.FLAG_MOD_ADD
, attr
)
1903 if self
.do_modify(nmsg
, [], "Failed to add %s for %s" % (attr
, obj
.dn
)):
1904 self
.report("Fixed %s for %s" % (attr
, obj
.dn
))
1906 def is_fsmo_role(self
, dn
):
1907 if dn
== self
.samdb
.domain_dn
:
1909 if dn
== self
.infrastructure_dn
:
1911 if dn
== self
.naming_dn
:
1913 if dn
== self
.schema_dn
:
1915 if dn
== self
.rid_dn
:
1920 def calculate_instancetype(self
, dn
):
1922 nc_root
= self
.samdb
.get_nc_root(dn
)
1924 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
1926 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
1927 except ldb
.LdbError
, (enum
, estr
):
1928 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1931 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
1933 if self
.write_ncs
is not None and str(nc_root
) in self
.write_ncs
:
1934 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
1938 def get_wellknown_sd(self
, dn
):
1939 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
1941 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
1942 return ndr_unpack(security
.descriptor
,
1943 descriptor_fn(domain_sid
,
1944 name_map
=self
.name_map
))
1948 def check_object(self
, dn
, attrs
=['*']):
1949 '''check one object'''
1951 self
.report("Checking object %s" % dn
)
1953 # If we modify the pass-by-reference attrs variable, then we get a
1954 # replPropertyMetadata for every object that we check.
1956 if "dn" in map(str.lower
, attrs
):
1957 attrs
.append("name")
1958 if "distinguishedname" in map(str.lower
, attrs
):
1959 attrs
.append("name")
1960 if str(dn
.get_rdn_name()).lower() in map(str.lower
, attrs
):
1961 attrs
.append("name")
1962 if 'name' in map(str.lower
, attrs
):
1963 attrs
.append(dn
.get_rdn_name())
1964 attrs
.append("isDeleted")
1965 attrs
.append("systemFlags")
1966 need_replPropertyMetaData
= False
1968 need_replPropertyMetaData
= True
1971 linkID
, _
= self
.get_attr_linkID_and_reverse_name(a
)
1976 need_replPropertyMetaData
= True
1978 if need_replPropertyMetaData
:
1979 attrs
.append("replPropertyMetaData")
1980 attrs
.append("objectGUID")
1984 sd_flags |
= security
.SECINFO_OWNER
1985 sd_flags |
= security
.SECINFO_GROUP
1986 sd_flags |
= security
.SECINFO_DACL
1987 sd_flags |
= security
.SECINFO_SACL
1989 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1994 "sd_flags:1:%d" % sd_flags
,
1995 "reveal_internals:0",
1998 except ldb
.LdbError
, (enum
, estr
):
1999 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2000 if self
.in_transaction
:
2001 self
.report("ERROR: Object %s disappeared during check" % dn
)
2006 self
.report("ERROR: Object %s failed to load during check" % dn
)
2010 set_attrs_from_md
= set()
2011 set_attrs_seen
= set()
2012 got_repl_property_meta_data
= False
2013 got_objectclass
= False
2015 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
2017 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
2018 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
2020 # We have no deleted objects DN for schema, and we check for this above for the other
2022 deleted_objects_dn
= None
2025 object_rdn_attr
= None
2026 object_rdn_val
= None
2031 for attrname
in obj
:
2032 if attrname
== 'dn' or attrname
== "distinguishedName":
2035 if str(attrname
).lower() == 'objectclass':
2036 got_objectclass
= True
2038 if str(attrname
).lower() == "name":
2039 if len(obj
[attrname
]) != 1:
2041 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2042 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2044 name_val
= obj
[attrname
][0]
2046 if str(attrname
).lower() == str(obj
.dn
.get_rdn_name()).lower():
2047 object_rdn_attr
= attrname
2048 if len(obj
[attrname
]) != 1:
2050 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2051 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2053 object_rdn_val
= obj
[attrname
][0]
2055 if str(attrname
).lower() == 'isdeleted':
2056 if obj
[attrname
][0] != "FALSE":
2059 if str(attrname
).lower() == 'systemflags':
2060 systemFlags
= int(obj
[attrname
][0])
2062 if str(attrname
).lower() == 'replpropertymetadata':
2063 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
]):
2065 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
])
2066 # We don't continue, as we may also have other fixes for this attribute
2067 # based on what other attributes we see.
2070 (set_attrs_from_md
, list_attid_from_md
, wrong_attids
) \
2071 = self
.process_metadata(dn
, obj
[attrname
])
2074 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
2077 if len(set_attrs_from_md
) < len(list_attid_from_md
) \
2078 or len(wrong_attids
) > 0 \
2079 or sorted(list_attid_from_md
) != list_attid_from_md
:
2081 self
.err_replmetadata_incorrect_attid(dn
, attrname
, obj
[attrname
], wrong_attids
)
2084 # Here we check that the first attid is 0
2086 if list_attid_from_md
[0] != 0:
2088 self
.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2089 (attrname
, str(dn
)))
2091 got_repl_property_meta_data
= True
2094 if str(attrname
).lower() == 'ntsecuritydescriptor':
2095 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
2096 if sd_broken
is not None:
2097 self
.err_wrong_sd(dn
, sd
, sd_broken
)
2101 if sd
.owner_sid
is None or sd
.group_sid
is None:
2102 self
.err_missing_sd_owner(dn
, sd
)
2106 if self
.reset_well_known_acls
:
2108 well_known_sd
= self
.get_wellknown_sd(dn
)
2112 current_sd
= ndr_unpack(security
.descriptor
,
2113 str(obj
[attrname
][0]))
2115 diff
= get_diff_sds(well_known_sd
, current_sd
, security
.dom_sid(self
.samdb
.get_domain_sid()))
2117 self
.err_wrong_default_sd(dn
, well_known_sd
, current_sd
, diff
)
2122 if str(attrname
).lower() == 'objectclass':
2123 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
2124 # Do not consider the attribute incorrect if:
2125 # - The sorted (alphabetically) list is the same, inclding case
2126 # - The first and last elements are the same
2128 # This avoids triggering an error due to
2129 # non-determinism in the sort routine in (at least)
2130 # 4.3 and earlier, and the fact that any AUX classes
2131 # in these attributes are also not sorted when
2132 # imported from Windows (they are just in the reverse
2133 # order of last set)
2134 if sorted(normalised
) != sorted(obj
[attrname
]) \
2135 or normalised
[0] != obj
[attrname
][0] \
2136 or normalised
[-1] != obj
[attrname
][-1]:
2137 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
2141 if str(attrname
).lower() == 'userparameters':
2142 if len(obj
[attrname
][0]) == 1 and obj
[attrname
][0][0] == '\x20':
2144 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
2147 elif obj
[attrname
][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2148 # This is the correct, normal prefix
2151 elif obj
[attrname
][0][:20] == 'IAAgACAAIAAgACAAIAAg':
2152 # this is the typical prefix from a windows migration
2154 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
2157 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':
2158 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2160 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
2163 elif len(obj
[attrname
][0]) % 2 != 0:
2164 # This is a value that isn't even in length
2166 self
.err_odd_userParameters(obj
, attrname
, obj
[attrname
])
2169 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':
2170 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2172 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
2175 if attrname
.lower() == 'attributeid' or attrname
.lower() == 'governsid':
2176 if obj
[attrname
][0] in self
.attribute_or_class_ids
:
2178 self
.report('Error: %s %s on %s already exists as an attributeId or governsId'
2179 % (attrname
, obj
.dn
, obj
[attrname
][0]))
2181 self
.attribute_or_class_ids
.add(obj
[attrname
][0])
2183 # check for empty attributes
2184 for val
in obj
[attrname
]:
2186 self
.err_empty_attribute(dn
, attrname
)
2190 # get the syntax oid for the attribute, so we can can have
2191 # special handling for some specific attribute types
2193 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
2194 except Exception as msg
:
2195 self
.err_unknown_attribute(obj
, attrname
)
2199 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
2201 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
2202 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
2203 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
2205 set_attrs_seen
.add(str(attrname
).lower())
2207 if syntax_oid
in [ dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
2208 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
2209 # it's some form of DN, do specialised checking on those
2210 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
2214 # check for incorrectly normalised attributes
2215 for val
in obj
[attrname
]:
2216 values
.add(str(val
))
2218 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
2219 if len(normalised
) != 1 or normalised
[0] != val
:
2220 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
2224 if len(obj
[attrname
]) != len(values
):
2225 self
.err_duplicate_values(dn
, attrname
, obj
[attrname
], list(values
))
2229 if str(attrname
).lower() == "instancetype":
2230 calculated_instancetype
= self
.calculate_instancetype(dn
)
2231 if len(obj
["instanceType"]) != 1 or obj
["instanceType"][0] != str(calculated_instancetype
):
2233 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
2235 if not got_objectclass
and ("*" in attrs
or "objectclass" in map(str.lower
, attrs
)):
2237 self
.err_missing_objectclass(dn
)
2239 if ("*" in attrs
or "name" in map(str.lower
, attrs
)):
2240 if name_val
is None:
2242 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
2243 if object_rdn_attr
is None:
2245 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
2247 if name_val
is not None:
2250 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
2251 parent_dn
= deleted_objects_dn
2252 if parent_dn
is None:
2253 parent_dn
= obj
.dn
.parent()
2254 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
2255 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
2257 if obj
.dn
== deleted_objects_dn
:
2258 expected_dn
= obj
.dn
2260 if expected_dn
!= obj
.dn
:
2262 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
, object_rdn_val
, name_val
)
2263 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
2265 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
, object_rdn_val
, str(obj
.dn
)))
2268 if got_repl_property_meta_data
:
2269 if obj
.dn
== deleted_objects_dn
:
2270 isDeletedAttId
= 131120
2271 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2273 expectedTimeDo
= 2650466015990000000
2274 originating
= self
.get_originating_time(obj
["replPropertyMetaData"], isDeletedAttId
)
2275 if originating
!= expectedTimeDo
:
2276 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
2277 nmsg
= ldb
.Message()
2279 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2281 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
2284 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
2286 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
2288 self
.report("On object %s" % dn
)
2291 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
2292 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
2293 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
2295 self
.fix_metadata(obj
, att
)
2297 if self
.is_fsmo_role(dn
):
2298 if "fSMORoleOwner" not in obj
and ("*" in attrs
or "fsmoroleowner" in map(str.lower
, attrs
)):
2299 self
.err_no_fsmoRoleOwner(obj
)
2303 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
2304 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
2305 controls
=["show_recycled:1", "show_deleted:1"])
2306 except ldb
.LdbError
, (enum
, estr
):
2307 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2308 self
.err_missing_parent(obj
)
2313 if dn
in self
.deleted_objects_containers
and '*' in attrs
:
2314 if self
.is_deleted_deleted_objects(obj
):
2315 self
.err_deleted_deleted_objects(obj
)
2318 for (dns_part
, msg
) in self
.dns_partitions
:
2319 if dn
== dns_part
and 'repsFrom' in obj
:
2320 location
= "msDS-NC-Replica-Locations"
2321 if self
.samdb
.am_rodc():
2322 location
= "msDS-NC-RO-Replica-Locations"
2324 if location
not in msg
:
2325 # There are no replica locations!
2326 self
.err_replica_locations(obj
, msg
.dn
, location
)
2331 for loc
in msg
[location
]:
2332 if loc
== self
.samdb
.get_dsServiceName():
2335 # This DC is not in the replica locations
2336 self
.err_replica_locations(obj
, msg
.dn
, location
)
2339 if dn
== self
.server_ref_dn
:
2340 # Check we have a valid RID Set
2341 if "*" in attrs
or "rIDSetReferences" in attrs
:
2342 if "rIDSetReferences" not in obj
:
2343 # NO RID SET reference
2344 # We are RID master, allocate it.
2347 if self
.is_rid_master
:
2348 # Allocate a RID Set
2349 if self
.confirm_all('Allocate the missing RID set for RID master?',
2350 'fix_missing_rid_set_master'):
2352 # We don't have auto-transaction logic on
2353 # extended operations, so we have to do it
2356 self
.samdb
.transaction_start()
2359 self
.samdb
.create_own_rid_set()
2362 self
.samdb
.transaction_cancel()
2365 self
.samdb
.transaction_commit()
2368 elif not self
.samdb
.am_rodc():
2369 self
.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn
)
2372 # Check some details of our own RID Set
2373 if dn
== self
.rid_set_dn
:
2374 res
= self
.samdb
.search(base
=self
.rid_set_dn
, scope
=ldb
.SCOPE_BASE
,
2375 attrs
=["rIDAllocationPool",
2376 "rIDPreviousAllocationPool",
2379 if "rIDAllocationPool" not in res
[0]:
2380 self
.report("No rIDAllocationPool found in %s" % dn
)
2383 next_pool
= int(res
[0]["rIDAllocationPool"][0])
2385 high
= (0xFFFFFFFF00000000 & next_pool
) >> 32
2386 low
= 0x00000000FFFFFFFF & next_pool
2389 self
.report("Invalid RID set %d-%s, %d > %d!" % (low
, high
, low
, high
))
2392 if "rIDNextRID" in res
[0]:
2393 next_free_rid
= int(res
[0]["rIDNextRID"][0])
2397 if next_free_rid
== 0:
2402 # Check the remainder of this pool for conflicts. If
2403 # ridalloc_allocate_rid() moves to a new pool, this
2404 # will be above high, so we will stop.
2405 while next_free_rid
<= high
:
2406 sid
= "%s-%d" % (self
.samdb
.get_domain_sid(), next_free_rid
)
2408 res
= self
.samdb
.search(base
="<SID=%s>" % sid
, scope
=ldb
.SCOPE_BASE
,
2410 except ldb
.LdbError
, (enum
, estr
):
2411 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2415 self
.report("SID %s for %s conflicts with our current RID set in %s" % (sid
, res
[0].dn
, dn
))
2418 if self
.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2420 'fix_sid_rid_set_conflict'):
2421 self
.samdb
.transaction_start()
2423 # This will burn RIDs, which will move
2424 # past the conflict. We then check again
2425 # to see if the new RID conflicts, until
2426 # the end of the current pool. We don't
2427 # look at the next pool to avoid burning
2428 # all RIDs in one go in some strange
2432 allocated_rid
= self
.samdb
.allocate_rid()
2433 if allocated_rid
>= next_free_rid
:
2434 next_free_rid
= allocated_rid
+ 1
2437 self
.samdb
.transaction_cancel()
2440 self
.samdb
.transaction_commit()
2449 ################################################################
2450 # check special @ROOTDSE attributes
2451 def check_rootdse(self
):
2452 '''check the @ROOTDSE special object'''
2453 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
2455 self
.report("Checking object %s" % dn
)
2456 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
2458 self
.report("Object %s disappeared during check" % dn
)
2463 # check that the dsServiceName is in GUID form
2464 if not 'dsServiceName' in obj
:
2465 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
2466 return error_count
+1
2468 if not obj
['dsServiceName'][0].startswith('<GUID='):
2469 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2471 if not self
.confirm('Change dsServiceName to GUID form?'):
2473 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0]),
2474 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
2475 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
2478 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
2479 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
2480 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
2481 self
.report("Changed dsServiceName to GUID form")
2485 ###############################################
2486 # re-index the database
2487 def reindex_database(self
):
2488 '''re-index the whole database'''
2490 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
2491 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
2492 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
2493 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
2495 ###############################################
2497 def reset_modules(self
):
2498 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2500 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
2501 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
2502 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)