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