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