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