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