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