dbcheck: remove indentation level
[metze/samba/wip.git] / python / samba / dbchecker.py
1 # Samba4 AD database checker
2 #
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import ldb
21 import samba
22 import time
23 from base64 import b64decode
24 from samba import dsdb
25 from samba import common
26 from samba.dcerpc import misc
27 from samba.dcerpc import drsuapi
28 from samba.ndr import ndr_unpack, ndr_pack
29 from samba.dcerpc import drsblobs
30 from samba.common import dsdb_Dn
31 from samba.dcerpc import security
32 from samba.descriptor import get_wellknown_sds, get_diff_sds
33 from samba.auth import system_session, admin_session
34 from samba.netcmd import CommandError
35 from samba.netcmd.fsmo import get_fsmo_roleowner
36
37
38 class dbcheck(object):
39     """check a SAM database for errors"""
40
41     def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
42                  yes=False, quiet=False, in_transaction=False,
43                  reset_well_known_acls=False):
44         self.samdb = samdb
45         self.dict_oid_name = None
46         self.samdb_schema = (samdb_schema or samdb)
47         self.verbose = verbose
48         self.fix = fix
49         self.yes = yes
50         self.quiet = quiet
51         self.remove_all_unknown_attributes = False
52         self.remove_all_empty_attributes = False
53         self.fix_all_normalisation = False
54         self.fix_all_duplicates = False
55         self.fix_all_DN_GUIDs = False
56         self.fix_all_binary_dn = False
57         self.remove_implausible_deleted_DN_links = False
58         self.remove_plausible_deleted_DN_links = False
59         self.fix_all_string_dn_component_mismatch = False
60         self.fix_all_GUID_dn_component_mismatch = False
61         self.fix_all_SID_dn_component_mismatch = False
62         self.fix_all_old_dn_string_component_mismatch = False
63         self.fix_all_metadata = False
64         self.fix_time_metadata = False
65         self.fix_undead_linked_attributes = False
66         self.fix_all_missing_backlinks = False
67         self.fix_all_orphaned_backlinks = False
68         self.fix_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         for val in obj[attrname]:
880             dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
881
882             # all DNs should have a GUID component
883             guid = dsdb_dn.dn.get_extended_component("GUID")
884             if guid is None:
885                 error_count += 1
886                 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
887                     "missing GUID")
888                 continue
889
890             guidstr = str(misc.GUID(guid))
891             attrs = ['isDeleted', 'replPropertyMetaData']
892
893             if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
894                 fixing_msDS_HasInstantiatedNCs = True
895                 attrs.append("instanceType")
896             else:
897                 fixing_msDS_HasInstantiatedNCs = False
898
899             linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
900             if reverse_link_name is not None:
901                 attrs.append(reverse_link_name)
902
903             # check its the right GUID
904             try:
905                 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
906                                         attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
907                                                                "reveal_internals:0"
908                                         ])
909             except ldb.LdbError, (enum, estr):
910                 if enum != ldb.ERR_NO_SUCH_OBJECT:
911                     raise
912
913                 # We don't always want to
914                 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
915                                                                   attrname,
916                                                                   val,
917                                                                   dsdb_dn)
918                 continue
919
920             if fixing_msDS_HasInstantiatedNCs:
921                 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
922                 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
923
924                 if str(dsdb_dn) != val:
925                     error_count +=1
926                     self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
927                     continue
928
929             # now we have two cases - the source object might or might not be deleted
930             is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
931             target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
932
933
934             if is_deleted and not obj.dn in self.deleted_objects_containers and linkID:
935                 # A fully deleted object should not have any linked
936                 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
937                 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
938                 # Requirements)
939                 self.err_undead_linked_attribute(obj, attrname, val)
940                 error_count += 1
941                 continue
942             elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
943                 # the target DN is not allowed to be deleted, unless the target DN is the
944                 # special Deleted Objects container
945                 error_count += 1
946                 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
947                 if local_usn:
948                     if 'replPropertyMetaData' in res[0]:
949                         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
950                                           str(res[0]['replPropertyMetadata']))
951                         found_data = False
952                         for o in repl.ctr.array:
953                             if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
954                                 deleted_usn = o.local_usn
955                                 if deleted_usn >= int(local_usn):
956                                     # If the object was deleted after the link
957                                     # was last modified then, clean it up here
958                                     found_data = True
959                                     break
960
961                         if found_data:
962                             self.err_deleted_dn(obj.dn, attrname,
963                                                 val, dsdb_dn, res[0].dn, True)
964                             continue
965
966                 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
967                 continue
968
969             # We should not check for incorrect
970             # components on deleted links, as these are allowed to
971             # go stale (we just need the GUID, not the name)
972             rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
973             if rmd_blob is not None:
974                 rmd_flags = int(rmd_blob)
975                 if rmd_flags & 1:
976                     continue
977
978             # assert the DN matches in string form, where a reverse
979             # link exists, otherwise (below) offer to fix it as a non-error.
980             # The string form is essentially only kept for forensics,
981             # as we always re-resolve by GUID in normal operations.
982             if reverse_link_name is not None:
983                 if str(res[0].dn) != str(dsdb_dn.dn):
984                     error_count += 1
985                     self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
986                                                           res[0].dn, "string")
987                     continue
988
989             if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
990                 error_count += 1
991                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
992                                                       res[0].dn, "GUID")
993                 continue
994
995             if res[0].dn.get_extended_component("SID") != dsdb_dn.dn.get_extended_component("SID"):
996                 error_count += 1
997                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
998                                                       res[0].dn, "SID")
999                 continue
1000
1001             # Now we have checked the GUID and SID, offer to fix old
1002             # DN strings as a non-error (for forward links with no
1003             # backlink).  Samba does not maintain this string
1004             # otherwise, so we don't increment error_count.
1005             if reverse_link_name is None:
1006                 if str(res[0].dn) != str(dsdb_dn.dn):
1007                     self.err_dn_string_component_old(obj.dn, attrname, val, dsdb_dn,
1008                                                      res[0].dn)
1009                 continue
1010
1011             # check the reverse_link is correct if there should be one
1012             match_count = 0
1013             if reverse_link_name in res[0]:
1014                 for v in res[0][reverse_link_name]:
1015                     v_guid = dsdb_Dn(self.samdb, v).dn.get_extended_component("GUID")
1016                     if v_guid == obj_guid:
1017                         match_count += 1
1018             if match_count != 1:
1019                 reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
1020                 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1021                     if not linkID & 1:
1022                         # Forward binary multi-valued linked attribute
1023                         forward_count = 0
1024                         for w in obj[attrname]:
1025                             w_guid = dsdb_Dn(self.samdb, w).dn.get_extended_component("GUID")
1026                             if w_guid == guid:
1027                                 forward_count += 1
1028
1029                         if match_count == forward_count:
1030                             continue
1031
1032                         error_count += 1
1033
1034                         # Add or remove the missing number of backlinks
1035                         diff_count = forward_count - match_count
1036
1037                         # Loop until the difference between the forward and
1038                         # the backward links is resolved.
1039                         while diff_count != 0:
1040                             if diff_count > 0:
1041                                 # self.err_missing_backlink(obj, attrname,
1042                                 #                          obj.dn.extended_str(),
1043                                 #                          reverse_link_name,
1044                                 #                          dsdb_dn.dn)
1045                                 # diff_count -= 1
1046                                 # TODO no method to fix these right now
1047                                 self.report("ERROR: Can't fix missing "
1048                                             "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1049                                 break
1050                             else:
1051                                 self.err_orphaned_backlink(res[0], reverse_link_name,
1052                                                            obj.dn.extended_str(), attrname,
1053                                                            dsdb_dn.dn)
1054                                 diff_count += 1
1055
1056                     else:
1057                         # If there's a backward link on binary multi-valued linked attribute,
1058                         # let the check on the forward link remedy the value.
1059                         # UNLESS, there is no forward link detected.
1060                         if match_count == 0:
1061                             self.err_orphaned_backlink(obj, attrname,
1062                                                        val, reverse_link_name,
1063                                                        dsdb_dn.dn)
1064
1065                     continue
1066
1067                 error_count += 1
1068                 if linkID & 1:
1069                     # Backlink exists, but forward link does not
1070                     # Delete the hanging backlink
1071                     self.err_orphaned_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
1072                 else:
1073                     # Forward link exists, but backlink does not
1074                     # Add the missing backlink (if the target object is not Deleted Objects?)
1075                     if not target_is_deleted:
1076                         self.err_missing_backlink(obj, attrname, obj.dn.extended_str(), reverse_link_name, dsdb_dn.dn)
1077                 continue
1078
1079
1080
1081
1082         return error_count
1083
1084
1085     def get_originating_time(self, val, attid):
1086         '''Read metadata properties and return the originating time for
1087            a given attributeId.
1088
1089            :return: the originating time or 0 if not found
1090         '''
1091
1092         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1093         obj = repl.ctr
1094
1095         for o in repl.ctr.array:
1096             if o.attid == attid:
1097                 return o.originating_change_time
1098
1099         return 0
1100
1101     def process_metadata(self, dn, val):
1102         '''Read metadata properties and list attributes in it.
1103            raises KeyError if the attid is unknown.'''
1104
1105         set_att = set()
1106         wrong_attids = set()
1107         list_attid = []
1108         in_schema_nc = dn.is_child_of(self.schema_dn)
1109
1110         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1111         obj = repl.ctr
1112
1113         for o in repl.ctr.array:
1114             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1115             set_att.add(att.lower())
1116             list_attid.append(o.attid)
1117             correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1118                                                                              is_schema_nc=in_schema_nc)
1119             if correct_attid != o.attid:
1120                 wrong_attids.add(o.attid)
1121
1122         return (set_att, list_attid, wrong_attids)
1123
1124
1125     def fix_metadata(self, obj, attr):
1126         '''re-write replPropertyMetaData elements for a single attribute for a
1127         object. This is used to fix missing replPropertyMetaData elements'''
1128         guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1129         dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1130         res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
1131                                 controls = ["search_options:1:2",
1132                                             "show_recycled:1"])
1133         msg = res[0]
1134         nmsg = ldb.Message()
1135         nmsg.dn = dn
1136         nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1137         if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1138                           "Failed to fix metadata for attribute %s" % attr):
1139             self.report("Fixed metadata for attribute %s" % attr)
1140
1141     def ace_get_effective_inherited_type(self, ace):
1142         if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1143             return None
1144
1145         check = False
1146         if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1147             check = True
1148         elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1149             check = True
1150         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1151             check = True
1152         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1153             check = True
1154
1155         if not check:
1156             return None
1157
1158         if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1159             return None
1160
1161         return str(ace.object.inherited_type)
1162
1163     def lookup_class_schemaIDGUID(self, cls):
1164         if cls in self.class_schemaIDGUID:
1165             return self.class_schemaIDGUID[cls]
1166
1167         flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1168         res = self.samdb.search(base=self.schema_dn,
1169                                 expression=flt,
1170                                 attrs=["schemaIDGUID"])
1171         t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1172
1173         self.class_schemaIDGUID[cls] = t
1174         return t
1175
1176     def process_sd(self, dn, obj):
1177         sd_attr = "nTSecurityDescriptor"
1178         sd_val = obj[sd_attr]
1179
1180         sd = ndr_unpack(security.descriptor, str(sd_val))
1181
1182         is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1183         if is_deleted:
1184             # we don't fix deleted objects
1185             return (sd, None)
1186
1187         sd_clean = security.descriptor()
1188         sd_clean.owner_sid = sd.owner_sid
1189         sd_clean.group_sid = sd.group_sid
1190         sd_clean.type = sd.type
1191         sd_clean.revision = sd.revision
1192
1193         broken = False
1194         last_inherited_type = None
1195
1196         aces = []
1197         if sd.sacl is not None:
1198             aces = sd.sacl.aces
1199         for i in range(0, len(aces)):
1200             ace = aces[i]
1201
1202             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1203                 sd_clean.sacl_add(ace)
1204                 continue
1205
1206             t = self.ace_get_effective_inherited_type(ace)
1207             if t is None:
1208                 continue
1209
1210             if last_inherited_type is not None:
1211                 if t != last_inherited_type:
1212                     # if it inherited from more than
1213                     # one type it's very likely to be broken
1214                     #
1215                     # If not the recalculation will calculate
1216                     # the same result.
1217                     broken = True
1218                 continue
1219
1220             last_inherited_type = t
1221
1222         aces = []
1223         if sd.dacl is not None:
1224             aces = sd.dacl.aces
1225         for i in range(0, len(aces)):
1226             ace = aces[i]
1227
1228             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1229                 sd_clean.dacl_add(ace)
1230                 continue
1231
1232             t = self.ace_get_effective_inherited_type(ace)
1233             if t is None:
1234                 continue
1235
1236             if last_inherited_type is not None:
1237                 if t != last_inherited_type:
1238                     # if it inherited from more than
1239                     # one type it's very likely to be broken
1240                     #
1241                     # If not the recalculation will calculate
1242                     # the same result.
1243                     broken = True
1244                 continue
1245
1246             last_inherited_type = t
1247
1248         if broken:
1249             return (sd_clean, sd)
1250
1251         if last_inherited_type is None:
1252             # ok
1253             return (sd, None)
1254
1255         cls = None
1256         try:
1257             cls = obj["objectClass"][-1]
1258         except KeyError, e:
1259             pass
1260
1261         if cls is None:
1262             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1263                                     attrs=["isDeleted", "objectClass"],
1264                                     controls=["show_recycled:1"])
1265             o = res[0]
1266             is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
1267             if is_deleted:
1268                 # we don't fix deleted objects
1269                 return (sd, None)
1270             cls = o["objectClass"][-1]
1271
1272         t = self.lookup_class_schemaIDGUID(cls)
1273
1274         if t != last_inherited_type:
1275             # broken
1276             return (sd_clean, sd)
1277
1278         # ok
1279         return (sd, None)
1280
1281     def err_wrong_sd(self, dn, sd, sd_broken):
1282         '''re-write the SD due to incorrect inherited ACEs'''
1283         sd_attr = "nTSecurityDescriptor"
1284         sd_val = ndr_pack(sd)
1285         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1286
1287         if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1288             self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1289             return
1290
1291         nmsg = ldb.Message()
1292         nmsg.dn = dn
1293         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1294         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1295                           "Failed to fix attribute %s" % sd_attr):
1296             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1297
1298     def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1299         '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1300         sd_attr = "nTSecurityDescriptor"
1301         sd_val = ndr_pack(sd)
1302         sd_old_val = ndr_pack(sd_old)
1303         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1304         if sd.owner_sid is not None:
1305             sd_flags |= security.SECINFO_OWNER
1306         if sd.group_sid is not None:
1307             sd_flags |= security.SECINFO_GROUP
1308
1309         if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1310             self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1311             return
1312
1313         m = ldb.Message()
1314         m.dn = dn
1315         m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1316         if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1317                           "Failed to reset attribute %s" % sd_attr):
1318             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1319
1320     def err_missing_sd_owner(self, dn, sd):
1321         '''re-write the SD due to a missing owner or group'''
1322         sd_attr = "nTSecurityDescriptor"
1323         sd_val = ndr_pack(sd)
1324         sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1325
1326         if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1327             self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1328             return
1329
1330         nmsg = ldb.Message()
1331         nmsg.dn = dn
1332         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1333
1334         # By setting the session_info to admin_session_info and
1335         # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1336         # flags we cause the descriptor module to set the correct
1337         # owner and group on the SD, replacing the None/NULL values
1338         # for owner_sid and group_sid currently present.
1339         #
1340         # The admin_session_info matches that used in provision, and
1341         # is the best guess we can make for an existing object that
1342         # hasn't had something specifically set.
1343         #
1344         # This is important for the dns related naming contexts.
1345         self.samdb.set_session_info(self.admin_session_info)
1346         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1347                           "Failed to fix metadata for attribute %s" % sd_attr):
1348             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1349         self.samdb.set_session_info(self.system_session_info)
1350
1351
1352     def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1353         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1354                           str(repl_meta_data))
1355         ctr = repl.ctr
1356         found = False
1357         for o in ctr.array:
1358             # Search for a zero invocationID
1359             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1360                 continue
1361
1362             found = True
1363             self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1364                            version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1365                            but should be non-zero.  Proposed fix is to set to our invocationID (%s).'''
1366                         % (dn, o.attid, o.version,
1367                            time.ctime(samba.nttime2unix(o.originating_change_time)),
1368                            self.samdb.get_invocation_id()))
1369
1370         return found
1371
1372
1373     def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1374         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1375                           str(repl_meta_data))
1376         ctr = repl.ctr
1377         now = samba.unix2nttime(int(time.time()))
1378         found = False
1379         for o in ctr.array:
1380             # Search for a zero invocationID
1381             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1382                 continue
1383
1384             found = True
1385             seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1386             o.version = o.version + 1
1387             o.originating_change_time = now
1388             o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1389             o.originating_usn = seq
1390             o.local_usn = seq
1391
1392         if found:
1393             replBlob = ndr_pack(repl)
1394             msg = ldb.Message()
1395             msg.dn = dn
1396
1397             if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1398                                     % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1399                 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1400                 return
1401
1402             nmsg = ldb.Message()
1403             nmsg.dn = dn
1404             nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1405             if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1406                                      "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1407                               "Failed to fix attribute %s" % attr):
1408                 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1409
1410
1411     def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1412         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1413                           str(repl_meta_data))
1414         ctr = repl.ctr
1415         for o in ctr.array:
1416             # Search for an invalid attid
1417             try:
1418                 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1419             except KeyError:
1420                 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1421                 return
1422
1423
1424     def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1425         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1426                           str(repl_meta_data))
1427         fix = False
1428
1429         set_att = set()
1430         remove_attid = set()
1431         hash_att = {}
1432
1433         in_schema_nc = dn.is_child_of(self.schema_dn)
1434
1435         ctr = repl.ctr
1436         # Sort the array, except for the last element.  This strange
1437         # construction, creating a new list, due to bugs in samba's
1438         # array handling in IDL generated objects.
1439         ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1440         # Now walk it in reverse, so we see the low (and so incorrect,
1441         # the correct values are above 0x80000000) values first and
1442         # remove the 'second' value we see.
1443         for o in reversed(ctr.array):
1444             print "%s: 0x%08x" % (dn, o.attid)
1445             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1446             if att.lower() in set_att:
1447                 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1448                 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1449                                         % (attr, dn, o.attid, att, hash_att[att].attid),
1450                                         'fix_replmetadata_duplicate_attid'):
1451                     self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1452                                 % (o.attid, att, attr, dn))
1453                     return
1454                 fix = True
1455                 remove_attid.add(o.attid)
1456                 # We want to set the metadata for the most recent
1457                 # update to have been applied locally, that is the metadata
1458                 # matching the (eg string) value in the attribute
1459                 if o.local_usn > hash_att[att].local_usn:
1460                     # This is always what we would have sent over DRS,
1461                     # because the DRS server will have sent the
1462                     # msDS-IntID, but with the values from both
1463                     # attribute entries.
1464                     hash_att[att].version = o.version
1465                     hash_att[att].originating_change_time = o.originating_change_time
1466                     hash_att[att].originating_invocation_id = o.originating_invocation_id
1467                     hash_att[att].originating_usn = o.originating_usn
1468                     hash_att[att].local_usn = o.local_usn
1469
1470                 # Do not re-add the value to the set or overwrite the hash value
1471                 continue
1472
1473             hash_att[att] = o
1474             set_att.add(att.lower())
1475
1476         # Generate a real list we can sort on properly
1477         new_list = [o for o in ctr.array if o.attid not in remove_attid]
1478
1479         if (len(wrong_attids) > 0):
1480             for o in new_list:
1481                 if o.attid in wrong_attids:
1482                     att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1483                     correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1484                     self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1485                     if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1486                                             % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1487                         self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1488                                     % (o.attid, correct_attid, att, attr, dn))
1489                         return
1490                     fix = True
1491                     o.attid = correct_attid
1492             if fix:
1493                 # Sort the array, (we changed the value so must re-sort)
1494                 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1495
1496         # If we did not already need to fix it, then ask about sorting
1497         if not fix:
1498             self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1499             if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1500                                     % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1501                 self.report('Not fixing %s on %s\n' % (attr, dn))
1502                 return
1503
1504             # The actual sort done is done at the top of the function
1505
1506         ctr.count = len(new_list)
1507         ctr.array = new_list
1508         replBlob = ndr_pack(repl)
1509
1510         nmsg = ldb.Message()
1511         nmsg.dn = dn
1512         nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1513         if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1514                              "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1515                              "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1516                       "Failed to fix attribute %s" % attr):
1517             self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1518
1519
1520     def is_deleted_deleted_objects(self, obj):
1521         faulty = False
1522         if "description" not in obj:
1523             self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1524             faulty = True
1525         if "showInAdvancedViewOnly" not in obj or obj['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1526             self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1527             faulty = True
1528         if "objectCategory" not in obj:
1529             self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1530             faulty = True
1531         if "isCriticalSystemObject" not in obj or obj['isCriticalSystemObject'][0].upper() == 'FALSE':
1532             self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1533             faulty = True
1534         if "isRecycled" in obj:
1535             self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1536             faulty = True
1537         if "isDeleted" in obj and obj['isDeleted'][0].upper() == 'FALSE':
1538             self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1539             faulty = True
1540         if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1541                                         obj['objectClass'][0] != 'top' or
1542                                         obj['objectClass'][1] != 'container'):
1543             self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1544             faulty = True
1545         if "systemFlags" not in obj or obj['systemFlags'][0] != '-1946157056':
1546             self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1547             faulty = True
1548         return faulty
1549
1550     def err_deleted_deleted_objects(self, obj):
1551         nmsg = ldb.Message()
1552         nmsg.dn = dn = obj.dn
1553
1554         if "description" not in obj:
1555             nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1556         if "showInAdvancedViewOnly" not in obj:
1557             nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1558         if "objectCategory" not in obj:
1559             nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1560         if "isCriticalSystemObject" not in obj:
1561             nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1562         if "isRecycled" in obj:
1563             nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1564
1565         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1566         nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1567         nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1568
1569         if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1570                                 % (dn), 'fix_deleted_deleted_objects'):
1571             self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1572             return
1573
1574         if self.do_modify(nmsg, ["relax:0"],
1575                           "Failed to fix Deleted Objects container  %s" % dn):
1576             self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1577
1578     def err_replica_locations(self, obj, cross_ref, attr):
1579         nmsg = ldb.Message()
1580         nmsg.dn = cross_ref
1581         target = self.samdb.get_dsServiceName()
1582
1583         if self.samdb.am_rodc():
1584             self.report('Not fixing %s for the RODC' % (attr, obj.dn))
1585             return
1586
1587         if not self.confirm_all('Add yourself to the replica locations for %s?'
1588                                 % (obj.dn), 'fix_replica_locations'):
1589             self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1590             return
1591
1592         nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1593         if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1594             self.report("Fixed %s for %s" % (attr, obj.dn))
1595
1596     def is_fsmo_role(self, dn):
1597         if dn == self.samdb.domain_dn:
1598             return True
1599         if dn == self.infrastructure_dn:
1600             return True
1601         if dn == self.naming_dn:
1602             return True
1603         if dn == self.schema_dn:
1604             return True
1605         if dn == self.rid_dn:
1606             return True
1607
1608         return False
1609
1610     def calculate_instancetype(self, dn):
1611         instancetype = 0
1612         nc_root = self.samdb.get_nc_root(dn)
1613         if dn == nc_root:
1614             instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1615             try:
1616                 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1617             except ldb.LdbError, (enum, estr):
1618                 if enum != ldb.ERR_NO_SUCH_OBJECT:
1619                     raise
1620             else:
1621                 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1622
1623         if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1624             instancetype |= dsdb.INSTANCE_TYPE_WRITE
1625
1626         return instancetype
1627
1628     def get_wellknown_sd(self, dn):
1629         for [sd_dn, descriptor_fn] in self.wellknown_sds:
1630             if dn == sd_dn:
1631                 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1632                 return ndr_unpack(security.descriptor,
1633                                   descriptor_fn(domain_sid,
1634                                                 name_map=self.name_map))
1635
1636         raise KeyError
1637
1638     def check_object(self, dn, attrs=['*']):
1639         '''check one object'''
1640         if self.verbose:
1641             self.report("Checking object %s" % dn)
1642
1643         # If we modify the pass-by-reference attrs variable, then we get a
1644         # replPropertyMetadata for every object that we check.
1645         attrs = list(attrs)
1646         if "dn" in map(str.lower, attrs):
1647             attrs.append("name")
1648         if "distinguishedname" in map(str.lower, attrs):
1649             attrs.append("name")
1650         if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1651             attrs.append("name")
1652         if 'name' in map(str.lower, attrs):
1653             attrs.append(dn.get_rdn_name())
1654             attrs.append("isDeleted")
1655             attrs.append("systemFlags")
1656         if '*' in attrs:
1657             attrs.append("replPropertyMetaData")
1658         else:
1659             attrs.append("objectGUID")
1660
1661         try:
1662             sd_flags = 0
1663             sd_flags |= security.SECINFO_OWNER
1664             sd_flags |= security.SECINFO_GROUP
1665             sd_flags |= security.SECINFO_DACL
1666             sd_flags |= security.SECINFO_SACL
1667
1668             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1669                                     controls=[
1670                                         "extended_dn:1:1",
1671                                         "show_recycled:1",
1672                                         "show_deleted:1",
1673                                         "sd_flags:1:%d" % sd_flags,
1674                                         "reveal_internals:0",
1675                                     ],
1676                                     attrs=attrs)
1677         except ldb.LdbError, (enum, estr):
1678             if enum == ldb.ERR_NO_SUCH_OBJECT:
1679                 if self.in_transaction:
1680                     self.report("ERROR: Object %s disappeared during check" % dn)
1681                     return 1
1682                 return 0
1683             raise
1684         if len(res) != 1:
1685             self.report("ERROR: Object %s failed to load during check" % dn)
1686             return 1
1687         obj = res[0]
1688         error_count = 0
1689         set_attrs_from_md = set()
1690         set_attrs_seen = set()
1691         got_repl_property_meta_data = False
1692         got_objectclass = False
1693
1694         nc_dn = self.samdb.get_nc_root(obj.dn)
1695         try:
1696             deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
1697                                                              samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
1698         except KeyError:
1699             # We have no deleted objects DN for schema, and we check for this above for the other
1700             # NCs
1701             deleted_objects_dn = None
1702
1703
1704         object_rdn_attr = None
1705         object_rdn_val = None
1706         name_val = None
1707         isDeleted = False
1708         systemFlags = 0
1709
1710         for attrname in obj:
1711             if attrname == 'dn' or attrname == "distinguishedName":
1712                 continue
1713
1714             if str(attrname).lower() == 'objectclass':
1715                 got_objectclass = True
1716
1717             if str(attrname).lower() == "name":
1718                 if len(obj[attrname]) != 1:
1719                     error_count += 1
1720                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1721                                 (len(obj[attrname]), attrname, str(obj.dn)))
1722                 else:
1723                     name_val = obj[attrname][0]
1724
1725             if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
1726                 object_rdn_attr = attrname
1727                 if len(obj[attrname]) != 1:
1728                     error_count += 1
1729                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1730                                 (len(obj[attrname]), attrname, str(obj.dn)))
1731                 else:
1732                     object_rdn_val = obj[attrname][0]
1733
1734             if str(attrname).lower() == 'isdeleted':
1735                 if obj[attrname][0] != "FALSE":
1736                     isDeleted = True
1737
1738             if str(attrname).lower() == 'systemflags':
1739                 systemFlags = int(obj[attrname][0])
1740
1741             if str(attrname).lower() == 'replpropertymetadata':
1742                 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
1743                     error_count += 1
1744                     self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
1745                     # We don't continue, as we may also have other fixes for this attribute
1746                     # based on what other attributes we see.
1747
1748                 try:
1749                     (set_attrs_from_md, list_attid_from_md, wrong_attids) \
1750                         = self.process_metadata(dn, obj[attrname])
1751                 except KeyError:
1752                     error_count += 1
1753                     self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
1754                     continue
1755
1756                 if len(set_attrs_from_md) < len(list_attid_from_md) \
1757                    or len(wrong_attids) > 0 \
1758                    or sorted(list_attid_from_md) != list_attid_from_md:
1759                     error_count +=1
1760                     self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname], wrong_attids)
1761
1762                 else:
1763                     # Here we check that the first attid is 0
1764                     # (objectClass).
1765                     if list_attid_from_md[0] != 0:
1766                         error_count += 1
1767                         self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
1768                                     (attrname, str(dn)))
1769
1770                 got_repl_property_meta_data = True
1771                 continue
1772
1773             if str(attrname).lower() == 'ntsecuritydescriptor':
1774                 (sd, sd_broken) = self.process_sd(dn, obj)
1775                 if sd_broken is not None:
1776                     self.err_wrong_sd(dn, sd, sd_broken)
1777                     error_count += 1
1778                     continue
1779
1780                 if sd.owner_sid is None or sd.group_sid is None:
1781                     self.err_missing_sd_owner(dn, sd)
1782                     error_count += 1
1783                     continue
1784
1785                 if self.reset_well_known_acls:
1786                     try:
1787                         well_known_sd = self.get_wellknown_sd(dn)
1788                     except KeyError:
1789                         continue
1790
1791                     current_sd = ndr_unpack(security.descriptor,
1792                                             str(obj[attrname][0]))
1793
1794                     diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
1795                     if diff != "":
1796                         self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
1797                         error_count += 1
1798                         continue
1799                 continue
1800
1801             if str(attrname).lower() == 'objectclass':
1802                 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
1803                 # Do not consider the attribute incorrect if:
1804                 #  - The sorted (alphabetically) list is the same, inclding case
1805                 #  - The first and last elements are the same
1806                 #
1807                 # This avoids triggering an error due to
1808                 # non-determinism in the sort routine in (at least)
1809                 # 4.3 and earlier, and the fact that any AUX classes
1810                 # in these attributes are also not sorted when
1811                 # imported from Windows (they are just in the reverse
1812                 # order of last set)
1813                 if sorted(normalised) != sorted(obj[attrname]) \
1814                    or normalised[0] != obj[attrname][0] \
1815                    or normalised[-1] != obj[attrname][-1]:
1816                     self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
1817                     error_count += 1
1818                 continue
1819
1820             if str(attrname).lower() == 'userparameters':
1821                 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
1822                     error_count += 1
1823                     self.err_short_userParameters(obj, attrname, obj[attrname])
1824                     continue
1825
1826                 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1827                     # This is the correct, normal prefix
1828                     continue
1829
1830                 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1831                     # this is the typical prefix from a windows migration
1832                     error_count += 1
1833                     self.err_base64_userParameters(obj, attrname, obj[attrname])
1834                     continue
1835
1836                 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':
1837                     # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1838                     error_count += 1
1839                     self.err_utf8_userParameters(obj, attrname, obj[attrname])
1840                     continue
1841
1842                 elif len(obj[attrname][0]) % 2 != 0:
1843                     # This is a value that isn't even in length
1844                     error_count += 1
1845                     self.err_odd_userParameters(obj, attrname, obj[attrname])
1846                     continue
1847
1848                 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':
1849                     # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
1850                     error_count += 1
1851                     self.err_doubled_userParameters(obj, attrname, obj[attrname])
1852                     continue
1853
1854             if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
1855                 if obj[attrname][0] in self.attribute_or_class_ids:
1856                     error_count += 1
1857                     self.report('Error: %s %s on %s already exists as an attributeId or governsId'
1858                                 % (attrname, obj.dn, obj[attrname][0]))
1859                 else:
1860                     self.attribute_or_class_ids.add(obj[attrname][0])
1861
1862             # check for empty attributes
1863             for val in obj[attrname]:
1864                 if val == '':
1865                     self.err_empty_attribute(dn, attrname)
1866                     error_count += 1
1867                     continue
1868
1869             # get the syntax oid for the attribute, so we can can have
1870             # special handling for some specific attribute types
1871             try:
1872                 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1873             except Exception, msg:
1874                 self.err_unknown_attribute(obj, attrname)
1875                 error_count += 1
1876                 continue
1877
1878             linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1879
1880             flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
1881             if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
1882                 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
1883                 and not linkID):
1884                 set_attrs_seen.add(str(attrname).lower())
1885
1886             if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
1887                                dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
1888                 # it's some form of DN, do specialised checking on those
1889                 error_count += self.check_dn(obj, attrname, syntax_oid)
1890             else:
1891
1892                 values = set()
1893                 # check for incorrectly normalised attributes
1894                 for val in obj[attrname]:
1895                     values.add(str(val))
1896
1897                     normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
1898                     if len(normalised) != 1 or normalised[0] != val:
1899                         self.err_normalise_mismatch(dn, attrname, obj[attrname])
1900                         error_count += 1
1901                         break
1902
1903                 if len(obj[attrname]) != len(values):
1904                     self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
1905                     error_count += 1
1906                     break
1907
1908             if str(attrname).lower() == "instancetype":
1909                 calculated_instancetype = self.calculate_instancetype(dn)
1910                 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
1911                     error_count += 1
1912                     self.err_wrong_instancetype(obj, calculated_instancetype)
1913
1914         if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
1915             error_count += 1
1916             self.err_missing_objectclass(dn)
1917
1918         if ("*" in attrs or "name" in map(str.lower, attrs)):
1919             if name_val is None:
1920                 error_count += 1
1921                 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
1922             if object_rdn_attr is None:
1923                 error_count += 1
1924                 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
1925
1926         if name_val is not None:
1927             parent_dn = None
1928             if isDeleted:
1929                 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
1930                     parent_dn = deleted_objects_dn
1931             if parent_dn is None:
1932                 parent_dn = obj.dn.parent()
1933             expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
1934             expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
1935
1936             if obj.dn == deleted_objects_dn:
1937                 expected_dn = obj.dn
1938
1939             if expected_dn != obj.dn:
1940                 error_count += 1
1941                 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
1942             elif obj.dn.get_rdn_value() != object_rdn_val:
1943                 error_count += 1
1944                 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
1945
1946         show_dn = True
1947         if got_repl_property_meta_data:
1948             if obj.dn == deleted_objects_dn:
1949                 isDeletedAttId = 131120
1950                 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
1951
1952                 expectedTimeDo = 2650466015990000000
1953                 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
1954                 if originating != expectedTimeDo:
1955                     if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
1956                         nmsg = ldb.Message()
1957                         nmsg.dn = dn
1958                         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1959                         error_count += 1
1960                         self.samdb.modify(nmsg, controls=["provision:0"])
1961
1962                     else:
1963                         self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
1964
1965             for att in set_attrs_seen.difference(set_attrs_from_md):
1966                 if show_dn:
1967                     self.report("On object %s" % dn)
1968                     show_dn = False
1969                 error_count += 1
1970                 self.report("ERROR: Attribute %s not present in replication metadata" % att)
1971                 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
1972                     self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
1973                     continue
1974                 self.fix_metadata(obj, att)
1975
1976         if self.is_fsmo_role(dn):
1977             if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
1978                 self.err_no_fsmoRoleOwner(obj)
1979                 error_count += 1
1980
1981         try:
1982             if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
1983                 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
1984                                         controls=["show_recycled:1", "show_deleted:1"])
1985         except ldb.LdbError, (enum, estr):
1986             if enum == ldb.ERR_NO_SUCH_OBJECT:
1987                 self.err_missing_parent(obj)
1988                 error_count += 1
1989             else:
1990                 raise
1991
1992         if dn in self.deleted_objects_containers and '*' in attrs:
1993             if self.is_deleted_deleted_objects(obj):
1994                 self.err_deleted_deleted_objects(obj)
1995                 error_count += 1
1996
1997         for (dns_part, msg) in self.dns_partitions:
1998             if dn == dns_part and 'repsFrom' in obj:
1999                 location = "msDS-NC-Replica-Locations"
2000                 if self.samdb.am_rodc():
2001                     location = "msDS-NC-RO-Replica-Locations"
2002
2003                 if location not in msg:
2004                     # There are no replica locations!
2005                     self.err_replica_locations(obj, msg.dn, location)
2006                     error_count += 1
2007                     continue
2008
2009                 found = False
2010                 for loc in msg[location]:
2011                     if loc == self.samdb.get_dsServiceName():
2012                         found = True
2013                 if not found:
2014                     # This DC is not in the replica locations
2015                     self.err_replica_locations(obj, msg.dn, location)
2016                     error_count += 1
2017
2018         if dn == self.server_ref_dn:
2019             # Check we have a valid RID Set
2020             if "*" in attrs or "rIDSetReferences" in attrs:
2021                 if "rIDSetReferences" not in obj:
2022                     # NO RID SET reference
2023                     # We are RID master, allocate it.
2024                     error_count += 1
2025
2026                     if self.is_rid_master:
2027                         # Allocate a RID Set
2028                         if self.confirm_all('Allocate the missing RID set for RID master?',
2029                                             'fix_missing_rid_set_master'):
2030
2031                             # We don't have auto-transaction logic on
2032                             # extended operations, so we have to do it
2033                             # here.
2034
2035                             self.samdb.transaction_start()
2036
2037                             try:
2038                                 self.samdb.create_own_rid_set()
2039
2040                             except:
2041                                 self.samdb.transaction_cancel()
2042                                 raise
2043
2044                             self.samdb.transaction_commit()
2045
2046
2047                     elif not self.samdb.am_rodc():
2048                         self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2049
2050
2051         # Check some details of our own RID Set
2052         if dn == self.rid_set_dn:
2053             res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2054                                     attrs=["rIDAllocationPool",
2055                                            "rIDPreviousAllocationPool",
2056                                            "rIDUsedPool",
2057                                            "rIDNextRID"])
2058             if "rIDAllocationPool" not in res[0]:
2059                 self.report("No rIDAllocationPool found in %s" % dn)
2060                 error_count += 1
2061             else:
2062                 next_pool = int(res[0]["rIDAllocationPool"][0])
2063
2064                 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2065                 low = 0x00000000FFFFFFFF & next_pool
2066
2067                 if high <= low:
2068                     self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2069                     error_count += 1
2070
2071                 if "rIDNextRID" in res[0]:
2072                     next_free_rid = int(res[0]["rIDNextRID"][0])
2073                 else:
2074                     next_free_rid = 0
2075
2076                 if next_free_rid == 0:
2077                     next_free_rid = low
2078                 else:
2079                     next_free_rid += 1
2080
2081                 # Check the remainder of this pool for conflicts.  If
2082                 # ridalloc_allocate_rid() moves to a new pool, this
2083                 # will be above high, so we will stop.
2084                 while next_free_rid <= high:
2085                     sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2086                     try:
2087                         res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2088                                                 attrs=[])
2089                     except ldb.LdbError, (enum, estr):
2090                         if enum != ldb.ERR_NO_SUCH_OBJECT:
2091                             raise
2092                         res = None
2093                     if res is not None:
2094                         self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2095                         error_count += 1
2096
2097                         if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2098                                             % (sid, dn),
2099                                             'fix_sid_rid_set_conflict'):
2100                             self.samdb.transaction_start()
2101
2102                             # This will burn RIDs, which will move
2103                             # past the conflict.  We then check again
2104                             # to see if the new RID conflicts, until
2105                             # the end of the current pool.  We don't
2106                             # look at the next pool to avoid burning
2107                             # all RIDs in one go in some strange
2108                             # failure case.
2109                             try:
2110                                 while True:
2111                                     allocated_rid = self.samdb.allocate_rid()
2112                                     if allocated_rid >= next_free_rid:
2113                                         next_free_rid = allocated_rid + 1
2114                                         break
2115                             except:
2116                                 self.samdb.transaction_cancel()
2117                                 raise
2118
2119                             self.samdb.transaction_commit()
2120                         else:
2121                             break
2122                     else:
2123                         next_free_rid += 1
2124
2125
2126         return error_count
2127
2128     ################################################################
2129     # check special @ROOTDSE attributes
2130     def check_rootdse(self):
2131         '''check the @ROOTDSE special object'''
2132         dn = ldb.Dn(self.samdb, '@ROOTDSE')
2133         if self.verbose:
2134             self.report("Checking object %s" % dn)
2135         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2136         if len(res) != 1:
2137             self.report("Object %s disappeared during check" % dn)
2138             return 1
2139         obj = res[0]
2140         error_count = 0
2141
2142         # check that the dsServiceName is in GUID form
2143         if not 'dsServiceName' in obj:
2144             self.report('ERROR: dsServiceName missing in @ROOTDSE')
2145             return error_count+1
2146
2147         if not obj['dsServiceName'][0].startswith('<GUID='):
2148             self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2149             error_count += 1
2150             if not self.confirm('Change dsServiceName to GUID form?'):
2151                 return error_count
2152             res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
2153                                     scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2154             guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2155             m = ldb.Message()
2156             m.dn = dn
2157             m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2158                                                     ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2159             if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2160                 self.report("Changed dsServiceName to GUID form")
2161         return error_count
2162
2163
2164     ###############################################
2165     # re-index the database
2166     def reindex_database(self):
2167         '''re-index the whole database'''
2168         m = ldb.Message()
2169         m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2170         m['add']    = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2171         m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2172         return self.do_modify(m, [], 're-indexed database', validate=False)
2173
2174     ###############################################
2175     # reset @MODULES
2176     def reset_modules(self):
2177         '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2178         m = ldb.Message()
2179         m.dn = ldb.Dn(self.samdb, "@MODULES")
2180         m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2181         return self.do_modify(m, [], 'reset @MODULES on database', validate=False)