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 def check_database(self
, DN
=None, scope
=ldb
.SCOPE_SUBTREE
, controls
=[], attrs
=['*']):
190 '''perform a database check, returning the number of errors found'''
191 res
= self
.samdb
.search(base
=DN
, scope
=scope
, attrs
=['dn'], controls
=controls
)
192 self
.report('Checking %u objects' % len(res
))
195 error_count
+= self
.check_deleted_objects_containers()
197 self
.attribute_or_class_ids
= set()
200 self
.dn_set
.add(str(object.dn
))
201 error_count
+= self
.check_object(object.dn
, attrs
=attrs
)
204 error_count
+= self
.check_rootdse()
206 if error_count
!= 0 and not self
.fix
:
207 self
.report("Please use --fix to fix these errors")
209 self
.report('Checked %u objects (%u errors)' % (len(res
), error_count
))
212 def check_deleted_objects_containers(self
):
213 """This function only fixes conflicts on the Deleted Objects
214 containers, not the attributes"""
216 for nc
in self
.ncs_lacking_deleted_containers
:
217 if nc
== self
.schema_dn
:
220 self
.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc
)
221 if not self
.confirm_all('Fix missing Deleted Objects container for %s?' % (nc
), 'fix_missing_deleted_objects'):
224 dn
= ldb
.Dn(self
.samdb
, "CN=Deleted Objects")
229 # If something already exists here, add a conflict
230 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[],
231 controls
=["show_deleted:1", "extended_dn:1:1",
232 "show_recycled:1", "reveal_internals:0"])
234 guid
= res
[0].dn
.get_extended_component("GUID")
235 conflict_dn
= ldb
.Dn(self
.samdb
,
236 "CN=Deleted Objects\\0ACNF:%s" % str(misc
.GUID(guid
)))
237 conflict_dn
.add_base(nc
)
239 except ldb
.LdbError
, (enum
, estr
):
240 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
243 self
.report("Couldn't check for conflicting Deleted Objects container: %s" % estr
)
246 if conflict_dn
is not None:
248 self
.samdb
.rename(dn
, conflict_dn
, ["show_deleted:1", "relax:0", "show_recycled:1"])
249 except ldb
.LdbError
, (enum
, estr
):
250 self
.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn
, conflict_dn
, estr
))
253 # Refresh wellKnownObjects links
254 res
= self
.samdb
.search(base
=nc
, scope
=ldb
.SCOPE_BASE
,
255 attrs
=['wellKnownObjects'],
256 controls
=["show_deleted:1", "extended_dn:0",
257 "show_recycled:1", "reveal_internals:0"])
259 self
.report("wellKnownObjects was not found for NC %s" % nc
)
262 # Prevent duplicate deleted objects containers just in case
263 wko
= res
[0]["wellKnownObjects"]
265 proposed_objectguid
= None
267 dsdb_dn
= dsdb_Dn(self
.samdb
, o
, dsdb
.DSDB_SYNTAX_BINARY_DN
)
268 if self
.is_deleted_objects_dn(dsdb_dn
):
269 self
.report("wellKnownObjects had duplicate Deleted Objects value %s" % o
)
270 # We really want to put this back in the same spot
271 # as the original one, so that on replication we
272 # merge, rather than conflict.
273 proposed_objectguid
= dsdb_dn
.dn
.get_extended_component("GUID")
276 if proposed_objectguid
is not None:
277 guid_suffix
= "\nobjectGUID: %s" % str(misc
.GUID(proposed_objectguid
))
279 wko_prefix
= "B:32:%s" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
280 listwko
.append('%s:%s' % (wko_prefix
, dn
))
283 # Insert a brand new Deleted Objects container
284 self
.samdb
.add_ldif("""dn: %s
286 objectClass: container
287 description: Container for deleted objects
289 isCriticalSystemObject: TRUE
290 showInAdvancedViewOnly: TRUE
291 systemFlags: -1946157056%s""" % (dn
, guid_suffix
),
292 controls
=["relax:0", "provision:0"])
294 delta
= ldb
.Message()
295 delta
.dn
= ldb
.Dn(self
.samdb
, str(res
[0]["dn"]))
296 delta
["wellKnownObjects"] = ldb
.MessageElement(listwko
,
297 ldb
.FLAG_MOD_REPLACE
,
300 # Insert the link to the brand new container
301 if self
.do_modify(delta
, ["relax:0"],
302 "NC %s lacks Deleted Objects WKGUID" % nc
,
304 self
.report("Added %s well known guid link" % dn
)
306 self
.deleted_objects_containers
.append(dn
)
310 def report(self
, msg
):
311 '''print a message unless quiet is set'''
315 def confirm(self
, msg
, allow_all
=False, forced
=False):
316 '''confirm a change'''
323 return common
.confirm(msg
, forced
=forced
, allow_all
=allow_all
)
325 ################################################################
326 # a local confirm function with support for 'all'
327 def confirm_all(self
, msg
, all_attr
):
328 '''confirm a change with support for "all" '''
331 if getattr(self
, all_attr
) == 'NONE':
333 if getattr(self
, all_attr
) == 'ALL':
339 c
= common
.confirm(msg
, forced
=forced
, allow_all
=True)
341 setattr(self
, all_attr
, 'ALL')
344 setattr(self
, all_attr
, 'NONE')
348 def do_delete(self
, dn
, controls
, msg
):
349 '''delete dn with optional verbose output'''
351 self
.report("delete DN %s" % dn
)
353 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
354 self
.samdb
.delete(dn
, controls
=controls
)
355 except Exception, err
:
356 if self
.in_transaction
:
357 raise CommandError("%s : %s" % (msg
, err
))
358 self
.report("%s : %s" % (msg
, err
))
362 def do_modify(self
, m
, controls
, msg
, validate
=True):
363 '''perform a modify with optional verbose output'''
365 self
.report(self
.samdb
.write_ldif(m
, ldb
.CHANGETYPE_MODIFY
))
367 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
368 self
.samdb
.modify(m
, controls
=controls
, validate
=validate
)
369 except Exception, err
:
370 if self
.in_transaction
:
371 raise CommandError("%s : %s" % (msg
, err
))
372 self
.report("%s : %s" % (msg
, err
))
376 def do_rename(self
, from_dn
, to_rdn
, to_base
, controls
, msg
):
377 '''perform a modify with optional verbose output'''
379 self
.report("""dn: %s
383 newSuperior: %s""" % (str(from_dn
), str(to_rdn
), str(to_base
)))
385 to_dn
= to_rdn
+ to_base
386 controls
= controls
+ ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
]
387 self
.samdb
.rename(from_dn
, to_dn
, controls
=controls
)
388 except Exception, err
:
389 if self
.in_transaction
:
390 raise CommandError("%s : %s" % (msg
, err
))
391 self
.report("%s : %s" % (msg
, err
))
395 def get_attr_linkID_and_reverse_name(self
, attrname
):
396 if attrname
in self
.link_id_cache
:
397 return self
.link_id_cache
[attrname
]
398 linkID
= self
.samdb_schema
.get_linkId_from_lDAPDisplayName(attrname
)
400 revname
= self
.samdb_schema
.get_backlink_from_lDAPDisplayName(attrname
)
403 self
.link_id_cache
[attrname
] = (linkID
, revname
)
404 return linkID
, revname
406 def err_empty_attribute(self
, dn
, attrname
):
407 '''fix empty attributes'''
408 self
.report("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
409 if not self
.confirm_all('Remove empty attribute %s from %s?' % (attrname
, dn
), 'remove_all_empty_attributes'):
410 self
.report("Not fixing empty attribute %s" % attrname
)
415 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
416 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
417 "Failed to remove empty attribute %s" % attrname
, validate
=False):
418 self
.report("Removed empty attribute %s" % attrname
)
420 def err_normalise_mismatch(self
, dn
, attrname
, values
):
421 '''fix attribute normalisation errors'''
422 self
.report("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
425 normalised
= self
.samdb
.dsdb_normalise_attributes(
426 self
.samdb_schema
, attrname
, [val
])
427 if len(normalised
) != 1:
428 self
.report("Unable to normalise value '%s'" % val
)
429 mod_list
.append((val
, ''))
430 elif (normalised
[0] != val
):
431 self
.report("value '%s' should be '%s'" % (val
, normalised
[0]))
432 mod_list
.append((val
, normalised
[0]))
433 if not self
.confirm_all('Fix normalisation for %s from %s?' % (attrname
, dn
), 'fix_all_normalisation'):
434 self
.report("Not fixing attribute %s" % attrname
)
439 for i
in range(0, len(mod_list
)):
440 (val
, nval
) = mod_list
[i
]
441 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
443 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
,
446 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
447 "Failed to normalise attribute %s" % attrname
,
449 self
.report("Normalised attribute %s" % attrname
)
451 def err_normalise_mismatch_replace(self
, dn
, attrname
, values
):
452 '''fix attribute normalisation errors'''
453 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, values
)
454 self
.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname
, dn
))
455 self
.report("Values/Order of values do/does not match: %s/%s!" % (values
, list(normalised
)))
456 if list(normalised
) == values
:
458 if not self
.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_normalisation'):
459 self
.report("Not fixing attribute '%s'" % attrname
)
464 m
[attrname
] = ldb
.MessageElement(normalised
, ldb
.FLAG_MOD_REPLACE
, attrname
)
466 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
467 "Failed to normalise attribute %s" % attrname
,
469 self
.report("Normalised attribute %s" % attrname
)
471 def err_duplicate_values(self
, dn
, attrname
, dup_values
, values
):
472 '''fix attribute normalisation errors'''
473 self
.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname
, dn
))
474 self
.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values
), ','.join(values
)))
475 if not self
.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname
, dn
), 'fix_all_duplicates'):
476 self
.report("Not fixing attribute '%s'" % attrname
)
481 m
[attrname
] = ldb
.MessageElement(values
, ldb
.FLAG_MOD_REPLACE
, attrname
)
483 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
484 "Failed to remove duplicate value on attribute %s" % attrname
,
486 self
.report("Removed duplicate value on attribute %s" % attrname
)
488 def is_deleted_objects_dn(self
, dsdb_dn
):
489 '''see if a dsdb_Dn is the special Deleted Objects DN'''
490 return dsdb_dn
.prefix
== "B:32:%s:" % dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
492 def err_missing_objectclass(self
, dn
):
493 """handle object without objectclass"""
494 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
)))
495 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'):
496 self
.report("Not deleting object with missing objectclass '%s'" % dn
)
498 if self
.do_delete(dn
, ["relax:0"],
499 "Failed to remove DN %s" % dn
):
500 self
.report("Removed DN %s" % dn
)
502 def err_deleted_dn(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, remove_plausible
=False):
503 """handle a DN pointing to a deleted object"""
504 if not remove_plausible
:
505 self
.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
506 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
507 if not self
.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
508 self
.report("Not removing")
511 self
.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname
, dn
, val
))
512 self
.report("Target GUID points at deleted DN %r" % str(correct_dn
))
513 if not self
.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
514 self
.report("Not removing")
519 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
520 if self
.do_modify(m
, ["show_recycled:1",
521 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
522 "Failed to remove deleted DN attribute %s" % attrname
):
523 self
.report("Removed deleted DN on attribute %s" % attrname
)
525 def err_missing_target_dn_or_GUID(self
, dn
, attrname
, val
, dsdb_dn
):
526 """handle a missing target DN (if specified, GUID form can't be found,
527 and otherwise DN string form can't be found)"""
528 # check if its a backlink
529 linkID
, _
= self
.get_attr_linkID_and_reverse_name(attrname
)
530 if (linkID
& 1 == 0) and str(dsdb_dn
).find('\\0ADEL') == -1:
532 linkID
, reverse_link_name \
533 = self
.get_attr_linkID_and_reverse_name(attrname
)
534 if reverse_link_name
is not None:
535 self
.report("WARNING: no target object found for GUID "
536 "component for one-way forward link "
538 "%s - %s" % (attrname
, dn
, val
))
539 self
.report("Not removing dangling forward link")
542 nc_root
= self
.samdb
.get_nc_root(dn
)
543 target_nc_root
= self
.samdb
.get_nc_root(dsdb_dn
.dn
)
544 if nc_root
!= target_nc_root
:
545 # We don't bump the error count as Samba produces these
546 # in normal operation
547 self
.report("WARNING: no target object found for GUID "
548 "component for cross-partition link "
550 "%s - %s" % (attrname
, dn
, val
))
551 self
.report("Not removing dangling one-way "
552 "cross-partition link "
553 "(we might be mid-replication)")
556 # Due to our link handling one-way links pointing to
557 # missing objects are plausible.
559 # We don't bump the error count as Samba produces these
560 # in normal operation
561 self
.report("WARNING: no target object found for GUID "
562 "component for DN value %s in object "
563 "%s - %s" % (attrname
, dn
, val
))
564 self
.err_deleted_dn(dn
, attrname
, val
,
565 dsdb_dn
, dsdb_dn
, True)
568 # We bump the error count here, as we should have deleted this
569 self
.report("ERROR: no target object found for GUID "
570 "component for link %s in object "
571 "%s - %s" % (attrname
, dn
, val
))
572 self
.err_deleted_dn(dn
, attrname
, val
, dsdb_dn
, dsdb_dn
, False)
575 def err_missing_dn_GUID_component(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
576 """handle a missing GUID extended DN component"""
577 self
.report("ERROR: %s component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
578 controls
=["extended_dn:1:1", "show_recycled:1"]
580 res
= self
.samdb
.search(base
=str(dsdb_dn
.dn
), scope
=ldb
.SCOPE_BASE
,
581 attrs
=[], controls
=controls
)
582 except ldb
.LdbError
, (enum
, estr
):
583 self
.report("unable to find object for DN %s - (%s)" % (dsdb_dn
.dn
, estr
))
584 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
586 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
589 self
.report("unable to find object for DN %s" % dsdb_dn
.dn
)
590 self
.err_missing_target_dn_or_GUID(dn
, attrname
, val
, dsdb_dn
)
592 dsdb_dn
.dn
= res
[0].dn
594 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_DN_GUIDs'):
595 self
.report("Not fixing %s" % errstr
)
599 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
600 m
['new_value'] = ldb
.MessageElement(str(dsdb_dn
), ldb
.FLAG_MOD_ADD
, attrname
)
602 if self
.do_modify(m
, ["show_recycled:1"],
603 "Failed to fix %s on attribute %s" % (errstr
, attrname
)):
604 self
.report("Fixed %s on attribute %s" % (errstr
, attrname
))
606 def err_incorrect_binary_dn(self
, dn
, attrname
, val
, dsdb_dn
, errstr
):
607 """handle an incorrect binary DN component"""
608 self
.report("ERROR: %s binary component for %s in object %s - %s" % (errstr
, attrname
, dn
, val
))
609 controls
=["extended_dn:1:1", "show_recycled:1"]
611 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
), 'fix_all_binary_dn'):
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_dn_string_component_old(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
):
624 """handle a DN string being incorrect"""
625 self
.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname
, dn
, val
))
626 dsdb_dn
.dn
= correct_dn
628 if not self
.confirm_all('Change DN to %s?' % str(dsdb_dn
),
629 'fix_all_old_dn_string_component_mismatch'):
630 self
.report("Not fixing old string component")
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
)
636 if self
.do_modify(m
, ["show_recycled:1"],
637 "Failed to fix old DN string on attribute %s" % (attrname
)):
638 self
.report("Fixed old DN string on attribute %s" % (attrname
))
640 def err_dn_component_target_mismatch(self
, dn
, attrname
, val
, dsdb_dn
, correct_dn
, mismatch_type
):
641 """handle a DN string being incorrect"""
642 self
.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type
, 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_%s_dn_component_mismatch' % mismatch_type
):
647 self
.report("Not fixing %s component mismatch" % mismatch_type
)
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 incorrect DN %s on attribute %s" % (mismatch_type
, attrname
)):
655 self
.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type
, attrname
))
657 def err_unknown_attribute(self
, obj
, attrname
):
658 '''handle an unknown attribute error'''
659 self
.report("ERROR: unknown attribute '%s' in %s" % (attrname
, obj
.dn
))
660 if not self
.confirm_all('Remove unknown attribute %s' % attrname
, 'remove_all_unknown_attributes'):
661 self
.report("Not removing %s" % attrname
)
665 m
['old_value'] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attrname
)
666 if self
.do_modify(m
, ["relax:0", "show_recycled:1"],
667 "Failed to remove unknown attribute %s" % attrname
):
668 self
.report("Removed unknown attribute %s" % (attrname
))
670 def err_undead_linked_attribute(self
, obj
, attrname
, val
):
671 '''handle a link that should not be there on a deleted object'''
672 self
.report("ERROR: linked attribute '%s' to '%s' is present on "
673 "deleted object %s" % (attrname
, val
, obj
.dn
))
674 if not self
.confirm_all('Remove linked attribute %s' % attrname
, 'fix_undead_linked_attributes'):
675 self
.report("Not removing linked attribute %s" % attrname
)
679 m
['old_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
681 if self
.do_modify(m
, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
682 "local_oid:%s:0" % dsdb
.DSDB_CONTROL_REPLMD_VANISH_LINKS
],
683 "Failed to delete forward link %s" % attrname
):
684 self
.report("Fixed undead forward link %s" % (attrname
))
686 def err_missing_backlink(self
, obj
, attrname
, val
, backlink_name
, target_dn
):
687 '''handle a missing backlink value'''
688 self
.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name
, target_dn
, attrname
, obj
.dn
))
689 if not self
.confirm_all('Fix missing backlink %s' % backlink_name
, 'fix_all_missing_backlinks'):
690 self
.report("Not fixing missing backlink %s" % backlink_name
)
694 m
['new_value'] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_ADD
, backlink_name
)
695 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
696 "Failed to fix missing backlink %s" % backlink_name
):
697 self
.report("Fixed missing backlink %s" % (backlink_name
))
699 def err_incorrect_rmd_flags(self
, obj
, attrname
, revealed_dn
):
700 '''handle a incorrect RMD_FLAGS value'''
701 rmd_flags
= int(revealed_dn
.dn
.get_extended_component("RMD_FLAGS"))
702 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()))
703 if not self
.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags
, 'fix_rmd_flags'):
704 self
.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags
)
708 m
['old_value'] = ldb
.MessageElement(str(revealed_dn
), ldb
.FLAG_MOD_DELETE
, attrname
)
709 if self
.do_modify(m
, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
710 "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags
):
711 self
.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags
))
713 def err_orphaned_backlink(self
, obj_dn
, backlink_attr
, backlink_val
,
714 target_dn
, forward_attr
, forward_syntax
,
715 check_duplicates
=True):
716 '''handle a orphaned backlink value'''
717 if check_duplicates
is True and self
.has_duplicate_links(target_dn
, forward_attr
, forward_syntax
):
718 self
.report("WARNING: Keep orphaned backlink attribute " + \
719 "'%s' in '%s' for link '%s' in '%s'" % (
720 backlink_attr
, obj_dn
, forward_attr
, target_dn
))
722 self
.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr
, obj_dn
, forward_attr
, target_dn
))
723 if not self
.confirm_all('Remove orphaned backlink %s' % backlink_attr
, 'fix_all_orphaned_backlinks'):
724 self
.report("Not removing orphaned backlink %s" % backlink_attr
)
728 m
['value'] = ldb
.MessageElement(backlink_val
, ldb
.FLAG_MOD_DELETE
, backlink_attr
)
729 if self
.do_modify(m
, ["show_recycled:1", "relax:0"],
730 "Failed to fix orphaned backlink %s" % backlink_attr
):
731 self
.report("Fixed orphaned backlink %s" % (backlink_attr
))
733 def err_recover_forward_links(self
, obj
, forward_attr
, forward_vals
):
734 '''handle a duplicate links value'''
736 self
.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr
, obj
.dn
))
738 if not self
.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr
, 'recover_all_forward_links'):
739 self
.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
740 forward_attr
, obj
.dn
))
744 m
['value'] = ldb
.MessageElement(forward_vals
, ldb
.FLAG_MOD_REPLACE
, forward_attr
)
745 if self
.do_modify(m
, ["local_oid:1.3.6.1.4.1.7165.4.3.19.2:1"],
746 "Failed to fix duplicate links in attribute '%s'" % forward_attr
):
747 self
.report("Fixed duplicate links in attribute '%s'" % (forward_attr
))
749 def err_no_fsmoRoleOwner(self
, obj
):
750 '''handle a missing fSMORoleOwner'''
751 self
.report("ERROR: fSMORoleOwner not found for role %s" % (obj
.dn
))
752 res
= self
.samdb
.search("",
753 scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
755 serviceName
= res
[0]["dsServiceName"][0]
756 if not self
.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj
.dn
, serviceName
), 'seize_fsmo_role'):
757 self
.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
761 m
['value'] = ldb
.MessageElement(serviceName
, ldb
.FLAG_MOD_ADD
, 'fSMORoleOwner')
762 if self
.do_modify(m
, [],
763 "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
)):
764 self
.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj
.dn
, serviceName
))
766 def err_missing_parent(self
, obj
):
767 '''handle a missing parent'''
768 self
.report("ERROR: parent object not found for %s" % (obj
.dn
))
769 if not self
.confirm_all('Move object %s into LostAndFound?' % (obj
.dn
), 'move_to_lost_and_found'):
770 self
.report('Not moving object %s into LostAndFound' % (obj
.dn
))
773 keep_transaction
= False
774 self
.samdb
.transaction_start()
776 nc_root
= self
.samdb
.get_nc_root(obj
.dn
);
777 lost_and_found
= self
.samdb
.get_wellknown_dn(nc_root
, dsdb
.DS_GUID_LOSTANDFOUND_CONTAINER
)
778 new_dn
= ldb
.Dn(self
.samdb
, str(obj
.dn
))
779 new_dn
.remove_base_components(len(new_dn
) - 1)
780 if self
.do_rename(obj
.dn
, new_dn
, lost_and_found
, ["show_deleted:0", "relax:0"],
781 "Failed to rename object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
)):
782 self
.report("Renamed object %s into lostAndFound at %s" % (obj
.dn
, new_dn
+ lost_and_found
))
786 m
['lastKnownParent'] = ldb
.MessageElement(str(obj
.dn
.parent()), ldb
.FLAG_MOD_REPLACE
, 'lastKnownParent')
788 if self
.do_modify(m
, [],
789 "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
)):
790 self
.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn
+ lost_and_found
))
791 keep_transaction
= True
793 self
.samdb
.transaction_cancel()
797 self
.samdb
.transaction_commit()
799 self
.samdb
.transaction_cancel()
801 def err_wrong_dn(self
, obj
, new_dn
, rdn_attr
, rdn_val
, name_val
):
802 '''handle a wrong dn'''
804 new_rdn
= ldb
.Dn(self
.samdb
, str(new_dn
))
805 new_rdn
.remove_base_components(len(new_rdn
) - 1)
806 new_parent
= new_dn
.parent()
809 if rdn_val
!= name_val
:
810 attributes
+= "%s=%r " % (rdn_attr
, rdn_val
)
811 attributes
+= "name=%r" % (name_val
)
813 self
.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj
.dn
, attributes
, new_dn
))
814 if not self
.confirm_all("Rename %s to %s?" % (obj
.dn
, new_dn
), 'fix_dn'):
815 self
.report("Not renaming %s to %s" % (obj
.dn
, new_dn
))
818 if self
.do_rename(obj
.dn
, new_rdn
, new_parent
, ["show_recycled:1", "relax:0"],
819 "Failed to rename object %s into %s" % (obj
.dn
, new_dn
)):
820 self
.report("Renamed %s into %s" % (obj
.dn
, new_dn
))
822 def err_wrong_instancetype(self
, obj
, calculated_instancetype
):
823 '''handle a wrong instanceType'''
824 self
.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj
["instanceType"], obj
.dn
, calculated_instancetype
))
825 if not self
.confirm_all('Change instanceType from %s to %d on %s?' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
), 'fix_instancetype'):
826 self
.report('Not changing instanceType from %s to %d on %s' % (obj
["instanceType"], calculated_instancetype
, obj
.dn
))
831 m
['value'] = ldb
.MessageElement(str(calculated_instancetype
), ldb
.FLAG_MOD_REPLACE
, 'instanceType')
832 if self
.do_modify(m
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
],
833 "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
)):
834 self
.report("Corrected instancetype on %s by setting instanceType=%d" % (obj
.dn
, calculated_instancetype
))
836 def err_short_userParameters(self
, obj
, attrname
, value
):
837 # This is a truncated userParameters due to a pre 4.1 replication bug
838 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
)))
840 def err_base64_userParameters(self
, obj
, attrname
, value
):
841 '''handle a wrong userParameters'''
842 self
.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value
, obj
.dn
))
843 if not self
.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj
.dn
), 'fix_base64_userparameters'):
844 self
.report('Not changing userParameters from base64 encoding on %s' % (obj
.dn
))
849 m
['value'] = ldb
.MessageElement(b64decode(obj
[attrname
][0]), ldb
.FLAG_MOD_REPLACE
, 'userParameters')
850 if self
.do_modify(m
, [],
851 "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj
.dn
)):
852 self
.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj
.dn
))
854 def err_utf8_userParameters(self
, obj
, attrname
, value
):
855 '''handle a wrong userParameters'''
856 self
.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj
.dn
))
857 if not self
.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj
.dn
), 'fix_utf8_userparameters'):
858 self
.report('Not changing userParameters from UTF8 encoding on %s' % (obj
.dn
))
863 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf8').encode('utf-16-le'),
864 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
865 if self
.do_modify(m
, [],
866 "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
)):
867 self
.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj
.dn
))
869 def err_doubled_userParameters(self
, obj
, attrname
, value
):
870 '''handle a wrong userParameters'''
871 self
.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj
.dn
))
872 if not self
.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj
.dn
), 'fix_doubled_userparameters'):
873 self
.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj
.dn
))
878 m
['value'] = ldb
.MessageElement(obj
[attrname
][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
879 ldb
.FLAG_MOD_REPLACE
, 'userParameters')
880 if self
.do_modify(m
, [],
881 "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
)):
882 self
.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj
.dn
))
884 def err_odd_userParameters(self
, obj
, attrname
):
885 # This is a truncated userParameters due to a pre 4.1 replication bug
886 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
)))
888 def find_revealed_link(self
, dn
, attrname
, guid
):
889 '''return a revealed link in an object'''
890 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[attrname
],
891 controls
=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
892 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
893 for val
in res
[0][attrname
]:
894 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, syntax_oid
)
895 guid2
= dsdb_dn
.dn
.get_extended_component("GUID")
900 def check_duplicate_links(self
, obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
):
901 '''check a linked values for duplicate forward links'''
904 duplicate_dict
= dict()
907 # Only forward links can have this problem
908 if forward_linkID
& 1:
909 # If we got the reverse, skip it
910 return (error_count
, duplicate_dict
, unique_dict
)
912 if backlink_attr
is None:
913 return (error_count
, duplicate_dict
, unique_dict
)
915 duplicate_cache_key
= "%s:%s" % (str(obj
.dn
), forward_attr
)
916 if duplicate_cache_key
not in self
.duplicate_link_cache
:
917 self
.duplicate_link_cache
[duplicate_cache_key
] = False
919 for val
in obj
[forward_attr
]:
920 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, forward_syntax
)
922 # all DNs should have a GUID component
923 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
926 guidstr
= str(misc
.GUID(guid
))
927 keystr
= guidstr
+ dsdb_dn
.prefix
928 if keystr
not in unique_dict
:
929 unique_dict
[keystr
] = dsdb_dn
932 if keystr
not in duplicate_dict
:
933 duplicate_dict
[keystr
] = dict()
934 duplicate_dict
[keystr
]["keep"] = None
935 duplicate_dict
[keystr
]["delete"] = list()
937 # Now check for the highest RMD_VERSION
938 v1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_VERSION"))
939 v2
= int(dsdb_dn
.dn
.get_extended_component("RMD_VERSION"))
941 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
942 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
945 duplicate_dict
[keystr
]["keep"] = dsdb_dn
946 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
947 unique_dict
[keystr
] = dsdb_dn
949 # Fallback to the highest RMD_LOCAL_USN
950 u1
= int(unique_dict
[keystr
].dn
.get_extended_component("RMD_LOCAL_USN"))
951 u2
= int(dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN"))
953 duplicate_dict
[keystr
]["keep"] = unique_dict
[keystr
]
954 duplicate_dict
[keystr
]["delete"].append(dsdb_dn
)
956 duplicate_dict
[keystr
]["keep"] = dsdb_dn
957 duplicate_dict
[keystr
]["delete"].append(unique_dict
[keystr
])
958 unique_dict
[keystr
] = dsdb_dn
961 self
.duplicate_link_cache
[duplicate_cache_key
] = True
963 return (error_count
, duplicate_dict
, unique_dict
)
965 def has_duplicate_links(self
, dn
, forward_attr
, forward_syntax
):
966 '''check a linked values for duplicate forward links'''
969 duplicate_cache_key
= "%s:%s" % (str(dn
), forward_attr
)
970 if duplicate_cache_key
in self
.duplicate_link_cache
:
971 return self
.duplicate_link_cache
[duplicate_cache_key
]
973 forward_linkID
, backlink_attr
= self
.get_attr_linkID_and_reverse_name(forward_attr
)
975 attrs
= [forward_attr
]
976 controls
= ["extended_dn:1:1", "reveal_internals:0"]
978 # check its the right GUID
980 res
= self
.samdb
.search(base
=str(dn
), scope
=ldb
.SCOPE_BASE
,
981 attrs
=attrs
, controls
=controls
)
982 except ldb
.LdbError
, (enum
, estr
):
983 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
989 error_count
, duplicate_dict
, unique_dict
= \
990 self
.check_duplicate_links(obj
, forward_attr
, forward_syntax
, forward_linkID
, backlink_attr
)
992 if duplicate_cache_key
in self
.duplicate_link_cache
:
993 return self
.duplicate_link_cache
[duplicate_cache_key
]
997 def find_missing_forward_links_from_backlinks(self
, obj
,
1001 forward_unique_dict
):
1002 '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1003 missing_forward_links
= []
1006 if backlink_attr
is None:
1007 return (missing_forward_links
, error_count
)
1009 if forward_syntax
!= ldb
.SYNTAX_DN
:
1010 self
.report("Not checking for missing forward links for syntax: %s",
1012 return (missing_forward_links
, error_count
)
1015 obj_guid
= obj
['objectGUID'][0]
1016 obj_guid_str
= str(ndr_unpack(misc
.GUID
, obj_guid
))
1017 filter = "(%s=<GUID=%s>)" % (backlink_attr
, obj_guid_str
)
1019 res
= self
.samdb
.search(expression
=filter,
1020 scope
=ldb
.SCOPE_SUBTREE
, attrs
=["objectGUID"],
1021 controls
=["extended_dn:1:1",
1022 "search_options:1:2",
1023 "paged_results:1:1000"])
1024 except ldb
.LdbError
, (enum
, estr
):
1028 target_dn
= dsdb_Dn(self
.samdb
, r
.dn
.extended_str(), forward_syntax
)
1030 guid
= target_dn
.dn
.get_extended_component("GUID")
1031 guidstr
= str(misc
.GUID(guid
))
1032 if guidstr
in forward_unique_dict
:
1035 # A valid forward link looks like this:
1037 # <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1038 # <RMD_ADDTIME=131607546230000000>;
1039 # <RMD_CHANGETIME=131607546230000000>;
1041 # <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1042 # <RMD_LOCAL_USN=3765>;
1043 # <RMD_ORIGINATING_USN=3765>;
1045 # <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1046 # CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1048 # Note that versions older than Samba 4.8 create
1049 # links with RMD_VERSION=0.
1051 # Try to get the local_usn and time from objectClass
1052 # if possible and fallback to any other one.
1053 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1054 obj
['replPropertyMetadata'][0])
1055 for o
in repl
.ctr
.array
:
1056 local_usn
= o
.local_usn
1057 t
= o
.originating_change_time
1058 if o
.attid
== drsuapi
.DRSUAPI_ATTID_objectClass
:
1061 # We use a magic invocationID for restoring missing
1062 # forward links to recover from bug #13228.
1063 # This should allow some more future magic to fix the
1066 # It also means it looses the conflict resolution
1067 # against almost every real invocation, if the
1068 # version is also 0.
1069 originating_invocid
= misc
.GUID("ffffffff-4700-4700-4700-000000b13228")
1075 rmd_invocid
= originating_invocid
1076 rmd_originating_usn
= originating_usn
1077 rmd_local_usn
= local_usn
1080 target_dn
.dn
.set_extended_component("RMD_ADDTIME", str(rmd_addtime
))
1081 target_dn
.dn
.set_extended_component("RMD_CHANGETIME", str(rmd_changetime
))
1082 target_dn
.dn
.set_extended_component("RMD_FLAGS", str(rmd_flags
))
1083 target_dn
.dn
.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid
))
1084 target_dn
.dn
.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn
))
1085 target_dn
.dn
.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn
))
1086 target_dn
.dn
.set_extended_component("RMD_VERSION", str(rmd_version
))
1089 missing_forward_links
.append(target_dn
)
1091 return (missing_forward_links
, error_count
)
1093 def check_dn(self
, obj
, attrname
, syntax_oid
):
1094 '''check a DN attribute for correctness'''
1096 obj_guid
= obj
['objectGUID'][0]
1098 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
1099 if reverse_link_name
is not None:
1100 reverse_syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(reverse_link_name
)
1102 reverse_syntax_oid
= None
1104 error_count
, duplicate_dict
, unique_dict
= \
1105 self
.check_duplicate_links(obj
, attrname
, syntax_oid
, linkID
, reverse_link_name
)
1107 if len(duplicate_dict
) != 0:
1109 missing_forward_links
, missing_error_count
= \
1110 self
.find_missing_forward_links_from_backlinks(obj
,
1111 attrname
, syntax_oid
,
1114 error_count
+= missing_error_count
1116 forward_links
= [dn
for dn
in unique_dict
.values()]
1118 if missing_error_count
!= 0:
1119 self
.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1122 self
.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname
, obj
.dn
))
1123 for m
in missing_forward_links
:
1124 self
.report("Missing link '%s'" % (m
))
1125 if not self
.confirm_all("Schedule readding missing forward link for attribute %s" % attrname
,
1126 'fix_all_missing_forward_links'):
1127 self
.err_orphaned_backlink(m
.dn
, reverse_link_name
,
1128 obj
.dn
.extended_str(), obj
.dn
,
1129 attrname
, syntax_oid
,
1130 check_duplicates
=False)
1132 forward_links
+= [m
]
1133 for keystr
in duplicate_dict
.keys():
1134 d
= duplicate_dict
[keystr
]
1135 for dd
in d
["delete"]:
1136 self
.report("Duplicate link '%s'" % dd
)
1137 self
.report("Correct link '%s'" % d
["keep"])
1139 # We now construct the sorted dn values.
1140 # They're sorted by the objectGUID of the target
1141 # See dsdb_Dn.__cmp__()
1142 vals
= [str(dn
) for dn
in sorted(forward_links
)]
1143 self
.err_recover_forward_links(obj
, attrname
, vals
)
1144 # We should continue with the fixed values
1145 obj
[attrname
] = ldb
.MessageElement(vals
, 0, attrname
)
1147 for val
in obj
[attrname
]:
1148 dsdb_dn
= dsdb_Dn(self
.samdb
, val
, syntax_oid
)
1150 # all DNs should have a GUID component
1151 guid
= dsdb_dn
.dn
.get_extended_component("GUID")
1154 self
.err_missing_dn_GUID_component(obj
.dn
, attrname
, val
, dsdb_dn
,
1158 guidstr
= str(misc
.GUID(guid
))
1159 attrs
= ['isDeleted', 'replPropertyMetaData']
1161 if (str(attrname
).lower() == 'msds-hasinstantiatedncs') and (obj
.dn
== self
.ntds_dsa
):
1162 fixing_msDS_HasInstantiatedNCs
= True
1163 attrs
.append("instanceType")
1165 fixing_msDS_HasInstantiatedNCs
= False
1167 if reverse_link_name
is not None:
1168 attrs
.append(reverse_link_name
)
1170 # check its the right GUID
1172 res
= self
.samdb
.search(base
="<GUID=%s>" % guidstr
, scope
=ldb
.SCOPE_BASE
,
1173 attrs
=attrs
, controls
=["extended_dn:1:1", "show_recycled:1",
1174 "reveal_internals:0"
1176 except ldb
.LdbError
, (enum
, estr
):
1177 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1180 # We don't always want to
1181 error_count
+= self
.err_missing_target_dn_or_GUID(obj
.dn
,
1187 if fixing_msDS_HasInstantiatedNCs
:
1188 dsdb_dn
.prefix
= "B:8:%08X:" % int(res
[0]['instanceType'][0])
1189 dsdb_dn
.binary
= "%08X" % int(res
[0]['instanceType'][0])
1191 if str(dsdb_dn
) != val
:
1193 self
.err_incorrect_binary_dn(obj
.dn
, attrname
, val
, dsdb_dn
, "incorrect instanceType part of Binary DN")
1196 # now we have two cases - the source object might or might not be deleted
1197 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
1198 target_is_deleted
= 'isDeleted' in res
[0] and res
[0]['isDeleted'][0].upper() == 'TRUE'
1201 if is_deleted
and not obj
.dn
in self
.deleted_objects_containers
and linkID
:
1202 # A fully deleted object should not have any linked
1203 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1204 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1206 self
.err_undead_linked_attribute(obj
, attrname
, val
)
1209 elif target_is_deleted
and not self
.is_deleted_objects_dn(dsdb_dn
) and linkID
:
1210 # the target DN is not allowed to be deleted, unless the target DN is the
1211 # special Deleted Objects container
1213 local_usn
= dsdb_dn
.dn
.get_extended_component("RMD_LOCAL_USN")
1215 if 'replPropertyMetaData' in res
[0]:
1216 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1217 str(res
[0]['replPropertyMetadata']))
1219 for o
in repl
.ctr
.array
:
1220 if o
.attid
== drsuapi
.DRSUAPI_ATTID_isDeleted
:
1221 deleted_usn
= o
.local_usn
1222 if deleted_usn
>= int(local_usn
):
1223 # If the object was deleted after the link
1224 # was last modified then, clean it up here
1229 self
.err_deleted_dn(obj
.dn
, attrname
,
1230 val
, dsdb_dn
, res
[0].dn
, True)
1233 self
.err_deleted_dn(obj
.dn
, attrname
, val
, dsdb_dn
, res
[0].dn
, False)
1236 # We should not check for incorrect
1237 # components on deleted links, as these are allowed to
1238 # go stale (we just need the GUID, not the name)
1239 rmd_blob
= dsdb_dn
.dn
.get_extended_component("RMD_FLAGS")
1241 if rmd_blob
is not None:
1242 rmd_flags
= int(rmd_blob
)
1244 # assert the DN matches in string form, where a reverse
1245 # link exists, otherwise (below) offer to fix it as a non-error.
1246 # The string form is essentially only kept for forensics,
1247 # as we always re-resolve by GUID in normal operations.
1248 if not rmd_flags
& 1 and reverse_link_name
is not None:
1249 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1251 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1252 res
[0].dn
, "string")
1255 if res
[0].dn
.get_extended_component("GUID") != dsdb_dn
.dn
.get_extended_component("GUID"):
1257 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1261 if res
[0].dn
.get_extended_component("SID") != dsdb_dn
.dn
.get_extended_component("SID"):
1263 self
.err_dn_component_target_mismatch(obj
.dn
, attrname
, val
, dsdb_dn
,
1267 # Now we have checked the GUID and SID, offer to fix old
1268 # DN strings as a non-error (for forward links with no
1269 # backlink). Samba does not maintain this string
1270 # otherwise, so we don't increment error_count.
1271 if reverse_link_name
is None:
1272 if str(res
[0].dn
) != str(dsdb_dn
.dn
):
1273 self
.err_dn_string_component_old(obj
.dn
, attrname
, val
, dsdb_dn
,
1277 # check the reverse_link is correct if there should be one
1279 if reverse_link_name
in res
[0]:
1280 for v
in res
[0][reverse_link_name
]:
1281 v_dn
= dsdb_Dn(self
.samdb
, v
)
1282 v_guid
= v_dn
.dn
.get_extended_component("GUID")
1283 v_blob
= v_dn
.dn
.get_extended_component("RMD_FLAGS")
1285 if v_blob
is not None:
1286 v_rmd_flags
= int(v_blob
)
1289 if v_guid
== obj_guid
:
1292 if match_count
!= 1:
1293 if syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
or reverse_syntax_oid
== dsdb
.DSDB_SYNTAX_BINARY_DN
:
1295 # Forward binary multi-valued linked attribute
1297 for w
in obj
[attrname
]:
1298 w_guid
= dsdb_Dn(self
.samdb
, w
).dn
.get_extended_component("GUID")
1302 if match_count
== forward_count
:
1305 for v
in obj
[attrname
]:
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
)
1317 if match_count
== expected_count
:
1320 diff_count
= expected_count
- match_count
1323 # If there's a backward link on binary multi-valued linked attribute,
1324 # let the check on the forward link remedy the value.
1325 # UNLESS, there is no forward link detected.
1326 if match_count
== 0:
1328 self
.err_orphaned_backlink(obj
.dn
, attrname
,
1333 # Only warn here and let the forward link logic fix it.
1334 self
.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1335 attrname
, expected_count
, str(obj
.dn
),
1336 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1339 assert not target_is_deleted
1341 self
.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1342 attrname
, expected_count
, str(obj
.dn
),
1343 reverse_link_name
, match_count
, str(dsdb_dn
.dn
)))
1345 # Loop until the difference between the forward and
1346 # the backward links is resolved.
1347 while diff_count
!= 0:
1350 if match_count
> 0 or diff_count
> 1:
1351 # TODO no method to fix these right now
1352 self
.report("ERROR: Can't fix missing "
1353 "multi-valued backlinks on %s" % str(dsdb_dn
.dn
))
1355 self
.err_missing_backlink(obj
, attrname
,
1356 obj
.dn
.extended_str(),
1361 self
.err_orphaned_backlink(res
[0].dn
, reverse_link_name
,
1362 obj
.dn
.extended_str(), obj
.dn
,
1363 attrname
, syntax_oid
)
1370 def get_originating_time(self
, val
, attid
):
1371 '''Read metadata properties and return the originating time for
1372 a given attributeId.
1374 :return: the originating time or 0 if not found
1377 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
1380 for o
in repl
.ctr
.array
:
1381 if o
.attid
== attid
:
1382 return o
.originating_change_time
1386 def process_metadata(self
, dn
, val
):
1387 '''Read metadata properties and list attributes in it.
1388 raises KeyError if the attid is unknown.'''
1391 wrong_attids
= set()
1393 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1395 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
, str(val
))
1398 for o
in repl
.ctr
.array
:
1399 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1400 set_att
.add(att
.lower())
1401 list_attid
.append(o
.attid
)
1402 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
,
1403 is_schema_nc
=in_schema_nc
)
1404 if correct_attid
!= o
.attid
:
1405 wrong_attids
.add(o
.attid
)
1407 return (set_att
, list_attid
, wrong_attids
)
1410 def fix_metadata(self
, obj
, attr
):
1411 '''re-write replPropertyMetaData elements for a single attribute for a
1412 object. This is used to fix missing replPropertyMetaData elements'''
1413 guid_str
= str(ndr_unpack(misc
.GUID
, obj
['objectGUID'][0]))
1414 dn
= ldb
.Dn(self
.samdb
, "<GUID=%s>" % guid_str
)
1415 res
= self
.samdb
.search(base
= dn
, scope
=ldb
.SCOPE_BASE
, attrs
= [attr
],
1416 controls
= ["search_options:1:2",
1419 nmsg
= ldb
.Message()
1421 nmsg
[attr
] = ldb
.MessageElement(msg
[attr
], ldb
.FLAG_MOD_REPLACE
, attr
)
1422 if self
.do_modify(nmsg
, ["relax:0", "provision:0", "show_recycled:1"],
1423 "Failed to fix metadata for attribute %s" % attr
):
1424 self
.report("Fixed metadata for attribute %s" % attr
)
1426 def ace_get_effective_inherited_type(self
, ace
):
1427 if ace
.flags
& security
.SEC_ACE_FLAG_INHERIT_ONLY
:
1431 if ace
.type == security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
:
1433 elif ace
.type == security
.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT
:
1435 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT
:
1437 elif ace
.type == security
.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT
:
1443 if not ace
.object.flags
& security
.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT
:
1446 return str(ace
.object.inherited_type
)
1448 def lookup_class_schemaIDGUID(self
, cls
):
1449 if cls
in self
.class_schemaIDGUID
:
1450 return self
.class_schemaIDGUID
[cls
]
1452 flt
= "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1453 res
= self
.samdb
.search(base
=self
.schema_dn
,
1455 attrs
=["schemaIDGUID"])
1456 t
= str(ndr_unpack(misc
.GUID
, res
[0]["schemaIDGUID"][0]))
1458 self
.class_schemaIDGUID
[cls
] = t
1461 def process_sd(self
, dn
, obj
):
1462 sd_attr
= "nTSecurityDescriptor"
1463 sd_val
= obj
[sd_attr
]
1465 sd
= ndr_unpack(security
.descriptor
, str(sd_val
))
1467 is_deleted
= 'isDeleted' in obj
and obj
['isDeleted'][0].upper() == 'TRUE'
1469 # we don't fix deleted objects
1472 sd_clean
= security
.descriptor()
1473 sd_clean
.owner_sid
= sd
.owner_sid
1474 sd_clean
.group_sid
= sd
.group_sid
1475 sd_clean
.type = sd
.type
1476 sd_clean
.revision
= sd
.revision
1479 last_inherited_type
= None
1482 if sd
.sacl
is not None:
1484 for i
in range(0, len(aces
)):
1487 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1488 sd_clean
.sacl_add(ace
)
1491 t
= self
.ace_get_effective_inherited_type(ace
)
1495 if last_inherited_type
is not None:
1496 if t
!= last_inherited_type
:
1497 # if it inherited from more than
1498 # one type it's very likely to be broken
1500 # If not the recalculation will calculate
1505 last_inherited_type
= t
1508 if sd
.dacl
is not None:
1510 for i
in range(0, len(aces
)):
1513 if not ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
1514 sd_clean
.dacl_add(ace
)
1517 t
= self
.ace_get_effective_inherited_type(ace
)
1521 if last_inherited_type
is not None:
1522 if t
!= last_inherited_type
:
1523 # if it inherited from more than
1524 # one type it's very likely to be broken
1526 # If not the recalculation will calculate
1531 last_inherited_type
= t
1534 return (sd_clean
, sd
)
1536 if last_inherited_type
is None:
1542 cls
= obj
["objectClass"][-1]
1547 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1548 attrs
=["isDeleted", "objectClass"],
1549 controls
=["show_recycled:1"])
1551 is_deleted
= 'isDeleted' in o
and o
['isDeleted'][0].upper() == 'TRUE'
1553 # we don't fix deleted objects
1555 cls
= o
["objectClass"][-1]
1557 t
= self
.lookup_class_schemaIDGUID(cls
)
1559 if t
!= last_inherited_type
:
1561 return (sd_clean
, sd
)
1566 def err_wrong_sd(self
, dn
, sd
, sd_broken
):
1567 '''re-write the SD due to incorrect inherited ACEs'''
1568 sd_attr
= "nTSecurityDescriptor"
1569 sd_val
= ndr_pack(sd
)
1570 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1572 if not self
.confirm_all('Fix %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor'):
1573 self
.report('Not fixing %s on %s\n' % (sd_attr
, dn
))
1576 nmsg
= ldb
.Message()
1578 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1579 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1580 "Failed to fix attribute %s" % sd_attr
):
1581 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1583 def err_wrong_default_sd(self
, dn
, sd
, sd_old
, diff
):
1584 '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1585 sd_attr
= "nTSecurityDescriptor"
1586 sd_val
= ndr_pack(sd
)
1587 sd_old_val
= ndr_pack(sd_old
)
1588 sd_flags
= security
.SECINFO_DACL | security
.SECINFO_SACL
1589 if sd
.owner_sid
is not None:
1590 sd_flags |
= security
.SECINFO_OWNER
1591 if sd
.group_sid
is not None:
1592 sd_flags |
= security
.SECINFO_GROUP
1594 if not self
.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr
, dn
, diff
), 'reset_all_well_known_acls'):
1595 self
.report('Not resetting %s on %s\n' % (sd_attr
, dn
))
1600 m
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1601 if self
.do_modify(m
, ["sd_flags:1:%d" % sd_flags
],
1602 "Failed to reset attribute %s" % sd_attr
):
1603 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1605 def err_missing_sd_owner(self
, dn
, sd
):
1606 '''re-write the SD due to a missing owner or group'''
1607 sd_attr
= "nTSecurityDescriptor"
1608 sd_val
= ndr_pack(sd
)
1609 sd_flags
= security
.SECINFO_OWNER | security
.SECINFO_GROUP
1611 if not self
.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr
, dn
), 'fix_ntsecuritydescriptor_owner_group'):
1612 self
.report('Not fixing missing owner or group %s on %s\n' % (sd_attr
, dn
))
1615 nmsg
= ldb
.Message()
1617 nmsg
[sd_attr
] = ldb
.MessageElement(sd_val
, ldb
.FLAG_MOD_REPLACE
, sd_attr
)
1619 # By setting the session_info to admin_session_info and
1620 # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1621 # flags we cause the descriptor module to set the correct
1622 # owner and group on the SD, replacing the None/NULL values
1623 # for owner_sid and group_sid currently present.
1625 # The admin_session_info matches that used in provision, and
1626 # is the best guess we can make for an existing object that
1627 # hasn't had something specifically set.
1629 # This is important for the dns related naming contexts.
1630 self
.samdb
.set_session_info(self
.admin_session_info
)
1631 if self
.do_modify(nmsg
, ["sd_flags:1:%d" % sd_flags
],
1632 "Failed to fix metadata for attribute %s" % sd_attr
):
1633 self
.report("Fixed attribute '%s' of '%s'\n" % (sd_attr
, dn
))
1634 self
.samdb
.set_session_info(self
.system_session_info
)
1637 def has_replmetadata_zero_invocationid(self
, dn
, repl_meta_data
):
1638 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1639 str(repl_meta_data
))
1643 # Search for a zero invocationID
1644 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1648 self
.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1649 version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1650 but should be non-zero. Proposed fix is to set to our invocationID (%s).'''
1651 % (dn
, o
.attid
, o
.version
,
1652 time
.ctime(samba
.nttime2unix(o
.originating_change_time
)),
1653 self
.samdb
.get_invocation_id()))
1658 def err_replmetadata_zero_invocationid(self
, dn
, attr
, repl_meta_data
):
1659 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1660 str(repl_meta_data
))
1662 now
= samba
.unix2nttime(int(time
.time()))
1665 # Search for a zero invocationID
1666 if o
.originating_invocation_id
!= misc
.GUID("00000000-0000-0000-0000-000000000000"):
1670 seq
= self
.samdb
.sequence_number(ldb
.SEQ_NEXT
)
1671 o
.version
= o
.version
+ 1
1672 o
.originating_change_time
= now
1673 o
.originating_invocation_id
= misc
.GUID(self
.samdb
.get_invocation_id())
1674 o
.originating_usn
= seq
1678 replBlob
= ndr_pack(repl
)
1682 if not self
.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1683 % (attr
, dn
, self
.samdb
.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1684 self
.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr
, dn
))
1687 nmsg
= ldb
.Message()
1689 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1690 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1691 "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1692 "Failed to fix attribute %s" % attr
):
1693 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1696 def err_replmetadata_unknown_attid(self
, dn
, attr
, repl_meta_data
):
1697 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1698 str(repl_meta_data
))
1701 # Search for an invalid attid
1703 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1705 self
.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o
.attid
, attr
, dn
))
1709 def err_replmetadata_incorrect_attid(self
, dn
, attr
, repl_meta_data
, wrong_attids
):
1710 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1711 str(repl_meta_data
))
1715 remove_attid
= set()
1718 in_schema_nc
= dn
.is_child_of(self
.schema_dn
)
1721 # Sort the array, except for the last element. This strange
1722 # construction, creating a new list, due to bugs in samba's
1723 # array handling in IDL generated objects.
1724 ctr
.array
= sorted(ctr
.array
[:], key
=lambda o
: o
.attid
)
1725 # Now walk it in reverse, so we see the low (and so incorrect,
1726 # the correct values are above 0x80000000) values first and
1727 # remove the 'second' value we see.
1728 for o
in reversed(ctr
.array
):
1729 print "%s: 0x%08x" % (dn
, o
.attid
)
1730 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1731 if att
.lower() in set_att
:
1732 self
.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att
, attr
, dn
))
1733 if not self
.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1734 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
),
1735 'fix_replmetadata_duplicate_attid'):
1736 self
.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1737 % (o
.attid
, att
, attr
, dn
))
1740 remove_attid
.add(o
.attid
)
1741 # We want to set the metadata for the most recent
1742 # update to have been applied locally, that is the metadata
1743 # matching the (eg string) value in the attribute
1744 if o
.local_usn
> hash_att
[att
].local_usn
:
1745 # This is always what we would have sent over DRS,
1746 # because the DRS server will have sent the
1747 # msDS-IntID, but with the values from both
1748 # attribute entries.
1749 hash_att
[att
].version
= o
.version
1750 hash_att
[att
].originating_change_time
= o
.originating_change_time
1751 hash_att
[att
].originating_invocation_id
= o
.originating_invocation_id
1752 hash_att
[att
].originating_usn
= o
.originating_usn
1753 hash_att
[att
].local_usn
= o
.local_usn
1755 # Do not re-add the value to the set or overwrite the hash value
1759 set_att
.add(att
.lower())
1761 # Generate a real list we can sort on properly
1762 new_list
= [o
for o
in ctr
.array
if o
.attid
not in remove_attid
]
1764 if (len(wrong_attids
) > 0):
1766 if o
.attid
in wrong_attids
:
1767 att
= self
.samdb_schema
.get_lDAPDisplayName_by_attid(o
.attid
)
1768 correct_attid
= self
.samdb_schema
.get_attid_from_lDAPDisplayName(att
, is_schema_nc
=in_schema_nc
)
1769 self
.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr
, dn
))
1770 if not self
.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1771 % (attr
, dn
, o
.attid
, att
, hash_att
[att
].attid
), 'fix_replmetadata_wrong_attid'):
1772 self
.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1773 % (o
.attid
, correct_attid
, att
, attr
, dn
))
1776 o
.attid
= correct_attid
1778 # Sort the array, (we changed the value so must re-sort)
1779 new_list
[:] = sorted(new_list
[:], key
=lambda o
: o
.attid
)
1781 # If we did not already need to fix it, then ask about sorting
1783 self
.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr
, dn
))
1784 if not self
.confirm_all('Fix %s on %s by sorting the attribute list?'
1785 % (attr
, dn
), 'fix_replmetadata_unsorted_attid'):
1786 self
.report('Not fixing %s on %s\n' % (attr
, dn
))
1789 # The actual sort done is done at the top of the function
1791 ctr
.count
= len(new_list
)
1792 ctr
.array
= new_list
1793 replBlob
= ndr_pack(repl
)
1795 nmsg
= ldb
.Message()
1797 nmsg
[attr
] = ldb
.MessageElement(replBlob
, ldb
.FLAG_MOD_REPLACE
, attr
)
1798 if self
.do_modify(nmsg
, ["local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA
,
1799 "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1800 "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1801 "Failed to fix attribute %s" % attr
):
1802 self
.report("Fixed attribute '%s' of '%s'\n" % (attr
, dn
))
1805 def is_deleted_deleted_objects(self
, obj
):
1807 if "description" not in obj
:
1808 self
.report("ERROR: description not present on Deleted Objects container %s" % obj
.dn
)
1810 if "showInAdvancedViewOnly" not in obj
or obj
['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1811 self
.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj
.dn
)
1813 if "objectCategory" not in obj
:
1814 self
.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj
.dn
)
1816 if "isCriticalSystemObject" not in obj
or obj
['isCriticalSystemObject'][0].upper() == 'FALSE':
1817 self
.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj
.dn
)
1819 if "isRecycled" in obj
:
1820 self
.report("ERROR: isRecycled present on Deleted Objects container %s" % obj
.dn
)
1822 if "isDeleted" in obj
and obj
['isDeleted'][0].upper() == 'FALSE':
1823 self
.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj
.dn
)
1825 if "objectClass" not in obj
or (len(obj
['objectClass']) != 2 or
1826 obj
['objectClass'][0] != 'top' or
1827 obj
['objectClass'][1] != 'container'):
1828 self
.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj
.dn
)
1830 if "systemFlags" not in obj
or obj
['systemFlags'][0] != '-1946157056':
1831 self
.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj
.dn
)
1835 def err_deleted_deleted_objects(self
, obj
):
1836 nmsg
= ldb
.Message()
1837 nmsg
.dn
= dn
= obj
.dn
1839 if "description" not in obj
:
1840 nmsg
["description"] = ldb
.MessageElement("Container for deleted objects", ldb
.FLAG_MOD_REPLACE
, "description")
1841 if "showInAdvancedViewOnly" not in obj
:
1842 nmsg
["showInAdvancedViewOnly"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "showInAdvancedViewOnly")
1843 if "objectCategory" not in obj
:
1844 nmsg
["objectCategory"] = ldb
.MessageElement("CN=Container,%s" % self
.schema_dn
, ldb
.FLAG_MOD_REPLACE
, "objectCategory")
1845 if "isCriticalSystemObject" not in obj
:
1846 nmsg
["isCriticalSystemObject"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isCriticalSystemObject")
1847 if "isRecycled" in obj
:
1848 nmsg
["isRecycled"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_DELETE
, "isRecycled")
1850 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
1851 nmsg
["systemFlags"] = ldb
.MessageElement("-1946157056", ldb
.FLAG_MOD_REPLACE
, "systemFlags")
1852 nmsg
["objectClass"] = ldb
.MessageElement(["top", "container"], ldb
.FLAG_MOD_REPLACE
, "objectClass")
1854 if not self
.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1855 % (dn
), 'fix_deleted_deleted_objects'):
1856 self
.report('Not fixing missing/incorrect attributes on %s\n' % (dn
))
1859 if self
.do_modify(nmsg
, ["relax:0"],
1860 "Failed to fix Deleted Objects container %s" % dn
):
1861 self
.report("Fixed Deleted Objects container '%s'\n" % (dn
))
1863 def err_replica_locations(self
, obj
, cross_ref
, attr
):
1864 nmsg
= ldb
.Message()
1866 target
= self
.samdb
.get_dsServiceName()
1868 if self
.samdb
.am_rodc():
1869 self
.report('Not fixing %s for the RODC' % (attr
, obj
.dn
))
1872 if not self
.confirm_all('Add yourself to the replica locations for %s?'
1873 % (obj
.dn
), 'fix_replica_locations'):
1874 self
.report('Not fixing missing/incorrect attributes on %s\n' % (obj
.dn
))
1877 nmsg
[attr
] = ldb
.MessageElement(target
, ldb
.FLAG_MOD_ADD
, attr
)
1878 if self
.do_modify(nmsg
, [], "Failed to add %s for %s" % (attr
, obj
.dn
)):
1879 self
.report("Fixed %s for %s" % (attr
, obj
.dn
))
1881 def is_fsmo_role(self
, dn
):
1882 if dn
== self
.samdb
.domain_dn
:
1884 if dn
== self
.infrastructure_dn
:
1886 if dn
== self
.naming_dn
:
1888 if dn
== self
.schema_dn
:
1890 if dn
== self
.rid_dn
:
1895 def calculate_instancetype(self
, dn
):
1897 nc_root
= self
.samdb
.get_nc_root(dn
)
1899 instancetype |
= dsdb
.INSTANCE_TYPE_IS_NC_HEAD
1901 self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
, attrs
=[], controls
=["show_recycled:1"])
1902 except ldb
.LdbError
, (enum
, estr
):
1903 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
1906 instancetype |
= dsdb
.INSTANCE_TYPE_NC_ABOVE
1908 if self
.write_ncs
is not None and str(nc_root
) in self
.write_ncs
:
1909 instancetype |
= dsdb
.INSTANCE_TYPE_WRITE
1913 def get_wellknown_sd(self
, dn
):
1914 for [sd_dn
, descriptor_fn
] in self
.wellknown_sds
:
1916 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
1917 return ndr_unpack(security
.descriptor
,
1918 descriptor_fn(domain_sid
,
1919 name_map
=self
.name_map
))
1923 def check_object(self
, dn
, attrs
=['*']):
1924 '''check one object'''
1926 self
.report("Checking object %s" % dn
)
1928 # If we modify the pass-by-reference attrs variable, then we get a
1929 # replPropertyMetadata for every object that we check.
1931 if "dn" in map(str.lower
, attrs
):
1932 attrs
.append("name")
1933 if "distinguishedname" in map(str.lower
, attrs
):
1934 attrs
.append("name")
1935 if str(dn
.get_rdn_name()).lower() in map(str.lower
, attrs
):
1936 attrs
.append("name")
1937 if 'name' in map(str.lower
, attrs
):
1938 attrs
.append(dn
.get_rdn_name())
1939 attrs
.append("isDeleted")
1940 attrs
.append("systemFlags")
1941 need_replPropertyMetaData
= False
1943 need_replPropertyMetaData
= True
1946 linkID
, _
= self
.get_attr_linkID_and_reverse_name(a
)
1951 need_replPropertyMetaData
= True
1953 if need_replPropertyMetaData
:
1954 attrs
.append("replPropertyMetaData")
1955 attrs
.append("objectGUID")
1959 sd_flags |
= security
.SECINFO_OWNER
1960 sd_flags |
= security
.SECINFO_GROUP
1961 sd_flags |
= security
.SECINFO_DACL
1962 sd_flags |
= security
.SECINFO_SACL
1964 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
1969 "sd_flags:1:%d" % sd_flags
,
1970 "reveal_internals:0",
1973 except ldb
.LdbError
, (enum
, estr
):
1974 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
1975 if self
.in_transaction
:
1976 self
.report("ERROR: Object %s disappeared during check" % dn
)
1981 self
.report("ERROR: Object %s failed to load during check" % dn
)
1985 set_attrs_from_md
= set()
1986 set_attrs_seen
= set()
1987 got_repl_property_meta_data
= False
1988 got_objectclass
= False
1990 nc_dn
= self
.samdb
.get_nc_root(obj
.dn
)
1992 deleted_objects_dn
= self
.samdb
.get_wellknown_dn(nc_dn
,
1993 samba
.dsdb
.DS_GUID_DELETED_OBJECTS_CONTAINER
)
1995 # We have no deleted objects DN for schema, and we check for this above for the other
1997 deleted_objects_dn
= None
2000 object_rdn_attr
= None
2001 object_rdn_val
= None
2006 for attrname
in obj
:
2007 if attrname
== 'dn' or attrname
== "distinguishedName":
2010 if str(attrname
).lower() == 'objectclass':
2011 got_objectclass
= True
2013 if str(attrname
).lower() == "name":
2014 if len(obj
[attrname
]) != 1:
2016 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2017 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2019 name_val
= obj
[attrname
][0]
2021 if str(attrname
).lower() == str(obj
.dn
.get_rdn_name()).lower():
2022 object_rdn_attr
= attrname
2023 if len(obj
[attrname
]) != 1:
2025 self
.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2026 (len(obj
[attrname
]), attrname
, str(obj
.dn
)))
2028 object_rdn_val
= obj
[attrname
][0]
2030 if str(attrname
).lower() == 'isdeleted':
2031 if obj
[attrname
][0] != "FALSE":
2034 if str(attrname
).lower() == 'systemflags':
2035 systemFlags
= int(obj
[attrname
][0])
2037 if str(attrname
).lower() == 'replpropertymetadata':
2038 if self
.has_replmetadata_zero_invocationid(dn
, obj
[attrname
]):
2040 self
.err_replmetadata_zero_invocationid(dn
, attrname
, obj
[attrname
])
2041 # We don't continue, as we may also have other fixes for this attribute
2042 # based on what other attributes we see.
2045 (set_attrs_from_md
, list_attid_from_md
, wrong_attids
) \
2046 = self
.process_metadata(dn
, obj
[attrname
])
2049 self
.err_replmetadata_unknown_attid(dn
, attrname
, obj
[attrname
])
2052 if len(set_attrs_from_md
) < len(list_attid_from_md
) \
2053 or len(wrong_attids
) > 0 \
2054 or sorted(list_attid_from_md
) != list_attid_from_md
:
2056 self
.err_replmetadata_incorrect_attid(dn
, attrname
, obj
[attrname
], wrong_attids
)
2059 # Here we check that the first attid is 0
2061 if list_attid_from_md
[0] != 0:
2063 self
.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2064 (attrname
, str(dn
)))
2066 got_repl_property_meta_data
= True
2069 if str(attrname
).lower() == 'ntsecuritydescriptor':
2070 (sd
, sd_broken
) = self
.process_sd(dn
, obj
)
2071 if sd_broken
is not None:
2072 self
.err_wrong_sd(dn
, sd
, sd_broken
)
2076 if sd
.owner_sid
is None or sd
.group_sid
is None:
2077 self
.err_missing_sd_owner(dn
, sd
)
2081 if self
.reset_well_known_acls
:
2083 well_known_sd
= self
.get_wellknown_sd(dn
)
2087 current_sd
= ndr_unpack(security
.descriptor
,
2088 str(obj
[attrname
][0]))
2090 diff
= get_diff_sds(well_known_sd
, current_sd
, security
.dom_sid(self
.samdb
.get_domain_sid()))
2092 self
.err_wrong_default_sd(dn
, well_known_sd
, current_sd
, diff
)
2097 if str(attrname
).lower() == 'objectclass':
2098 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, obj
[attrname
])
2099 # Do not consider the attribute incorrect if:
2100 # - The sorted (alphabetically) list is the same, inclding case
2101 # - The first and last elements are the same
2103 # This avoids triggering an error due to
2104 # non-determinism in the sort routine in (at least)
2105 # 4.3 and earlier, and the fact that any AUX classes
2106 # in these attributes are also not sorted when
2107 # imported from Windows (they are just in the reverse
2108 # order of last set)
2109 if sorted(normalised
) != sorted(obj
[attrname
]) \
2110 or normalised
[0] != obj
[attrname
][0] \
2111 or normalised
[-1] != obj
[attrname
][-1]:
2112 self
.err_normalise_mismatch_replace(dn
, attrname
, list(obj
[attrname
]))
2116 if str(attrname
).lower() == 'userparameters':
2117 if len(obj
[attrname
][0]) == 1 and obj
[attrname
][0][0] == '\x20':
2119 self
.err_short_userParameters(obj
, attrname
, obj
[attrname
])
2122 elif obj
[attrname
][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2123 # This is the correct, normal prefix
2126 elif obj
[attrname
][0][:20] == 'IAAgACAAIAAgACAAIAAg':
2127 # this is the typical prefix from a windows migration
2129 self
.err_base64_userParameters(obj
, attrname
, obj
[attrname
])
2132 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':
2133 # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2135 self
.err_utf8_userParameters(obj
, attrname
, obj
[attrname
])
2138 elif len(obj
[attrname
][0]) % 2 != 0:
2139 # This is a value that isn't even in length
2141 self
.err_odd_userParameters(obj
, attrname
, obj
[attrname
])
2144 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':
2145 # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2147 self
.err_doubled_userParameters(obj
, attrname
, obj
[attrname
])
2150 if attrname
.lower() == 'attributeid' or attrname
.lower() == 'governsid':
2151 if obj
[attrname
][0] in self
.attribute_or_class_ids
:
2153 self
.report('Error: %s %s on %s already exists as an attributeId or governsId'
2154 % (attrname
, obj
.dn
, obj
[attrname
][0]))
2156 self
.attribute_or_class_ids
.add(obj
[attrname
][0])
2158 # check for empty attributes
2159 for val
in obj
[attrname
]:
2161 self
.err_empty_attribute(dn
, attrname
)
2165 # get the syntax oid for the attribute, so we can can have
2166 # special handling for some specific attribute types
2168 syntax_oid
= self
.samdb_schema
.get_syntax_oid_from_lDAPDisplayName(attrname
)
2169 except Exception, msg
:
2170 self
.err_unknown_attribute(obj
, attrname
)
2174 linkID
, reverse_link_name
= self
.get_attr_linkID_and_reverse_name(attrname
)
2176 flag
= self
.samdb_schema
.get_systemFlags_from_lDAPDisplayName(attrname
)
2177 if (not flag
& dsdb
.DS_FLAG_ATTR_NOT_REPLICATED
2178 and not flag
& dsdb
.DS_FLAG_ATTR_IS_CONSTRUCTED
2180 set_attrs_seen
.add(str(attrname
).lower())
2182 if syntax_oid
in [ dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_OR_NAME
,
2183 dsdb
.DSDB_SYNTAX_STRING_DN
, ldb
.SYNTAX_DN
]:
2184 # it's some form of DN, do specialised checking on those
2185 error_count
+= self
.check_dn(obj
, attrname
, syntax_oid
)
2189 # check for incorrectly normalised attributes
2190 for val
in obj
[attrname
]:
2191 values
.add(str(val
))
2193 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb_schema
, attrname
, [val
])
2194 if len(normalised
) != 1 or normalised
[0] != val
:
2195 self
.err_normalise_mismatch(dn
, attrname
, obj
[attrname
])
2199 if len(obj
[attrname
]) != len(values
):
2200 self
.err_duplicate_values(dn
, attrname
, obj
[attrname
], list(values
))
2204 if str(attrname
).lower() == "instancetype":
2205 calculated_instancetype
= self
.calculate_instancetype(dn
)
2206 if len(obj
["instanceType"]) != 1 or obj
["instanceType"][0] != str(calculated_instancetype
):
2208 self
.err_wrong_instancetype(obj
, calculated_instancetype
)
2210 if not got_objectclass
and ("*" in attrs
or "objectclass" in map(str.lower
, attrs
)):
2212 self
.err_missing_objectclass(dn
)
2214 if ("*" in attrs
or "name" in map(str.lower
, attrs
)):
2215 if name_val
is None:
2217 self
.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj
.dn
)))
2218 if object_rdn_attr
is None:
2220 self
.report("ERROR: Not fixing missing '%s' on '%s'" % (obj
.dn
.get_rdn_name(), str(obj
.dn
)))
2222 if name_val
is not None:
2225 if not (systemFlags
& samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
):
2226 parent_dn
= deleted_objects_dn
2227 if parent_dn
is None:
2228 parent_dn
= obj
.dn
.parent()
2229 expected_dn
= ldb
.Dn(self
.samdb
, "RDN=RDN,%s" % (parent_dn
))
2230 expected_dn
.set_component(0, obj
.dn
.get_rdn_name(), name_val
)
2232 if obj
.dn
== deleted_objects_dn
:
2233 expected_dn
= obj
.dn
2235 if expected_dn
!= obj
.dn
:
2237 self
.err_wrong_dn(obj
, expected_dn
, object_rdn_attr
, object_rdn_val
, name_val
)
2238 elif obj
.dn
.get_rdn_value() != object_rdn_val
:
2240 self
.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr
, object_rdn_val
, str(obj
.dn
)))
2243 if got_repl_property_meta_data
:
2244 if obj
.dn
== deleted_objects_dn
:
2245 isDeletedAttId
= 131120
2246 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2248 expectedTimeDo
= 2650466015990000000
2249 originating
= self
.get_originating_time(obj
["replPropertyMetaData"], isDeletedAttId
)
2250 if originating
!= expectedTimeDo
:
2251 if self
.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn
), 'fix_time_metadata'):
2252 nmsg
= ldb
.Message()
2254 nmsg
["isDeleted"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isDeleted")
2256 self
.samdb
.modify(nmsg
, controls
=["provision:0"])
2259 self
.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn
))
2261 for att
in set_attrs_seen
.difference(set_attrs_from_md
):
2263 self
.report("On object %s" % dn
)
2266 self
.report("ERROR: Attribute %s not present in replication metadata" % att
)
2267 if not self
.confirm_all("Fix missing replPropertyMetaData element '%s'" % att
, 'fix_all_metadata'):
2268 self
.report("Not fixing missing replPropertyMetaData element '%s'" % att
)
2270 self
.fix_metadata(obj
, att
)
2272 if self
.is_fsmo_role(dn
):
2273 if "fSMORoleOwner" not in obj
and ("*" in attrs
or "fsmoroleowner" in map(str.lower
, attrs
)):
2274 self
.err_no_fsmoRoleOwner(obj
)
2278 if dn
!= self
.samdb
.get_root_basedn() and str(dn
.parent()) not in self
.dn_set
:
2279 res
= self
.samdb
.search(base
=dn
.parent(), scope
=ldb
.SCOPE_BASE
,
2280 controls
=["show_recycled:1", "show_deleted:1"])
2281 except ldb
.LdbError
, (enum
, estr
):
2282 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
2283 self
.err_missing_parent(obj
)
2288 if dn
in self
.deleted_objects_containers
and '*' in attrs
:
2289 if self
.is_deleted_deleted_objects(obj
):
2290 self
.err_deleted_deleted_objects(obj
)
2293 for (dns_part
, msg
) in self
.dns_partitions
:
2294 if dn
== dns_part
and 'repsFrom' in obj
:
2295 location
= "msDS-NC-Replica-Locations"
2296 if self
.samdb
.am_rodc():
2297 location
= "msDS-NC-RO-Replica-Locations"
2299 if location
not in msg
:
2300 # There are no replica locations!
2301 self
.err_replica_locations(obj
, msg
.dn
, location
)
2306 for loc
in msg
[location
]:
2307 if loc
== self
.samdb
.get_dsServiceName():
2310 # This DC is not in the replica locations
2311 self
.err_replica_locations(obj
, msg
.dn
, location
)
2314 if dn
== self
.server_ref_dn
:
2315 # Check we have a valid RID Set
2316 if "*" in attrs
or "rIDSetReferences" in attrs
:
2317 if "rIDSetReferences" not in obj
:
2318 # NO RID SET reference
2319 # We are RID master, allocate it.
2322 if self
.is_rid_master
:
2323 # Allocate a RID Set
2324 if self
.confirm_all('Allocate the missing RID set for RID master?',
2325 'fix_missing_rid_set_master'):
2327 # We don't have auto-transaction logic on
2328 # extended operations, so we have to do it
2331 self
.samdb
.transaction_start()
2334 self
.samdb
.create_own_rid_set()
2337 self
.samdb
.transaction_cancel()
2340 self
.samdb
.transaction_commit()
2343 elif not self
.samdb
.am_rodc():
2344 self
.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn
)
2347 # Check some details of our own RID Set
2348 if dn
== self
.rid_set_dn
:
2349 res
= self
.samdb
.search(base
=self
.rid_set_dn
, scope
=ldb
.SCOPE_BASE
,
2350 attrs
=["rIDAllocationPool",
2351 "rIDPreviousAllocationPool",
2354 if "rIDAllocationPool" not in res
[0]:
2355 self
.report("No rIDAllocationPool found in %s" % dn
)
2358 next_pool
= int(res
[0]["rIDAllocationPool"][0])
2360 high
= (0xFFFFFFFF00000000 & next_pool
) >> 32
2361 low
= 0x00000000FFFFFFFF & next_pool
2364 self
.report("Invalid RID set %d-%s, %d > %d!" % (low
, high
, low
, high
))
2367 if "rIDNextRID" in res
[0]:
2368 next_free_rid
= int(res
[0]["rIDNextRID"][0])
2372 if next_free_rid
== 0:
2377 # Check the remainder of this pool for conflicts. If
2378 # ridalloc_allocate_rid() moves to a new pool, this
2379 # will be above high, so we will stop.
2380 while next_free_rid
<= high
:
2381 sid
= "%s-%d" % (self
.samdb
.get_domain_sid(), next_free_rid
)
2383 res
= self
.samdb
.search(base
="<SID=%s>" % sid
, scope
=ldb
.SCOPE_BASE
,
2385 except ldb
.LdbError
, (enum
, estr
):
2386 if enum
!= ldb
.ERR_NO_SUCH_OBJECT
:
2390 self
.report("SID %s for %s conflicts with our current RID set in %s" % (sid
, res
[0].dn
, dn
))
2393 if self
.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2395 'fix_sid_rid_set_conflict'):
2396 self
.samdb
.transaction_start()
2398 # This will burn RIDs, which will move
2399 # past the conflict. We then check again
2400 # to see if the new RID conflicts, until
2401 # the end of the current pool. We don't
2402 # look at the next pool to avoid burning
2403 # all RIDs in one go in some strange
2407 allocated_rid
= self
.samdb
.allocate_rid()
2408 if allocated_rid
>= next_free_rid
:
2409 next_free_rid
= allocated_rid
+ 1
2412 self
.samdb
.transaction_cancel()
2415 self
.samdb
.transaction_commit()
2424 ################################################################
2425 # check special @ROOTDSE attributes
2426 def check_rootdse(self
):
2427 '''check the @ROOTDSE special object'''
2428 dn
= ldb
.Dn(self
.samdb
, '@ROOTDSE')
2430 self
.report("Checking object %s" % dn
)
2431 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
)
2433 self
.report("Object %s disappeared during check" % dn
)
2438 # check that the dsServiceName is in GUID form
2439 if not 'dsServiceName' in obj
:
2440 self
.report('ERROR: dsServiceName missing in @ROOTDSE')
2441 return error_count
+1
2443 if not obj
['dsServiceName'][0].startswith('<GUID='):
2444 self
.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2446 if not self
.confirm('Change dsServiceName to GUID form?'):
2448 res
= self
.samdb
.search(base
=ldb
.Dn(self
.samdb
, obj
['dsServiceName'][0]),
2449 scope
=ldb
.SCOPE_BASE
, attrs
=['objectGUID'])
2450 guid_str
= str(ndr_unpack(misc
.GUID
, res
[0]['objectGUID'][0]))
2453 m
['dsServiceName'] = ldb
.MessageElement("<GUID=%s>" % guid_str
,
2454 ldb
.FLAG_MOD_REPLACE
, 'dsServiceName')
2455 if self
.do_modify(m
, [], "Failed to change dsServiceName to GUID form", validate
=False):
2456 self
.report("Changed dsServiceName to GUID form")
2460 ###############################################
2461 # re-index the database
2462 def reindex_database(self
):
2463 '''re-index the whole database'''
2465 m
.dn
= ldb
.Dn(self
.samdb
, "@ATTRIBUTES")
2466 m
['add'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_ADD
, 'force_reindex')
2467 m
['delete'] = ldb
.MessageElement('NONE', ldb
.FLAG_MOD_DELETE
, 'force_reindex')
2468 return self
.do_modify(m
, [], 're-indexed database', validate
=False)
2470 ###############################################
2472 def reset_modules(self
):
2473 '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2475 m
.dn
= ldb
.Dn(self
.samdb
, "@MODULES")
2476 m
['@LIST'] = ldb
.MessageElement('samba_dsdb', ldb
.FLAG_MOD_REPLACE
, '@LIST')
2477 return self
.do_modify(m
, [], 'reset @MODULES on database', validate
=False)