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