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