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