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