python/samba: extra ndr_unpack needs bytes function
[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.decode('utf8')),
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.decode('utf8')))
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].decode('utf8'))
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].decode('utf8'))
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(str(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                               "local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME],
661                           "Failed to fix old DN string on attribute %s" % (attrname)):
662             self.report("Fixed old DN string on attribute %s" % (attrname))
663
664     def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
665         """handle a DN string being incorrect"""
666         self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
667         dsdb_dn.dn = correct_dn
668
669         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
670                                 'fix_all_%s_dn_component_mismatch' % mismatch_type):
671             self.report("Not fixing %s component mismatch" % mismatch_type)
672             return
673         m = ldb.Message()
674         m.dn = dn
675         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
676         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
677         if self.do_modify(m, ["show_recycled:1"],
678                           "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
679             self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
680
681     def err_unknown_attribute(self, obj, attrname):
682         '''handle an unknown attribute error'''
683         self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
684         if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
685             self.report("Not removing %s" % attrname)
686             return
687         m = ldb.Message()
688         m.dn = obj.dn
689         m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
690         if self.do_modify(m, ["relax:0", "show_recycled:1"],
691                           "Failed to remove unknown attribute %s" % attrname):
692             self.report("Removed unknown attribute %s" % (attrname))
693
694     def err_undead_linked_attribute(self, obj, attrname, val):
695         '''handle a link that should not be there on a deleted object'''
696         self.report("ERROR: linked attribute '%s' to '%s' is present on "
697                     "deleted object %s" % (attrname, val, obj.dn))
698         if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
699             self.report("Not removing linked attribute %s" % attrname)
700             return
701         m = ldb.Message()
702         m.dn = obj.dn
703         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
704
705         if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
706                               "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
707                           "Failed to delete forward link %s" % attrname):
708             self.report("Fixed undead forward link %s" % (attrname))
709
710     def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
711         '''handle a missing backlink value'''
712         self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
713         if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
714             self.report("Not fixing missing backlink %s" % backlink_name)
715             return
716         m = ldb.Message()
717         m.dn = target_dn
718         m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
719         if self.do_modify(m, ["show_recycled:1", "relax:0"],
720                           "Failed to fix missing backlink %s" % backlink_name):
721             self.report("Fixed missing backlink %s" % (backlink_name))
722
723     def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
724         '''handle a incorrect RMD_FLAGS value'''
725         rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
726         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()))
727         if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
728             self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
729             return
730         m = ldb.Message()
731         m.dn = obj.dn
732         m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
733         if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
734                           "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
735             self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
736
737     def err_orphaned_backlink(self, obj_dn, backlink_attr, backlink_val,
738                               target_dn, forward_attr, forward_syntax,
739                               check_duplicates=True):
740         '''handle a orphaned backlink value'''
741         if check_duplicates is True and self.has_duplicate_links(target_dn, forward_attr, forward_syntax):
742             self.report("WARNING: Keep orphaned backlink attribute " +
743                         "'%s' in '%s' for link '%s' in '%s'" % (
744                             backlink_attr, obj_dn, forward_attr, target_dn))
745             return
746         self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (backlink_attr, obj_dn, forward_attr, target_dn))
747         if not self.confirm_all('Remove orphaned backlink %s' % backlink_attr, 'fix_all_orphaned_backlinks'):
748             self.report("Not removing orphaned backlink %s" % backlink_attr)
749             return
750         m = ldb.Message()
751         m.dn = obj_dn
752         m['value'] = ldb.MessageElement(backlink_val, ldb.FLAG_MOD_DELETE, backlink_attr)
753         if self.do_modify(m, ["show_recycled:1", "relax:0"],
754                           "Failed to fix orphaned backlink %s" % backlink_attr):
755             self.report("Fixed orphaned backlink %s" % (backlink_attr))
756
757     def err_recover_forward_links(self, obj, forward_attr, forward_vals):
758         '''handle a duplicate links value'''
759
760         self.report("RECHECK: 'Missing/Duplicate/Correct link' lines above for attribute '%s' in '%s'" % (forward_attr, obj.dn))
761
762         if not self.confirm_all("Commit fixes for (missing/duplicate) forward links in attribute '%s'" % forward_attr, 'recover_all_forward_links'):
763             self.report("Not fixing corrupted (missing/duplicate) forward links in attribute '%s' of '%s'" % (
764                         forward_attr, obj.dn))
765             return
766         m = ldb.Message()
767         m.dn = obj.dn
768         m['value'] = ldb.MessageElement(forward_vals, ldb.FLAG_MOD_REPLACE, forward_attr)
769         if self.do_modify(m, ["local_oid:%s:1" % dsdb.DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS],
770                           "Failed to fix duplicate links in attribute '%s'" % forward_attr):
771             self.report("Fixed duplicate links in attribute '%s'" % (forward_attr))
772             duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
773             assert duplicate_cache_key in self.duplicate_link_cache
774             self.duplicate_link_cache[duplicate_cache_key] = False
775
776     def err_no_fsmoRoleOwner(self, obj):
777         '''handle a missing fSMORoleOwner'''
778         self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
779         res = self.samdb.search("",
780                                 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
781         assert len(res) == 1
782         serviceName = res[0]["dsServiceName"][0]
783         if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
784             self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
785             return
786         m = ldb.Message()
787         m.dn = obj.dn
788         m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
789         if self.do_modify(m, [],
790                           "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
791             self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
792
793     def err_missing_parent(self, obj):
794         '''handle a missing parent'''
795         self.report("ERROR: parent object not found for %s" % (obj.dn))
796         if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
797             self.report('Not moving object %s into LostAndFound' % (obj.dn))
798             return
799
800         keep_transaction = False
801         self.samdb.transaction_start()
802         try:
803             nc_root = self.samdb.get_nc_root(obj.dn)
804             lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
805             new_dn = ldb.Dn(self.samdb, str(obj.dn))
806             new_dn.remove_base_components(len(new_dn) - 1)
807             if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
808                               "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
809                 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
810
811                 m = ldb.Message()
812                 m.dn = obj.dn
813                 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
814
815                 if self.do_modify(m, [],
816                                   "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
817                     self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
818                     keep_transaction = True
819         except:
820             self.samdb.transaction_cancel()
821             raise
822
823         if keep_transaction:
824             self.samdb.transaction_commit()
825         else:
826             self.samdb.transaction_cancel()
827
828     def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
829         '''handle a wrong dn'''
830
831         new_rdn = ldb.Dn(self.samdb, str(new_dn))
832         new_rdn.remove_base_components(len(new_rdn) - 1)
833         new_parent = new_dn.parent()
834
835         attributes = ""
836         if rdn_val != name_val:
837             attributes += "%s=%r " % (rdn_attr, rdn_val)
838         attributes += "name=%r" % (name_val)
839
840         self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
841         if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
842             self.report("Not renaming %s to %s" % (obj.dn, new_dn))
843             return
844
845         if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
846                           "Failed to rename object %s into %s" % (obj.dn, new_dn)):
847             self.report("Renamed %s into %s" % (obj.dn, new_dn))
848
849     def err_wrong_instancetype(self, obj, calculated_instancetype):
850         '''handle a wrong instanceType'''
851         self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
852         if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
853             self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
854             return
855
856         m = ldb.Message()
857         m.dn = obj.dn
858         m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
859         if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
860                           "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
861             self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
862
863     def err_short_userParameters(self, obj, attrname, value):
864         # This is a truncated userParameters due to a pre 4.1 replication bug
865         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)))
866
867     def err_base64_userParameters(self, obj, attrname, value):
868         '''handle a wrong userParameters'''
869         self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
870         if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
871             self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
872             return
873
874         m = ldb.Message()
875         m.dn = obj.dn
876         m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
877         if self.do_modify(m, [],
878                           "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
879             self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
880
881     def err_utf8_userParameters(self, obj, attrname, value):
882         '''handle a wrong userParameters'''
883         self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
884         if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
885             self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
886             return
887
888         m = ldb.Message()
889         m.dn = obj.dn
890         m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
891                                         ldb.FLAG_MOD_REPLACE, 'userParameters')
892         if self.do_modify(m, [],
893                           "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
894             self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
895
896     def err_doubled_userParameters(self, obj, attrname, value):
897         '''handle a wrong userParameters'''
898         self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
899         if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
900             self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
901             return
902
903         m = ldb.Message()
904         m.dn = obj.dn
905         m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
906                                         ldb.FLAG_MOD_REPLACE, 'userParameters')
907         if self.do_modify(m, [],
908                           "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
909             self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
910
911     def err_odd_userParameters(self, obj, attrname):
912         # This is a truncated userParameters due to a pre 4.1 replication bug
913         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)))
914
915     def find_revealed_link(self, dn, attrname, guid):
916         '''return a revealed link in an object'''
917         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
918                                 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
919         syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
920         for val in res[0][attrname]:
921             dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
922             guid2 = dsdb_dn.dn.get_extended_component("GUID")
923             if guid == guid2:
924                 return dsdb_dn
925         return None
926
927     def check_duplicate_links(self, obj, forward_attr, forward_syntax, forward_linkID, backlink_attr):
928         '''check a linked values for duplicate forward links'''
929         error_count = 0
930
931         duplicate_dict = dict()
932         unique_dict = dict()
933
934         # Only forward links can have this problem
935         if forward_linkID & 1:
936             # If we got the reverse, skip it
937             return (error_count, duplicate_dict, unique_dict)
938
939         if backlink_attr is None:
940             return (error_count, duplicate_dict, unique_dict)
941
942         duplicate_cache_key = "%s:%s" % (str(obj.dn), forward_attr)
943         if duplicate_cache_key not in self.duplicate_link_cache:
944             self.duplicate_link_cache[duplicate_cache_key] = False
945
946         for val in obj[forward_attr]:
947             dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), forward_syntax)
948
949             # all DNs should have a GUID component
950             guid = dsdb_dn.dn.get_extended_component("GUID")
951             if guid is None:
952                 continue
953             guidstr = str(misc.GUID(guid))
954             keystr = guidstr + dsdb_dn.prefix
955             if keystr not in unique_dict:
956                 unique_dict[keystr] = dsdb_dn
957                 continue
958             error_count += 1
959             if keystr not in duplicate_dict:
960                 duplicate_dict[keystr] = dict()
961                 duplicate_dict[keystr]["keep"] = None
962                 duplicate_dict[keystr]["delete"] = list()
963
964             # Now check for the highest RMD_VERSION
965             v1 = int(unique_dict[keystr].dn.get_extended_component("RMD_VERSION"))
966             v2 = int(dsdb_dn.dn.get_extended_component("RMD_VERSION"))
967             if v1 > v2:
968                 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
969                 duplicate_dict[keystr]["delete"].append(dsdb_dn)
970                 continue
971             if v1 < v2:
972                 duplicate_dict[keystr]["keep"] = dsdb_dn
973                 duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
974                 unique_dict[keystr] = dsdb_dn
975                 continue
976             # Fallback to the highest RMD_LOCAL_USN
977             u1 = int(unique_dict[keystr].dn.get_extended_component("RMD_LOCAL_USN"))
978             u2 = int(dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN"))
979             if u1 >= u2:
980                 duplicate_dict[keystr]["keep"] = unique_dict[keystr]
981                 duplicate_dict[keystr]["delete"].append(dsdb_dn)
982                 continue
983             duplicate_dict[keystr]["keep"] = dsdb_dn
984             duplicate_dict[keystr]["delete"].append(unique_dict[keystr])
985             unique_dict[keystr] = dsdb_dn
986
987         if error_count != 0:
988             self.duplicate_link_cache[duplicate_cache_key] = True
989
990         return (error_count, duplicate_dict, unique_dict)
991
992     def has_duplicate_links(self, dn, forward_attr, forward_syntax):
993         '''check a linked values for duplicate forward links'''
994         error_count = 0
995
996         duplicate_cache_key = "%s:%s" % (str(dn), forward_attr)
997         if duplicate_cache_key in self.duplicate_link_cache:
998             return self.duplicate_link_cache[duplicate_cache_key]
999
1000         forward_linkID, backlink_attr = self.get_attr_linkID_and_reverse_name(forward_attr)
1001
1002         attrs = [forward_attr]
1003         controls = ["extended_dn:1:1", "reveal_internals:0"]
1004
1005         # check its the right GUID
1006         try:
1007             res = self.samdb.search(base=str(dn), scope=ldb.SCOPE_BASE,
1008                                     attrs=attrs, controls=controls)
1009         except ldb.LdbError as e8:
1010             (enum, estr) = e8.args
1011             if enum != ldb.ERR_NO_SUCH_OBJECT:
1012                 raise
1013
1014             return False
1015
1016         obj = res[0]
1017         error_count, duplicate_dict, unique_dict = \
1018             self.check_duplicate_links(obj, forward_attr, forward_syntax, forward_linkID, backlink_attr)
1019
1020         if duplicate_cache_key in self.duplicate_link_cache:
1021             return self.duplicate_link_cache[duplicate_cache_key]
1022
1023         return False
1024
1025     def find_missing_forward_links_from_backlinks(self, obj,
1026                                                   forward_attr,
1027                                                   forward_syntax,
1028                                                   backlink_attr,
1029                                                   forward_unique_dict):
1030         '''Find all backlinks linking to obj_guid_str not already in forward_unique_dict'''
1031         missing_forward_links = []
1032         error_count = 0
1033
1034         if backlink_attr is None:
1035             return (missing_forward_links, error_count)
1036
1037         if forward_syntax != ldb.SYNTAX_DN:
1038             self.report("Not checking for missing forward links for syntax: %s",
1039                         forward_syntax)
1040             return (missing_forward_links, error_count)
1041
1042         if "sortedLinks" in self.compatibleFeatures:
1043             self.report("Not checking for missing forward links because the db " +
1044                         "has the sortedLinks feature")
1045             return (missing_forward_links, error_count)
1046
1047         try:
1048             obj_guid = obj['objectGUID'][0]
1049             obj_guid_str = str(ndr_unpack(misc.GUID, obj_guid))
1050             filter = "(%s=<GUID=%s>)" % (backlink_attr, obj_guid_str)
1051
1052             res = self.samdb.search(expression=filter,
1053                                     scope=ldb.SCOPE_SUBTREE, attrs=["objectGUID"],
1054                                     controls=["extended_dn:1:1",
1055                                               "search_options:1:2",
1056                                               "paged_results:1:1000"])
1057         except ldb.LdbError as e9:
1058             (enum, estr) = e9.args
1059             raise
1060
1061         for r in res:
1062             target_dn = dsdb_Dn(self.samdb, r.dn.extended_str(), forward_syntax)
1063
1064             guid = target_dn.dn.get_extended_component("GUID")
1065             guidstr = str(misc.GUID(guid))
1066             if guidstr in forward_unique_dict:
1067                 continue
1068
1069             # A valid forward link looks like this:
1070             #
1071             #    <GUID=9f92d30a-fc23-11e4-a5f6-30be15454808>;
1072             #    <RMD_ADDTIME=131607546230000000>;
1073             #    <RMD_CHANGETIME=131607546230000000>;
1074             #    <RMD_FLAGS=0>;
1075             #    <RMD_INVOCID=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d>;
1076             #    <RMD_LOCAL_USN=3765>;
1077             #    <RMD_ORIGINATING_USN=3765>;
1078             #    <RMD_VERSION=1>;
1079             #    <SID=S-1-5-21-4177067393-1453636373-93818738-1124>;
1080             #    CN=unsorted-u8,CN=Users,DC=release-4-5-0-pre1,DC=samba,DC=corp
1081             #
1082             # Note that versions older than Samba 4.8 create
1083             # links with RMD_VERSION=0.
1084             #
1085             # Try to get the local_usn and time from objectClass
1086             # if possible and fallback to any other one.
1087             repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1088                               obj['replPropertyMetadata'][0])
1089             for o in repl.ctr.array:
1090                 local_usn = o.local_usn
1091                 t = o.originating_change_time
1092                 if o.attid == drsuapi.DRSUAPI_ATTID_objectClass:
1093                     break
1094
1095             # We use a magic invocationID for restoring missing
1096             # forward links to recover from bug #13228.
1097             # This should allow some more future magic to fix the
1098             # problem.
1099             #
1100             # It also means it looses the conflict resolution
1101             # against almost every real invocation, if the
1102             # version is also 0.
1103             originating_invocid = misc.GUID("ffffffff-4700-4700-4700-000000b13228")
1104             originating_usn = 1
1105
1106             rmd_addtime = t
1107             rmd_changetime = t
1108             rmd_flags = 0
1109             rmd_invocid = originating_invocid
1110             rmd_originating_usn = originating_usn
1111             rmd_local_usn = local_usn
1112             rmd_version = 0
1113
1114             target_dn.dn.set_extended_component("RMD_ADDTIME", str(rmd_addtime))
1115             target_dn.dn.set_extended_component("RMD_CHANGETIME", str(rmd_changetime))
1116             target_dn.dn.set_extended_component("RMD_FLAGS", str(rmd_flags))
1117             target_dn.dn.set_extended_component("RMD_INVOCID", ndr_pack(rmd_invocid))
1118             target_dn.dn.set_extended_component("RMD_ORIGINATING_USN", str(rmd_originating_usn))
1119             target_dn.dn.set_extended_component("RMD_LOCAL_USN", str(rmd_local_usn))
1120             target_dn.dn.set_extended_component("RMD_VERSION", str(rmd_version))
1121
1122             error_count += 1
1123             missing_forward_links.append(target_dn)
1124
1125         return (missing_forward_links, error_count)
1126
1127     def check_dn(self, obj, attrname, syntax_oid):
1128         '''check a DN attribute for correctness'''
1129         error_count = 0
1130         obj_guid = obj['objectGUID'][0]
1131
1132         linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1133         if reverse_link_name is not None:
1134             reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
1135         else:
1136             reverse_syntax_oid = None
1137
1138         error_count, duplicate_dict, unique_dict = \
1139             self.check_duplicate_links(obj, attrname, syntax_oid, linkID, reverse_link_name)
1140
1141         if len(duplicate_dict) != 0:
1142
1143             missing_forward_links, missing_error_count = \
1144                 self.find_missing_forward_links_from_backlinks(obj,
1145                                                                attrname, syntax_oid,
1146                                                                reverse_link_name,
1147                                                                unique_dict)
1148             error_count += missing_error_count
1149
1150             forward_links = [dn for dn in unique_dict.values()]
1151
1152             if missing_error_count != 0:
1153                 self.report("ERROR: Missing and duplicate forward link values for attribute '%s' in '%s'" % (
1154                             attrname, obj.dn))
1155             else:
1156                 self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
1157             for m in missing_forward_links:
1158                 self.report("Missing   link '%s'" % (m))
1159                 if not self.confirm_all("Schedule readding missing forward link for attribute %s" % attrname,
1160                                         'fix_all_missing_forward_links'):
1161                     self.err_orphaned_backlink(m.dn, reverse_link_name,
1162                                                obj.dn.extended_str(), obj.dn,
1163                                                attrname, syntax_oid,
1164                                                check_duplicates=False)
1165                     continue
1166                 forward_links += [m]
1167             for keystr in duplicate_dict.keys():
1168                 d = duplicate_dict[keystr]
1169                 for dd in d["delete"]:
1170                     self.report("Duplicate link '%s'" % dd)
1171                 self.report("Correct   link '%s'" % d["keep"])
1172
1173             # We now construct the sorted dn values.
1174             # They're sorted by the objectGUID of the target
1175             # See dsdb_Dn.__cmp__()
1176             vals = [str(dn) for dn in sorted(forward_links)]
1177             self.err_recover_forward_links(obj, attrname, vals)
1178             # We should continue with the fixed values
1179             obj[attrname] = ldb.MessageElement(vals, 0, attrname)
1180
1181         for val in obj[attrname]:
1182             dsdb_dn = dsdb_Dn(self.samdb, val.decode('utf8'), syntax_oid)
1183
1184             # all DNs should have a GUID component
1185             guid = dsdb_dn.dn.get_extended_component("GUID")
1186             if guid is None:
1187                 error_count += 1
1188                 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
1189                                                    "missing GUID")
1190                 continue
1191
1192             guidstr = str(misc.GUID(guid))
1193             attrs = ['isDeleted', 'replPropertyMetaData']
1194
1195             if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
1196                 fixing_msDS_HasInstantiatedNCs = True
1197                 attrs.append("instanceType")
1198             else:
1199                 fixing_msDS_HasInstantiatedNCs = False
1200
1201             if reverse_link_name is not None:
1202                 attrs.append(reverse_link_name)
1203
1204             # check its the right GUID
1205             try:
1206                 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
1207                                         attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
1208                                                                "reveal_internals:0"
1209                                                                ])
1210             except ldb.LdbError as e3:
1211                 (enum, estr) = e3.args
1212                 if enum != ldb.ERR_NO_SUCH_OBJECT:
1213                     raise
1214
1215                 # We don't always want to
1216                 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
1217                                                                   attrname,
1218                                                                   val,
1219                                                                   dsdb_dn)
1220                 continue
1221
1222             if fixing_msDS_HasInstantiatedNCs:
1223                 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
1224                 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
1225
1226                 if str(dsdb_dn) != val:
1227                     error_count += 1
1228                     self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
1229                     continue
1230
1231             # now we have two cases - the source object might or might not be deleted
1232             is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1233             target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
1234
1235             if is_deleted and obj.dn not 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                                           res[0]['replPropertyMetadata'][0])
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             # Only for non-links, not even forward-only links
1302             # (otherwise this breaks repl_meta_data):
1303             #
1304             # Now we have checked the GUID and SID, offer to fix old
1305             # DN strings as a non-error (DNs, not links so no
1306             # backlink).  Samba does not maintain this string
1307             # otherwise, so we don't increment error_count.
1308             if reverse_link_name is None:
1309                 if linkID == 0 and str(res[0].dn) != str(dsdb_dn.dn):
1310                     # Pass in the old/bad DN without the <GUID=...> part,
1311                     # otherwise the LDB code will correct it on the way through
1312                     # (Note: we still want to preserve the DSDB DN prefix in the
1313                     # case of binary DNs)
1314                     bad_dn = dsdb_dn.prefix + dsdb_dn.dn.get_linearized()
1315                     self.err_dn_string_component_old(obj.dn, attrname, bad_dn,
1316                                                      dsdb_dn, res[0].dn)
1317                 continue
1318
1319             # check the reverse_link is correct if there should be one
1320             match_count = 0
1321             if reverse_link_name in res[0]:
1322                 for v in res[0][reverse_link_name]:
1323                     v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1324                     v_guid = v_dn.dn.get_extended_component("GUID")
1325                     v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1326                     v_rmd_flags = 0
1327                     if v_blob is not None:
1328                         v_rmd_flags = int(v_blob)
1329                     if v_rmd_flags & 1:
1330                         continue
1331                     if v_guid == obj_guid:
1332                         match_count += 1
1333
1334             if match_count != 1:
1335                 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1336                     if not linkID & 1:
1337                         # Forward binary multi-valued linked attribute
1338                         forward_count = 0
1339                         for w in obj[attrname]:
1340                             w_guid = dsdb_Dn(self.samdb, w.decode('utf8')).dn.get_extended_component("GUID")
1341                             if w_guid == guid:
1342                                 forward_count += 1
1343
1344                         if match_count == forward_count:
1345                             continue
1346             expected_count = 0
1347             for v in obj[attrname]:
1348                 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1349                 v_guid = v_dn.dn.get_extended_component("GUID")
1350                 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1351                 v_rmd_flags = 0
1352                 if v_blob is not None:
1353                     v_rmd_flags = int(v_blob)
1354                 if v_rmd_flags & 1:
1355                     continue
1356                 if v_guid == guid:
1357                     expected_count += 1
1358
1359             if match_count == expected_count:
1360                 continue
1361
1362             diff_count = expected_count - match_count
1363
1364             if linkID & 1:
1365                 # If there's a backward link on binary multi-valued linked attribute,
1366                 # let the check on the forward link remedy the value.
1367                 # UNLESS, there is no forward link detected.
1368                 if match_count == 0:
1369                     error_count += 1
1370                     self.err_orphaned_backlink(obj.dn, attrname,
1371                                                val, dsdb_dn.dn,
1372                                                reverse_link_name,
1373                                                reverse_syntax_oid)
1374                     continue
1375                 # Only warn here and let the forward link logic fix it.
1376                 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1377                             attrname, expected_count, str(obj.dn),
1378                             reverse_link_name, match_count, str(dsdb_dn.dn)))
1379                 continue
1380
1381             assert not target_is_deleted
1382
1383             self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1384                         attrname, expected_count, str(obj.dn),
1385                         reverse_link_name, match_count, str(dsdb_dn.dn)))
1386
1387             # Loop until the difference between the forward and
1388             # the backward links is resolved.
1389             while diff_count != 0:
1390                 error_count += 1
1391                 if diff_count > 0:
1392                     if match_count > 0 or diff_count > 1:
1393                         # TODO no method to fix these right now
1394                         self.report("ERROR: Can't fix missing "
1395                                     "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1396                         break
1397                     self.err_missing_backlink(obj, attrname,
1398                                               obj.dn.extended_str(),
1399                                               reverse_link_name,
1400                                               dsdb_dn.dn)
1401                     diff_count -= 1
1402                 else:
1403                     self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1404                                                obj.dn.extended_str(), obj.dn,
1405                                                attrname, syntax_oid)
1406                     diff_count += 1
1407
1408         return error_count
1409
1410     def get_originating_time(self, val, attid):
1411         '''Read metadata properties and return the originating time for
1412            a given attributeId.
1413
1414            :return: the originating time or 0 if not found
1415         '''
1416
1417         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1418         obj = repl.ctr
1419
1420         for o in repl.ctr.array:
1421             if o.attid == attid:
1422                 return o.originating_change_time
1423
1424         return 0
1425
1426     def process_metadata(self, dn, val):
1427         '''Read metadata properties and list attributes in it.
1428            raises KeyError if the attid is unknown.'''
1429
1430         set_att = set()
1431         wrong_attids = set()
1432         list_attid = []
1433         in_schema_nc = dn.is_child_of(self.schema_dn)
1434
1435         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, val)
1436         obj = repl.ctr
1437
1438         for o in repl.ctr.array:
1439             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1440             set_att.add(att.lower())
1441             list_attid.append(o.attid)
1442             correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1443                                                                              is_schema_nc=in_schema_nc)
1444             if correct_attid != o.attid:
1445                 wrong_attids.add(o.attid)
1446
1447         return (set_att, list_attid, wrong_attids)
1448
1449     def fix_metadata(self, obj, attr):
1450         '''re-write replPropertyMetaData elements for a single attribute for a
1451         object. This is used to fix missing replPropertyMetaData elements'''
1452         guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1453         dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1454         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attr],
1455                                 controls=["search_options:1:2",
1456                                           "show_recycled:1"])
1457         msg = res[0]
1458         nmsg = ldb.Message()
1459         nmsg.dn = dn
1460         nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1461         if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1462                           "Failed to fix metadata for attribute %s" % attr):
1463             self.report("Fixed metadata for attribute %s" % attr)
1464
1465     def ace_get_effective_inherited_type(self, ace):
1466         if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1467             return None
1468
1469         check = False
1470         if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1471             check = True
1472         elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1473             check = True
1474         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1475             check = True
1476         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1477             check = True
1478
1479         if not check:
1480             return None
1481
1482         if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1483             return None
1484
1485         return str(ace.object.inherited_type)
1486
1487     def lookup_class_schemaIDGUID(self, cls):
1488         if cls in self.class_schemaIDGUID:
1489             return self.class_schemaIDGUID[cls]
1490
1491         flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1492         res = self.samdb.search(base=self.schema_dn,
1493                                 expression=flt,
1494                                 attrs=["schemaIDGUID"])
1495         t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1496
1497         self.class_schemaIDGUID[cls] = t
1498         return t
1499
1500     def process_sd(self, dn, obj):
1501         sd_attr = "nTSecurityDescriptor"
1502         sd_val = obj[sd_attr]
1503
1504         sd = ndr_unpack(security.descriptor, sd_val[0])
1505
1506         is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1507         if is_deleted:
1508             # we don't fix deleted objects
1509             return (sd, None)
1510
1511         sd_clean = security.descriptor()
1512         sd_clean.owner_sid = sd.owner_sid
1513         sd_clean.group_sid = sd.group_sid
1514         sd_clean.type = sd.type
1515         sd_clean.revision = sd.revision
1516
1517         broken = False
1518         last_inherited_type = None
1519
1520         aces = []
1521         if sd.sacl is not None:
1522             aces = sd.sacl.aces
1523         for i in range(0, len(aces)):
1524             ace = aces[i]
1525
1526             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1527                 sd_clean.sacl_add(ace)
1528                 continue
1529
1530             t = self.ace_get_effective_inherited_type(ace)
1531             if t is None:
1532                 continue
1533
1534             if last_inherited_type is not None:
1535                 if t != last_inherited_type:
1536                     # if it inherited from more than
1537                     # one type it's very likely to be broken
1538                     #
1539                     # If not the recalculation will calculate
1540                     # the same result.
1541                     broken = True
1542                 continue
1543
1544             last_inherited_type = t
1545
1546         aces = []
1547         if sd.dacl is not None:
1548             aces = sd.dacl.aces
1549         for i in range(0, len(aces)):
1550             ace = aces[i]
1551
1552             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1553                 sd_clean.dacl_add(ace)
1554                 continue
1555
1556             t = self.ace_get_effective_inherited_type(ace)
1557             if t is None:
1558                 continue
1559
1560             if last_inherited_type is not None:
1561                 if t != last_inherited_type:
1562                     # if it inherited from more than
1563                     # one type it's very likely to be broken
1564                     #
1565                     # If not the recalculation will calculate
1566                     # the same result.
1567                     broken = True
1568                 continue
1569
1570             last_inherited_type = t
1571
1572         if broken:
1573             return (sd_clean, sd)
1574
1575         if last_inherited_type is None:
1576             # ok
1577             return (sd, None)
1578
1579         cls = None
1580         try:
1581             cls = obj["objectClass"][-1]
1582         except KeyError as e:
1583             pass
1584
1585         if cls is None:
1586             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1587                                     attrs=["isDeleted", "objectClass"],
1588                                     controls=["show_recycled:1"])
1589             o = res[0]
1590             is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
1591             if is_deleted:
1592                 # we don't fix deleted objects
1593                 return (sd, None)
1594             cls = o["objectClass"][-1]
1595
1596         t = self.lookup_class_schemaIDGUID(cls)
1597
1598         if t != last_inherited_type:
1599             # broken
1600             return (sd_clean, sd)
1601
1602         # ok
1603         return (sd, None)
1604
1605     def err_wrong_sd(self, dn, sd, sd_broken):
1606         '''re-write the SD due to incorrect inherited ACEs'''
1607         sd_attr = "nTSecurityDescriptor"
1608         sd_val = ndr_pack(sd)
1609         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1610
1611         if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1612             self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1613             return
1614
1615         nmsg = ldb.Message()
1616         nmsg.dn = dn
1617         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1618         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1619                           "Failed to fix attribute %s" % sd_attr):
1620             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1621
1622     def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1623         '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1624         sd_attr = "nTSecurityDescriptor"
1625         sd_val = ndr_pack(sd)
1626         sd_old_val = ndr_pack(sd_old)
1627         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1628         if sd.owner_sid is not None:
1629             sd_flags |= security.SECINFO_OWNER
1630         if sd.group_sid is not None:
1631             sd_flags |= security.SECINFO_GROUP
1632
1633         if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1634             self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1635             return
1636
1637         m = ldb.Message()
1638         m.dn = dn
1639         m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1640         if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1641                           "Failed to reset attribute %s" % sd_attr):
1642             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1643
1644     def err_missing_sd_owner(self, dn, sd):
1645         '''re-write the SD due to a missing owner or group'''
1646         sd_attr = "nTSecurityDescriptor"
1647         sd_val = ndr_pack(sd)
1648         sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1649
1650         if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1651             self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1652             return
1653
1654         nmsg = ldb.Message()
1655         nmsg.dn = dn
1656         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1657
1658         # By setting the session_info to admin_session_info and
1659         # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1660         # flags we cause the descriptor module to set the correct
1661         # owner and group on the SD, replacing the None/NULL values
1662         # for owner_sid and group_sid currently present.
1663         #
1664         # The admin_session_info matches that used in provision, and
1665         # is the best guess we can make for an existing object that
1666         # hasn't had something specifically set.
1667         #
1668         # This is important for the dns related naming contexts.
1669         self.samdb.set_session_info(self.admin_session_info)
1670         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1671                           "Failed to fix metadata for attribute %s" % sd_attr):
1672             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1673         self.samdb.set_session_info(self.system_session_info)
1674
1675     def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1676         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1677                           repl_meta_data)
1678         ctr = repl.ctr
1679         found = False
1680         for o in ctr.array:
1681             # Search for a zero invocationID
1682             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1683                 continue
1684
1685             found = True
1686             self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1687                            version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1688                            but should be non-zero.  Proposed fix is to set to our invocationID (%s).'''
1689                         % (dn, o.attid, o.version,
1690                            time.ctime(samba.nttime2unix(o.originating_change_time)),
1691                            self.samdb.get_invocation_id()))
1692
1693         return found
1694
1695     def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1696         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1697                           repl_meta_data)
1698         ctr = repl.ctr
1699         now = samba.unix2nttime(int(time.time()))
1700         found = False
1701         for o in ctr.array:
1702             # Search for a zero invocationID
1703             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1704                 continue
1705
1706             found = True
1707             seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1708             o.version = o.version + 1
1709             o.originating_change_time = now
1710             o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1711             o.originating_usn = seq
1712             o.local_usn = seq
1713
1714         if found:
1715             replBlob = ndr_pack(repl)
1716             msg = ldb.Message()
1717             msg.dn = dn
1718
1719             if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1720                                     % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1721                 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1722                 return
1723
1724             nmsg = ldb.Message()
1725             nmsg.dn = dn
1726             nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1727             if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1728                                      "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1729                               "Failed to fix attribute %s" % attr):
1730                 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1731
1732     def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1733         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1734                           repl_meta_data)
1735         ctr = repl.ctr
1736         for o in ctr.array:
1737             # Search for an invalid attid
1738             try:
1739                 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1740             except KeyError:
1741                 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1742                 return
1743
1744     def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1745         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1746                           repl_meta_data)
1747         fix = False
1748
1749         set_att = set()
1750         remove_attid = set()
1751         hash_att = {}
1752
1753         in_schema_nc = dn.is_child_of(self.schema_dn)
1754
1755         ctr = repl.ctr
1756         # Sort the array, except for the last element.  This strange
1757         # construction, creating a new list, due to bugs in samba's
1758         # array handling in IDL generated objects.
1759         ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1760         # Now walk it in reverse, so we see the low (and so incorrect,
1761         # the correct values are above 0x80000000) values first and
1762         # remove the 'second' value we see.
1763         for o in reversed(ctr.array):
1764             print("%s: 0x%08x" % (dn, o.attid))
1765             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1766             if att.lower() in set_att:
1767                 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1768                 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1769                                         % (attr, dn, o.attid, att, hash_att[att].attid),
1770                                         'fix_replmetadata_duplicate_attid'):
1771                     self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1772                                 % (o.attid, att, attr, dn))
1773                     return
1774                 fix = True
1775                 remove_attid.add(o.attid)
1776                 # We want to set the metadata for the most recent
1777                 # update to have been applied locally, that is the metadata
1778                 # matching the (eg string) value in the attribute
1779                 if o.local_usn > hash_att[att].local_usn:
1780                     # This is always what we would have sent over DRS,
1781                     # because the DRS server will have sent the
1782                     # msDS-IntID, but with the values from both
1783                     # attribute entries.
1784                     hash_att[att].version = o.version
1785                     hash_att[att].originating_change_time = o.originating_change_time
1786                     hash_att[att].originating_invocation_id = o.originating_invocation_id
1787                     hash_att[att].originating_usn = o.originating_usn
1788                     hash_att[att].local_usn = o.local_usn
1789
1790                 # Do not re-add the value to the set or overwrite the hash value
1791                 continue
1792
1793             hash_att[att] = o
1794             set_att.add(att.lower())
1795
1796         # Generate a real list we can sort on properly
1797         new_list = [o for o in ctr.array if o.attid not in remove_attid]
1798
1799         if (len(wrong_attids) > 0):
1800             for o in new_list:
1801                 if o.attid in wrong_attids:
1802                     att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1803                     correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1804                     self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1805                     if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1806                                             % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1807                         self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1808                                     % (o.attid, correct_attid, att, attr, dn))
1809                         return
1810                     fix = True
1811                     o.attid = correct_attid
1812             if fix:
1813                 # Sort the array, (we changed the value so must re-sort)
1814                 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1815
1816         # If we did not already need to fix it, then ask about sorting
1817         if not fix:
1818             self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1819             if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1820                                     % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1821                 self.report('Not fixing %s on %s\n' % (attr, dn))
1822                 return
1823
1824             # The actual sort done is done at the top of the function
1825
1826         ctr.count = len(new_list)
1827         ctr.array = new_list
1828         replBlob = ndr_pack(repl)
1829
1830         nmsg = ldb.Message()
1831         nmsg.dn = dn
1832         nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1833         if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1834                                  "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1835                                  "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1836                           "Failed to fix attribute %s" % attr):
1837             self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
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         if self.write_ncs is not None and str(nc_root) in [str(x) for x in self.write_ncs]:
1943             instancetype |= dsdb.INSTANCE_TYPE_WRITE
1944
1945         return instancetype
1946
1947     def get_wellknown_sd(self, dn):
1948         for [sd_dn, descriptor_fn] in self.wellknown_sds:
1949             if dn == sd_dn:
1950                 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1951                 return ndr_unpack(security.descriptor,
1952                                   descriptor_fn(domain_sid,
1953                                                 name_map=self.name_map))
1954
1955         raise KeyError
1956
1957     def check_object(self, dn, attrs=['*']):
1958         '''check one object'''
1959         if self.verbose:
1960             self.report("Checking object %s" % dn)
1961
1962         # If we modify the pass-by-reference attrs variable, then we get a
1963         # replPropertyMetadata for every object that we check.
1964         attrs = list(attrs)
1965         if "dn" in map(str.lower, attrs):
1966             attrs.append("name")
1967         if "distinguishedname" in map(str.lower, attrs):
1968             attrs.append("name")
1969         if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1970             attrs.append("name")
1971         if 'name' in map(str.lower, attrs):
1972             attrs.append(dn.get_rdn_name())
1973             attrs.append("isDeleted")
1974             attrs.append("systemFlags")
1975         need_replPropertyMetaData = False
1976         if '*' in attrs:
1977             need_replPropertyMetaData = True
1978         else:
1979             for a in attrs:
1980                 linkID, _ = self.get_attr_linkID_and_reverse_name(a)
1981                 if linkID == 0:
1982                     continue
1983                 if linkID & 1:
1984                     continue
1985                 need_replPropertyMetaData = True
1986                 break
1987         if need_replPropertyMetaData:
1988             attrs.append("replPropertyMetaData")
1989         attrs.append("objectGUID")
1990
1991         try:
1992             sd_flags = 0
1993             sd_flags |= security.SECINFO_OWNER
1994             sd_flags |= security.SECINFO_GROUP
1995             sd_flags |= security.SECINFO_DACL
1996             sd_flags |= security.SECINFO_SACL
1997
1998             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1999                                     controls=[
2000                                         "extended_dn:1:1",
2001                                         "show_recycled:1",
2002                                         "show_deleted:1",
2003                                         "sd_flags:1:%d" % sd_flags,
2004                                         "reveal_internals:0",
2005                                     ],
2006                                     attrs=attrs)
2007         except ldb.LdbError as e10:
2008             (enum, estr) = e10.args
2009             if enum == ldb.ERR_NO_SUCH_OBJECT:
2010                 if self.in_transaction:
2011                     self.report("ERROR: Object %s disappeared during check" % dn)
2012                     return 1
2013                 return 0
2014             raise
2015         if len(res) != 1:
2016             self.report("ERROR: Object %s failed to load during check" % dn)
2017             return 1
2018         obj = res[0]
2019         error_count = 0
2020         set_attrs_from_md = set()
2021         set_attrs_seen = set()
2022         got_repl_property_meta_data = False
2023         got_objectclass = False
2024
2025         nc_dn = self.samdb.get_nc_root(obj.dn)
2026         try:
2027             deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
2028                                                              samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
2029         except KeyError:
2030             # We have no deleted objects DN for schema, and we check for this above for the other
2031             # NCs
2032             deleted_objects_dn = None
2033
2034         object_rdn_attr = None
2035         object_rdn_val = None
2036         name_val = None
2037         isDeleted = False
2038         systemFlags = 0
2039
2040         for attrname in obj:
2041             if attrname == 'dn' or attrname == "distinguishedName":
2042                 continue
2043
2044             if str(attrname).lower() == 'objectclass':
2045                 got_objectclass = True
2046
2047             if str(attrname).lower() == "name":
2048                 if len(obj[attrname]) != 1:
2049                     error_count += 1
2050                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2051                                 (len(obj[attrname]), attrname, str(obj.dn)))
2052                 else:
2053                     name_val = obj[attrname][0]
2054
2055             if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
2056                 object_rdn_attr = attrname
2057                 if len(obj[attrname]) != 1:
2058                     error_count += 1
2059                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2060                                 (len(obj[attrname]), attrname, str(obj.dn)))
2061                 else:
2062                     object_rdn_val = str(obj[attrname][0])
2063
2064             if str(attrname).lower() == 'isdeleted':
2065                 if str(obj[attrname][0]) != "FALSE":
2066                     isDeleted = True
2067
2068             if str(attrname).lower() == 'systemflags':
2069                 systemFlags = int(obj[attrname][0])
2070
2071             if str(attrname).lower() == 'replpropertymetadata':
2072                 if self.has_replmetadata_zero_invocationid(dn, obj[attrname][0]):
2073                     error_count += 1
2074                     self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname][0])
2075                     # We don't continue, as we may also have other fixes for this attribute
2076                     # based on what other attributes we see.
2077
2078                 try:
2079                     (set_attrs_from_md, list_attid_from_md, wrong_attids) \
2080                         = self.process_metadata(dn, obj[attrname][0])
2081                 except KeyError:
2082                     error_count += 1
2083                     self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
2084                     continue
2085
2086                 if len(set_attrs_from_md) < len(list_attid_from_md) \
2087                    or len(wrong_attids) > 0 \
2088                    or sorted(list_attid_from_md) != list_attid_from_md:
2089                     error_count += 1
2090                     self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname][0], wrong_attids)
2091
2092                 else:
2093                     # Here we check that the first attid is 0
2094                     # (objectClass).
2095                     if list_attid_from_md[0] != 0:
2096                         error_count += 1
2097                         self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2098                                     (attrname, str(dn)))
2099
2100                 got_repl_property_meta_data = True
2101                 continue
2102
2103             if str(attrname).lower() == 'ntsecuritydescriptor':
2104                 (sd, sd_broken) = self.process_sd(dn, obj)
2105                 if sd_broken is not None:
2106                     self.err_wrong_sd(dn, sd, sd_broken)
2107                     error_count += 1
2108                     continue
2109
2110                 if sd.owner_sid is None or sd.group_sid is None:
2111                     self.err_missing_sd_owner(dn, sd)
2112                     error_count += 1
2113                     continue
2114
2115                 if self.reset_well_known_acls:
2116                     try:
2117                         well_known_sd = self.get_wellknown_sd(dn)
2118                     except KeyError:
2119                         continue
2120
2121                     current_sd = ndr_unpack(security.descriptor,
2122                                             obj[attrname][0])
2123
2124                     diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
2125                     if diff != "":
2126                         self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
2127                         error_count += 1
2128                         continue
2129                 continue
2130
2131             if str(attrname).lower() == 'objectclass':
2132                 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
2133                 # Do not consider the attribute incorrect if:
2134                 #  - The sorted (alphabetically) list is the same, inclding case
2135                 #  - The first and last elements are the same
2136                 #
2137                 # This avoids triggering an error due to
2138                 # non-determinism in the sort routine in (at least)
2139                 # 4.3 and earlier, and the fact that any AUX classes
2140                 # in these attributes are also not sorted when
2141                 # imported from Windows (they are just in the reverse
2142                 # order of last set)
2143                 if sorted(normalised) != sorted(obj[attrname]) \
2144                    or normalised[0] != obj[attrname][0] \
2145                    or normalised[-1] != obj[attrname][-1]:
2146                     self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
2147                     error_count += 1
2148                 continue
2149
2150             if str(attrname).lower() == 'userparameters':
2151                 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
2152                     error_count += 1
2153                     self.err_short_userParameters(obj, attrname, obj[attrname])
2154                     continue
2155
2156                 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2157                     # This is the correct, normal prefix
2158                     continue
2159
2160                 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
2161                     # this is the typical prefix from a windows migration
2162                     error_count += 1
2163                     self.err_base64_userParameters(obj, attrname, obj[attrname])
2164                     continue
2165
2166                 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':
2167                     # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2168                     error_count += 1
2169                     self.err_utf8_userParameters(obj, attrname, obj[attrname])
2170                     continue
2171
2172                 elif len(obj[attrname][0]) % 2 != 0:
2173                     # This is a value that isn't even in length
2174                     error_count += 1
2175                     self.err_odd_userParameters(obj, attrname, obj[attrname])
2176                     continue
2177
2178                 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':
2179                     # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2180                     error_count += 1
2181                     self.err_doubled_userParameters(obj, attrname, obj[attrname])
2182                     continue
2183
2184             if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2185                 if obj[attrname][0] in self.attribute_or_class_ids:
2186                     error_count += 1
2187                     self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2188                                 % (attrname, obj.dn, obj[attrname][0]))
2189                 else:
2190                     self.attribute_or_class_ids.add(obj[attrname][0])
2191
2192             # check for empty attributes
2193             for val in obj[attrname]:
2194                 if val == '':
2195                     self.err_empty_attribute(dn, attrname)
2196                     error_count += 1
2197                     continue
2198
2199             # get the syntax oid for the attribute, so we can can have
2200             # special handling for some specific attribute types
2201             try:
2202                 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2203             except Exception as msg:
2204                 self.err_unknown_attribute(obj, attrname)
2205                 error_count += 1
2206                 continue
2207
2208             linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2209
2210             flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2211             if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2212                 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2213                 and not linkID):
2214                 set_attrs_seen.add(str(attrname).lower())
2215
2216             if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2217                               dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN]:
2218                 # it's some form of DN, do specialised checking on those
2219                 error_count += self.check_dn(obj, attrname, syntax_oid)
2220             else:
2221
2222                 values = set()
2223                 # check for incorrectly normalised attributes
2224                 for val in obj[attrname]:
2225                     values.add(val)
2226
2227                     normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2228                     if len(normalised) != 1 or normalised[0] != val:
2229                         self.err_normalise_mismatch(dn, attrname, obj[attrname])
2230                         error_count += 1
2231                         break
2232
2233                 if len(obj[attrname]) != len(values):
2234                     self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2235                     error_count += 1
2236                     break
2237
2238             if str(attrname).lower() == "instancetype":
2239                 calculated_instancetype = self.calculate_instancetype(dn)
2240                 if len(obj["instanceType"]) != 1 or int(obj["instanceType"][0]) != calculated_instancetype:
2241                     error_count += 1
2242                     self.err_wrong_instancetype(obj, calculated_instancetype)
2243
2244         if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
2245             error_count += 1
2246             self.err_missing_objectclass(dn)
2247
2248         if ("*" in attrs or "name" in map(str.lower, attrs)):
2249             if name_val is None:
2250                 error_count += 1
2251                 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2252             if object_rdn_attr is None:
2253                 error_count += 1
2254                 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2255
2256         if name_val is not None:
2257             parent_dn = None
2258             if isDeleted:
2259                 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2260                     parent_dn = deleted_objects_dn
2261             if parent_dn is None:
2262                 parent_dn = obj.dn.parent()
2263             expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2264             expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2265
2266             if obj.dn == deleted_objects_dn:
2267                 expected_dn = obj.dn
2268
2269             if expected_dn != obj.dn:
2270                 error_count += 1
2271                 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
2272             elif obj.dn.get_rdn_value() != object_rdn_val:
2273                 error_count += 1
2274                 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
2275
2276         show_dn = True
2277         if got_repl_property_meta_data:
2278             if obj.dn == deleted_objects_dn:
2279                 isDeletedAttId = 131120
2280                 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2281
2282                 expectedTimeDo = 2650466015990000000
2283                 originating = self.get_originating_time(obj["replPropertyMetaData"][0], isDeletedAttId)
2284                 if originating != expectedTimeDo:
2285                     if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2286                         nmsg = ldb.Message()
2287                         nmsg.dn = dn
2288                         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2289                         error_count += 1
2290                         self.samdb.modify(nmsg, controls=["provision:0"])
2291
2292                     else:
2293                         self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2294
2295             for att in set_attrs_seen.difference(set_attrs_from_md):
2296                 if show_dn:
2297                     self.report("On object %s" % dn)
2298                     show_dn = False
2299                 error_count += 1
2300                 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2301                 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2302                     self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2303                     continue
2304                 self.fix_metadata(obj, att)
2305
2306         if self.is_fsmo_role(dn):
2307             if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
2308                 self.err_no_fsmoRoleOwner(obj)
2309                 error_count += 1
2310
2311         try:
2312             if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2313                 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2314                                         controls=["show_recycled:1", "show_deleted:1"])
2315         except ldb.LdbError as e11:
2316             (enum, estr) = e11.args
2317             if enum == ldb.ERR_NO_SUCH_OBJECT:
2318                 self.err_missing_parent(obj)
2319                 error_count += 1
2320             else:
2321                 raise
2322
2323         if dn in self.deleted_objects_containers and '*' in attrs:
2324             if self.is_deleted_deleted_objects(obj):
2325                 self.err_deleted_deleted_objects(obj)
2326                 error_count += 1
2327
2328         for (dns_part, msg) in self.dns_partitions:
2329             if dn == dns_part and 'repsFrom' in obj:
2330                 location = "msDS-NC-Replica-Locations"
2331                 if self.samdb.am_rodc():
2332                     location = "msDS-NC-RO-Replica-Locations"
2333
2334                 if location not in msg:
2335                     # There are no replica locations!
2336                     self.err_replica_locations(obj, msg.dn, location)
2337                     error_count += 1
2338                     continue
2339
2340                 found = False
2341                 for loc in msg[location]:
2342                     if loc == self.samdb.get_dsServiceName():
2343                         found = True
2344                 if not found:
2345                     # This DC is not in the replica locations
2346                     self.err_replica_locations(obj, msg.dn, location)
2347                     error_count += 1
2348
2349         if dn == self.server_ref_dn:
2350             # Check we have a valid RID Set
2351             if "*" in attrs or "rIDSetReferences" in attrs:
2352                 if "rIDSetReferences" not in obj:
2353                     # NO RID SET reference
2354                     # We are RID master, allocate it.
2355                     error_count += 1
2356
2357                     if self.is_rid_master:
2358                         # Allocate a RID Set
2359                         if self.confirm_all('Allocate the missing RID set for RID master?',
2360                                             'fix_missing_rid_set_master'):
2361
2362                             # We don't have auto-transaction logic on
2363                             # extended operations, so we have to do it
2364                             # here.
2365
2366                             self.samdb.transaction_start()
2367
2368                             try:
2369                                 self.samdb.create_own_rid_set()
2370
2371                             except:
2372                                 self.samdb.transaction_cancel()
2373                                 raise
2374
2375                             self.samdb.transaction_commit()
2376
2377                     elif not self.samdb.am_rodc():
2378                         self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2379
2380         # Check some details of our own RID Set
2381         if dn == self.rid_set_dn:
2382             res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2383                                     attrs=["rIDAllocationPool",
2384                                            "rIDPreviousAllocationPool",
2385                                            "rIDUsedPool",
2386                                            "rIDNextRID"])
2387             if "rIDAllocationPool" not in res[0]:
2388                 self.report("No rIDAllocationPool found in %s" % dn)
2389                 error_count += 1
2390             else:
2391                 next_pool = int(res[0]["rIDAllocationPool"][0])
2392
2393                 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2394                 low = 0x00000000FFFFFFFF & next_pool
2395
2396                 if high <= low:
2397                     self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2398                     error_count += 1
2399
2400                 if "rIDNextRID" in res[0]:
2401                     next_free_rid = int(res[0]["rIDNextRID"][0])
2402                 else:
2403                     next_free_rid = 0
2404
2405                 if next_free_rid == 0:
2406                     next_free_rid = low
2407                 else:
2408                     next_free_rid += 1
2409
2410                 # Check the remainder of this pool for conflicts.  If
2411                 # ridalloc_allocate_rid() moves to a new pool, this
2412                 # will be above high, so we will stop.
2413                 while next_free_rid <= high:
2414                     sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2415                     try:
2416                         res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2417                                                 attrs=[])
2418                     except ldb.LdbError as e:
2419                         (enum, estr) = e.args
2420                         if enum != ldb.ERR_NO_SUCH_OBJECT:
2421                             raise
2422                         res = None
2423                     if res is not None:
2424                         self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2425                         error_count += 1
2426
2427                         if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2428                                             % (sid, dn),
2429                                             'fix_sid_rid_set_conflict'):
2430                             self.samdb.transaction_start()
2431
2432                             # This will burn RIDs, which will move
2433                             # past the conflict.  We then check again
2434                             # to see if the new RID conflicts, until
2435                             # the end of the current pool.  We don't
2436                             # look at the next pool to avoid burning
2437                             # all RIDs in one go in some strange
2438                             # failure case.
2439                             try:
2440                                 while True:
2441                                     allocated_rid = self.samdb.allocate_rid()
2442                                     if allocated_rid >= next_free_rid:
2443                                         next_free_rid = allocated_rid + 1
2444                                         break
2445                             except:
2446                                 self.samdb.transaction_cancel()
2447                                 raise
2448
2449                             self.samdb.transaction_commit()
2450                         else:
2451                             break
2452                     else:
2453                         next_free_rid += 1
2454
2455         return error_count
2456
2457     ################################################################
2458     # check special @ROOTDSE attributes
2459     def check_rootdse(self):
2460         '''check the @ROOTDSE special object'''
2461         dn = ldb.Dn(self.samdb, '@ROOTDSE')
2462         if self.verbose:
2463             self.report("Checking object %s" % dn)
2464         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2465         if len(res) != 1:
2466             self.report("Object %s disappeared during check" % dn)
2467             return 1
2468         obj = res[0]
2469         error_count = 0
2470
2471         # check that the dsServiceName is in GUID form
2472         if 'dsServiceName' not in obj:
2473             self.report('ERROR: dsServiceName missing in @ROOTDSE')
2474             return error_count + 1
2475
2476         if not obj['dsServiceName'][0].startswith('<GUID='):
2477             self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2478             error_count += 1
2479             if not self.confirm('Change dsServiceName to GUID form?'):
2480                 return error_count
2481             res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0].decode('utf8')),
2482                                     scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2483             guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2484             m = ldb.Message()
2485             m.dn = dn
2486             m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2487                                                     ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2488             if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2489                 self.report("Changed dsServiceName to GUID form")
2490         return error_count
2491
2492     ###############################################
2493     # re-index the database
2494
2495     def reindex_database(self):
2496         '''re-index the whole database'''
2497         m = ldb.Message()
2498         m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2499         m['add']    = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2500         m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2501         return self.do_modify(m, [], 're-indexed database', validate=False)
2502
2503     ###############################################
2504     # reset @MODULES
2505     def reset_modules(self):
2506         '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2507         m = ldb.Message()
2508         m.dn = ldb.Dn(self.samdb, "@MODULES")
2509         m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2510         return self.do_modify(m, [], 'reset @MODULES on database', validate=False)