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