libndr: Avoid assigning duplicate versions to symbols
[amitay/samba.git] / python / samba / dbchecker.py
1 # Samba4 AD database checker
2 #
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 from __future__ import print_function
21 import ldb
22 import samba
23 import time
24 from base64 import b64decode
25 from samba import dsdb
26 from samba import common
27 from samba.dcerpc import misc
28 from samba.dcerpc import drsuapi
29 from samba.ndr import ndr_unpack, ndr_pack
30 from samba.dcerpc import drsblobs
31 from samba.samdb import dsdb_Dn
32 from samba.dcerpc import security
33 from samba.descriptor import get_wellknown_sds, get_diff_sds
34 from samba.auth import system_session, admin_session
35 from samba.netcmd import CommandError
36 from 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
47 def 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
58 class 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
352 objectClass: top
353 objectClass: container
354 description: Container for deleted objects
355 isDeleted: TRUE
356 isCriticalSystemObject: TRUE
357 showInAdvancedViewOnly: TRUE
358 systemFlags: -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
448 changeType: modrdn
449 newrdn: %s
450 deleteOldRdn: 1
451 newSuperior: %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, 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, 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)