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