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