PEP8: fix E301: expected 1 blank line, found 0
[amitay/samba.git] / python / samba / dbchecker.py
1 # Samba4 AD database checker
2 #
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 from __future__ import print_function
21 import ldb
22 import samba
23 import time
24 from base64 import b64decode
25 from samba import dsdb
26 from samba import common
27 from samba.dcerpc import misc
28 from samba.dcerpc import drsuapi
29 from samba.ndr import ndr_unpack, ndr_pack
30 from samba.dcerpc import drsblobs
31 from samba.common import dsdb_Dn
32 from samba.dcerpc import security
33 from samba.descriptor import get_wellknown_sds, get_diff_sds
34 from samba.auth import system_session, admin_session
35 from samba.netcmd import CommandError
36 from samba.netcmd.fsmo import get_fsmo_roleowner
37
38
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(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
1236             if is_deleted and not obj.dn in self.deleted_objects_containers and linkID:
1237                 # A fully deleted object should not have any linked
1238                 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1239                 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1240                 # Requirements)
1241                 self.err_undead_linked_attribute(obj, attrname, val)
1242                 error_count += 1
1243                 continue
1244             elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
1245                 # the target DN is not allowed to be deleted, unless the target DN is the
1246                 # special Deleted Objects container
1247                 error_count += 1
1248                 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
1249                 if local_usn:
1250                     if 'replPropertyMetaData' in res[0]:
1251                         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1252                                           str(res[0]['replPropertyMetadata']))
1253                         found_data = False
1254                         for o in repl.ctr.array:
1255                             if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1256                                 deleted_usn = o.local_usn
1257                                 if deleted_usn >= int(local_usn):
1258                                     # If the object was deleted after the link
1259                                     # was last modified then, clean it up here
1260                                     found_data = True
1261                                     break
1262
1263                         if found_data:
1264                             self.err_deleted_dn(obj.dn, attrname,
1265                                                 val, dsdb_dn, res[0].dn, True)
1266                             continue
1267
1268                 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
1269                 continue
1270
1271             # We should not check for incorrect
1272             # components on deleted links, as these are allowed to
1273             # go stale (we just need the GUID, not the name)
1274             rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
1275             rmd_flags = 0
1276             if rmd_blob is not None:
1277                 rmd_flags = int(rmd_blob)
1278
1279             # assert the DN matches in string form, where a reverse
1280             # link exists, otherwise (below) offer to fix it as a non-error.
1281             # The string form is essentially only kept for forensics,
1282             # as we always re-resolve by GUID in normal operations.
1283             if not rmd_flags & 1 and reverse_link_name is not None:
1284                 if str(res[0].dn) != str(dsdb_dn.dn):
1285                     error_count += 1
1286                     self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1287                                                           res[0].dn, "string")
1288                     continue
1289
1290             if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
1291                 error_count += 1
1292                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1293                                                       res[0].dn, "GUID")
1294                 continue
1295
1296             if res[0].dn.get_extended_component("SID") != dsdb_dn.dn.get_extended_component("SID"):
1297                 error_count += 1
1298                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1299                                                       res[0].dn, "SID")
1300                 continue
1301
1302             # Only for non-links, not even forward-only links
1303             # (otherwise this breaks repl_meta_data):
1304             #
1305             # Now we have checked the GUID and SID, offer to fix old
1306             # DN strings as a non-error (DNs, not links so no
1307             # backlink).  Samba does not maintain this string
1308             # otherwise, so we don't increment error_count.
1309             if reverse_link_name is None:
1310                 if linkID == 0 and str(res[0].dn) != str(dsdb_dn.dn):
1311                     # Pass in the old/bad DN without the <GUID=...> part,
1312                     # otherwise the LDB code will correct it on the way through
1313                     # (Note: we still want to preserve the DSDB DN prefix in the
1314                     # case of binary DNs)
1315                     bad_dn = dsdb_dn.prefix + dsdb_dn.dn.get_linearized()
1316                     self.err_dn_string_component_old(obj.dn, attrname, bad_dn,
1317                                                      dsdb_dn, res[0].dn)
1318                 continue
1319
1320             # check the reverse_link is correct if there should be one
1321             match_count = 0
1322             if reverse_link_name in res[0]:
1323                 for v in res[0][reverse_link_name]:
1324                     v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1325                     v_guid = v_dn.dn.get_extended_component("GUID")
1326                     v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1327                     v_rmd_flags = 0
1328                     if v_blob is not None:
1329                         v_rmd_flags = int(v_blob)
1330                     if v_rmd_flags & 1:
1331                         continue
1332                     if v_guid == obj_guid:
1333                         match_count += 1
1334
1335             if match_count != 1:
1336                 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1337                     if not linkID & 1:
1338                         # Forward binary multi-valued linked attribute
1339                         forward_count = 0
1340                         for w in obj[attrname]:
1341                             w_guid = dsdb_Dn(self.samdb, w.decode('utf8')).dn.get_extended_component("GUID")
1342                             if w_guid == guid:
1343                                 forward_count += 1
1344
1345                         if match_count == forward_count:
1346                             continue
1347             expected_count = 0
1348             for v in obj[attrname]:
1349                 v_dn = dsdb_Dn(self.samdb, v.decode('utf8'))
1350                 v_guid = v_dn.dn.get_extended_component("GUID")
1351                 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1352                 v_rmd_flags = 0
1353                 if v_blob is not None:
1354                     v_rmd_flags = int(v_blob)
1355                 if v_rmd_flags & 1:
1356                     continue
1357                 if v_guid == guid:
1358                     expected_count += 1
1359
1360             if match_count == expected_count:
1361                 continue
1362
1363             diff_count = expected_count - match_count
1364
1365             if linkID & 1:
1366                 # If there's a backward link on binary multi-valued linked attribute,
1367                 # let the check on the forward link remedy the value.
1368                 # UNLESS, there is no forward link detected.
1369                 if match_count == 0:
1370                     error_count += 1
1371                     self.err_orphaned_backlink(obj.dn, attrname,
1372                                                val, dsdb_dn.dn,
1373                                                reverse_link_name,
1374                                                reverse_syntax_oid)
1375                     continue
1376                 # Only warn here and let the forward link logic fix it.
1377                 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1378                             attrname, expected_count, str(obj.dn),
1379                             reverse_link_name, match_count, str(dsdb_dn.dn)))
1380                 continue
1381
1382             assert not target_is_deleted
1383
1384             self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1385                         attrname, expected_count, str(obj.dn),
1386                         reverse_link_name, match_count, str(dsdb_dn.dn)))
1387
1388             # Loop until the difference between the forward and
1389             # the backward links is resolved.
1390             while diff_count != 0:
1391                 error_count += 1
1392                 if diff_count > 0:
1393                     if match_count > 0 or diff_count > 1:
1394                         # TODO no method to fix these right now
1395                         self.report("ERROR: Can't fix missing "
1396                                     "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1397                         break
1398                     self.err_missing_backlink(obj, attrname,
1399                                               obj.dn.extended_str(),
1400                                               reverse_link_name,
1401                                               dsdb_dn.dn)
1402                     diff_count -= 1
1403                 else:
1404                     self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1405                                                obj.dn.extended_str(), obj.dn,
1406                                                attrname, syntax_oid)
1407                     diff_count += 1
1408
1409
1410         return error_count
1411
1412
1413     def get_originating_time(self, val, attid):
1414         '''Read metadata properties and return the originating time for
1415            a given attributeId.
1416
1417            :return: the originating time or 0 if not found
1418         '''
1419
1420         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1421         obj = repl.ctr
1422
1423         for o in repl.ctr.array:
1424             if o.attid == attid:
1425                 return o.originating_change_time
1426
1427         return 0
1428
1429     def process_metadata(self, dn, val):
1430         '''Read metadata properties and list attributes in it.
1431            raises KeyError if the attid is unknown.'''
1432
1433         set_att = set()
1434         wrong_attids = set()
1435         list_attid = []
1436         in_schema_nc = dn.is_child_of(self.schema_dn)
1437
1438         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1439         obj = repl.ctr
1440
1441         for o in repl.ctr.array:
1442             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1443             set_att.add(att.lower())
1444             list_attid.append(o.attid)
1445             correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1446                                                                              is_schema_nc=in_schema_nc)
1447             if correct_attid != o.attid:
1448                 wrong_attids.add(o.attid)
1449
1450         return (set_att, list_attid, wrong_attids)
1451
1452
1453     def fix_metadata(self, obj, attr):
1454         '''re-write replPropertyMetaData elements for a single attribute for a
1455         object. This is used to fix missing replPropertyMetaData elements'''
1456         guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1457         dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1458         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attr],
1459                                 controls=["search_options:1:2",
1460                                             "show_recycled:1"])
1461         msg = res[0]
1462         nmsg = ldb.Message()
1463         nmsg.dn = dn
1464         nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1465         if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1466                           "Failed to fix metadata for attribute %s" % attr):
1467             self.report("Fixed metadata for attribute %s" % attr)
1468
1469     def ace_get_effective_inherited_type(self, ace):
1470         if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1471             return None
1472
1473         check = False
1474         if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1475             check = True
1476         elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1477             check = True
1478         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1479             check = True
1480         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1481             check = True
1482
1483         if not check:
1484             return None
1485
1486         if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1487             return None
1488
1489         return str(ace.object.inherited_type)
1490
1491     def lookup_class_schemaIDGUID(self, cls):
1492         if cls in self.class_schemaIDGUID:
1493             return self.class_schemaIDGUID[cls]
1494
1495         flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1496         res = self.samdb.search(base=self.schema_dn,
1497                                 expression=flt,
1498                                 attrs=["schemaIDGUID"])
1499         t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1500
1501         self.class_schemaIDGUID[cls] = t
1502         return t
1503
1504     def process_sd(self, dn, obj):
1505         sd_attr = "nTSecurityDescriptor"
1506         sd_val = obj[sd_attr]
1507
1508         sd = ndr_unpack(security.descriptor, str(sd_val))
1509
1510         is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1511         if is_deleted:
1512             # we don't fix deleted objects
1513             return (sd, None)
1514
1515         sd_clean = security.descriptor()
1516         sd_clean.owner_sid = sd.owner_sid
1517         sd_clean.group_sid = sd.group_sid
1518         sd_clean.type = sd.type
1519         sd_clean.revision = sd.revision
1520
1521         broken = False
1522         last_inherited_type = None
1523
1524         aces = []
1525         if sd.sacl is not None:
1526             aces = sd.sacl.aces
1527         for i in range(0, len(aces)):
1528             ace = aces[i]
1529
1530             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1531                 sd_clean.sacl_add(ace)
1532                 continue
1533
1534             t = self.ace_get_effective_inherited_type(ace)
1535             if t is None:
1536                 continue
1537
1538             if last_inherited_type is not None:
1539                 if t != last_inherited_type:
1540                     # if it inherited from more than
1541                     # one type it's very likely to be broken
1542                     #
1543                     # If not the recalculation will calculate
1544                     # the same result.
1545                     broken = True
1546                 continue
1547
1548             last_inherited_type = t
1549
1550         aces = []
1551         if sd.dacl is not None:
1552             aces = sd.dacl.aces
1553         for i in range(0, len(aces)):
1554             ace = aces[i]
1555
1556             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1557                 sd_clean.dacl_add(ace)
1558                 continue
1559
1560             t = self.ace_get_effective_inherited_type(ace)
1561             if t is None:
1562                 continue
1563
1564             if last_inherited_type is not None:
1565                 if t != last_inherited_type:
1566                     # if it inherited from more than
1567                     # one type it's very likely to be broken
1568                     #
1569                     # If not the recalculation will calculate
1570                     # the same result.
1571                     broken = True
1572                 continue
1573
1574             last_inherited_type = t
1575
1576         if broken:
1577             return (sd_clean, sd)
1578
1579         if last_inherited_type is None:
1580             # ok
1581             return (sd, None)
1582
1583         cls = None
1584         try:
1585             cls = obj["objectClass"][-1]
1586         except KeyError as e:
1587             pass
1588
1589         if cls is None:
1590             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1591                                     attrs=["isDeleted", "objectClass"],
1592                                     controls=["show_recycled:1"])
1593             o = res[0]
1594             is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
1595             if is_deleted:
1596                 # we don't fix deleted objects
1597                 return (sd, None)
1598             cls = o["objectClass"][-1]
1599
1600         t = self.lookup_class_schemaIDGUID(cls)
1601
1602         if t != last_inherited_type:
1603             # broken
1604             return (sd_clean, sd)
1605
1606         # ok
1607         return (sd, None)
1608
1609     def err_wrong_sd(self, dn, sd, sd_broken):
1610         '''re-write the SD due to incorrect inherited ACEs'''
1611         sd_attr = "nTSecurityDescriptor"
1612         sd_val = ndr_pack(sd)
1613         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1614
1615         if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1616             self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1617             return
1618
1619         nmsg = ldb.Message()
1620         nmsg.dn = dn
1621         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1622         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1623                           "Failed to fix attribute %s" % sd_attr):
1624             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1625
1626     def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1627         '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1628         sd_attr = "nTSecurityDescriptor"
1629         sd_val = ndr_pack(sd)
1630         sd_old_val = ndr_pack(sd_old)
1631         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1632         if sd.owner_sid is not None:
1633             sd_flags |= security.SECINFO_OWNER
1634         if sd.group_sid is not None:
1635             sd_flags |= security.SECINFO_GROUP
1636
1637         if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1638             self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1639             return
1640
1641         m = ldb.Message()
1642         m.dn = dn
1643         m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1644         if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1645                           "Failed to reset attribute %s" % sd_attr):
1646             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1647
1648     def err_missing_sd_owner(self, dn, sd):
1649         '''re-write the SD due to a missing owner or group'''
1650         sd_attr = "nTSecurityDescriptor"
1651         sd_val = ndr_pack(sd)
1652         sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1653
1654         if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1655             self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1656             return
1657
1658         nmsg = ldb.Message()
1659         nmsg.dn = dn
1660         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1661
1662         # By setting the session_info to admin_session_info and
1663         # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1664         # flags we cause the descriptor module to set the correct
1665         # owner and group on the SD, replacing the None/NULL values
1666         # for owner_sid and group_sid currently present.
1667         #
1668         # The admin_session_info matches that used in provision, and
1669         # is the best guess we can make for an existing object that
1670         # hasn't had something specifically set.
1671         #
1672         # This is important for the dns related naming contexts.
1673         self.samdb.set_session_info(self.admin_session_info)
1674         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1675                           "Failed to fix metadata for attribute %s" % sd_attr):
1676             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1677         self.samdb.set_session_info(self.system_session_info)
1678
1679
1680     def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1681         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1682                           str(repl_meta_data))
1683         ctr = repl.ctr
1684         found = False
1685         for o in ctr.array:
1686             # Search for a zero invocationID
1687             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1688                 continue
1689
1690             found = True
1691             self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1692                            version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1693                            but should be non-zero.  Proposed fix is to set to our invocationID (%s).'''
1694                         % (dn, o.attid, o.version,
1695                            time.ctime(samba.nttime2unix(o.originating_change_time)),
1696                            self.samdb.get_invocation_id()))
1697
1698         return found
1699
1700
1701     def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1702         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1703                           str(repl_meta_data))
1704         ctr = repl.ctr
1705         now = samba.unix2nttime(int(time.time()))
1706         found = False
1707         for o in ctr.array:
1708             # Search for a zero invocationID
1709             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1710                 continue
1711
1712             found = True
1713             seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1714             o.version = o.version + 1
1715             o.originating_change_time = now
1716             o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1717             o.originating_usn = seq
1718             o.local_usn = seq
1719
1720         if found:
1721             replBlob = ndr_pack(repl)
1722             msg = ldb.Message()
1723             msg.dn = dn
1724
1725             if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1726                                     % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1727                 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1728                 return
1729
1730             nmsg = ldb.Message()
1731             nmsg.dn = dn
1732             nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1733             if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1734                                      "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1735                               "Failed to fix attribute %s" % attr):
1736                 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1737
1738
1739     def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1740         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1741                           str(repl_meta_data))
1742         ctr = repl.ctr
1743         for o in ctr.array:
1744             # Search for an invalid attid
1745             try:
1746                 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1747             except KeyError:
1748                 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1749                 return
1750
1751
1752     def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1753         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1754                           str(repl_meta_data))
1755         fix = False
1756
1757         set_att = set()
1758         remove_attid = set()
1759         hash_att = {}
1760
1761         in_schema_nc = dn.is_child_of(self.schema_dn)
1762
1763         ctr = repl.ctr
1764         # Sort the array, except for the last element.  This strange
1765         # construction, creating a new list, due to bugs in samba's
1766         # array handling in IDL generated objects.
1767         ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1768         # Now walk it in reverse, so we see the low (and so incorrect,
1769         # the correct values are above 0x80000000) values first and
1770         # remove the 'second' value we see.
1771         for o in reversed(ctr.array):
1772             print("%s: 0x%08x" % (dn, o.attid))
1773             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1774             if att.lower() in set_att:
1775                 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1776                 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1777                                         % (attr, dn, o.attid, att, hash_att[att].attid),
1778                                         'fix_replmetadata_duplicate_attid'):
1779                     self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1780                                 % (o.attid, att, attr, dn))
1781                     return
1782                 fix = True
1783                 remove_attid.add(o.attid)
1784                 # We want to set the metadata for the most recent
1785                 # update to have been applied locally, that is the metadata
1786                 # matching the (eg string) value in the attribute
1787                 if o.local_usn > hash_att[att].local_usn:
1788                     # This is always what we would have sent over DRS,
1789                     # because the DRS server will have sent the
1790                     # msDS-IntID, but with the values from both
1791                     # attribute entries.
1792                     hash_att[att].version = o.version
1793                     hash_att[att].originating_change_time = o.originating_change_time
1794                     hash_att[att].originating_invocation_id = o.originating_invocation_id
1795                     hash_att[att].originating_usn = o.originating_usn
1796                     hash_att[att].local_usn = o.local_usn
1797
1798                 # Do not re-add the value to the set or overwrite the hash value
1799                 continue
1800
1801             hash_att[att] = o
1802             set_att.add(att.lower())
1803
1804         # Generate a real list we can sort on properly
1805         new_list = [o for o in ctr.array if o.attid not in remove_attid]
1806
1807         if (len(wrong_attids) > 0):
1808             for o in new_list:
1809                 if o.attid in wrong_attids:
1810                     att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1811                     correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1812                     self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1813                     if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1814                                             % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1815                         self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1816                                     % (o.attid, correct_attid, att, attr, dn))
1817                         return
1818                     fix = True
1819                     o.attid = correct_attid
1820             if fix:
1821                 # Sort the array, (we changed the value so must re-sort)
1822                 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1823
1824         # If we did not already need to fix it, then ask about sorting
1825         if not fix:
1826             self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1827             if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1828                                     % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1829                 self.report('Not fixing %s on %s\n' % (attr, dn))
1830                 return
1831
1832             # The actual sort done is done at the top of the function
1833
1834         ctr.count = len(new_list)
1835         ctr.array = new_list
1836         replBlob = ndr_pack(repl)
1837
1838         nmsg = ldb.Message()
1839         nmsg.dn = dn
1840         nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1841         if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1842                                  "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1843                                  "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1844                           "Failed to fix attribute %s" % attr):
1845             self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1846
1847
1848     def is_deleted_deleted_objects(self, obj):
1849         faulty = False
1850         if "description" not in obj:
1851             self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1852             faulty = True
1853         if "showInAdvancedViewOnly" not in obj or obj['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1854             self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1855             faulty = True
1856         if "objectCategory" not in obj:
1857             self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1858             faulty = True
1859         if "isCriticalSystemObject" not in obj or obj['isCriticalSystemObject'][0].upper() == 'FALSE':
1860             self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1861             faulty = True
1862         if "isRecycled" in obj:
1863             self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1864             faulty = True
1865         if "isDeleted" in obj and obj['isDeleted'][0].upper() == 'FALSE':
1866             self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1867             faulty = True
1868         if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1869                                         obj['objectClass'][0] != 'top' or
1870                                         obj['objectClass'][1] != 'container'):
1871             self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1872             faulty = True
1873         if "systemFlags" not in obj or obj['systemFlags'][0] != '-1946157056':
1874             self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1875             faulty = True
1876         return faulty
1877
1878     def err_deleted_deleted_objects(self, obj):
1879         nmsg = ldb.Message()
1880         nmsg.dn = dn = obj.dn
1881
1882         if "description" not in obj:
1883             nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1884         if "showInAdvancedViewOnly" not in obj:
1885             nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1886         if "objectCategory" not in obj:
1887             nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1888         if "isCriticalSystemObject" not in obj:
1889             nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1890         if "isRecycled" in obj:
1891             nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1892
1893         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1894         nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1895         nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1896
1897         if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1898                                 % (dn), 'fix_deleted_deleted_objects'):
1899             self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1900             return
1901
1902         if self.do_modify(nmsg, ["relax:0"],
1903                           "Failed to fix Deleted Objects container  %s" % dn):
1904             self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1905
1906     def err_replica_locations(self, obj, cross_ref, attr):
1907         nmsg = ldb.Message()
1908         nmsg.dn = cross_ref
1909         target = self.samdb.get_dsServiceName()
1910
1911         if self.samdb.am_rodc():
1912             self.report('Not fixing %s for the RODC' % (attr, obj.dn))
1913             return
1914
1915         if not self.confirm_all('Add yourself to the replica locations for %s?'
1916                                 % (obj.dn), 'fix_replica_locations'):
1917             self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1918             return
1919
1920         nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1921         if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1922             self.report("Fixed %s for %s" % (attr, obj.dn))
1923
1924     def is_fsmo_role(self, dn):
1925         if dn == self.samdb.domain_dn:
1926             return True
1927         if dn == self.infrastructure_dn:
1928             return True
1929         if dn == self.naming_dn:
1930             return True
1931         if dn == self.schema_dn:
1932             return True
1933         if dn == self.rid_dn:
1934             return True
1935
1936         return False
1937
1938     def calculate_instancetype(self, dn):
1939         instancetype = 0
1940         nc_root = self.samdb.get_nc_root(dn)
1941         if dn == nc_root:
1942             instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1943             try:
1944                 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1945             except ldb.LdbError as e4:
1946                 (enum, estr) = e4.args
1947                 if enum != ldb.ERR_NO_SUCH_OBJECT:
1948                     raise
1949             else:
1950                 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1951
1952         if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1953             instancetype |= dsdb.INSTANCE_TYPE_WRITE
1954
1955         return instancetype
1956
1957     def get_wellknown_sd(self, dn):
1958         for [sd_dn, descriptor_fn] in self.wellknown_sds:
1959             if dn == sd_dn:
1960                 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1961                 return ndr_unpack(security.descriptor,
1962                                   descriptor_fn(domain_sid,
1963                                                 name_map=self.name_map))
1964
1965         raise KeyError
1966
1967     def check_object(self, dn, attrs=['*']):
1968         '''check one object'''
1969         if self.verbose:
1970             self.report("Checking object %s" % dn)
1971
1972         # If we modify the pass-by-reference attrs variable, then we get a
1973         # replPropertyMetadata for every object that we check.
1974         attrs = list(attrs)
1975         if "dn" in map(str.lower, attrs):
1976             attrs.append("name")
1977         if "distinguishedname" in map(str.lower, attrs):
1978             attrs.append("name")
1979         if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1980             attrs.append("name")
1981         if 'name' in map(str.lower, attrs):
1982             attrs.append(dn.get_rdn_name())
1983             attrs.append("isDeleted")
1984             attrs.append("systemFlags")
1985         need_replPropertyMetaData = False
1986         if '*' in attrs:
1987             need_replPropertyMetaData = True
1988         else:
1989             for a in attrs:
1990                 linkID, _ = self.get_attr_linkID_and_reverse_name(a)
1991                 if linkID == 0:
1992                     continue
1993                 if linkID & 1:
1994                     continue
1995                 need_replPropertyMetaData = True
1996                 break
1997         if need_replPropertyMetaData:
1998             attrs.append("replPropertyMetaData")
1999         attrs.append("objectGUID")
2000
2001         try:
2002             sd_flags = 0
2003             sd_flags |= security.SECINFO_OWNER
2004             sd_flags |= security.SECINFO_GROUP
2005             sd_flags |= security.SECINFO_DACL
2006             sd_flags |= security.SECINFO_SACL
2007
2008             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
2009                                     controls=[
2010                                         "extended_dn:1:1",
2011                                         "show_recycled:1",
2012                                         "show_deleted:1",
2013                                         "sd_flags:1:%d" % sd_flags,
2014                                         "reveal_internals:0",
2015                                     ],
2016                                     attrs=attrs)
2017         except ldb.LdbError as e10:
2018             (enum, estr) = e10.args
2019             if enum == ldb.ERR_NO_SUCH_OBJECT:
2020                 if self.in_transaction:
2021                     self.report("ERROR: Object %s disappeared during check" % dn)
2022                     return 1
2023                 return 0
2024             raise
2025         if len(res) != 1:
2026             self.report("ERROR: Object %s failed to load during check" % dn)
2027             return 1
2028         obj = res[0]
2029         error_count = 0
2030         set_attrs_from_md = set()
2031         set_attrs_seen = set()
2032         got_repl_property_meta_data = False
2033         got_objectclass = False
2034
2035         nc_dn = self.samdb.get_nc_root(obj.dn)
2036         try:
2037             deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
2038                                                              samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
2039         except KeyError:
2040             # We have no deleted objects DN for schema, and we check for this above for the other
2041             # NCs
2042             deleted_objects_dn = None
2043
2044
2045         object_rdn_attr = None
2046         object_rdn_val = None
2047         name_val = None
2048         isDeleted = False
2049         systemFlags = 0
2050
2051         for attrname in obj:
2052             if attrname == 'dn' or attrname == "distinguishedName":
2053                 continue
2054
2055             if str(attrname).lower() == 'objectclass':
2056                 got_objectclass = True
2057
2058             if str(attrname).lower() == "name":
2059                 if len(obj[attrname]) != 1:
2060                     error_count += 1
2061                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2062                                 (len(obj[attrname]), attrname, str(obj.dn)))
2063                 else:
2064                     name_val = obj[attrname][0]
2065
2066             if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
2067                 object_rdn_attr = attrname
2068                 if len(obj[attrname]) != 1:
2069                     error_count += 1
2070                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
2071                                 (len(obj[attrname]), attrname, str(obj.dn)))
2072                 else:
2073                     object_rdn_val = obj[attrname][0]
2074
2075             if str(attrname).lower() == 'isdeleted':
2076                 if obj[attrname][0] != "FALSE":
2077                     isDeleted = True
2078
2079             if str(attrname).lower() == 'systemflags':
2080                 systemFlags = int(obj[attrname][0])
2081
2082             if str(attrname).lower() == 'replpropertymetadata':
2083                 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
2084                     error_count += 1
2085                     self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
2086                     # We don't continue, as we may also have other fixes for this attribute
2087                     # based on what other attributes we see.
2088
2089                 try:
2090                     (set_attrs_from_md, list_attid_from_md, wrong_attids) \
2091                         = self.process_metadata(dn, obj[attrname])
2092                 except KeyError:
2093                     error_count += 1
2094                     self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
2095                     continue
2096
2097                 if len(set_attrs_from_md) < len(list_attid_from_md) \
2098                    or len(wrong_attids) > 0 \
2099                    or sorted(list_attid_from_md) != list_attid_from_md:
2100                     error_count += 1
2101                     self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname], wrong_attids)
2102
2103                 else:
2104                     # Here we check that the first attid is 0
2105                     # (objectClass).
2106                     if list_attid_from_md[0] != 0:
2107                         error_count += 1
2108                         self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
2109                                     (attrname, str(dn)))
2110
2111                 got_repl_property_meta_data = True
2112                 continue
2113
2114             if str(attrname).lower() == 'ntsecuritydescriptor':
2115                 (sd, sd_broken) = self.process_sd(dn, obj)
2116                 if sd_broken is not None:
2117                     self.err_wrong_sd(dn, sd, sd_broken)
2118                     error_count += 1
2119                     continue
2120
2121                 if sd.owner_sid is None or sd.group_sid is None:
2122                     self.err_missing_sd_owner(dn, sd)
2123                     error_count += 1
2124                     continue
2125
2126                 if self.reset_well_known_acls:
2127                     try:
2128                         well_known_sd = self.get_wellknown_sd(dn)
2129                     except KeyError:
2130                         continue
2131
2132                     current_sd = ndr_unpack(security.descriptor,
2133                                             str(obj[attrname][0]))
2134
2135                     diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
2136                     if diff != "":
2137                         self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
2138                         error_count += 1
2139                         continue
2140                 continue
2141
2142             if str(attrname).lower() == 'objectclass':
2143                 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
2144                 # Do not consider the attribute incorrect if:
2145                 #  - The sorted (alphabetically) list is the same, inclding case
2146                 #  - The first and last elements are the same
2147                 #
2148                 # This avoids triggering an error due to
2149                 # non-determinism in the sort routine in (at least)
2150                 # 4.3 and earlier, and the fact that any AUX classes
2151                 # in these attributes are also not sorted when
2152                 # imported from Windows (they are just in the reverse
2153                 # order of last set)
2154                 if sorted(normalised) != sorted(obj[attrname]) \
2155                    or normalised[0] != obj[attrname][0] \
2156                    or normalised[-1] != obj[attrname][-1]:
2157                     self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
2158                     error_count += 1
2159                 continue
2160
2161             if str(attrname).lower() == 'userparameters':
2162                 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
2163                     error_count += 1
2164                     self.err_short_userParameters(obj, attrname, obj[attrname])
2165                     continue
2166
2167                 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
2168                     # This is the correct, normal prefix
2169                     continue
2170
2171                 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
2172                     # this is the typical prefix from a windows migration
2173                     error_count += 1
2174                     self.err_base64_userParameters(obj, attrname, obj[attrname])
2175                     continue
2176
2177                 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':
2178                     # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
2179                     error_count += 1
2180                     self.err_utf8_userParameters(obj, attrname, obj[attrname])
2181                     continue
2182
2183                 elif len(obj[attrname][0]) % 2 != 0:
2184                     # This is a value that isn't even in length
2185                     error_count += 1
2186                     self.err_odd_userParameters(obj, attrname, obj[attrname])
2187                     continue
2188
2189                 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':
2190                     # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2191                     error_count += 1
2192                     self.err_doubled_userParameters(obj, attrname, obj[attrname])
2193                     continue
2194
2195             if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2196                 if obj[attrname][0] in self.attribute_or_class_ids:
2197                     error_count += 1
2198                     self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2199                                 % (attrname, obj.dn, obj[attrname][0]))
2200                 else:
2201                     self.attribute_or_class_ids.add(obj[attrname][0])
2202
2203             # check for empty attributes
2204             for val in obj[attrname]:
2205                 if val == '':
2206                     self.err_empty_attribute(dn, attrname)
2207                     error_count += 1
2208                     continue
2209
2210             # get the syntax oid for the attribute, so we can can have
2211             # special handling for some specific attribute types
2212             try:
2213                 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2214             except Exception as msg:
2215                 self.err_unknown_attribute(obj, attrname)
2216                 error_count += 1
2217                 continue
2218
2219             linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2220
2221             flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2222             if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2223                 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2224                 and not linkID):
2225                 set_attrs_seen.add(str(attrname).lower())
2226
2227             if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2228                                dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN]:
2229                 # it's some form of DN, do specialised checking on those
2230                 error_count += self.check_dn(obj, attrname, syntax_oid)
2231             else:
2232
2233                 values = set()
2234                 # check for incorrectly normalised attributes
2235                 for val in obj[attrname]:
2236                     values.add(str(val))
2237
2238                     normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2239                     if len(normalised) != 1 or normalised[0] != val:
2240                         self.err_normalise_mismatch(dn, attrname, obj[attrname])
2241                         error_count += 1
2242                         break
2243
2244                 if len(obj[attrname]) != len(values):
2245                     self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2246                     error_count += 1
2247                     break
2248
2249             if str(attrname).lower() == "instancetype":
2250                 calculated_instancetype = self.calculate_instancetype(dn)
2251                 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
2252                     error_count += 1
2253                     self.err_wrong_instancetype(obj, calculated_instancetype)
2254
2255         if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
2256             error_count += 1
2257             self.err_missing_objectclass(dn)
2258
2259         if ("*" in attrs or "name" in map(str.lower, attrs)):
2260             if name_val is None:
2261                 error_count += 1
2262                 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2263             if object_rdn_attr is None:
2264                 error_count += 1
2265                 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2266
2267         if name_val is not None:
2268             parent_dn = None
2269             if isDeleted:
2270                 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2271                     parent_dn = deleted_objects_dn
2272             if parent_dn is None:
2273                 parent_dn = obj.dn.parent()
2274             expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2275             expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2276
2277             if obj.dn == deleted_objects_dn:
2278                 expected_dn = obj.dn
2279
2280             if expected_dn != obj.dn:
2281                 error_count += 1
2282                 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
2283             elif obj.dn.get_rdn_value() != object_rdn_val:
2284                 error_count += 1
2285                 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
2286
2287         show_dn = True
2288         if got_repl_property_meta_data:
2289             if obj.dn == deleted_objects_dn:
2290                 isDeletedAttId = 131120
2291                 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2292
2293                 expectedTimeDo = 2650466015990000000
2294                 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
2295                 if originating != expectedTimeDo:
2296                     if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2297                         nmsg = ldb.Message()
2298                         nmsg.dn = dn
2299                         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2300                         error_count += 1
2301                         self.samdb.modify(nmsg, controls=["provision:0"])
2302
2303                     else:
2304                         self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2305
2306             for att in set_attrs_seen.difference(set_attrs_from_md):
2307                 if show_dn:
2308                     self.report("On object %s" % dn)
2309                     show_dn = False
2310                 error_count += 1
2311                 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2312                 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2313                     self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2314                     continue
2315                 self.fix_metadata(obj, att)
2316
2317         if self.is_fsmo_role(dn):
2318             if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
2319                 self.err_no_fsmoRoleOwner(obj)
2320                 error_count += 1
2321
2322         try:
2323             if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2324                 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2325                                         controls=["show_recycled:1", "show_deleted:1"])
2326         except ldb.LdbError as e11:
2327             (enum, estr) = e11.args
2328             if enum == ldb.ERR_NO_SUCH_OBJECT:
2329                 self.err_missing_parent(obj)
2330                 error_count += 1
2331             else:
2332                 raise
2333
2334         if dn in self.deleted_objects_containers and '*' in attrs:
2335             if self.is_deleted_deleted_objects(obj):
2336                 self.err_deleted_deleted_objects(obj)
2337                 error_count += 1
2338
2339         for (dns_part, msg) in self.dns_partitions:
2340             if dn == dns_part and 'repsFrom' in obj:
2341                 location = "msDS-NC-Replica-Locations"
2342                 if self.samdb.am_rodc():
2343                     location = "msDS-NC-RO-Replica-Locations"
2344
2345                 if location not in msg:
2346                     # There are no replica locations!
2347                     self.err_replica_locations(obj, msg.dn, location)
2348                     error_count += 1
2349                     continue
2350
2351                 found = False
2352                 for loc in msg[location]:
2353                     if loc == self.samdb.get_dsServiceName():
2354                         found = True
2355                 if not found:
2356                     # This DC is not in the replica locations
2357                     self.err_replica_locations(obj, msg.dn, location)
2358                     error_count += 1
2359
2360         if dn == self.server_ref_dn:
2361             # Check we have a valid RID Set
2362             if "*" in attrs or "rIDSetReferences" in attrs:
2363                 if "rIDSetReferences" not in obj:
2364                     # NO RID SET reference
2365                     # We are RID master, allocate it.
2366                     error_count += 1
2367
2368                     if self.is_rid_master:
2369                         # Allocate a RID Set
2370                         if self.confirm_all('Allocate the missing RID set for RID master?',
2371                                             'fix_missing_rid_set_master'):
2372
2373                             # We don't have auto-transaction logic on
2374                             # extended operations, so we have to do it
2375                             # here.
2376
2377                             self.samdb.transaction_start()
2378
2379                             try:
2380                                 self.samdb.create_own_rid_set()
2381
2382                             except:
2383                                 self.samdb.transaction_cancel()
2384                                 raise
2385
2386                             self.samdb.transaction_commit()
2387
2388
2389                     elif not self.samdb.am_rodc():
2390                         self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2391
2392
2393         # Check some details of our own RID Set
2394         if dn == self.rid_set_dn:
2395             res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2396                                     attrs=["rIDAllocationPool",
2397                                            "rIDPreviousAllocationPool",
2398                                            "rIDUsedPool",
2399                                            "rIDNextRID"])
2400             if "rIDAllocationPool" not in res[0]:
2401                 self.report("No rIDAllocationPool found in %s" % dn)
2402                 error_count += 1
2403             else:
2404                 next_pool = int(res[0]["rIDAllocationPool"][0])
2405
2406                 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2407                 low = 0x00000000FFFFFFFF & next_pool
2408
2409                 if high <= low:
2410                     self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2411                     error_count += 1
2412
2413                 if "rIDNextRID" in res[0]:
2414                     next_free_rid = int(res[0]["rIDNextRID"][0])
2415                 else:
2416                     next_free_rid = 0
2417
2418                 if next_free_rid == 0:
2419                     next_free_rid = low
2420                 else:
2421                     next_free_rid += 1
2422
2423                 # Check the remainder of this pool for conflicts.  If
2424                 # ridalloc_allocate_rid() moves to a new pool, this
2425                 # will be above high, so we will stop.
2426                 while next_free_rid <= high:
2427                     sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2428                     try:
2429                         res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2430                                                 attrs=[])
2431                     except ldb.LdbError as e:
2432                         (enum, estr) = e.args
2433                         if enum != ldb.ERR_NO_SUCH_OBJECT:
2434                             raise
2435                         res = None
2436                     if res is not None:
2437                         self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2438                         error_count += 1
2439
2440                         if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2441                                             % (sid, dn),
2442                                             'fix_sid_rid_set_conflict'):
2443                             self.samdb.transaction_start()
2444
2445                             # This will burn RIDs, which will move
2446                             # past the conflict.  We then check again
2447                             # to see if the new RID conflicts, until
2448                             # the end of the current pool.  We don't
2449                             # look at the next pool to avoid burning
2450                             # all RIDs in one go in some strange
2451                             # failure case.
2452                             try:
2453                                 while True:
2454                                     allocated_rid = self.samdb.allocate_rid()
2455                                     if allocated_rid >= next_free_rid:
2456                                         next_free_rid = allocated_rid + 1
2457                                         break
2458                             except:
2459                                 self.samdb.transaction_cancel()
2460                                 raise
2461
2462                             self.samdb.transaction_commit()
2463                         else:
2464                             break
2465                     else:
2466                         next_free_rid += 1
2467
2468
2469         return error_count
2470
2471     ################################################################
2472     # check special @ROOTDSE attributes
2473     def check_rootdse(self):
2474         '''check the @ROOTDSE special object'''
2475         dn = ldb.Dn(self.samdb, '@ROOTDSE')
2476         if self.verbose:
2477             self.report("Checking object %s" % dn)
2478         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2479         if len(res) != 1:
2480             self.report("Object %s disappeared during check" % dn)
2481             return 1
2482         obj = res[0]
2483         error_count = 0
2484
2485         # check that the dsServiceName is in GUID form
2486         if not 'dsServiceName' in obj:
2487             self.report('ERROR: dsServiceName missing in @ROOTDSE')
2488             return error_count + 1
2489
2490         if not obj['dsServiceName'][0].startswith('<GUID='):
2491             self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2492             error_count += 1
2493             if not self.confirm('Change dsServiceName to GUID form?'):
2494                 return error_count
2495             res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0].decode('utf8')),
2496                                     scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2497             guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2498             m = ldb.Message()
2499             m.dn = dn
2500             m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2501                                                     ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2502             if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2503                 self.report("Changed dsServiceName to GUID form")
2504         return error_count
2505
2506
2507     ###############################################
2508     # re-index the database
2509
2510
2511     def reindex_database(self):
2512         '''re-index the whole database'''
2513         m = ldb.Message()
2514         m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2515         m['add']    = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2516         m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2517         return self.do_modify(m, [], 're-indexed database', validate=False)
2518
2519     ###############################################
2520     # reset @MODULES
2521     def reset_modules(self):
2522         '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2523         m = ldb.Message()
2524         m.dn = ldb.Dn(self.samdb, "@MODULES")
2525         m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2526         return self.do_modify(m, [], 'reset @MODULES on database', validate=False)