e52cd7b067d500ab2155457ca4e6ab0990ceadd8
[metze/samba-autobuild-v4-18-test/.git] / python / samba / dbchecker.py
1 # Samba4 AD database checker
2 #
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
5 #
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.
10 #
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.
15 #
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/>.
18 #
19
20 import ldb
21 import samba
22 import time
23 from base64 import b64decode, b64encode
24 from samba import dsdb
25 from samba import common
26 from samba.dcerpc import misc
27 from samba.dcerpc import drsuapi
28 from samba.ndr import ndr_unpack, ndr_pack
29 from samba.dcerpc import drsblobs
30 from samba.samdb import dsdb_Dn
31 from samba.dcerpc import security
32 from samba.descriptor import (
33         get_wellknown_sds,
34         get_deletedobjects_descriptor,
35         get_diff_sds
36 )
37 from samba.auth import system_session, admin_session
38 from samba.netcmd import CommandError
39 from samba.netcmd.fsmo import get_fsmo_roleowner
40 from samba.colour import c_RED, c_DARK_YELLOW, c_DARK_CYAN, c_DARK_GREEN
41
42 def dump_attr_values(vals):
43     """Stringify a value list, using utf-8 if possible (which some tests
44     want), or the python bytes representation otherwise (with leading
45     'b' and escapes like b'\x00').
46     """
47     result = []
48     for value in vals:
49         try:
50             result.append(value.decode('utf-8'))
51         except UnicodeDecodeError:
52             result.append(repr(value))
53     return ','.join(result)
54
55
56 class dbcheck(object):
57     """check a SAM database for errors"""
58
59     def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
60                  yes=False, quiet=False, in_transaction=False,
61                  quick_membership_checks=False,
62                  reset_well_known_acls=False,
63                  check_expired_tombstones=False,
64                  colour=False):
65         self.samdb = samdb
66         self.dict_oid_name = None
67         self.samdb_schema = (samdb_schema or samdb)
68         self.verbose = verbose
69         self.fix = fix
70         self.yes = yes
71         self.quiet = quiet
72         self.colour = colour
73         self.remove_all_unknown_attributes = False
74         self.remove_all_empty_attributes = False
75         self.fix_all_normalisation = False
76         self.fix_all_duplicates = False
77         self.fix_all_DN_GUIDs = False
78         self.fix_all_binary_dn = False
79         self.remove_implausible_deleted_DN_links = False
80         self.remove_plausible_deleted_DN_links = False
81         self.fix_all_string_dn_component_mismatch = False
82         self.fix_all_GUID_dn_component_mismatch = False
83         self.fix_all_SID_dn_component_mismatch = False
84         self.fix_all_SID_dn_component_missing = False
85         self.fix_all_old_dn_string_component_mismatch = False
86         self.fix_all_metadata = False
87         self.fix_time_metadata = False
88         self.fix_undead_linked_attributes = False
89         self.fix_all_missing_backlinks = False
90         self.fix_all_orphaned_backlinks = False
91         self.fix_all_missing_forward_links = False
92         self.duplicate_link_cache = dict()
93         self.recover_all_forward_links = False
94         self.fix_rmd_flags = False
95         self.fix_ntsecuritydescriptor = False
96         self.fix_ntsecuritydescriptor_owner_group = False
97         self.seize_fsmo_role = False
98         self.move_to_lost_and_found = False
99         self.fix_instancetype = False
100         self.fix_replmetadata_zero_invocationid = False
101         self.fix_replmetadata_duplicate_attid = False
102         self.fix_replmetadata_wrong_attid = False
103         self.fix_replmetadata_unsorted_attid = False
104         self.fix_deleted_deleted_objects = False
105         self.fix_dn = False
106         self.fix_base64_userparameters = False
107         self.fix_utf8_userparameters = False
108         self.fix_doubled_userparameters = False
109         self.fix_sid_rid_set_conflict = False
110         self.quick_membership_checks = quick_membership_checks
111         self.reset_well_known_acls = reset_well_known_acls
112         self.check_expired_tombstones = check_expired_tombstones
113         self.expired_tombstones = 0
114         self.reset_all_well_known_acls = False
115         self.in_transaction = in_transaction
116         self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
117         self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
118         self.schema_dn = samdb.get_schema_basedn()
119         self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
120         self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
121         self.class_schemaIDGUID = {}
122         self.wellknown_sds = get_wellknown_sds(self.samdb)
123         self.fix_all_missing_objectclass = False
124         self.fix_missing_deleted_objects = False
125         self.fix_replica_locations = False
126         self.fix_missing_rid_set_master = False
127         self.fix_changes_after_deletion_bug = False
128
129         self.dn_set = set()
130         self.link_id_cache = {}
131         self.name_map = {}
132         try:
133             base_dn = "CN=DnsAdmins,%s" % samdb.get_wellknown_dn(
134                                                 samdb.get_default_basedn(),
135                                                 dsdb.DS_GUID_USERS_CONTAINER)
136             res = samdb.search(base=base_dn, scope=ldb.SCOPE_BASE,
137                                attrs=["objectSid"])
138             dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
139             self.name_map['DnsAdmins'] = str(dnsadmins_sid)
140         except ldb.LdbError as e5:
141             (enum, estr) = e5.args
142             if enum != ldb.ERR_NO_SUCH_OBJECT:
143                 raise
144
145         self.system_session_info = system_session()
146         self.admin_session_info = admin_session(None, samdb.get_domain_sid())
147
148         res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
149         if "msDS-hasMasterNCs" in res[0]:
150             self.write_ncs = res[0]["msDS-hasMasterNCs"]
151         else:
152             # If the Forest Level is less than 2003 then there is no
153             # msDS-hasMasterNCs, so we fall back to hasMasterNCs
154             # no need to merge as all the NCs that are in hasMasterNCs must
155             # also be in msDS-hasMasterNCs (but not the opposite)
156             if "hasMasterNCs" in res[0]:
157                 self.write_ncs = res[0]["hasMasterNCs"]
158             else:
159                 self.write_ncs = None
160
161         res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
162         self.deleted_objects_containers = []
163         self.ncs_lacking_deleted_containers = []
164         self.dns_partitions = []
165         try:
166             self.ncs = res[0]["namingContexts"]
167         except KeyError:
168             pass
169         except IndexError:
170             pass
171
172         for nc in self.ncs:
173             try:
174                 dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc.decode('utf8')),
175                                                  dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
176                 self.deleted_objects_containers.append(dn)
177             except KeyError:
178                 self.ncs_lacking_deleted_containers.append(ldb.Dn(self.samdb, nc.decode('utf8')))
179
180         domaindns_zone = 'DC=DomainDnsZones,%s' % self.samdb.get_default_basedn()
181         forestdns_zone = 'DC=ForestDnsZones,%s' % self.samdb.get_root_basedn()
182         domain = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
183                                    attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
184                                    base=self.samdb.get_partitions_dn(),
185                                    expression="(&(objectClass=crossRef)(ncName=%s))" % domaindns_zone)
186         if len(domain) == 1:
187             self.dns_partitions.append((ldb.Dn(self.samdb, forestdns_zone), domain[0]))
188
189         forest = self.samdb.search(scope=ldb.SCOPE_ONELEVEL,
190                                    attrs=["msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"],
191                                    base=self.samdb.get_partitions_dn(),
192                                    expression="(&(objectClass=crossRef)(ncName=%s))" % forestdns_zone)
193         if len(forest) == 1:
194             self.dns_partitions.append((ldb.Dn(self.samdb, domaindns_zone), forest[0]))
195
196         fsmo_dn = ldb.Dn(self.samdb, "CN=RID Manager$,CN=System," + self.samdb.domain_dn())
197         rid_master = get_fsmo_roleowner(self.samdb, fsmo_dn, "rid")
198         if ldb.Dn(self.samdb, self.samdb.get_dsServiceName()) == rid_master:
199             self.is_rid_master = True
200         else:
201             self.is_rid_master = False
202
203         # To get your rid set
204         # 1. Get server name
205         res = self.samdb.search(base=ldb.Dn(self.samdb, self.samdb.get_serverName()),
206                                 scope=ldb.SCOPE_BASE, attrs=["serverReference"])
207         # 2. Get server reference
208         self.server_ref_dn = ldb.Dn(self.samdb, res[0]['serverReference'][0].decode('utf8'))
209
210         # 3. Get RID Set
211         res = self.samdb.search(base=self.server_ref_dn,
212                                 scope=ldb.SCOPE_BASE, attrs=['rIDSetReferences'])
213         if "rIDSetReferences" in res[0]:
214             self.rid_set_dn = ldb.Dn(self.samdb, res[0]['rIDSetReferences'][0].decode('utf8'))
215         else:
216             self.rid_set_dn = None
217
218         ntds_service_dn = "CN=Directory Service,CN=Windows NT,CN=Services,%s" % \
219                           self.samdb.get_config_basedn().get_linearized()
220         res = samdb.search(base=ntds_service_dn,
221                            scope=ldb.SCOPE_BASE,
222                            expression="(objectClass=nTDSService)",
223                            attrs=["tombstoneLifetime"])
224         if "tombstoneLifetime" in res[0]:
225             self.tombstoneLifetime = int(res[0]["tombstoneLifetime"][0])
226         else:
227             self.tombstoneLifetime = 180
228
229         self.compatibleFeatures = []
230         self.requiredFeatures = []
231
232         try:
233             res = self.samdb.search(scope=ldb.SCOPE_BASE,
234                                     base="@SAMBA_DSDB",
235                                     attrs=["compatibleFeatures",
236                                            "requiredFeatures"])
237             if "compatibleFeatures" in res[0]:
238                 self.compatibleFeatures = res[0]["compatibleFeatures"]
239             if "requiredFeatures" in res[0]:
240                 self.requiredFeatures = res[0]["requiredFeatures"]
241         except ldb.LdbError as e6:
242             (enum, estr) = e6.args
243             if enum != ldb.ERR_NO_SUCH_OBJECT:
244                 raise
245
246     def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=None,
247                        attrs=None):
248         '''perform a database check, returning the number of errors found'''
249         res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
250         self.report('Checking %u objects' % len(res))
251         error_count = 0
252         self.unfixable_errors = 0
253
254         error_count += self.check_deleted_objects_containers()
255
256         self.attribute_or_class_ids = set()
257
258         for object in res:
259             self.dn_set.add(str(object.dn))
260             error_count += self.check_object(object.dn, requested_attrs=attrs)
261
262         if DN is None:
263             error_count += self.check_rootdse()
264
265         if self.expired_tombstones > 0:
266             self.report("NOTICE: found %d expired tombstones, "
267                         "'samba' will remove them daily, "
268                         "'samba-tool domain tombstones expunge' "
269                         "would do that immediately." % (
270                         self.expired_tombstones))
271
272         self.report('Checked %u objects (%u errors)' %
273                     (len(res), error_count + self.unfixable_errors))
274
275         if self.unfixable_errors != 0:
276             self.report(f"WARNING: {self.unfixable_errors} "
277                         "of these errors cannot be automatically fixed.")
278
279         if error_count != 0 and not self.fix:
280             self.report("Please use 'samba-tool dbcheck --fix' to fix "
281                         f"{error_count} errors")
282
283         return error_count
284
285     def check_deleted_objects_containers(self):
286         """This function only fixes conflicts on the Deleted Objects
287         containers, not the attributes"""
288         error_count = 0
289         for nc in self.ncs_lacking_deleted_containers:
290             if nc == self.schema_dn:
291                 continue
292             error_count += 1
293             self.report("ERROR: NC %s lacks a reference to a Deleted Objects container" % nc)
294             if not self.confirm_all('Fix missing Deleted Objects container for %s?' % (nc), 'fix_missing_deleted_objects'):
295                 continue
296
297             dn = ldb.Dn(self.samdb, "CN=Deleted Objects")
298             dn.add_base(nc)
299
300             conflict_dn = None
301             try:
302                 # If something already exists here, add a conflict
303                 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[],
304                                         controls=["show_deleted:1", "extended_dn:1:1",
305                                                   "show_recycled:1", "reveal_internals:0"])
306                 if len(res) != 0:
307                     guid = res[0].dn.get_extended_component("GUID")
308                     conflict_dn = ldb.Dn(self.samdb,
309                                          "CN=Deleted Objects\\0ACNF:%s" % str(misc.GUID(guid)))
310                     conflict_dn.add_base(nc)
311
312             except ldb.LdbError as e2:
313                 (enum, estr) = e2.args
314                 if enum == ldb.ERR_NO_SUCH_OBJECT:
315                     pass
316                 else:
317                     self.report("Couldn't check for conflicting Deleted Objects container: %s" % estr)
318                     return 1
319
320             if conflict_dn is not None:
321                 try:
322                     self.samdb.rename(dn, conflict_dn, ["show_deleted:1", "relax:0", "show_recycled:1"])
323                 except ldb.LdbError as e1:
324                     (enum, estr) = e1.args
325                     self.report("Couldn't move old Deleted Objects placeholder: %s to %s: %s" % (dn, conflict_dn, estr))
326                     return 1
327
328             # Refresh wellKnownObjects links
329             res = self.samdb.search(base=nc, scope=ldb.SCOPE_BASE,
330                                     attrs=['wellKnownObjects'],
331                                     controls=["show_deleted:1", "extended_dn:0",
332                                               "show_recycled:1", "reveal_internals:0"])
333             if len(res) != 1:
334                 self.report("wellKnownObjects was not found for NC %s" % nc)
335                 return 1
336
337             # Prevent duplicate deleted objects containers just in case
338             wko = res[0]["wellKnownObjects"]
339             listwko = []
340             proposed_objectguid = None
341             for o in wko:
342                 dsdb_dn = dsdb_Dn(self.samdb, o.decode('utf8'), dsdb.DSDB_SYNTAX_BINARY_DN)
343                 if self.is_deleted_objects_dn(dsdb_dn):
344                     self.report("wellKnownObjects had duplicate Deleted Objects value %s" % o)
345                     # We really want to put this back in the same spot
346                     # as the original one, so that on replication we
347                     # merge, rather than conflict.
348                     proposed_objectguid = dsdb_dn.dn.get_extended_component("GUID")
349                 listwko.append(str(o))
350
351             if proposed_objectguid is not None:
352                 guid_suffix = "\nobjectGUID: %s" % str(misc.GUID(proposed_objectguid))
353             else:
354                 wko_prefix = "B:32:%s" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
355                 listwko.append('%s:%s' % (wko_prefix, dn))
356                 guid_suffix = ""
357
358
359             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
360             sec_desc = get_deletedobjects_descriptor(domain_sid,
361                                                      name_map=self.name_map)
362             sec_desc_b64 = b64encode(sec_desc).decode('utf8')
363
364             # Insert a brand new Deleted Objects container
365             self.samdb.add_ldif("""dn: %s
366 objectClass: top
367 objectClass: container
368 description: Container for deleted objects
369 isDeleted: TRUE
370 isCriticalSystemObject: TRUE
371 showInAdvancedViewOnly: TRUE
372 nTSecurityDescriptor:: %s
373 systemFlags: -1946157056%s""" % (dn, sec_desc_b64, guid_suffix),
374                                 controls=["relax:0", "provision:0"])
375
376             delta = ldb.Message()
377             delta.dn = ldb.Dn(self.samdb, str(res[0]["dn"]))
378             delta["wellKnownObjects"] = ldb.MessageElement(listwko,
379                                                            ldb.FLAG_MOD_REPLACE,
380                                                            "wellKnownObjects")
381
382             # Insert the link to the brand new container
383             if self.do_modify(delta, ["relax:0"],
384                               "NC %s lacks Deleted Objects WKGUID" % nc,
385                               validate=False):
386                 self.report("Added %s well known guid link" % dn)
387
388             self.deleted_objects_containers.append(dn)
389
390         return error_count
391
392     def report(self, msg):
393         '''print a message unless quiet is set'''
394         if self.quiet:
395             return
396         if self.colour:
397             if msg.startswith('ERROR'):
398                 msg = c_RED('ERROR') + msg[5:]
399             elif msg.startswith('WARNING'):
400                 msg = c_DARK_YELLOW('WARNING') + msg[7:]
401             elif msg.startswith('INFO'):
402                 msg = c_DARK_CYAN('INFO') + msg[4:]
403             elif msg.startswith('NOTICE'):
404                 msg = c_DARK_CYAN('NOTICE') + msg[6:]
405             elif msg.startswith('NOTE'):
406                 msg = c_DARK_CYAN('NOTE') + msg[4:]
407             elif msg.startswith('SKIPPING'):
408                 msg = c_DARK_GREEN('SKIPPING') + msg[8:]
409
410         print(msg)
411
412     def confirm(self, msg, allow_all=False, forced=False):
413         '''confirm a change'''
414         if not self.fix:
415             return False
416         if self.quiet:
417             return self.yes
418         if self.yes:
419             forced = True
420         return common.confirm(msg, forced=forced, allow_all=allow_all)
421
422     ################################################################
423     # a local confirm function with support for 'all'
424     def confirm_all(self, msg, all_attr):
425         '''confirm a change with support for "all" '''
426         if not self.fix:
427             return False
428         if getattr(self, all_attr) == 'NONE':
429             return False
430         if getattr(self, all_attr) == 'ALL':
431             forced = True
432         else:
433             forced = self.yes
434         if self.quiet:
435             return forced
436         c = common.confirm(msg, forced=forced, allow_all=True)
437         if c == 'ALL':
438             setattr(self, all_attr, 'ALL')
439             return True
440         if c == 'NONE':
441             setattr(self, all_attr, 'NONE')
442             return False
443         return c
444
445     def do_delete(self, dn, controls, msg):
446         '''delete dn with optional verbose output'''
447         if self.verbose:
448             self.report("delete DN %s" % dn)
449         try:
450             controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
451             self.samdb.delete(dn, controls=controls)
452         except Exception as err:
453             if self.in_transaction:
454                 raise CommandError("%s : %s" % (msg, err))
455             self.report("%s : %s" % (msg, err))
456             return False
457         return True
458
459     def do_modify(self, m, controls, msg, validate=True):
460         '''perform a modify with optional verbose output'''
461         controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
462         if self.verbose:
463             self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
464             self.report("controls: %r" % controls)
465         try:
466             self.samdb.modify(m, controls=controls, validate=validate)
467         except Exception as err:
468             if self.in_transaction:
469                 raise CommandError("%s : %s" % (msg, err))
470             self.report("%s : %s" % (msg, err))
471             return False
472         return True
473
474     def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
475         '''perform a rename with optional verbose output'''
476         if self.verbose:
477             self.report("""dn: %s
478 changeType: modrdn
479 newrdn: %s
480 deleteOldRdn: 1
481 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
482         try:
483             to_dn = to_rdn + to_base
484             controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
485             self.samdb.rename(from_dn, to_dn, controls=controls)
486         except Exception as err:
487             if self.in_transaction:
488                 raise CommandError("%s : %s" % (msg, err))
489             self.report("%s : %s" % (msg, err))
490             return False
491         return True
492
493     def get_attr_linkID_and_reverse_name(self, attrname):
494         if attrname in self.link_id_cache:
495             return self.link_id_cache[attrname]
496         linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
497         if linkID:
498             revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
499         else:
500             revname = None
501         self.link_id_cache[attrname] = (linkID, revname)
502         return linkID, revname
503
504     def err_empty_attribute(self, dn, attrname):
505         '''fix empty attributes'''
506         self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
507         if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
508             self.report("Not fixing empty attribute %s" % attrname)
509             return
510
511         m = ldb.Message()
512         m.dn = dn
513         m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
514         if self.do_modify(m, ["relax:0", "show_recycled:1"],
515                           "Failed to remove empty attribute %s" % attrname, validate=False):
516             self.report("Removed empty attribute %s" % attrname)
517
518     def err_normalise_mismatch(self, dn, attrname, values):
519         '''fix attribute normalisation errors, without altering sort order'''
520         self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
521         mod_list = []
522         for val in values:
523             normalised = self.samdb.dsdb_normalise_attributes(
524                 self.samdb_schema, attrname, [val])
525             if len(normalised) != 1:
526                 self.report("Unable to normalise value '%s'" % val)
527                 mod_list.append((val, ''))
528             elif (normalised[0] != val):
529                 self.report("value '%s' should be '%s'" % (val, normalised[0]))
530                 mod_list.append((val, normalised[0]))
531         if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
532             self.report("Not fixing attribute %s" % attrname)
533             return
534
535         m = ldb.Message()
536         m.dn = dn
537         for i in range(0, len(mod_list)):
538             (val, nval) = mod_list[i]
539             m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
540             if nval != '':
541                 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
542                                                        attrname)
543
544         if self.do_modify(m, ["relax:0", "show_recycled:1"],
545                           "Failed to normalise attribute %s" % attrname,
546                           validate=False):
547             self.report("Normalised attribute %s" % attrname)
548
549     def err_normalise_mismatch_replace(self, dn, attrname, values):
550         '''fix attribute normalisation and/or sort errors'''
551         normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
552         if list(normalised) == values:
553             # how we got here is a mystery.
554             return
555         self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
556         self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
557         if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
558             self.report("Not fixing attribute '%s'" % attrname)
559             return
560
561         m = ldb.Message()
562         m.dn = dn
563         m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
564
565         if self.do_modify(m, ["relax:0", "show_recycled:1"],
566                           "Failed to normalise attribute %s" % attrname,
567                           validate=False):
568             self.report("Normalised attribute %s" % attrname)
569
570     def err_duplicate_values(self, dn, attrname, dup_values, values):
571         '''fix duplicate attribute values'''
572         self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
573         self.report("Values contain a duplicate: [%s]/[%s]!" %
574                     (dump_attr_values(dup_values), dump_attr_values(values)))
575         if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
576             self.report("Not fixing attribute '%s'" % attrname)
577             return
578
579         m = ldb.Message()
580         m.dn = dn
581         m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
582
583         if self.do_modify(m, ["relax:0", "show_recycled:1"],
584                           "Failed to remove duplicate value on attribute %s" % attrname,
585                           validate=False):
586             self.report("Removed duplicate value on attribute %s" % attrname)
587
588     def is_deleted_objects_dn(self, dsdb_dn):
589         '''see if a dsdb_Dn is the special Deleted Objects DN'''
590         return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
591
592     def err_missing_objectclass(self, dn):
593         """handle object without objectclass"""
594         self.report("ERROR: missing objectclass in object %s.  If you have another working DC, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (dn, self.samdb.get_nc_root(dn)))
595         if not self.confirm_all("If you cannot re-sync from another DC, do you wish to delete object '%s'?" % dn, 'fix_all_missing_objectclass'):
596             self.report("Not deleting object with missing objectclass '%s'" % dn)
597             return
598         if self.do_delete(dn, ["relax:0"],
599                           "Failed to remove DN %s" % dn):
600             self.report("Removed DN %s" % dn)
601
602     def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn, remove_plausible=False):
603         """handle a DN pointing to a deleted object"""
604         if not remove_plausible:
605             self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
606             self.report("Target GUID points at deleted DN %r" % str(correct_dn))
607             if not self.confirm_all('Remove DN link?', 'remove_implausible_deleted_DN_links'):
608                 self.report("Not removing")
609                 return
610         else:
611             self.report("WARNING: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
612             self.report("Target GUID points at deleted DN %r" % str(correct_dn))
613             if not self.confirm_all('Remove stale DN link?', 'remove_plausible_deleted_DN_links'):
614                 self.report("Not removing")
615                 return
616
617         m = ldb.Message()
618         m.dn = dn
619         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
620         if self.do_modify(m, ["show_recycled:1",
621                               "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
622                           "Failed to remove deleted DN attribute %s" % attrname):
623             self.report("Removed deleted DN on attribute %s" % attrname)
624
625     def err_missing_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
626         """handle a missing target DN (if specified, GUID form can't be found,
627         and otherwise DN string form can't be found)"""
628
629         # Don't change anything if the object itself is deleted
630         if str(dn).find('\\0ADEL') != -1:
631             # We don't bump the error count as Samba produces these
632             # in normal operation
633             self.report("WARNING: no target object found for GUID "
634                         "component link %s in deleted object "
635                         "%s - %s" % (attrname, dn, val))
636             self.report("Not removing dangling one-way "
637                         "link on deleted object "
638                         "(tombstone garbage collection in progress?)")
639             return 0
640
641         # check if its a backlink
642         linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
643         if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
644
645             linkID, reverse_link_name \
646                 = self.get_attr_linkID_and_reverse_name(attrname)
647             if reverse_link_name is not None:
648                 self.report("WARNING: no target object found for GUID "
649                             "component for one-way forward link "
650                             "%s in object "
651                             "%s - %s" % (attrname, dn, val))
652                 self.report("Not removing dangling forward link")
653                 return 0
654
655             nc_root = self.samdb.get_nc_root(dn)
656             try:
657                 target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn)
658             except ldb.LdbError as e:
659                 (enum, estr) = e.args
660                 if enum != ldb.ERR_NO_SUCH_OBJECT:
661                     raise
662                 target_nc_root = None
663
664             if target_nc_root is None:
665                 # We don't bump the error count as Samba produces
666                 # these in normal operation creating a lab domain (due
667                 # to the way the rename is handled, links to
668                 # now-expunged objects will never be fixed to stay
669                 # inside the NC
670                 self.report("WARNING: no target object found for GUID "
671                             "component for link "
672                             "%s in object to %s outside our NCs"
673                             "%s - %s" % (attrname, dsdb_dn.dn, dn, val))
674                 self.report("Not removing dangling one-way "
675                             "left-over link outside our NCs "
676                             "(we might be building a renamed/lab domain)")
677                 return 0
678
679             if nc_root != target_nc_root:
680                 # We don't bump the error count as Samba produces these
681                 # in normal operation
682                 self.report("WARNING: no target object found for GUID "
683                             "component for cross-partition link "
684                             "%s in object "
685                             "%s - %s" % (attrname, dn, val))
686                 self.report("Not removing dangling one-way "
687                             "cross-partition link "
688                             "(we might be mid-replication)")
689                 return 0
690
691             # Due to our link handling one-way links pointing to
692             # missing objects are plausible.
693             #
694             # We don't bump the error count as Samba produces these
695             # in normal operation
696             self.report("WARNING: no target object found for GUID "
697                         "component for DN value %s in object "
698                         "%s - %s" % (attrname, dn, val))
699             self.err_deleted_dn(dn, attrname, val,
700                                 dsdb_dn, dsdb_dn, True)
701             return 0
702
703         # We bump the error count here, as we should have deleted this
704         self.report("ERROR: no target object found for GUID "
705                     "component for link %s in object "
706                     "%s - %s" % (attrname, dn, val))
707         self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
708         return 1
709
710     def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
711         """handle a missing GUID extended DN component"""
712         self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
713         controls = ["extended_dn:1:1", "show_recycled:1"]
714         try:
715             res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
716                                     attrs=[], controls=controls)
717         except ldb.LdbError as e7:
718             (enum, estr) = e7.args
719             self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
720             if enum != ldb.ERR_NO_SUCH_OBJECT:
721                 raise
722             self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
723             return
724         if len(res) == 0:
725             self.report("unable to find object for DN %s" % dsdb_dn.dn)
726             self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
727             return
728         dsdb_dn.dn = res[0].dn
729
730         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
731             self.report("Not fixing %s" % errstr)
732             return
733         m = ldb.Message()
734         m.dn = dn
735         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
736         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
737
738         if self.do_modify(m, ["show_recycled:1"],
739                           "Failed to fix %s on attribute %s" % (errstr, attrname)):
740             self.report("Fixed %s on attribute %s" % (errstr, attrname))
741
742     def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
743         """handle an incorrect binary DN component"""
744         self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
745
746         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
747             self.report("Not fixing %s" % errstr)
748             return
749         m = ldb.Message()
750         m.dn = dn
751         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
752         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
753
754         if self.do_modify(m, ["show_recycled:1"],
755                           "Failed to fix %s on attribute %s" % (errstr, attrname)):
756             self.report("Fixed %s on attribute %s" % (errstr, attrname))
757
758     def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn):
759         """handle a DN string being incorrect due to a rename or delete"""
760         self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val))
761         dsdb_dn.dn = correct_dn
762
763         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
764                                 'fix_all_old_dn_string_component_mismatch'):
765             self.report("Not fixing old string component")
766             return
767         m = ldb.Message()
768         m.dn = dn
769         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
770         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
771         if self.do_modify(m, ["show_recycled:1",
772                               "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME],
773                           "Failed to fix old DN string on attribute %s" % (attrname)):
774             self.report("Fixed old DN string on attribute %s" % (attrname))
775
776     def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
777         """handle a DN string being incorrect"""
778         self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
779         dsdb_dn.dn = correct_dn
780
781         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
782                                 'fix_all_%s_dn_component_mismatch' % mismatch_type):
783             self.report("Not fixing %s component mismatch" % mismatch_type)
784             return
785         m = ldb.Message()
786         m.dn = dn
787         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
788         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
789         if self.do_modify(m, ["show_recycled:1"],
790                           "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
791             self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
792
793     def err_dn_component_missing_target_sid(self, dn, attrname, val, dsdb_dn, target_sid_blob):
794         """fix missing <SID=...> on linked attributes"""
795         self.report("ERROR: missing DN SID component for %s in object %s - %s" % (attrname, dn, val))
796
797         if len(dsdb_dn.prefix) != 0:
798             self.report("Not fixing missing DN SID on DN+BINARY or DN+STRING")
799             return
800
801         correct_dn = ldb.Dn(self.samdb, dsdb_dn.dn.extended_str())
802         correct_dn.set_extended_component("SID", target_sid_blob)
803
804         if not self.confirm_all('Change DN to %s?' % correct_dn.extended_str(),
805                                 'fix_all_SID_dn_component_missing'):
806             self.report("Not fixing missing DN SID component")
807             return
808
809         target_guid_blob = correct_dn.get_extended_component("GUID")
810         guid_sid_dn = ldb.Dn(self.samdb, "")
811         guid_sid_dn.set_extended_component("GUID", target_guid_blob)
812         guid_sid_dn.set_extended_component("SID", target_sid_blob)
813
814         m = ldb.Message()
815         m.dn = dn
816         m['new_value'] = ldb.MessageElement(guid_sid_dn.extended_str(), ldb.FLAG_MOD_ADD, attrname)
817         controls = [
818             "show_recycled:1",
819             "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID
820         ]
821         if self.do_modify(m, controls,
822                           "Failed to ADD missing DN SID on attribute %s" % (attrname)):
823             self.report("Fixed missing DN SID on attribute %s" % (attrname))
824
825     def err_unknown_attribute(self, obj, attrname):
826         '''handle an unknown attribute error'''
827         self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
828         if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
829             self.report("Not removing %s" % attrname)
830             return
831         m = ldb.Message()
832         m.dn = obj.dn
833         m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
834         if self.do_modify(m, ["relax:0", "show_recycled:1"],
835                           "Failed to remove unknown attribute %s" % attrname):
836             self.report("Removed unknown attribute %s" % (attrname))
837
838     def err_undead_linked_attribute(self, obj, attrname, val):
839         '''handle a link that should not be there on a deleted object'''
840         self.report("ERROR: linked attribute '%s' to '%s' is present on "
841                     "deleted object %s" % (attrname, val, obj.dn))
842         if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
843             self.report("Not removing linked attribute %s" % attrname)
844             return
845         m = ldb.Message()
846         m.dn = obj.dn
847         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
848
849         if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
850                               "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
851                           "Failed to delete forward link %s" % attrname):
852             self.report("Fixed undead forward link %s" % (attrname))
853
854     def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
855         '''handle a missing backlink value'''
856         self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
857         if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
858             self.report("Not fixing missing backlink %s" % backlink_name)
859             return
860         m = ldb.Message()
861         m.dn = target_dn
862         m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
863         if self.do_modify(m, ["show_recycled:1", "relax:0"],
864                           "Failed to fix missing backlink %s" % backlink_name):
865             self.report("Fixed missing backlink %s" % (backlink_name))
866
867     def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
868         '''handle a incorrect RMD_FLAGS value'''
869         rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
870         self.report("ERROR: incorrect RMD_FLAGS value %u for attribute '%s' in %s for link %s" % (rmd_flags, attrname, obj.dn, revealed_dn.dn.extended_str()))
871         if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
872             self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
873             return
874         m = ldb.Message()
875         m.dn = obj.dn
876         m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
877         if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
878                           "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
879             self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
880
881     def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val,
882                               target_dn, forward_attr, forward_syntax,
883                               check_duplicates=True):
884         '''handle a orphaned backlink value'''
885         if check_duplicates is True and self.has_duplicate_links(target_dn, forward_attr, forward_syntax):
886             self.report("WARNING: Keep orphaned backlink attribute " +
887                         "'%s' in '%s' for link '%s' in '%s'" % (
888                             backlink_attr, obj_dn, forward_attr, target_dn))
889             return
890         self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn))
891         if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'):
892             self.report("Not removing orphaned backlink %s" % backlink_attr)
893             return
894         m = ldb.Message()
895         m.dn = obj_dn
896         m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr)
897         if self.do_modify(m, ["show_recycled:1", "relax:0"],
898                           "Failed to fix orphaned backlink %s" % backlink_attr):
899             self.report("Fixed orphaned backlink %s" % (backlink_attr))
900
901     def err_recover_forward_links(self, obj, forward_attr, forward_vals):
902         '''handle a duplicate links value'''
903
904         self.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn))
905
906         if not self.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'):
907             self.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
908                         forward_attr, obj.dn))
909             return
910         m = ldb.Message()
911         m.dn = obj.dn
912         m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr)
913         if self.do_modify(m, ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS],
914                           "Failed to fix duplicate links in attribute '%s'" % forward_attr):
915             self.report("Fixed duplicate links in attribute '%s'" % (forward_attr))
916             duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
917             assert duplicate_cache_key in self.duplicate_link_cache
918             self.duplicate_link_cache[duplicate_cache_key] = False
919
920     def err_no_fsmoRoleOwner(self, obj):
921         '''handle a missing fSMORoleOwner'''
922         self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
923         res = self.samdb.search("",
924                                 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
925         assert len(res) == 1
926         serviceName = str(res[0]["dsServiceName"][0])
927         if not self.confirm_all('Seize role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
928             self.report("Not Seizing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
929             return
930         m = ldb.Message()
931         m.dn = obj.dn
932         m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
933         if self.do_modify(m, [],
934                           "Failed to seize role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
935             self.report("Seized role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
936
937     def err_missing_parent(self, obj):
938         '''handle a missing parent'''
939         self.report("ERROR: parent object not found for %s" % (obj.dn))
940         if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
941             self.report('Not moving object %s into LostAndFound' % (obj.dn))
942             return
943
944         keep_transaction = False
945         self.samdb.transaction_start()
946         try:
947             nc_root = self.samdb.get_nc_root(obj.dn)
948             lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
949             new_dn = ldb.Dn(self.samdb, str(obj.dn))
950             new_dn.remove_base_components(len(new_dn) - 1)
951             if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
952                               "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
953                 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
954
955                 m = ldb.Message()
956                 m.dn = obj.dn
957                 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
958
959                 if self.do_modify(m, [],
960                                   "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
961                     self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
962                     keep_transaction = True
963         except:
964             self.samdb.transaction_cancel()
965             raise
966
967         if keep_transaction:
968             self.samdb.transaction_commit()
969         else:
970             self.samdb.transaction_cancel()
971
972     def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val, controls):
973         '''handle a wrong dn'''
974
975         new_rdn = ldb.Dn(self.samdb, str(new_dn))
976         new_rdn.remove_base_components(len(new_rdn) - 1)
977         new_parent = new_dn.parent()
978
979         attributes = ""
980         if rdn_val != name_val:
981             attributes += "%s=%r " % (rdn_attr, rdn_val)
982         attributes += "name=%r" % (name_val)
983
984         self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
985         if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
986             self.report("Not renaming %s to %s" % (obj.dn, new_dn))
987             return
988
989         if self.do_rename(obj.dn, new_rdn, new_parent, controls,
990                           "Failed to rename object %s into %s" % (obj.dn, new_dn)):
991             self.report("Renamed %s into %s" % (obj.dn, new_dn))
992
993     def err_wrong_instancetype(self, obj, calculated_instancetype):
994         '''handle a wrong instanceType'''
995         self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
996         if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
997             self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
998             return
999
1000         m = ldb.Message()
1001         m.dn = obj.dn
1002         m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
1003         if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
1004                           "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
1005             self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
1006
1007     def err_short_userParameters(self, obj, attrname, value):
1008         # This is a truncated userParameters due to a pre 4.1 replication bug
1009         self.report("ERROR: incorrect userParameters value on object %s.  If you have another working DC that does not give this warning, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (obj.dn, self.samdb.get_nc_root(obj.dn)))
1010
1011     def err_base64_userParameters(self, obj, attrname, value):
1012         '''handle a userParameters that is wrongly base64 encoded'''
1013         self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
1014         if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
1015             self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
1016             return
1017
1018         m = ldb.Message()
1019         m.dn = obj.dn
1020         m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
1021         if self.do_modify(m, [],
1022                           "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
1023             self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
1024
1025     def err_utf8_userParameters(self, obj, attrname, value):
1026         '''handle a userParameters that is wrongly utf-8 encoded'''
1027         self.report("ERROR: wrongly formatted userParameters on %s, "
1028                     "should not be pseudo-UTF8 encoded" % (obj.dn))
1029         if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
1030             self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
1031             return
1032
1033         m = ldb.Message()
1034         m.dn = obj.dn
1035         m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
1036                                         ldb.FLAG_MOD_REPLACE, 'userParameters')
1037         if self.do_modify(m, [],
1038                           "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
1039             self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
1040
1041     def err_doubled_userParameters(self, obj, attrname, value):
1042         '''handle a userParameters that has been utf-16 encoded twice'''
1043         self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
1044         if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
1045             self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
1046             return
1047
1048         m = ldb.Message()
1049         m.dn = obj.dn
1050         # m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
1051         # hmm the above old python2 code doesn't make sense to me and cannot
1052         # work in python3 because a string doesn't have a decode method.
1053         # However in python2 for some unknown reason this double decode
1054         # followed by encode seems to result in what looks like utf8.
1055         # In python2 just .decode('utf-16-le').encode('utf-16-le') does nothing
1056         # but trigger the 'double UTF16 encoded' condition again :/
1057         #
1058         # In python2 and python3 value.decode('utf-16-le').encode('utf8') seems
1059         # to do the trick and work as expected.
1060         m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').encode('utf8'),
1061                                         ldb.FLAG_MOD_REPLACE, 'userParameters')
1062
1063         if self.do_modify(m, [],
1064                           "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
1065             self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
1066
1067     def err_odd_userParameters(self, obj, attrname):
1068         """Fix a truncated userParameters due to a pre 4.1 replication bug"""
1069         self.report("ERROR: incorrect userParameters value on object %s (odd length).  If you have another working DC that does not give this warning, please run 'samba-tool drs replicate --full-sync --local <destinationDC> <sourceDC> %s'" % (obj.dn, self.samdb.get_nc_root(obj.dn)))
1070
1071     def find_revealed_link(self, dn, attrname, guid):
1072         '''return a revealed link in an object'''
1073         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
1074                                 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
1075         syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1076         for val in res[0][attrname]:
1077             dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
1078             guid2 = dsdb_dn.dn.get_extended_component("GUID")
1079             if guid == guid2:
1080                 return dsdb_dn
1081         return None
1082
1083     def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr):
1084         '''check a linked values for duplicate forward links'''
1085         error_count = 0
1086
1087         duplicate_dict = dict()
1088         unique_dict = dict()
1089
1090         # Only forward links can have this problem
1091         if forward_linkID & 1:
1092             # If we got the reverse, skip it
1093             return (error_count, duplicate_dict, unique_dict)
1094
1095         if backlink_attr is None:
1096             return (error_count, duplicate_dict, unique_dict)
1097
1098         duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
1099         if duplicate_cache_key not in self.duplicate_link_cache:
1100             self.duplicate_link_cache[duplicate_cache_key] = False
1101
1102         for val in obj[forward_attr]:
1103             dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), forward_syntax)
1104
1105             # all DNs should have a GUID component
1106             guid = dsdb_dn.dn.get_extended_component("GUID")
1107             if guid is None:
1108                 continue
1109             guidstr = str(misc.GUID(guid))
1110             keystr = guidstr + dsdb_dn.prefix
1111             if keystr not in unique_dict:
1112                 unique_dict[keystr] = dsdb_dn
1113                 continue
1114             error_count += 1
1115             if keystr not in duplicate_dict:
1116                 duplicate_dict[keystr] = dict()
1117                 duplicate_dict[keystr]["keep"] = None
1118                 duplicate_dict[keystr]["delete"] = list()
1119
1120             # Now check for the highest RMD_VERSION
1121             v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION"))
1122             v2 = int(dsdb_dn.dn.get_extended_component("RMD_VERSION"))
1123             if v1 > v2:
1124                 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
1125                 duplicate_dict[keystr]["delete"].append(dsdb_dn)
1126                 continue
1127             if v1 < v2:
1128                 duplicate_dict[keystr]["keep"] = dsdb_dn
1129                 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
1130                 unique_dict[keystr] = dsdb_dn
1131                 continue
1132             # Fallback to the highest RMD_LOCAL_USN
1133             u1 = int(unique_dict[keystr].dn.get_extended_component("RMD_LOCAL_USN"))
1134             u2 = int(dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN"))
1135             if u1 >= u2:
1136                 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
1137                 duplicate_dict[keystr]["delete"].append(dsdb_dn)
1138                 continue
1139             duplicate_dict[keystr]["keep"] = dsdb_dn
1140             duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
1141             unique_dict[keystr] = dsdb_dn
1142
1143         if error_count != 0:
1144             self.duplicate_link_cache[duplicate_cache_key] = True
1145
1146         return (error_count, duplicate_dict, unique_dict)
1147
1148     def has_duplicate_links(self, dn, forward_attr, forward_syntax):
1149         '''check a linked values for duplicate forward links'''
1150         error_count = 0
1151
1152         duplicate_cache_key = "%s:%s" % (str(dn), forward_attr)
1153         if duplicate_cache_key in self.duplicate_link_cache:
1154             return self.duplicate_link_cache[duplicate_cache_key]
1155
1156         forward_linkID, backlink_attr = self.get_attr_linkID_and_reverse_name(forward_attr)
1157
1158         attrs = [forward_attr]
1159         controls = ["extended_dn:1:1", "reveal_internals:0"]
1160
1161         # check its the right GUID
1162         try:
1163             res = self.samdb.search(base=str(dn), scope=ldb.SCOPE_BASE,
1164                                     attrs=attrs, controls=controls)
1165         except ldb.LdbError as e8:
1166             (enum, estr) = e8.args
1167             if enum != ldb.ERR_NO_SUCH_OBJECT:
1168                 raise
1169
1170             return False
1171
1172         obj = res[0]
1173         error_count, duplicate_dict, unique_dict = \
1174             self.check_duplicate_links(obj, forward_attr, forward_syntax, forward_linkID, backlink_attr)
1175
1176         if duplicate_cache_key in self.duplicate_link_cache:
1177             return self.duplicate_link_cache[duplicate_cache_key]
1178
1179         return False
1180
1181     def find_missing_forward_links_from_backlinks(self, obj,
1182                                                   forward_attr,
1183                                                   forward_syntax,
1184                                                   backlink_attr,
1185                                                   forward_unique_dict):
1186         '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1187         missing_forward_links = []
1188         error_count = 0
1189
1190         if backlink_attr is None:
1191             return (missing_forward_links, error_count)
1192
1193         if forward_syntax != ldb.SYNTAX_DN:
1194             self.report("Not checking for missing forward links for syntax: %s" %
1195                         forward_syntax)
1196             return (missing_forward_links, error_count)
1197
1198         if "sortedLinks" in self.compatibleFeatures:
1199             self.report("Not checking for missing forward links because the db " +
1200                         "has the sortedLinks feature")
1201             return (missing_forward_links, error_count)
1202
1203         try:
1204             obj_guid = obj['objectGUID'][0]
1205             obj_guid_str = str(ndr_unpack(misc.GUID, obj_guid))
1206             filter = "(%s=<GUID=%s>)" % (backlink_attr, obj_guid_str)
1207
1208             res = self.samdb.search(expression=filter,
1209                                     scope=ldb.SCOPE_SUBTREE, attrs=["objectGUID"],
1210                                     controls=["extended_dn:1:1",
1211                                               "search_options:1:2",
1212                                               "paged_results:1:1000"])
1213         except ldb.LdbError as e9:
1214             (enum, estr) = e9.args
1215             raise
1216
1217         for r in res:
1218             target_dn = dsdb_Dn(self.samdb, r.dn.extended_str(), forward_syntax)
1219
1220             guid = target_dn.dn.get_extended_component("GUID")
1221             guidstr = str(misc.GUID(guid))
1222             if guidstr in forward_unique_dict:
1223                 continue
1224
1225             # A valid forward link looks like this:
1226             #
1227             #    <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1228             #    <RMD_ADDTIME=131607546230000000>;
1229             #    <RMD_CHANGETIME=131607546230000000>;
1230             #    <RMD_FLAGS=0>;
1231             #    <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1232             #    <RMD_LOCAL_USN=3765>;
1233             #    <RMD_ORIGINATING_USN=3765>;
1234             #    <RMD_VERSION=1>;
1235             #    <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1236             #    CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1237             #
1238             # Note that versions older than Samba 4.8 create
1239             # links with RMD_VERSION=0.
1240             #
1241             # Try to get the local_usn and time from objectClass
1242             # if possible and fallback to any other one.
1243             repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1244                               obj['replPropertyMetadata'][0])
1245             for o in repl.ctr.array:
1246                 local_usn = o.local_usn
1247                 t = o.originating_change_time
1248                 if o.attid == drsuapi.DRSUAPI_ATTID_objectClass:
1249                     break
1250
1251             # We use a magic invocationID for restoring missing
1252             # forward links to recover from bug #13228.
1253             # This should allow some more future magic to fix the
1254             # problem.
1255             #
1256             # It also means it looses the conflict resolution
1257             # against almost every real invocation, if the
1258             # version is also 0.
1259             originating_invocid = misc.GUID("ffffffff-4700-4700-4700-000000b13228")
1260             originating_usn = 1
1261
1262             rmd_addtime = t
1263             rmd_changetime = t
1264             rmd_flags = 0
1265             rmd_invocid = originating_invocid
1266             rmd_originating_usn = originating_usn
1267             rmd_local_usn = local_usn
1268             rmd_version = 0
1269
1270             target_dn.dn.set_extended_component("RMD_ADDTIME", str(rmd_addtime))
1271             target_dn.dn.set_extended_component("RMD_CHANGETIME", str(rmd_changetime))
1272             target_dn.dn.set_extended_component("RMD_FLAGS", str(rmd_flags))
1273             target_dn.dn.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid))
1274             target_dn.dn.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn))
1275             target_dn.dn.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn))
1276             target_dn.dn.set_extended_component("RMD_VERSION", str(rmd_version))
1277
1278             error_count += 1
1279             missing_forward_links.append(target_dn)
1280
1281         return (missing_forward_links, error_count)
1282
1283     def check_dn(self, obj, attrname, syntax_oid):
1284         '''check a DN attribute for correctness'''
1285         error_count = 0
1286         obj_guid = obj['objectGUID'][0]
1287
1288         linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1289         if reverse_link_name is not None:
1290             reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
1291         else:
1292             reverse_syntax_oid = None
1293
1294         is_member_link = attrname in ("member", "memberOf")
1295         if is_member_link and self.quick_membership_checks:
1296             duplicate_dict = {}
1297         else:
1298             error_count, duplicate_dict, unique_dict = \
1299                 self.check_duplicate_links(obj, attrname, syntax_oid,
1300                                            linkID, reverse_link_name)
1301
1302         if len(duplicate_dict) != 0:
1303
1304             missing_forward_links, missing_error_count = \
1305                 self.find_missing_forward_links_from_backlinks(obj,
1306                                                                attrname, syntax_oid,
1307                                                                reverse_link_name,
1308                                                                unique_dict)
1309             error_count += missing_error_count
1310
1311             forward_links = [dn for dn in unique_dict.values()]
1312
1313             if missing_error_count != 0:
1314                 self.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1315                             attrname, obj.dn))
1316             else:
1317                 self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
1318             for m in missing_forward_links:
1319                 self.report("Missing   link '%s'" % (m))
1320                 if not self.confirm_all("Schedule readding missing forward link for attribute %s" % attrname,
1321                                         'fix_all_missing_forward_links'):
1322                     self.err_orphaned_backlink(m.dn, reverse_link_name,
1323                                                obj.dn.extended_str(), obj.dn,
1324                                                attrname, syntax_oid,
1325                                                check_duplicates=False)
1326                     continue
1327                 forward_links += [m]
1328             for keystr in duplicate_dict.keys():
1329                 d = duplicate_dict[keystr]
1330                 for dd in d["delete"]:
1331                     self.report("Duplicate link '%s'" % dd)
1332                 self.report("Correct   link '%s'" % d["keep"])
1333
1334             # We now construct the sorted dn values.
1335             # They're sorted by the objectGUID of the target
1336             # See dsdb_Dn.__cmp__()
1337             vals = [str(dn) for dn in sorted(forward_links)]
1338             self.err_recover_forward_links(obj, attrname, vals)
1339             # We should continue with the fixed values
1340             obj[attrname] = ldb.MessageElement(vals, 0, attrname)
1341
1342         for val in obj[attrname]:
1343             dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
1344
1345             # all DNs should have a GUID component
1346             guid = dsdb_dn.dn.get_extended_component("GUID")
1347             if guid is None:
1348                 error_count += 1
1349                 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
1350                                                    "missing GUID")
1351                 continue
1352
1353             guidstr = str(misc.GUID(guid))
1354             attrs = ['isDeleted', 'replPropertyMetaData']
1355
1356             if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
1357                 fixing_msDS_HasInstantiatedNCs = True
1358                 attrs.append("instanceType")
1359             else:
1360                 fixing_msDS_HasInstantiatedNCs = False
1361
1362             if reverse_link_name is not None:
1363                 attrs.append(reverse_link_name)
1364
1365             # check its the right GUID
1366             try:
1367                 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
1368                                         attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
1369                                                                "reveal_internals:0"
1370                                                                ])
1371             except ldb.LdbError as e3:
1372                 (enum, estr) = e3.args
1373                 if enum != ldb.ERR_NO_SUCH_OBJECT:
1374                     raise
1375
1376                 # We don't always want to
1377                 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
1378                                                                   attrname,
1379                                                                   val,
1380                                                                   dsdb_dn)
1381                 continue
1382
1383             if fixing_msDS_HasInstantiatedNCs:
1384                 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
1385                 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
1386
1387                 if str(dsdb_dn) != str(val):
1388                     error_count += 1
1389                     self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
1390                     continue
1391
1392             # now we have two cases - the source object might or might not be deleted
1393             is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE'
1394             target_is_deleted = 'isDeleted' in res[0] and str(res[0]['isDeleted'][0]).upper() == 'TRUE'
1395
1396             if is_deleted and obj.dn not in self.deleted_objects_containers and linkID:
1397                 # A fully deleted object should not have any linked
1398                 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1399                 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1400                 # Requirements)
1401                 self.err_undead_linked_attribute(obj, attrname, val)
1402                 error_count += 1
1403                 continue
1404             elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
1405                 # the target DN is not allowed to be deleted, unless the target DN is the
1406                 # special Deleted Objects container
1407                 error_count += 1
1408                 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
1409                 if local_usn:
1410                     if 'replPropertyMetaData' in res[0]:
1411                         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1412                                           res[0]['replPropertyMetadata'][0])
1413                         found_data = False
1414                         for o in repl.ctr.array:
1415                             if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1416                                 deleted_usn = o.local_usn
1417                                 if deleted_usn >= int(local_usn):
1418                                     # If the object was deleted after the link
1419                                     # was last modified then, clean it up here
1420                                     found_data = True
1421                                     break
1422
1423                         if found_data:
1424                             self.err_deleted_dn(obj.dn, attrname,
1425                                                 val, dsdb_dn, res[0].dn, True)
1426                             continue
1427
1428                 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
1429                 continue
1430
1431             # We should not check for incorrect
1432             # components on deleted links, as these are allowed to
1433             # go stale (we just need the GUID, not the name)
1434             rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
1435             rmd_flags = 0
1436             if rmd_blob is not None:
1437                 rmd_flags = int(rmd_blob)
1438
1439             # assert the DN matches in string form, where a reverse
1440             # link exists, otherwise (below) offer to fix it as a non-error.
1441             # The string form is essentially only kept for forensics,
1442             # as we always re-resolve by GUID in normal operations.
1443             if not rmd_flags & 1 and reverse_link_name is not None:
1444                 if str(res[0].dn) != str(dsdb_dn.dn):
1445                     error_count += 1
1446                     self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1447                                                           res[0].dn, "string")
1448                     continue
1449
1450             if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
1451                 error_count += 1
1452                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1453                                                       res[0].dn, "GUID")
1454                 continue
1455
1456             target_sid = res[0].dn.get_extended_component("SID")
1457             link_sid = dsdb_dn.dn.get_extended_component("SID")
1458             if link_sid is None and target_sid is not None:
1459                 error_count += 1
1460                 self.err_dn_component_missing_target_sid(obj.dn, attrname, val,
1461                                                          dsdb_dn, target_sid)
1462                 continue
1463             if link_sid != target_sid:
1464                 error_count += 1
1465                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1466                                                       res[0].dn, "SID")
1467                 continue
1468
1469             # Only for non-links, not even forward-only links
1470             # (otherwise this breaks repl_meta_data):
1471             #
1472             # Now we have checked the GUID and SID, offer to fix old
1473             # DN strings as a non-error (DNs, not links so no
1474             # backlink).  Samba does not maintain this string
1475             # otherwise, so we don't increment error_count.
1476             if reverse_link_name is None:
1477                 if linkID == 0 and str(res[0].dn) != str(dsdb_dn.dn):
1478                     # Pass in the old/bad DN without the <GUID=...> part,
1479                     # otherwise the LDB code will correct it on the way through
1480                     # (Note: we still want to preserve the DSDB DN prefix in the
1481                     # case of binary DNs)
1482                     bad_dn = dsdb_dn.prefix + dsdb_dn.dn.get_linearized()
1483                     self.err_dn_string_component_old(obj.dn, attrname, bad_dn,
1484                                                      dsdb_dn, res[0].dn)
1485                 continue
1486
1487             if is_member_link and self.quick_membership_checks:
1488                 continue
1489
1490             # check the reverse_link is correct if there should be one
1491             match_count = 0
1492             if reverse_link_name in res[0]:
1493                 for v in res[0][reverse_link_name]:
1494                     v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1495                     v_guid = v_dn.dn.get_extended_component("GUID")
1496                     v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1497                     v_rmd_flags = 0
1498                     if v_blob is not None:
1499                         v_rmd_flags = int(v_blob)
1500                     if v_rmd_flags & 1:
1501                         continue
1502                     if v_guid == obj_guid:
1503                         match_count += 1
1504
1505             if match_count != 1:
1506                 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1507                     if not linkID & 1:
1508                         # Forward binary multi-valued linked attribute
1509                         forward_count = 0
1510                         for w in obj[attrname]:
1511                             w_guid = dsdb_Dn(self.samdb, w.decode('utf8')).dn.get_extended_component("GUID")
1512                             if w_guid == guid:
1513                                 forward_count += 1
1514
1515                         if match_count == forward_count:
1516                             continue
1517             expected_count = 0
1518             for v in obj[attrname]:
1519                 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1520                 v_guid = v_dn.dn.get_extended_component("GUID")
1521                 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1522                 v_rmd_flags = 0
1523                 if v_blob is not None:
1524                     v_rmd_flags = int(v_blob)
1525                 if v_rmd_flags & 1:
1526                     continue
1527                 if v_guid == guid:
1528                     expected_count += 1
1529
1530             if match_count == expected_count:
1531                 continue
1532
1533             diff_count = expected_count - match_count
1534
1535             if linkID & 1:
1536                 # If there's a backward link on binary multi-valued linked attribute,
1537                 # let the check on the forward link remedy the value.
1538                 # UNLESS, there is no forward link detected.
1539                 if match_count == 0:
1540                     error_count += 1
1541                     self.err_orphaned_backlink(obj.dn, attrname,
1542                                                val, dsdb_dn.dn,
1543                                                reverse_link_name,
1544                                                reverse_syntax_oid)
1545                     continue
1546                 # Only warn here and let the forward link logic fix it.
1547                 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1548                             attrname, expected_count, str(obj.dn),
1549                             reverse_link_name, match_count, str(dsdb_dn.dn)))
1550                 continue
1551
1552             assert not target_is_deleted
1553
1554             self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1555                         attrname, expected_count, str(obj.dn),
1556                         reverse_link_name, match_count, str(dsdb_dn.dn)))
1557
1558             # Loop until the difference between the forward and
1559             # the backward links is resolved.
1560             while diff_count != 0:
1561                 error_count += 1
1562                 if diff_count > 0:
1563                     if match_count > 0 or diff_count > 1:
1564                         # TODO no method to fix these right now
1565                         self.report("ERROR: Can't fix missing "
1566                                     "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1567                         break
1568                     self.err_missing_backlink(obj, attrname,
1569                                               obj.dn.extended_str(),
1570                                               reverse_link_name,
1571                                               dsdb_dn.dn)
1572                     diff_count -= 1
1573                 else:
1574                     self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1575                                                obj.dn.extended_str(), obj.dn,
1576                                                attrname, syntax_oid)
1577                     diff_count += 1
1578
1579         return error_count
1580
1581     def find_repl_attid(self, repl, attid):
1582         for o in repl.ctr.array:
1583             if o.attid == attid:
1584                 return o
1585
1586         return None
1587
1588     def get_originating_time(self, val, attid):
1589         '''Read metadata properties and return the originating time for
1590            a given attributeId.
1591
1592            :return: the originating time or 0 if not found
1593         '''
1594
1595         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1596         o = self.find_repl_attid(repl, attid)
1597         if o is not None:
1598             return o.originating_change_time
1599         return 0
1600
1601     def process_metadata(self, dn, val):
1602         '''Read metadata properties and list attributes in it.
1603            raises KeyError if the attid is unknown.'''
1604
1605         set_att = set()
1606         wrong_attids = set()
1607         list_attid = []
1608         in_schema_nc = dn.is_child_of(self.schema_dn)
1609
1610         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1611
1612         for o in repl.ctr.array:
1613             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1614             set_att.add(att.lower())
1615             list_attid.append(o.attid)
1616             correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1617                                                                              is_schema_nc=in_schema_nc)
1618             if correct_attid != o.attid:
1619                 wrong_attids.add(o.attid)
1620
1621         return (set_att, list_attid, wrong_attids)
1622
1623     def fix_metadata(self, obj, attr):
1624         '''re-write replPropertyMetaData elements for a single attribute for a
1625         object. This is used to fix missing replPropertyMetaData elements'''
1626         guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1627         dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1628         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attr],
1629                                 controls=["search_options:1:2",
1630                                           "show_recycled:1"])
1631         msg = res[0]
1632         nmsg = ldb.Message()
1633         nmsg.dn = dn
1634         nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1635         if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1636                           "Failed to fix metadata for attribute %s" % attr):
1637             self.report("Fixed metadata for attribute %s" % attr)
1638
1639     def ace_get_effective_inherited_type(self, ace):
1640         if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1641             return None
1642
1643         check = False
1644         if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1645             check = True
1646         elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1647             check = True
1648         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1649             check = True
1650         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1651             check = True
1652
1653         if not check:
1654             return None
1655
1656         if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1657             return None
1658
1659         return str(ace.object.inherited_type)
1660
1661     def lookup_class_schemaIDGUID(self, cls):
1662         if cls in self.class_schemaIDGUID:
1663             return self.class_schemaIDGUID[cls]
1664
1665         flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1666         res = self.samdb.search(base=self.schema_dn,
1667                                 expression=flt,
1668                                 attrs=["schemaIDGUID"])
1669         t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1670
1671         self.class_schemaIDGUID[cls] = t
1672         return t
1673
1674     def process_sd(self, dn, obj):
1675         sd_attr = "nTSecurityDescriptor"
1676         sd_val = obj[sd_attr]
1677
1678         sd = ndr_unpack(security.descriptor, sd_val[0])
1679
1680         is_deleted = 'isDeleted' in obj and str(obj['isDeleted'][0]).upper() == 'TRUE'
1681         if is_deleted:
1682             # we don't fix deleted objects
1683             return (sd, None)
1684
1685         sd_clean = security.descriptor()
1686         sd_clean.owner_sid = sd.owner_sid
1687         sd_clean.group_sid = sd.group_sid
1688         sd_clean.type = sd.type
1689         sd_clean.revision = sd.revision
1690
1691         broken = False
1692         last_inherited_type = None
1693
1694         aces = []
1695         if sd.sacl is not None:
1696             aces = sd.sacl.aces
1697         for i in range(0, len(aces)):
1698             ace = aces[i]
1699
1700             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1701                 sd_clean.sacl_add(ace)
1702                 continue
1703
1704             t = self.ace_get_effective_inherited_type(ace)
1705             if t is None:
1706                 continue
1707
1708             if last_inherited_type is not None:
1709                 if t != last_inherited_type:
1710                     # if it inherited from more than
1711                     # one type it's very likely to be broken
1712                     #
1713                     # If not the recalculation will calculate
1714                     # the same result.
1715                     broken = True
1716                 continue
1717
1718             last_inherited_type = t
1719
1720         aces = []
1721         if sd.dacl is not None:
1722             aces = sd.dacl.aces
1723         for i in range(0, len(aces)):
1724             ace = aces[i]
1725
1726             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1727                 sd_clean.dacl_add(ace)
1728                 continue
1729
1730             t = self.ace_get_effective_inherited_type(ace)
1731             if t is None:
1732                 continue
1733
1734             if last_inherited_type is not None:
1735                 if t != last_inherited_type:
1736                     # if it inherited from more than
1737                     # one type it's very likely to be broken
1738                     #
1739                     # If not the recalculation will calculate
1740                     # the same result.
1741                     broken = True
1742                 continue
1743
1744             last_inherited_type = t
1745
1746         if broken:
1747             return (sd_clean, sd)
1748
1749         if last_inherited_type is None:
1750             # ok
1751             return (sd, None)
1752
1753         cls = None
1754         try:
1755             cls = obj["objectClass"][-1]
1756         except KeyError as e:
1757             pass
1758
1759         if cls is None:
1760             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1761                                     attrs=["isDeleted", "objectClass"],
1762                                     controls=["show_recycled:1"])
1763             o = res[0]
1764             is_deleted = 'isDeleted' in o and str(o['isDeleted'][0]).upper() == 'TRUE'
1765             if is_deleted:
1766                 # we don't fix deleted objects
1767                 return (sd, None)
1768             cls = o["objectClass"][-1]
1769
1770         t = self.lookup_class_schemaIDGUID(cls)
1771
1772         if t != last_inherited_type:
1773             # broken
1774             return (sd_clean, sd)
1775
1776         # ok
1777         return (sd, None)
1778
1779     def err_wrong_sd(self, dn, sd, sd_broken):
1780         '''re-write the SD due to incorrect inherited ACEs'''
1781         sd_attr = "nTSecurityDescriptor"
1782         sd_val = ndr_pack(sd)
1783         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1784
1785         if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1786             self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1787             return
1788
1789         nmsg = ldb.Message()
1790         nmsg.dn = dn
1791         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1792         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1793                           "Failed to fix attribute %s" % sd_attr):
1794             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1795
1796     def err_wrong_default_sd(self, dn, sd, diff):
1797         '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1798         sd_attr = "nTSecurityDescriptor"
1799         sd_val = ndr_pack(sd)
1800         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1801         if sd.owner_sid is not None:
1802             sd_flags |= security.SECINFO_OWNER
1803         if sd.group_sid is not None:
1804             sd_flags |= security.SECINFO_GROUP
1805
1806         if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1807             self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1808             return
1809
1810         m = ldb.Message()
1811         m.dn = dn
1812         m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1813         if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1814                           "Failed to reset attribute %s" % sd_attr):
1815             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1816
1817     def err_missing_sd_owner(self, dn, sd):
1818         '''re-write the SD due to a missing owner or group'''
1819         sd_attr = "nTSecurityDescriptor"
1820         sd_val = ndr_pack(sd)
1821         sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1822
1823         if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1824             self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1825             return
1826
1827         nmsg = ldb.Message()
1828         nmsg.dn = dn
1829         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1830
1831         # By setting the session_info to admin_session_info and
1832         # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1833         # flags we cause the descriptor module to set the correct
1834         # owner and group on the SD, replacing the None/NULL values
1835         # for owner_sid and group_sid currently present.
1836         #
1837         # The admin_session_info matches that used in provision, and
1838         # is the best guess we can make for an existing object that
1839         # hasn't had something specifically set.
1840         #
1841         # This is important for the dns related naming contexts.
1842         self.samdb.set_session_info(self.admin_session_info)
1843         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1844                           "Failed to fix metadata for attribute %s" % sd_attr):
1845             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1846         self.samdb.set_session_info(self.system_session_info)
1847
1848     def is_expired_tombstone(self, dn, repl_val):
1849         if self.check_expired_tombstones:
1850             # This is not the default, it's just
1851             # used to keep dbcheck tests work with
1852             # old static provision dumps
1853             return False
1854
1855         if dn in self.deleted_objects_containers:
1856             # The Deleted Objects container will look like an expired
1857             # tombstone
1858             return False
1859
1860         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl_val)
1861
1862         isDeleted = self.find_repl_attid(repl, drsuapi.DRSUAPI_ATTID_isDeleted)
1863
1864         delete_time = samba.nttime2unix(isDeleted.originating_change_time)
1865         current_time = time.time()
1866
1867         tombstone_delta = self.tombstoneLifetime * (24 * 60 * 60)
1868
1869         delta = current_time - delete_time
1870         if delta <= tombstone_delta:
1871             return False
1872
1873         expunge_time = delete_time + tombstone_delta
1874
1875         delta_days = delta / (24 * 60 * 60)
1876
1877         if delta_days <= 2:
1878             self.report("SKIPPING additional checks on object "
1879                         "%s which very recently "
1880                         "became an expired tombstone (normal)" % dn)
1881             self.report("INFO: it is expected this will be expunged "
1882                         "by the next daily task some time after %s, "
1883                         "%d hours ago"
1884                         % (time.ctime(expunge_time), delta // (60 * 60)))
1885         else:
1886             self.report("SKIPPING: object %s is an expired tombstone" % dn)
1887             self.report("INFO: it was expected this object would have "
1888                         "been expunged soon after"
1889                         "%s, %d days ago"
1890                         % (time.ctime(expunge_time), delta_days))
1891
1892         self.report("isDeleted: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1893                     isDeleted.attid,
1894                     isDeleted.version,
1895                     isDeleted.originating_invocation_id,
1896                     isDeleted.originating_usn,
1897                     isDeleted.local_usn,
1898                     time.ctime(samba.nttime2unix(isDeleted.originating_change_time))))
1899         self.expired_tombstones += 1
1900         return True
1901
1902     def find_changes_after_deletion(self, repl_val):
1903         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl_val)
1904
1905         isDeleted = self.find_repl_attid(repl, drsuapi.DRSUAPI_ATTID_isDeleted)
1906
1907         delete_time = samba.nttime2unix(isDeleted.originating_change_time)
1908
1909         tombstone_delta = self.tombstoneLifetime * (24 * 60 * 60)
1910
1911         found = []
1912         for o in repl.ctr.array:
1913             if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1914                 continue
1915
1916             if o.local_usn <= isDeleted.local_usn:
1917                 continue
1918
1919             if o.originating_change_time <= isDeleted.originating_change_time:
1920                 continue
1921
1922             change_time = samba.nttime2unix(o.originating_change_time)
1923
1924             delta = change_time - delete_time
1925             if delta <= tombstone_delta:
1926                 continue
1927
1928             # If the modification happened after the tombstone lifetime
1929             # has passed, we have a bug as the object might be deleted
1930             # already on other DCs and won't be able to replicate
1931             # back
1932             found.append(o)
1933
1934         return found, isDeleted
1935
1936     def has_changes_after_deletion(self, dn, repl_val):
1937         found, isDeleted = self.find_changes_after_deletion(repl_val)
1938         if len(found) == 0:
1939             return False
1940
1941         def report_attid(o):
1942             try:
1943                 attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1944             except KeyError:
1945                 attname = "<unknown:0x%x08x>" % o.attid
1946
1947             self.report("%s: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
1948                         attname, o.attid, o.version,
1949                         o.originating_invocation_id,
1950                         o.originating_usn,
1951                         o.local_usn,
1952                         time.ctime(samba.nttime2unix(o.originating_change_time))))
1953
1954         self.report("ERROR: object %s, has changes after deletion" % dn)
1955         report_attid(isDeleted)
1956         for o in found:
1957             report_attid(o)
1958
1959         return True
1960
1961     def err_changes_after_deletion(self, dn, repl_val):
1962         found, isDeleted = self.find_changes_after_deletion(repl_val)
1963
1964         in_schema_nc = dn.is_child_of(self.schema_dn)
1965         rdn_attr = dn.get_rdn_name()
1966         rdn_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(rdn_attr,
1967                                                      is_schema_nc=in_schema_nc)
1968
1969         unexpected = []
1970         for o in found:
1971             if o.attid == rdn_attid:
1972                 continue
1973             if o.attid == drsuapi.DRSUAPI_ATTID_name:
1974                 continue
1975             if o.attid == drsuapi.DRSUAPI_ATTID_lastKnownParent:
1976                 continue
1977             try:
1978                 attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1979             except KeyError:
1980                 attname = "<unknown:0x%x08x>" % o.attid
1981             unexpected.append(attname)
1982
1983         if len(unexpected) > 0:
1984             self.report('Unexpeted attributes: %s' % ",".join(unexpected))
1985             self.report('Not fixing changes after deletion bug')
1986             return
1987
1988         if not self.confirm_all('Delete broken tombstone object %s deleted %s days ago?' % (
1989                                 dn, self.tombstoneLifetime), 'fix_changes_after_deletion_bug'):
1990             self.report('Not fixing changes after deletion bug')
1991             return
1992
1993         if self.do_delete(dn, ["relax:0"],
1994                           "Failed to remove DN %s" % dn):
1995             self.report("Removed DN %s" % dn)
1996
1997     def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1998         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1999                           repl_meta_data)
2000         ctr = repl.ctr
2001         found = False
2002         for o in ctr.array:
2003             # Search for a zero invocationID
2004             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
2005                 continue
2006
2007             found = True
2008             self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
2009                            version %d changed at %s is 00000000-0000-0000-0000-000000000000,
2010                            but should be non-zero.  Proposed fix is to set to our invocationID (%s).'''
2011                         % (dn, o.attid, o.version,
2012                            time.ctime(samba.nttime2unix(o.originating_change_time)),
2013                            self.samdb.get_invocation_id()))
2014
2015         return found
2016
2017     def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
2018         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
2019                           repl_meta_data)
2020         ctr = repl.ctr
2021         now = samba.unix2nttime(int(time.time()))
2022         found = False
2023         for o in ctr.array:
2024             # Search for a zero invocationID
2025             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
2026                 continue
2027
2028             found = True
2029             seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
2030             o.version = o.version + 1
2031             o.originating_change_time = now
2032             o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
2033             o.originating_usn = seq
2034             o.local_usn = seq
2035
2036         if found:
2037             replBlob = ndr_pack(repl)
2038             msg = ldb.Message()
2039             msg.dn = dn
2040
2041             if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
2042                                     % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
2043                 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
2044                 return
2045
2046             nmsg = ldb.Message()
2047             nmsg.dn = dn
2048             nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
2049             if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
2050                                      "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
2051                               "Failed to fix attribute %s" % attr):
2052                 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
2053
2054     def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
2055         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
2056                           repl_meta_data)
2057         ctr = repl.ctr
2058         for o in ctr.array:
2059             # Search for an invalid attid
2060             try:
2061                 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
2062             except KeyError:
2063                 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
2064                 return
2065
2066     def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
2067         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
2068                           repl_meta_data)
2069         fix = False
2070
2071         set_att = set()
2072         remove_attid = set()
2073         hash_att = {}
2074
2075         in_schema_nc = dn.is_child_of(self.schema_dn)
2076
2077         ctr = repl.ctr
2078         # Sort the array, except for the last element.  This strange
2079         # construction, creating a new list, due to bugs in samba's
2080         # array handling in IDL generated objects.
2081         ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
2082         # Now walk it in reverse, so we see the low (and so incorrect,
2083         # the correct values are above 0x80000000) values first and
2084         # remove the 'second' value we see.
2085         for o in reversed(ctr.array):
2086             print("%s: 0x%08x" % (dn, o.attid))
2087             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
2088             if att.lower() in set_att:
2089                 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
2090                 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
2091                                         % (attr, dn, o.attid, att, hash_att[att].attid),
2092                                         'fix_replmetadata_duplicate_attid'):
2093                     self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
2094                                 % (o.attid, att, attr, dn))
2095                     return
2096                 fix = True
2097                 remove_attid.add(o.attid)
2098                 # We want to set the metadata for the most recent
2099                 # update to have been applied locally, that is the metadata
2100                 # matching the (eg string) value in the attribute
2101                 if o.local_usn > hash_att[att].local_usn:
2102                     # This is always what we would have sent over DRS,
2103                     # because the DRS server will have sent the
2104                     # msDS-IntID, but with the values from both
2105                     # attribute entries.
2106                     hash_att[att].version = o.version
2107                     hash_att[att].originating_change_time = o.originating_change_time
2108                     hash_att[att].originating_invocation_id = o.originating_invocation_id
2109                     hash_att[att].originating_usn = o.originating_usn
2110                     hash_att[att].local_usn = o.local_usn
2111
2112                 # Do not re-add the value to the set or overwrite the hash value
2113                 continue
2114
2115             hash_att[att] = o
2116             set_att.add(att.lower())
2117
2118         # Generate a real list we can sort on properly
2119         new_list = [o for o in ctr.array if o.attid not in remove_attid]
2120
2121         if (len(wrong_attids) > 0):
2122             for o in new_list:
2123                 if o.attid in wrong_attids:
2124                     att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
2125                     correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
2126                     self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
2127                     if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
2128                                             % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
2129                         self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
2130                                     % (o.attid, correct_attid, att, attr, dn))
2131                         return
2132                     fix = True
2133                     o.attid = correct_attid
2134             if fix:
2135                 # Sort the array, (we changed the value so must re-sort)
2136                 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
2137
2138         # If we did not already need to fix it, then ask about sorting
2139         if not fix:
2140             self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
2141             if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
2142                                     % (attr, dn), 'fix_replmetadata_unsorted_attid'):
2143                 self.report('Not fixing %s on %s\n' % (attr, dn))
2144                 return
2145
2146             # The actual sort done is done at the top of the function
2147
2148         ctr.count = len(new_list)
2149         ctr.array = new_list
2150         replBlob = ndr_pack(repl)
2151
2152         nmsg = ldb.Message()
2153         nmsg.dn = dn
2154         nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
2155         if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
2156                                  "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
2157                                  "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
2158                           "Failed to fix attribute %s" % attr):
2159             self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
2160
2161     def is_deleted_deleted_objects(self, obj):
2162         faulty = False
2163         if "description" not in obj:
2164             self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
2165             faulty = True
2166         if "showInAdvancedViewOnly" not in obj or str(obj['showInAdvancedViewOnly'][0]).upper() == 'FALSE':
2167             self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
2168             faulty = True
2169         if "objectCategory" not in obj:
2170             self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
2171             faulty = True
2172         if "isCriticalSystemObject" not in obj or str(obj['isCriticalSystemObject'][0]).upper() == 'FALSE':
2173             self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
2174             faulty = True
2175         if "isRecycled" in obj:
2176             self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
2177             faulty = True
2178         if "isDeleted" in obj and str(obj['isDeleted'][0]).upper() == 'FALSE':
2179             self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
2180             faulty = True
2181         if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
2182                                         str(obj['objectClass'][0]) != 'top' or
2183                                         str(obj['objectClass'][1]) != 'container'):
2184             self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
2185             faulty = True
2186         if "systemFlags" not in obj or str(obj['systemFlags'][0]) != '-1946157056':
2187             self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
2188             faulty = True
2189         return faulty
2190
2191     def err_deleted_deleted_objects(self, obj):
2192         nmsg = ldb.Message()
2193         nmsg.dn = dn = obj.dn
2194
2195         if "description" not in obj:
2196             nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
2197         if "showInAdvancedViewOnly" not in obj:
2198             nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
2199         if "objectCategory" not in obj:
2200             nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
2201         if "isCriticalSystemObject" not in obj:
2202             nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
2203         if "isRecycled" in obj:
2204             nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
2205
2206         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2207         nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
2208         nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
2209
2210         if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
2211                                 % (dn), 'fix_deleted_deleted_objects'):
2212             self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
2213             return
2214
2215         if self.do_modify(nmsg, ["relax:0"],
2216                           "Failed to fix Deleted Objects container  %s" % dn):
2217             self.report("Fixed Deleted Objects container '%s'\n" % (dn))
2218
2219     def err_replica_locations(self, obj, cross_ref, attr):
2220         nmsg = ldb.Message()
2221         nmsg.dn = cross_ref
2222         target = self.samdb.get_dsServiceName()
2223
2224         if self.samdb.am_rodc():
2225             self.report('Not fixing %s %s for the RODC' % (attr, obj.dn))
2226             return
2227
2228         if not self.confirm_all('Add yourself to the replica locations for %s?'
2229                                 % (obj.dn), 'fix_replica_locations'):
2230             self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
2231             return
2232
2233         nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
2234         if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
2235             self.report("Fixed %s for %s" % (attr, obj.dn))
2236
2237     def is_fsmo_role(self, dn):
2238         if dn == self.samdb.domain_dn:
2239             return True
2240         if dn == self.infrastructure_dn:
2241             return True
2242         if dn == self.naming_dn:
2243             return True
2244         if dn == self.schema_dn:
2245             return True
2246         if dn == self.rid_dn:
2247             return True
2248
2249         return False
2250
2251     def calculate_instancetype(self, dn):
2252         instancetype = 0
2253         nc_root = self.samdb.get_nc_root(dn)
2254         if dn == nc_root:
2255             instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
2256             try:
2257                 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
2258             except ldb.LdbError as e4:
2259                 (enum, estr) = e4.args
2260                 if enum != ldb.ERR_NO_SUCH_OBJECT:
2261                     raise
2262             else:
2263                 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
2264         if self.write_ncs is not None and str(nc_root) in [str(x) for x in self.write_ncs]:
2265             instancetype |= dsdb.INSTANCE_TYPE_WRITE
2266
2267         return instancetype
2268
2269     def get_wellknown_sd(self, dn):
2270         for [sd_dn, descriptor_fn] in self.wellknown_sds:
2271             if dn == sd_dn:
2272                 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
2273                 return ndr_unpack(security.descriptor,
2274                                   descriptor_fn(domain_sid,
2275                                                 name_map=self.name_map))
2276
2277         raise KeyError
2278
2279     def find_checkable_attrs(self, dn, requested_attrs):
2280         """A helper function for check_object() that calculates the list of
2281         attributes that need to be checked, and returns that as a list
2282         in the original case, and a set normalised to lowercase (for
2283         easy existence checks).
2284         """
2285         if requested_attrs is None:
2286             attrs = ['*']
2287         else:
2288             attrs = list(requested_attrs)
2289
2290         lc_attrs = set(x.lower() for x in attrs)
2291
2292         def add_attr(a):
2293             if a.lower() not in lc_attrs:
2294                 attrs.append(a)
2295                 lc_attrs.add(a.lower())
2296
2297         if ("dn" in lc_attrs or
2298             "distinguishedname" in lc_attrs or
2299             dn.get_rdn_name().lower() in lc_attrs):
2300             attrs.append("name")
2301             lc_attrs.add('name')
2302
2303         if 'name' in lc_attrs:
2304             for a in (dn.get_rdn_name(),
2305                       "isDeleted",
2306                       "systemFlags"):
2307                 add_attr(a)
2308
2309         need_replPropertyMetaData = False
2310         if '*' in lc_attrs:
2311             need_replPropertyMetaData = True
2312         else:
2313             for a in attrs:
2314                 linkID, _ = self.get_attr_linkID_and_reverse_name(a)
2315                 if linkID == 0:
2316                     continue
2317                 if linkID & 1:
2318                     continue
2319                 need_replPropertyMetaData = True
2320                 break
2321         if need_replPropertyMetaData:
2322             add_attr("replPropertyMetaData")
2323
2324         add_attr("objectGUID")
2325
2326         return attrs, lc_attrs
2327
2328     def check_object(self, dn, requested_attrs=None):
2329         '''check one object'''
2330         if self.verbose:
2331             self.report("Checking object %s" % dn)
2332
2333         # search attrs are used to find the attributes, lc_attrs are
2334         # used for existence checks
2335         search_attrs, lc_attrs = self.find_checkable_attrs(dn, requested_attrs)
2336
2337         try:
2338             sd_flags = 0
2339             sd_flags |= security.SECINFO_OWNER
2340             sd_flags |= security.SECINFO_GROUP
2341             sd_flags |= security.SECINFO_DACL
2342             sd_flags |= security.SECINFO_SACL
2343
2344             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
2345                                     controls=[
2346                                         "extended_dn:1:1",
2347                                         "show_recycled:1",
2348                                         "show_deleted:1",
2349                                         "sd_flags:1:%d" % sd_flags,
2350                                         "reveal_internals:0",
2351                                     ],
2352                                     attrs=search_attrs)
2353         except ldb.LdbError as e10:
2354             (enum, estr) = e10.args
2355             if enum == ldb.ERR_NO_SUCH_OBJECT:
2356                 if self.in_transaction:
2357                     self.report("ERROR: Object %s disappeared during check" % dn)
2358                     return 1
2359                 return 0
2360             raise
2361         if len(res) != 1:
2362             self.report("ERROR: Object %s failed to load during check" % dn)
2363             return 1
2364         obj = res[0]
2365         error_count = 0
2366         set_attrs_from_md = set()
2367         set_attrs_seen = set()
2368         got_objectclass = False
2369
2370         nc_dn = self.samdb.get_nc_root(obj.dn)
2371         try:
2372             deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
2373                                                              samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
2374         except KeyError:
2375             # We have no deleted objects DN for schema, and we check for this above for the other
2376             # NCs
2377             deleted_objects_dn = None
2378
2379         object_rdn_attr = None
2380         object_rdn_val = None
2381         name_val = None
2382         isDeleted = False
2383         systemFlags = 0
2384         repl_meta_data_val = None
2385
2386         for attrname in obj:
2387             if attrname.lower() == 'isdeleted':
2388                 if str(obj[attrname][0]) != "FALSE":
2389                     isDeleted = True
2390
2391             if attrname.lower() == 'systemflags':
2392                 systemFlags = int(obj[attrname][0])
2393
2394             if attrname.lower() == 'replpropertymetadata':
2395                 repl_meta_data_val = obj[attrname][0]
2396
2397         if isDeleted and repl_meta_data_val:
2398             if self.has_changes_after_deletion(dn, repl_meta_data_val):
2399                 error_count += 1
2400                 self.err_changes_after_deletion(dn, repl_meta_data_val)
2401                 return error_count
2402             if self.is_expired_tombstone(dn, repl_meta_data_val):
2403                 return error_count
2404
2405         for attrname in obj:
2406             if attrname == 'dn' or attrname == "distinguishedName":
2407                 continue
2408
2409             if attrname.lower() == 'objectclass':
2410                 got_objectclass = True
2411
2412             if attrname.lower() == "name":
2413                 if len(obj[attrname]) != 1:
2414                     self.unfixable_errors += 1
2415                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2416                                 (len(obj[attrname]), attrname, str(obj.dn)))
2417                 else:
2418                     name_val = str(obj[attrname][0])
2419
2420             if attrname.lower() == str(obj.dn.get_rdn_name()).lower():
2421                 object_rdn_attr = attrname
2422                 if len(obj[attrname]) != 1:
2423                     self.unfixable_errors += 1
2424                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2425                                 (len(obj[attrname]), attrname, str(obj.dn)))
2426                 else:
2427                     object_rdn_val = str(obj[attrname][0])
2428
2429             if attrname.lower() == 'replpropertymetadata':
2430                 if self.has_replmetadata_zero_invocationid(dn, obj[attrname][0]):
2431                     error_count += 1
2432                     self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname][0])
2433                     # We don't continue, as we may also have other fixes for this attribute
2434                     # based on what other attributes we see.
2435
2436                 try:
2437                     (set_attrs_from_md, list_attid_from_md, wrong_attids) \
2438                         = self.process_metadata(dn, obj[attrname][0])
2439                 except KeyError:
2440                     error_count += 1
2441                     self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
2442                     continue
2443
2444                 if len(set_attrs_from_md) < len(list_attid_from_md) \
2445                    or len(wrong_attids) > 0 \
2446                    or sorted(list_attid_from_md) != list_attid_from_md:
2447                     error_count += 1
2448                     self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname][0], wrong_attids)
2449
2450                 else:
2451                     # Here we check that the first attid is 0
2452                     # (objectClass).
2453                     if list_attid_from_md[0] != 0:
2454                         self.unfixable_errors += 1
2455                         self.report("ERROR: Not fixing incorrect initial attributeID in '%s' on '%s', it should be objectClass" %
2456                                     (attrname, str(dn)))
2457
2458                 continue
2459
2460             if attrname.lower() == 'ntsecuritydescriptor':
2461                 (sd, sd_broken) = self.process_sd(dn, obj)
2462                 if sd_broken is not None:
2463                     self.err_wrong_sd(dn, sd, sd_broken)
2464                     error_count += 1
2465                     continue
2466
2467                 if sd.owner_sid is None or sd.group_sid is None:
2468                     self.err_missing_sd_owner(dn, sd)
2469                     error_count += 1
2470                     continue
2471
2472                 if self.reset_well_known_acls:
2473                     try:
2474                         well_known_sd = self.get_wellknown_sd(dn)
2475                     except KeyError:
2476                         continue
2477
2478                     current_sd = ndr_unpack(security.descriptor,
2479                                             obj[attrname][0])
2480
2481                     diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
2482                     if diff != "":
2483                         self.err_wrong_default_sd(dn, well_known_sd, diff)
2484                         error_count += 1
2485                         continue
2486                 continue
2487
2488             if attrname.lower() == 'objectclass':
2489                 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
2490                 # Do not consider the attribute incorrect if:
2491                 #  - The sorted (alphabetically) list is the same, inclding case
2492                 #  - The first and last elements are the same
2493                 #
2494                 # This avoids triggering an error due to
2495                 # non-determinism in the sort routine in (at least)
2496                 # 4.3 and earlier, and the fact that any AUX classes
2497                 # in these attributes are also not sorted when
2498                 # imported from Windows (they are just in the reverse
2499                 # order of last set)
2500                 if sorted(normalised) != sorted(obj[attrname]) \
2501                    or normalised[0] != obj[attrname][0] \
2502                    or normalised[-1] != obj[attrname][-1]:
2503                     self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
2504                     error_count += 1
2505                 continue
2506
2507             if attrname.lower() == 'userparameters':
2508                 userparams = obj[attrname][0]
2509                 if userparams == b' ':
2510                     error_count += 1
2511                     self.err_short_userParameters(obj, attrname, obj[attrname])
2512                     continue
2513
2514                 elif userparams[:16] == b'\x20\x00' * 8:
2515                     # This is the correct, normal prefix
2516                     continue
2517
2518                 elif userparams[:20] == b'IAAgACAAIAAgACAAIAAg':
2519                     # this is the typical prefix from a windows migration
2520                     error_count += 1
2521                     self.err_base64_userParameters(obj, attrname, obj[attrname])
2522                     continue
2523
2524                 #43:00:00:00:74:00:00:00:78
2525                 elif (userparams[1] != 0 and
2526                       userparams[3] != 0 and
2527                       userparams[5] != 0 and
2528                       userparams[7] != 0 and
2529                       userparams[9] != 0):
2530                     # This is a prefix that is not in UTF-16 format
2531                     # for the space or munged dialback prefix
2532                     error_count += 1
2533                     self.err_utf8_userParameters(obj, attrname, obj[attrname])
2534                     continue
2535
2536                 elif len(userparams) % 2 != 0:
2537                     # This is a value that isn't even in length
2538                     error_count += 1
2539                     self.err_odd_userParameters(obj, attrname)
2540                     continue
2541
2542                 elif (userparams[1] == 0 and
2543                       userparams[2] == 0 and
2544                       userparams[3] == 0 and
2545                       userparams[4] != 0 and
2546                       userparams[5] == 0):
2547                     # This is a prefix that would happen if a
2548                     # SAMR-written value was replicated from a Samba
2549                     # 4.1 server to a working server
2550                     error_count += 1
2551                     self.err_doubled_userParameters(obj, attrname, obj[attrname])
2552                     continue
2553
2554             if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2555                 if obj[attrname][0] in self.attribute_or_class_ids:
2556                     self.unfixable_errors += 1
2557                     self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2558                                 % (attrname, obj.dn, obj[attrname][0]))
2559                 else:
2560                     self.attribute_or_class_ids.add(obj[attrname][0])
2561
2562             # check for empty attributes
2563             for val in obj[attrname]:
2564                 if val == b'':
2565                     self.err_empty_attribute(dn, attrname)
2566                     error_count += 1
2567                     continue
2568
2569             # get the syntax oid for the attribute, so we can can have
2570             # special handling for some specific attribute types
2571             try:
2572                 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2573             except Exception as msg:
2574                 self.err_unknown_attribute(obj, attrname)
2575                 error_count += 1
2576                 continue
2577
2578             linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2579
2580             flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2581             if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2582                 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2583                 and not linkID):
2584                 set_attrs_seen.add(attrname.lower())
2585
2586             if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2587                               dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN]:
2588                 # it's some form of DN, do specialised checking on those
2589                 error_count += self.check_dn(obj, attrname, syntax_oid)
2590             else:
2591
2592                 values = set()
2593                 # check for incorrectly normalised attributes
2594                 for val in obj[attrname]:
2595                     values.add(val)
2596
2597                     normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2598                     if len(normalised) != 1 or normalised[0] != val:
2599                         self.err_normalise_mismatch(dn, attrname, obj[attrname])
2600                         error_count += 1
2601                         break
2602
2603                 if len(obj[attrname]) != len(values):
2604                     self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2605                     error_count += 1
2606                     break
2607
2608             if attrname.lower() == "instancetype":
2609                 calculated_instancetype = self.calculate_instancetype(dn)
2610                 if len(obj["instanceType"]) != 1 or int(obj["instanceType"][0]) != calculated_instancetype:
2611                     error_count += 1
2612                     self.err_wrong_instancetype(obj, calculated_instancetype)
2613
2614         if not got_objectclass and ("*" in lc_attrs or "objectclass" in lc_attrs):
2615             error_count += 1
2616             self.err_missing_objectclass(dn)
2617
2618         if ("*" in lc_attrs or "name" in lc_attrs):
2619             if name_val is None:
2620                 self.unfixable_errors += 1
2621                 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2622             if object_rdn_attr is None:
2623                 self.unfixable_errors += 1
2624                 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2625
2626         if name_val is not None:
2627             parent_dn = None
2628             controls = ["show_recycled:1", "relax:0"]
2629             if isDeleted:
2630                 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2631                     parent_dn = deleted_objects_dn
2632                 controls += ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME]
2633             if parent_dn is None:
2634                 parent_dn = obj.dn.parent()
2635
2636             try:
2637                 expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2638             except ValueError as e:
2639                 self.unfixable_errors += 1
2640                 self.report(f"ERROR: could not handle parent DN '{parent_dn}': "
2641                             "skipping RDN checks")
2642             else:
2643                 expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2644
2645                 if obj.dn == deleted_objects_dn:
2646                     expected_dn = obj.dn
2647
2648                 if expected_dn != obj.dn:
2649                     error_count += 1
2650                     self.err_wrong_dn(obj, expected_dn, object_rdn_attr,
2651                             object_rdn_val, name_val, controls)
2652                 elif obj.dn.get_rdn_value() != object_rdn_val:
2653                     self.unfixable_errors += 1
2654                     self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr,
2655                                                                      object_rdn_val,
2656                                                                      obj.dn))
2657
2658         show_dn = True
2659         if repl_meta_data_val:
2660             if obj.dn == deleted_objects_dn:
2661                 isDeletedAttId = 131120
2662                 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2663
2664                 expectedTimeDo = 2650466015990000000
2665                 originating = self.get_originating_time(repl_meta_data_val, isDeletedAttId)
2666                 if originating != expectedTimeDo:
2667                     if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2668                         nmsg = ldb.Message()
2669                         nmsg.dn = dn
2670                         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2671                         error_count += 1
2672                         self.samdb.modify(nmsg, controls=["provision:0"])
2673
2674                     else:
2675                         self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2676
2677             for att in set_attrs_seen.difference(set_attrs_from_md):
2678                 if show_dn:
2679                     self.report("On object %s" % dn)
2680                     show_dn = False
2681                 error_count += 1
2682                 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2683                 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2684                     self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2685                     continue
2686                 self.fix_metadata(obj, att)
2687
2688         if self.is_fsmo_role(dn):
2689             if "fSMORoleOwner" not in obj and ("*" in lc_attrs or "fsmoroleowner" in lc_attrs):
2690                 self.err_no_fsmoRoleOwner(obj)
2691                 error_count += 1
2692
2693         try:
2694             if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2695                 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2696                                         controls=["show_recycled:1", "show_deleted:1"])
2697         except ldb.LdbError as e11:
2698             (enum, estr) = e11.args
2699             if enum == ldb.ERR_NO_SUCH_OBJECT:
2700                 if isDeleted:
2701                     self.report("WARNING: parent object not found for %s" % (obj.dn))
2702                     self.report("Not moving to LostAndFound "
2703                                 "(tombstone garbage collection in progress?)")
2704                 else:
2705                     self.err_missing_parent(obj)
2706                     error_count += 1
2707             else:
2708                 raise
2709
2710         if dn in self.deleted_objects_containers and '*' in lc_attrs:
2711             if self.is_deleted_deleted_objects(obj):
2712                 self.err_deleted_deleted_objects(obj)
2713                 error_count += 1
2714
2715         for (dns_part, msg) in self.dns_partitions:
2716             if dn == dns_part and 'repsFrom' in obj:
2717                 location = "msDS-NC-Replica-Locations"
2718                 if self.samdb.am_rodc():
2719                     location = "msDS-NC-RO-Replica-Locations"
2720
2721                 if location not in msg:
2722                     # There are no replica locations!
2723                     self.err_replica_locations(obj, msg.dn, location)
2724                     error_count += 1
2725                     continue
2726
2727                 found = False
2728                 for loc in msg[location]:
2729                     if str(loc) == self.samdb.get_dsServiceName():
2730                         found = True
2731                 if not found:
2732                     # This DC is not in the replica locations
2733                     self.err_replica_locations(obj, msg.dn, location)
2734                     error_count += 1
2735
2736         if dn == self.server_ref_dn:
2737             # Check we have a valid RID Set
2738             if "*" in lc_attrs or "ridsetreferences" in lc_attrs:
2739                 if "rIDSetReferences" not in obj:
2740                     # NO RID SET reference
2741                     # We are RID master, allocate it.
2742                     error_count += 1
2743
2744                     if self.is_rid_master:
2745                         # Allocate a RID Set
2746                         if self.confirm_all('Allocate the missing RID set for '
2747                                             'RID master?',
2748                                             'fix_missing_rid_set_master'):
2749
2750                             # We don't have auto-transaction logic on
2751                             # extended operations, so we have to do it
2752                             # here.
2753
2754                             self.samdb.transaction_start()
2755
2756                             try:
2757                                 self.samdb.create_own_rid_set()
2758
2759                             except:
2760                                 self.samdb.transaction_cancel()
2761                                 raise
2762
2763                             self.samdb.transaction_commit()
2764
2765                     elif not self.samdb.am_rodc():
2766                         self.report("No RID Set found for this server: %s, "
2767                                     "and we are not the RID Master (so can "
2768                                     "not self-allocate)" % dn)
2769
2770         # Check some details of our own RID Set
2771         #
2772         # Note that the attributes have very bad names.  From ridalloc.c:
2773         #
2774         #   Note: the RID allocation attributes in AD are very badly named.
2775         #     Here is what we think they really do:
2776         #
2777         #     in RID Set object:
2778         #       - rIDPreviousAllocationPool: the pool which a DC is currently
2779         #         pulling RIDs from. Managed by client DC
2780         #
2781         #       - rIDAllocationPool: the pool that the DC will switch to next,
2782         #         when rIDPreviousAllocationPool is exhausted. Managed by RID
2783         #         Manager.
2784         #
2785         #       - rIDNextRID: the last RID allocated by this DC. Managed by
2786         #         client DC
2787         #
2788         #     in RID Manager object:
2789         #       - rIDAvailablePool: the pool where the RID Manager gets new rID
2790         #         pools from when it gets a EXOP_RID_ALLOC getncchanges call
2791         #         (or locally when the DC is the RID Manager)
2792
2793         if dn == self.rid_set_dn:
2794             pool_attrs = ["rIDAllocationPool", "rIDPreviousAllocationPool"]
2795
2796             res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2797                                     attrs=pool_attrs)
2798
2799             for pool_attr in pool_attrs:
2800                 if pool_attr not in res[0]:
2801                     continue
2802
2803                 pool = int(res[0][pool_attr][0])
2804
2805                 high = pool >> 32
2806                 low = 0xFFFFFFFF & pool
2807
2808                 if pool != 0 and low >= high:
2809                     self.report("Invalid RID pool %d-%d, %d >= %d!" %
2810                                 (low, high, low, high))
2811                     self.unfixable_errors += 1
2812
2813             if "rIDAllocationPool" not in res[0]:
2814                 self.report("No rIDAllocationPool found in %s" % dn)
2815                 self.unfixable_errors += 1
2816
2817             try:
2818                 next_free_rid, high = self.samdb.free_rid_bounds()
2819             except ldb.LdbError as err:
2820                 enum, estr = err.args
2821                 self.report("Couldn't get available RIDs: %s" % estr)
2822                 self.unfixable_errors += 1
2823             else:
2824                 # Check the remainder of this pool for conflicts.  If
2825                 # ridalloc_allocate_rid() moves to a new pool, this
2826                 # will be above high, so we will stop.
2827                 domain_sid = self.samdb.get_domain_sid()
2828                 while next_free_rid <= high:
2829                     sid = "%s-%d" % (domain_sid, next_free_rid)
2830                     try:
2831                         res = self.samdb.search(base="<SID=%s>" % sid,
2832                                                 scope=ldb.SCOPE_BASE,
2833                                                 attrs=[])
2834                     except ldb.LdbError as e:
2835                         (enum, estr) = e.args
2836                         if enum != ldb.ERR_NO_SUCH_OBJECT:
2837                             raise
2838                         res = None
2839                     if res is not None:
2840                         self.report("SID %s for %s conflicts with our current "
2841                                     "RID set in %s" % (sid, res[0].dn, dn))
2842                         error_count += 1
2843
2844                         if self.confirm_all('Fix conflict between SID %s and '
2845                                             'RID pool in %s by allocating a '
2846                                             'new RID?'
2847                                             % (sid, dn),
2848                                             'fix_sid_rid_set_conflict'):
2849                             self.samdb.transaction_start()
2850
2851                             # This will burn RIDs, which will move
2852                             # past the conflict.  We then check again
2853                             # to see if the new RID conflicts, until
2854                             # the end of the current pool.  We don't
2855                             # look at the next pool to avoid burning
2856                             # all RIDs in one go in some strange
2857                             # failure case.
2858                             try:
2859                                 while True:
2860                                     allocated_rid = self.samdb.allocate_rid()
2861                                     if allocated_rid >= next_free_rid:
2862                                         next_free_rid = allocated_rid + 1
2863                                         break
2864                             except:
2865                                 self.samdb.transaction_cancel()
2866                                 raise
2867
2868                             self.samdb.transaction_commit()
2869                         else:
2870                             break
2871                     else:
2872                         next_free_rid += 1
2873
2874         return error_count
2875
2876     ################################################################
2877     # check special @ROOTDSE attributes
2878     def check_rootdse(self):
2879         '''check the @ROOTDSE special object'''
2880         dn = ldb.Dn(self.samdb, '@ROOTDSE')
2881         if self.verbose:
2882             self.report("Checking object %s" % dn)
2883         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2884         if len(res) != 1:
2885             self.report("Object %s disappeared during check" % dn)
2886             return 1
2887         obj = res[0]
2888         error_count = 0
2889
2890         # check that the dsServiceName is in GUID form
2891         if 'dsServiceName' not in obj:
2892             self.report('ERROR: dsServiceName missing in @ROOTDSE')
2893             return error_count + 1
2894
2895         if not str(obj['dsServiceName'][0]).startswith('<GUID='):
2896             self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2897             error_count += 1
2898             if not self.confirm('Change dsServiceName to GUID form?'):
2899                 return error_count
2900             res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0].decode('utf8')),
2901                                     scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2902             guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2903             m = ldb.Message()
2904             m.dn = dn
2905             m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2906                                                     ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2907             if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2908                 self.report("Changed dsServiceName to GUID form")
2909         return error_count
2910
2911     ###############################################
2912     # re-index the database
2913
2914     def reindex_database(self):
2915         '''re-index the whole database'''
2916         m = ldb.Message()
2917         m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2918         m['add']    = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2919         m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2920         return self.do_modify(m, [], 're-indexed database', validate=False)
2921
2922     ###############################################
2923     # reset @MODULES
2924     def reset_modules(self):
2925         '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2926         m = ldb.Message()
2927         m.dn = ldb.Dn(self.samdb, "@MODULES")
2928         m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2929         return self.do_modify(m, [], 'reset @MODULES on database', validate=False)