dbcheck: Allow removal of one-way links to missing objects
[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
527             linkID, reverse_link_name \
528                 = self.get_attr_linkID_and_reverse_name(attrname)
529             if reverse_link_name is not None:
530                 self.report("Not removing dangling forward link")
531                 return
532
533             nc_root = self.samdb.get_nc_root(dn)
534             target_nc_root = self.samdb.get_nc_root(dsdb_dn.dn)
535             if nc_root != target_nc_root:
536                 self.report("Not removing dangling one-way "
537                             "cross-partition link "
538                             "(we might be mid-replication)")
539                 return
540
541             # Due to our link handling one-way links pointing to
542             # missing objects are plausible.
543             self.err_deleted_dn(dn, attrname, val,
544                                 dsdb_dn, dsdb_dn, True)
545
546         self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
547
548     def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
549         """handle a missing GUID extended DN component"""
550         self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
551         controls=["extended_dn:1:1", "show_recycled:1"]
552         try:
553             res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
554                                     attrs=[], controls=controls)
555         except ldb.LdbError, (enum, estr):
556             self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
557             if enum != ldb.ERR_NO_SUCH_OBJECT:
558                 raise
559             self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
560             return
561         if len(res) == 0:
562             self.report("unable to find object for DN %s" % dsdb_dn.dn)
563             self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
564             return
565         dsdb_dn.dn = res[0].dn
566
567         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
568             self.report("Not fixing %s" % errstr)
569             return
570         m = ldb.Message()
571         m.dn = dn
572         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
573         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
574
575         if self.do_modify(m, ["show_recycled:1"],
576                           "Failed to fix %s on attribute %s" % (errstr, attrname)):
577             self.report("Fixed %s on attribute %s" % (errstr, attrname))
578
579     def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
580         """handle an incorrect binary DN component"""
581         self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
582         controls=["extended_dn:1:1", "show_recycled:1"]
583
584         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
585             self.report("Not fixing %s" % errstr)
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
592         if self.do_modify(m, ["show_recycled:1"],
593                           "Failed to fix %s on attribute %s" % (errstr, attrname)):
594             self.report("Fixed %s on attribute %s" % (errstr, attrname))
595
596     def err_dn_string_component_old(self, dn, attrname, val, dsdb_dn, correct_dn):
597         """handle a DN string being incorrect"""
598         self.report("NOTE: old (due to rename or delete) DN string component for %s in object %s - %s" % (attrname, dn, val))
599         dsdb_dn.dn = correct_dn
600
601         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
602                                 'fix_all_old_dn_string_component_mismatch'):
603             self.report("Not fixing old string component")
604             return
605         m = ldb.Message()
606         m.dn = dn
607         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
608         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
609         if self.do_modify(m, ["show_recycled:1"],
610                           "Failed to fix old DN string on attribute %s" % (attrname)):
611             self.report("Fixed old DN string on attribute %s" % (attrname))
612
613     def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
614         """handle a DN string being incorrect"""
615         self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
616         dsdb_dn.dn = correct_dn
617
618         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
619                                 'fix_all_%s_dn_component_mismatch' % mismatch_type):
620             self.report("Not fixing %s component mismatch" % mismatch_type)
621             return
622         m = ldb.Message()
623         m.dn = dn
624         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
625         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
626         if self.do_modify(m, ["show_recycled:1"],
627                           "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
628             self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
629
630     def err_unknown_attribute(self, obj, attrname):
631         '''handle an unknown attribute error'''
632         self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
633         if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
634             self.report("Not removing %s" % attrname)
635             return
636         m = ldb.Message()
637         m.dn = obj.dn
638         m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
639         if self.do_modify(m, ["relax:0", "show_recycled:1"],
640                           "Failed to remove unknown attribute %s" % attrname):
641             self.report("Removed unknown attribute %s" % (attrname))
642
643     def err_undead_linked_attribute(self, obj, attrname, val):
644         '''handle a link that should not be there on a deleted object'''
645         self.report("ERROR: linked attribute '%s' to '%s' is present on "
646                     "deleted object %s" % (attrname, val, obj.dn))
647         if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
648             self.report("Not removing linked attribute %s" % attrname)
649             return
650         m = ldb.Message()
651         m.dn = obj.dn
652         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
653
654         if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
655                               "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
656                           "Failed to delete forward link %s" % attrname):
657             self.report("Fixed undead forward link %s" % (attrname))
658
659     def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
660         '''handle a missing backlink value'''
661         self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
662         if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
663             self.report("Not fixing missing backlink %s" % backlink_name)
664             return
665         m = ldb.Message()
666         m.dn = target_dn
667         m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, backlink_name)
668         if self.do_modify(m, ["show_recycled:1", "relax:0"],
669                           "Failed to fix missing backlink %s" % backlink_name):
670             self.report("Fixed missing backlink %s" % (backlink_name))
671
672     def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
673         '''handle a incorrect RMD_FLAGS value'''
674         rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
675         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()))
676         if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
677             self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
678             return
679         m = ldb.Message()
680         m.dn = obj.dn
681         m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
682         if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
683                           "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
684             self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
685
686     def err_orphaned_backlink(self, obj, attrname, val, link_name, target_dn):
687         '''handle a orphaned backlink value'''
688         self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (attrname, obj.dn, link_name, target_dn))
689         if not self.confirm_all('Remove orphaned backlink %s' % attrname, 'fix_all_orphaned_backlinks'):
690             self.report("Not removing orphaned backlink %s" % attrname)
691             return
692         m = ldb.Message()
693         m.dn = obj.dn
694         m['value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
695         if self.do_modify(m, ["show_recycled:1", "relax:0"],
696                           "Failed to fix orphaned backlink %s" % attrname):
697             self.report("Fixed orphaned backlink %s" % (attrname))
698
699     def err_no_fsmoRoleOwner(self, obj):
700         '''handle a missing fSMORoleOwner'''
701         self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
702         res = self.samdb.search("",
703                                 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
704         assert len(res) == 1
705         serviceName = res[0]["dsServiceName"][0]
706         if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
707             self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
708             return
709         m = ldb.Message()
710         m.dn = obj.dn
711         m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
712         if self.do_modify(m, [],
713                           "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
714             self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
715
716     def err_missing_parent(self, obj):
717         '''handle a missing parent'''
718         self.report("ERROR: parent object not found for %s" % (obj.dn))
719         if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
720             self.report('Not moving object %s into LostAndFound' % (obj.dn))
721             return
722
723         keep_transaction = False
724         self.samdb.transaction_start()
725         try:
726             nc_root = self.samdb.get_nc_root(obj.dn);
727             lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
728             new_dn = ldb.Dn(self.samdb, str(obj.dn))
729             new_dn.remove_base_components(len(new_dn) - 1)
730             if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
731                               "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
732                 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
733
734                 m = ldb.Message()
735                 m.dn = obj.dn
736                 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
737
738                 if self.do_modify(m, [],
739                                   "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
740                     self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
741                     keep_transaction = True
742         except:
743             self.samdb.transaction_cancel()
744             raise
745
746         if keep_transaction:
747             self.samdb.transaction_commit()
748         else:
749             self.samdb.transaction_cancel()
750
751     def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
752         '''handle a wrong dn'''
753
754         new_rdn = ldb.Dn(self.samdb, str(new_dn))
755         new_rdn.remove_base_components(len(new_rdn) - 1)
756         new_parent = new_dn.parent()
757
758         attributes = ""
759         if rdn_val != name_val:
760             attributes += "%s=%r " % (rdn_attr, rdn_val)
761         attributes += "name=%r" % (name_val)
762
763         self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
764         if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
765             self.report("Not renaming %s to %s" % (obj.dn, new_dn))
766             return
767
768         if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
769                           "Failed to rename object %s into %s" % (obj.dn, new_dn)):
770             self.report("Renamed %s into %s" % (obj.dn, new_dn))
771
772     def err_wrong_instancetype(self, obj, calculated_instancetype):
773         '''handle a wrong instanceType'''
774         self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
775         if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
776             self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
777             return
778
779         m = ldb.Message()
780         m.dn = obj.dn
781         m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
782         if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
783                           "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
784             self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
785
786     def err_short_userParameters(self, obj, attrname, value):
787         # This is a truncated userParameters due to a pre 4.1 replication bug
788         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)))
789
790     def err_base64_userParameters(self, obj, attrname, value):
791         '''handle a wrong userParameters'''
792         self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
793         if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
794             self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
795             return
796
797         m = ldb.Message()
798         m.dn = obj.dn
799         m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
800         if self.do_modify(m, [],
801                           "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
802             self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
803
804     def err_utf8_userParameters(self, obj, attrname, value):
805         '''handle a wrong userParameters'''
806         self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
807         if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
808             self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
809             return
810
811         m = ldb.Message()
812         m.dn = obj.dn
813         m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
814                                         ldb.FLAG_MOD_REPLACE, 'userParameters')
815         if self.do_modify(m, [],
816                           "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
817             self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
818
819     def err_doubled_userParameters(self, obj, attrname, value):
820         '''handle a wrong userParameters'''
821         self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
822         if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
823             self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
824             return
825
826         m = ldb.Message()
827         m.dn = obj.dn
828         m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
829                                         ldb.FLAG_MOD_REPLACE, 'userParameters')
830         if self.do_modify(m, [],
831                           "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
832             self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
833
834     def err_odd_userParameters(self, obj, attrname):
835         # This is a truncated userParameters due to a pre 4.1 replication bug
836         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)))
837
838     def find_revealed_link(self, dn, attrname, guid):
839         '''return a revealed link in an object'''
840         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
841                                 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
842         syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
843         for val in res[0][attrname]:
844             dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
845             guid2 = dsdb_dn.dn.get_extended_component("GUID")
846             if guid == guid2:
847                 return dsdb_dn
848         return None
849
850     def check_dn(self, obj, attrname, syntax_oid):
851         '''check a DN attribute for correctness'''
852         error_count = 0
853         obj_guid = obj['objectGUID'][0]
854
855         for val in obj[attrname]:
856             dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
857
858             # all DNs should have a GUID component
859             guid = dsdb_dn.dn.get_extended_component("GUID")
860             if guid is None:
861                 error_count += 1
862                 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
863                     "missing GUID")
864                 continue
865
866             guidstr = str(misc.GUID(guid))
867             attrs = ['isDeleted', 'replPropertyMetaData']
868
869             if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
870                 fixing_msDS_HasInstantiatedNCs = True
871                 attrs.append("instanceType")
872             else:
873                 fixing_msDS_HasInstantiatedNCs = False
874
875             linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
876             if reverse_link_name is not None:
877                 attrs.append(reverse_link_name)
878
879             # check its the right GUID
880             try:
881                 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
882                                         attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
883                                                                "reveal_internals:0"
884                                         ])
885             except ldb.LdbError, (enum, estr):
886                 error_count += 1
887                 self.report("ERROR: no target object found for GUID component for %s in object %s - %s" % (attrname, obj.dn, val))
888                 if enum != ldb.ERR_NO_SUCH_OBJECT:
889                     raise
890
891                 self.err_missing_target_dn_or_GUID(obj.dn, attrname, val, dsdb_dn)
892                 continue
893
894             if fixing_msDS_HasInstantiatedNCs:
895                 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
896                 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
897
898                 if str(dsdb_dn) != val:
899                     error_count +=1
900                     self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
901                     continue
902
903             # now we have two cases - the source object might or might not be deleted
904             is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
905             target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
906
907
908             if is_deleted and not obj.dn in self.deleted_objects_containers and linkID:
909                 # A fully deleted object should not have any linked
910                 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
911                 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
912                 # Requirements)
913                 self.err_undead_linked_attribute(obj, attrname, val)
914                 error_count += 1
915                 continue
916             elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
917                 # the target DN is not allowed to be deleted, unless the target DN is the
918                 # special Deleted Objects container
919                 error_count += 1
920                 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
921                 if local_usn:
922                     if 'replPropertyMetaData' in res[0]:
923                         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
924                                           str(res[0]['replPropertyMetadata']))
925                         found_data = False
926                         for o in repl.ctr.array:
927                             if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
928                                 deleted_usn = o.local_usn
929                                 if deleted_usn >= int(local_usn):
930                                     # If the object was deleted after the link
931                                     # was last modified then, clean it up here
932                                     found_data = True
933                                     break
934
935                         if found_data:
936                             self.err_deleted_dn(obj.dn, attrname,
937                                                 val, dsdb_dn, res[0].dn, True)
938                             continue
939
940                 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
941                 continue
942
943             # We should not check for incorrect
944             # components on deleted links, as these are allowed to
945             # go stale (we just need the GUID, not the name)
946             rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
947             if rmd_blob is not None:
948                 rmd_flags = int(rmd_blob)
949                 if rmd_flags & 1:
950                     continue
951
952             # assert the DN matches in string form, where a reverse
953             # link exists, otherwise (below) offer to fix it as a non-error.
954             # The string form is essentially only kept for forensics,
955             # as we always re-resolve by GUID in normal operations.
956             if reverse_link_name is not None:
957                 if str(res[0].dn) != str(dsdb_dn.dn):
958                     error_count += 1
959                     self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
960                                                           res[0].dn, "string")
961                     continue
962
963             if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
964                 error_count += 1
965                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
966                                                       res[0].dn, "GUID")
967                 continue
968
969             if res[0].dn.get_extended_component("SID") != dsdb_dn.dn.get_extended_component("SID"):
970                 error_count += 1
971                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
972                                                       res[0].dn, "SID")
973                 continue
974
975             # Now we have checked the GUID and SID, offer to fix old
976             # DN strings as a non-error (for forward links with no
977             # backlink).  Samba does not maintain this string
978             # otherwise, so we don't increment error_count.
979             if reverse_link_name is None:
980                 if str(res[0].dn) != str(dsdb_dn.dn):
981                     self.err_dn_string_component_old(obj.dn, attrname, val, dsdb_dn,
982                                                      res[0].dn)
983                 continue
984
985             else:
986                 # check the reverse_link is correct if there should be one
987                 match_count = 0
988                 if reverse_link_name in res[0]:
989                     for v in res[0][reverse_link_name]:
990                         v_guid = dsdb_Dn(self.samdb, v).dn.get_extended_component("GUID")
991                         if v_guid == obj_guid:
992                             match_count += 1
993                 if match_count != 1:
994                     reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
995                     if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
996                         if not linkID & 1:
997                             # Forward binary multi-valued linked attribute
998                             forward_count = 0
999                             for w in obj[attrname]:
1000                                 w_guid = dsdb_Dn(self.samdb, w).dn.get_extended_component("GUID")
1001                                 if w_guid == guid:
1002                                     forward_count += 1
1003
1004                             if match_count == forward_count:
1005                                 continue
1006
1007                             error_count += 1
1008
1009                             # Add or remove the missing number of backlinks
1010                             diff_count = forward_count - match_count
1011
1012                             # Loop until the difference between the forward and
1013                             # the backward links is resolved.
1014                             while diff_count != 0:
1015                                 if diff_count > 0:
1016                                     # self.err_missing_backlink(obj, attrname,
1017                                     #                          obj.dn.extended_str(),
1018                                     #                          reverse_link_name,
1019                                     #                          dsdb_dn.dn)
1020                                     # diff_count -= 1
1021                                     # TODO no method to fix these right now
1022                                     self.report("ERROR: Can't fix missing "
1023                                                 "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1024                                     break
1025                                 else:
1026                                     self.err_orphaned_backlink(res[0], reverse_link_name,
1027                                                                obj.dn.extended_str(), attrname,
1028                                                                dsdb_dn.dn)
1029                                     diff_count += 1
1030
1031                         else:
1032                             # If there's a backward link on binary multi-valued linked attribute,
1033                             # let the check on the forward link remedy the value.
1034                             # UNLESS, there is no forward link detected.
1035                             if match_count == 0:
1036                                 self.err_orphaned_backlink(obj, attrname,
1037                                                            val, reverse_link_name,
1038                                                            dsdb_dn.dn)
1039
1040                         continue
1041
1042                     error_count += 1
1043                     if linkID & 1:
1044                         # Backlink exists, but forward link does not
1045                         # Delete the hanging backlink
1046                         self.err_orphaned_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
1047                     else:
1048                         # Forward link exists, but backlink does not
1049                         # Add the missing backlink (if the target object is not Deleted Objects?)
1050                         if not target_is_deleted:
1051                             self.err_missing_backlink(obj, attrname, obj.dn.extended_str(), reverse_link_name, dsdb_dn.dn)
1052                     continue
1053
1054
1055
1056
1057         return error_count
1058
1059
1060     def get_originating_time(self, val, attid):
1061         '''Read metadata properties and return the originating time for
1062            a given attributeId.
1063
1064            :return: the originating time or 0 if not found
1065         '''
1066
1067         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1068         obj = repl.ctr
1069
1070         for o in repl.ctr.array:
1071             if o.attid == attid:
1072                 return o.originating_change_time
1073
1074         return 0
1075
1076     def process_metadata(self, dn, val):
1077         '''Read metadata properties and list attributes in it.
1078            raises KeyError if the attid is unknown.'''
1079
1080         set_att = set()
1081         wrong_attids = set()
1082         list_attid = []
1083         in_schema_nc = dn.is_child_of(self.schema_dn)
1084
1085         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1086         obj = repl.ctr
1087
1088         for o in repl.ctr.array:
1089             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1090             set_att.add(att.lower())
1091             list_attid.append(o.attid)
1092             correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1093                                                                              is_schema_nc=in_schema_nc)
1094             if correct_attid != o.attid:
1095                 wrong_attids.add(o.attid)
1096
1097         return (set_att, list_attid, wrong_attids)
1098
1099
1100     def fix_metadata(self, dn, attr):
1101         '''re-write replPropertyMetaData elements for a single attribute for a
1102         object. This is used to fix missing replPropertyMetaData elements'''
1103         res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
1104                                 controls = ["search_options:1:2", "show_recycled:1"])
1105         msg = res[0]
1106         nmsg = ldb.Message()
1107         nmsg.dn = dn
1108         nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1109         if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1110                           "Failed to fix metadata for attribute %s" % attr):
1111             self.report("Fixed metadata for attribute %s" % attr)
1112
1113     def ace_get_effective_inherited_type(self, ace):
1114         if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1115             return None
1116
1117         check = False
1118         if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1119             check = True
1120         elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1121             check = True
1122         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1123             check = True
1124         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1125             check = True
1126
1127         if not check:
1128             return None
1129
1130         if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1131             return None
1132
1133         return str(ace.object.inherited_type)
1134
1135     def lookup_class_schemaIDGUID(self, cls):
1136         if cls in self.class_schemaIDGUID:
1137             return self.class_schemaIDGUID[cls]
1138
1139         flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1140         res = self.samdb.search(base=self.schema_dn,
1141                                 expression=flt,
1142                                 attrs=["schemaIDGUID"])
1143         t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1144
1145         self.class_schemaIDGUID[cls] = t
1146         return t
1147
1148     def process_sd(self, dn, obj):
1149         sd_attr = "nTSecurityDescriptor"
1150         sd_val = obj[sd_attr]
1151
1152         sd = ndr_unpack(security.descriptor, str(sd_val))
1153
1154         is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1155         if is_deleted:
1156             # we don't fix deleted objects
1157             return (sd, None)
1158
1159         sd_clean = security.descriptor()
1160         sd_clean.owner_sid = sd.owner_sid
1161         sd_clean.group_sid = sd.group_sid
1162         sd_clean.type = sd.type
1163         sd_clean.revision = sd.revision
1164
1165         broken = False
1166         last_inherited_type = None
1167
1168         aces = []
1169         if sd.sacl is not None:
1170             aces = sd.sacl.aces
1171         for i in range(0, len(aces)):
1172             ace = aces[i]
1173
1174             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1175                 sd_clean.sacl_add(ace)
1176                 continue
1177
1178             t = self.ace_get_effective_inherited_type(ace)
1179             if t is None:
1180                 continue
1181
1182             if last_inherited_type is not None:
1183                 if t != last_inherited_type:
1184                     # if it inherited from more than
1185                     # one type it's very likely to be broken
1186                     #
1187                     # If not the recalculation will calculate
1188                     # the same result.
1189                     broken = True
1190                 continue
1191
1192             last_inherited_type = t
1193
1194         aces = []
1195         if sd.dacl is not None:
1196             aces = sd.dacl.aces
1197         for i in range(0, len(aces)):
1198             ace = aces[i]
1199
1200             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1201                 sd_clean.dacl_add(ace)
1202                 continue
1203
1204             t = self.ace_get_effective_inherited_type(ace)
1205             if t is None:
1206                 continue
1207
1208             if last_inherited_type is not None:
1209                 if t != last_inherited_type:
1210                     # if it inherited from more than
1211                     # one type it's very likely to be broken
1212                     #
1213                     # If not the recalculation will calculate
1214                     # the same result.
1215                     broken = True
1216                 continue
1217
1218             last_inherited_type = t
1219
1220         if broken:
1221             return (sd_clean, sd)
1222
1223         if last_inherited_type is None:
1224             # ok
1225             return (sd, None)
1226
1227         cls = None
1228         try:
1229             cls = obj["objectClass"][-1]
1230         except KeyError, e:
1231             pass
1232
1233         if cls is None:
1234             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1235                                     attrs=["isDeleted", "objectClass"],
1236                                     controls=["show_recycled:1"])
1237             o = res[0]
1238             is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
1239             if is_deleted:
1240                 # we don't fix deleted objects
1241                 return (sd, None)
1242             cls = o["objectClass"][-1]
1243
1244         t = self.lookup_class_schemaIDGUID(cls)
1245
1246         if t != last_inherited_type:
1247             # broken
1248             return (sd_clean, sd)
1249
1250         # ok
1251         return (sd, None)
1252
1253     def err_wrong_sd(self, dn, sd, sd_broken):
1254         '''re-write the SD due to incorrect inherited ACEs'''
1255         sd_attr = "nTSecurityDescriptor"
1256         sd_val = ndr_pack(sd)
1257         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1258
1259         if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1260             self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1261             return
1262
1263         nmsg = ldb.Message()
1264         nmsg.dn = dn
1265         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1266         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1267                           "Failed to fix attribute %s" % sd_attr):
1268             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1269
1270     def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1271         '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1272         sd_attr = "nTSecurityDescriptor"
1273         sd_val = ndr_pack(sd)
1274         sd_old_val = ndr_pack(sd_old)
1275         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1276         if sd.owner_sid is not None:
1277             sd_flags |= security.SECINFO_OWNER
1278         if sd.group_sid is not None:
1279             sd_flags |= security.SECINFO_GROUP
1280
1281         if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1282             self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1283             return
1284
1285         m = ldb.Message()
1286         m.dn = dn
1287         m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1288         if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1289                           "Failed to reset attribute %s" % sd_attr):
1290             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1291
1292     def err_missing_sd_owner(self, dn, sd):
1293         '''re-write the SD due to a missing owner or group'''
1294         sd_attr = "nTSecurityDescriptor"
1295         sd_val = ndr_pack(sd)
1296         sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1297
1298         if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1299             self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1300             return
1301
1302         nmsg = ldb.Message()
1303         nmsg.dn = dn
1304         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1305
1306         # By setting the session_info to admin_session_info and
1307         # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1308         # flags we cause the descriptor module to set the correct
1309         # owner and group on the SD, replacing the None/NULL values
1310         # for owner_sid and group_sid currently present.
1311         #
1312         # The admin_session_info matches that used in provision, and
1313         # is the best guess we can make for an existing object that
1314         # hasn't had something specifically set.
1315         #
1316         # This is important for the dns related naming contexts.
1317         self.samdb.set_session_info(self.admin_session_info)
1318         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1319                           "Failed to fix metadata for attribute %s" % sd_attr):
1320             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1321         self.samdb.set_session_info(self.system_session_info)
1322
1323
1324     def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1325         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1326                           str(repl_meta_data))
1327         ctr = repl.ctr
1328         found = False
1329         for o in ctr.array:
1330             # Search for a zero invocationID
1331             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1332                 continue
1333
1334             found = True
1335             self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1336                            version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1337                            but should be non-zero.  Proposed fix is to set to our invocationID (%s).'''
1338                         % (dn, o.attid, o.version,
1339                            time.ctime(samba.nttime2unix(o.originating_change_time)),
1340                            self.samdb.get_invocation_id()))
1341
1342         return found
1343
1344
1345     def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1346         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1347                           str(repl_meta_data))
1348         ctr = repl.ctr
1349         now = samba.unix2nttime(int(time.time()))
1350         found = False
1351         for o in ctr.array:
1352             # Search for a zero invocationID
1353             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1354                 continue
1355
1356             found = True
1357             seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1358             o.version = o.version + 1
1359             o.originating_change_time = now
1360             o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1361             o.originating_usn = seq
1362             o.local_usn = seq
1363
1364         if found:
1365             replBlob = ndr_pack(repl)
1366             msg = ldb.Message()
1367             msg.dn = dn
1368
1369             if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1370                                     % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1371                 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1372                 return
1373
1374             nmsg = ldb.Message()
1375             nmsg.dn = dn
1376             nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1377             if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1378                                      "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1379                               "Failed to fix attribute %s" % attr):
1380                 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1381
1382
1383     def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1384         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1385                           str(repl_meta_data))
1386         ctr = repl.ctr
1387         for o in ctr.array:
1388             # Search for an invalid attid
1389             try:
1390                 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1391             except KeyError:
1392                 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1393                 return
1394
1395
1396     def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1397         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1398                           str(repl_meta_data))
1399         fix = False
1400
1401         set_att = set()
1402         remove_attid = set()
1403         hash_att = {}
1404
1405         in_schema_nc = dn.is_child_of(self.schema_dn)
1406
1407         ctr = repl.ctr
1408         # Sort the array, except for the last element.  This strange
1409         # construction, creating a new list, due to bugs in samba's
1410         # array handling in IDL generated objects.
1411         ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1412         # Now walk it in reverse, so we see the low (and so incorrect,
1413         # the correct values are above 0x80000000) values first and
1414         # remove the 'second' value we see.
1415         for o in reversed(ctr.array):
1416             print "%s: 0x%08x" % (dn, o.attid)
1417             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1418             if att.lower() in set_att:
1419                 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1420                 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1421                                         % (attr, dn, o.attid, att, hash_att[att].attid),
1422                                         'fix_replmetadata_duplicate_attid'):
1423                     self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1424                                 % (o.attid, att, attr, dn))
1425                     return
1426                 fix = True
1427                 remove_attid.add(o.attid)
1428                 # We want to set the metadata for the most recent
1429                 # update to have been applied locally, that is the metadata
1430                 # matching the (eg string) value in the attribute
1431                 if o.local_usn > hash_att[att].local_usn:
1432                     # This is always what we would have sent over DRS,
1433                     # because the DRS server will have sent the
1434                     # msDS-IntID, but with the values from both
1435                     # attribute entries.
1436                     hash_att[att].version = o.version
1437                     hash_att[att].originating_change_time = o.originating_change_time
1438                     hash_att[att].originating_invocation_id = o.originating_invocation_id
1439                     hash_att[att].originating_usn = o.originating_usn
1440                     hash_att[att].local_usn = o.local_usn
1441
1442                 # Do not re-add the value to the set or overwrite the hash value
1443                 continue
1444
1445             hash_att[att] = o
1446             set_att.add(att.lower())
1447
1448         # Generate a real list we can sort on properly
1449         new_list = [o for o in ctr.array if o.attid not in remove_attid]
1450
1451         if (len(wrong_attids) > 0):
1452             for o in new_list:
1453                 if o.attid in wrong_attids:
1454                     att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1455                     correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1456                     self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1457                     if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1458                                             % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1459                         self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1460                                     % (o.attid, correct_attid, att, attr, dn))
1461                         return
1462                     fix = True
1463                     o.attid = correct_attid
1464             if fix:
1465                 # Sort the array, (we changed the value so must re-sort)
1466                 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1467
1468         # If we did not already need to fix it, then ask about sorting
1469         if not fix:
1470             self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1471             if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1472                                     % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1473                 self.report('Not fixing %s on %s\n' % (attr, dn))
1474                 return
1475
1476             # The actual sort done is done at the top of the function
1477
1478         ctr.count = len(new_list)
1479         ctr.array = new_list
1480         replBlob = ndr_pack(repl)
1481
1482         nmsg = ldb.Message()
1483         nmsg.dn = dn
1484         nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1485         if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1486                              "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1487                              "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1488                       "Failed to fix attribute %s" % attr):
1489             self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1490
1491
1492     def is_deleted_deleted_objects(self, obj):
1493         faulty = False
1494         if "description" not in obj:
1495             self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1496             faulty = True
1497         if "showInAdvancedViewOnly" not in obj or obj['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1498             self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1499             faulty = True
1500         if "objectCategory" not in obj:
1501             self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1502             faulty = True
1503         if "isCriticalSystemObject" not in obj or obj['isCriticalSystemObject'][0].upper() == 'FALSE':
1504             self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1505             faulty = True
1506         if "isRecycled" in obj:
1507             self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1508             faulty = True
1509         if "isDeleted" in obj and obj['isDeleted'][0].upper() == 'FALSE':
1510             self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1511             faulty = True
1512         if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1513                                         obj['objectClass'][0] != 'top' or
1514                                         obj['objectClass'][1] != 'container'):
1515             self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1516             faulty = True
1517         if "systemFlags" not in obj or obj['systemFlags'][0] != '-1946157056':
1518             self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1519             faulty = True
1520         return faulty
1521
1522     def err_deleted_deleted_objects(self, obj):
1523         nmsg = ldb.Message()
1524         nmsg.dn = dn = obj.dn
1525
1526         if "description" not in obj:
1527             nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1528         if "showInAdvancedViewOnly" not in obj:
1529             nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1530         if "objectCategory" not in obj:
1531             nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1532         if "isCriticalSystemObject" not in obj:
1533             nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1534         if "isRecycled" in obj:
1535             nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1536
1537         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1538         nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1539         nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1540
1541         if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1542                                 % (dn), 'fix_deleted_deleted_objects'):
1543             self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1544             return
1545
1546         if self.do_modify(nmsg, ["relax:0"],
1547                           "Failed to fix Deleted Objects container  %s" % dn):
1548             self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1549
1550     def err_replica_locations(self, obj, cross_ref, attr):
1551         nmsg = ldb.Message()
1552         nmsg.dn = cross_ref
1553         target = self.samdb.get_dsServiceName()
1554
1555         if self.samdb.am_rodc():
1556             self.report('Not fixing %s for the RODC' % (attr, obj.dn))
1557             return
1558
1559         if not self.confirm_all('Add yourself to the replica locations for %s?'
1560                                 % (obj.dn), 'fix_replica_locations'):
1561             self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1562             return
1563
1564         nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1565         if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1566             self.report("Fixed %s for %s" % (attr, obj.dn))
1567
1568     def is_fsmo_role(self, dn):
1569         if dn == self.samdb.domain_dn:
1570             return True
1571         if dn == self.infrastructure_dn:
1572             return True
1573         if dn == self.naming_dn:
1574             return True
1575         if dn == self.schema_dn:
1576             return True
1577         if dn == self.rid_dn:
1578             return True
1579
1580         return False
1581
1582     def calculate_instancetype(self, dn):
1583         instancetype = 0
1584         nc_root = self.samdb.get_nc_root(dn)
1585         if dn == nc_root:
1586             instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1587             try:
1588                 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1589             except ldb.LdbError, (enum, estr):
1590                 if enum != ldb.ERR_NO_SUCH_OBJECT:
1591                     raise
1592             else:
1593                 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1594
1595         if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1596             instancetype |= dsdb.INSTANCE_TYPE_WRITE
1597
1598         return instancetype
1599
1600     def get_wellknown_sd(self, dn):
1601         for [sd_dn, descriptor_fn] in self.wellknown_sds:
1602             if dn == sd_dn:
1603                 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1604                 return ndr_unpack(security.descriptor,
1605                                   descriptor_fn(domain_sid,
1606                                                 name_map=self.name_map))
1607
1608         raise KeyError
1609
1610     def check_object(self, dn, attrs=['*']):
1611         '''check one object'''
1612         if self.verbose:
1613             self.report("Checking object %s" % dn)
1614
1615         # If we modify the pass-by-reference attrs variable, then we get a
1616         # replPropertyMetadata for every object that we check.
1617         attrs = list(attrs)
1618         if "dn" in map(str.lower, attrs):
1619             attrs.append("name")
1620         if "distinguishedname" in map(str.lower, attrs):
1621             attrs.append("name")
1622         if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1623             attrs.append("name")
1624         if 'name' in map(str.lower, attrs):
1625             attrs.append(dn.get_rdn_name())
1626             attrs.append("isDeleted")
1627             attrs.append("systemFlags")
1628         if '*' in attrs:
1629             attrs.append("replPropertyMetaData")
1630         else:
1631             attrs.append("objectGUID")
1632
1633         try:
1634             sd_flags = 0
1635             sd_flags |= security.SECINFO_OWNER
1636             sd_flags |= security.SECINFO_GROUP
1637             sd_flags |= security.SECINFO_DACL
1638             sd_flags |= security.SECINFO_SACL
1639
1640             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1641                                     controls=[
1642                                         "extended_dn:1:1",
1643                                         "show_recycled:1",
1644                                         "show_deleted:1",
1645                                         "sd_flags:1:%d" % sd_flags,
1646                                         "reveal_internals:0",
1647                                     ],
1648                                     attrs=attrs)
1649         except ldb.LdbError, (enum, estr):
1650             if enum == ldb.ERR_NO_SUCH_OBJECT:
1651                 if self.in_transaction:
1652                     self.report("ERROR: Object %s disappeared during check" % dn)
1653                     return 1
1654                 return 0
1655             raise
1656         if len(res) != 1:
1657             self.report("ERROR: Object %s failed to load during check" % dn)
1658             return 1
1659         obj = res[0]
1660         error_count = 0
1661         set_attrs_from_md = set()
1662         set_attrs_seen = set()
1663         got_repl_property_meta_data = False
1664         got_objectclass = False
1665
1666         nc_dn = self.samdb.get_nc_root(obj.dn)
1667         try:
1668             deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
1669                                                              samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
1670         except KeyError:
1671             # We have no deleted objects DN for schema, and we check for this above for the other
1672             # NCs
1673             deleted_objects_dn = None
1674
1675
1676         object_rdn_attr = None
1677         object_rdn_val = None
1678         name_val = None
1679         isDeleted = False
1680         systemFlags = 0
1681
1682         for attrname in obj:
1683             if attrname == 'dn' or attrname == "distinguishedName":
1684                 continue
1685
1686             if str(attrname).lower() == 'objectclass':
1687                 got_objectclass = True
1688
1689             if str(attrname).lower() == "name":
1690                 if len(obj[attrname]) != 1:
1691                     error_count += 1
1692                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1693                                 (len(obj[attrname]), attrname, str(obj.dn)))
1694                 else:
1695                     name_val = obj[attrname][0]
1696
1697             if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
1698                 object_rdn_attr = attrname
1699                 if len(obj[attrname]) != 1:
1700                     error_count += 1
1701                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1702                                 (len(obj[attrname]), attrname, str(obj.dn)))
1703                 else:
1704                     object_rdn_val = obj[attrname][0]
1705
1706             if str(attrname).lower() == 'isdeleted':
1707                 if obj[attrname][0] != "FALSE":
1708                     isDeleted = True
1709
1710             if str(attrname).lower() == 'systemflags':
1711                 systemFlags = int(obj[attrname][0])
1712
1713             if str(attrname).lower() == 'replpropertymetadata':
1714                 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
1715                     error_count += 1
1716                     self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
1717                     # We don't continue, as we may also have other fixes for this attribute
1718                     # based on what other attributes we see.
1719
1720                 try:
1721                     (set_attrs_from_md, list_attid_from_md, wrong_attids) \
1722                         = self.process_metadata(dn, obj[attrname])
1723                 except KeyError:
1724                     error_count += 1
1725                     self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
1726                     continue
1727
1728                 if len(set_attrs_from_md) < len(list_attid_from_md) \
1729                    or len(wrong_attids) > 0 \
1730                    or sorted(list_attid_from_md) != list_attid_from_md:
1731                     error_count +=1
1732                     self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname], wrong_attids)
1733
1734                 else:
1735                     # Here we check that the first attid is 0
1736                     # (objectClass).
1737                     if list_attid_from_md[0] != 0:
1738                         error_count += 1
1739                         self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
1740                                     (attrname, str(dn)))
1741
1742                 got_repl_property_meta_data = True
1743                 continue
1744
1745             if str(attrname).lower() == 'ntsecuritydescriptor':
1746                 (sd, sd_broken) = self.process_sd(dn, obj)
1747                 if sd_broken is not None:
1748                     self.err_wrong_sd(dn, sd, sd_broken)
1749                     error_count += 1
1750                     continue
1751
1752                 if sd.owner_sid is None or sd.group_sid is None:
1753                     self.err_missing_sd_owner(dn, sd)
1754                     error_count += 1
1755                     continue
1756
1757                 if self.reset_well_known_acls:
1758                     try:
1759                         well_known_sd = self.get_wellknown_sd(dn)
1760                     except KeyError:
1761                         continue
1762
1763                     current_sd = ndr_unpack(security.descriptor,
1764                                             str(obj[attrname][0]))
1765
1766                     diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
1767                     if diff != "":
1768                         self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
1769                         error_count += 1
1770                         continue
1771                 continue
1772
1773             if str(attrname).lower() == 'objectclass':
1774                 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
1775                 # Do not consider the attribute incorrect if:
1776                 #  - The sorted (alphabetically) list is the same, inclding case
1777                 #  - The first and last elements are the same
1778                 #
1779                 # This avoids triggering an error due to
1780                 # non-determinism in the sort routine in (at least)
1781                 # 4.3 and earlier, and the fact that any AUX classes
1782                 # in these attributes are also not sorted when
1783                 # imported from Windows (they are just in the reverse
1784                 # order of last set)
1785                 if sorted(normalised) != sorted(obj[attrname]) \
1786                    or normalised[0] != obj[attrname][0] \
1787                    or normalised[-1] != obj[attrname][-1]:
1788                     self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
1789                     error_count += 1
1790                 continue
1791
1792             if str(attrname).lower() == 'userparameters':
1793                 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
1794                     error_count += 1
1795                     self.err_short_userParameters(obj, attrname, obj[attrname])
1796                     continue
1797
1798                 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1799                     # This is the correct, normal prefix
1800                     continue
1801
1802                 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1803                     # this is the typical prefix from a windows migration
1804                     error_count += 1
1805                     self.err_base64_userParameters(obj, attrname, obj[attrname])
1806                     continue
1807
1808                 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':
1809                     # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1810                     error_count += 1
1811                     self.err_utf8_userParameters(obj, attrname, obj[attrname])
1812                     continue
1813
1814                 elif len(obj[attrname][0]) % 2 != 0:
1815                     # This is a value that isn't even in length
1816                     error_count += 1
1817                     self.err_odd_userParameters(obj, attrname, obj[attrname])
1818                     continue
1819
1820                 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':
1821                     # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
1822                     error_count += 1
1823                     self.err_doubled_userParameters(obj, attrname, obj[attrname])
1824                     continue
1825
1826             if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
1827                 if obj[attrname][0] in self.attribute_or_class_ids:
1828                     error_count += 1
1829                     self.report('Error: %s %s on %s already exists as an attributeId or governsId'
1830                                 % (attrname, obj.dn, obj[attrname][0]))
1831                 else:
1832                     self.attribute_or_class_ids.add(obj[attrname][0])
1833
1834             # check for empty attributes
1835             for val in obj[attrname]:
1836                 if val == '':
1837                     self.err_empty_attribute(dn, attrname)
1838                     error_count += 1
1839                     continue
1840
1841             # get the syntax oid for the attribute, so we can can have
1842             # special handling for some specific attribute types
1843             try:
1844                 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1845             except Exception, msg:
1846                 self.err_unknown_attribute(obj, attrname)
1847                 error_count += 1
1848                 continue
1849
1850             linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1851
1852             flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
1853             if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
1854                 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
1855                 and not linkID):
1856                 set_attrs_seen.add(str(attrname).lower())
1857
1858             if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
1859                                dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
1860                 # it's some form of DN, do specialised checking on those
1861                 error_count += self.check_dn(obj, attrname, syntax_oid)
1862             else:
1863
1864                 values = set()
1865                 # check for incorrectly normalised attributes
1866                 for val in obj[attrname]:
1867                     values.add(str(val))
1868
1869                     normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
1870                     if len(normalised) != 1 or normalised[0] != val:
1871                         self.err_normalise_mismatch(dn, attrname, obj[attrname])
1872                         error_count += 1
1873                         break
1874
1875                 if len(obj[attrname]) != len(values):
1876                     self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
1877                     error_count += 1
1878                     break
1879
1880             if str(attrname).lower() == "instancetype":
1881                 calculated_instancetype = self.calculate_instancetype(dn)
1882                 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
1883                     error_count += 1
1884                     self.err_wrong_instancetype(obj, calculated_instancetype)
1885
1886         if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
1887             error_count += 1
1888             self.err_missing_objectclass(dn)
1889
1890         if ("*" in attrs or "name" in map(str.lower, attrs)):
1891             if name_val is None:
1892                 error_count += 1
1893                 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
1894             if object_rdn_attr is None:
1895                 error_count += 1
1896                 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
1897
1898         if name_val is not None:
1899             parent_dn = None
1900             if isDeleted:
1901                 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
1902                     parent_dn = deleted_objects_dn
1903             if parent_dn is None:
1904                 parent_dn = obj.dn.parent()
1905             expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
1906             expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
1907
1908             if obj.dn == deleted_objects_dn:
1909                 expected_dn = obj.dn
1910
1911             if expected_dn != obj.dn:
1912                 error_count += 1
1913                 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
1914             elif obj.dn.get_rdn_value() != object_rdn_val:
1915                 error_count += 1
1916                 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
1917
1918         show_dn = True
1919         if got_repl_property_meta_data:
1920             if obj.dn == deleted_objects_dn:
1921                 isDeletedAttId = 131120
1922                 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
1923
1924                 expectedTimeDo = 2650466015990000000
1925                 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
1926                 if originating != expectedTimeDo:
1927                     if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
1928                         nmsg = ldb.Message()
1929                         nmsg.dn = dn
1930                         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1931                         error_count += 1
1932                         self.samdb.modify(nmsg, controls=["provision:0"])
1933
1934                     else:
1935                         self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
1936
1937             for att in set_attrs_seen.difference(set_attrs_from_md):
1938                 if show_dn:
1939                     self.report("On object %s" % dn)
1940                     show_dn = False
1941                 error_count += 1
1942                 self.report("ERROR: Attribute %s not present in replication metadata" % att)
1943                 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
1944                     self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
1945                     continue
1946                 self.fix_metadata(dn, att)
1947
1948         if self.is_fsmo_role(dn):
1949             if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
1950                 self.err_no_fsmoRoleOwner(obj)
1951                 error_count += 1
1952
1953         try:
1954             if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
1955                 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
1956                                         controls=["show_recycled:1", "show_deleted:1"])
1957         except ldb.LdbError, (enum, estr):
1958             if enum == ldb.ERR_NO_SUCH_OBJECT:
1959                 self.err_missing_parent(obj)
1960                 error_count += 1
1961             else:
1962                 raise
1963
1964         if dn in self.deleted_objects_containers and '*' in attrs:
1965             if self.is_deleted_deleted_objects(obj):
1966                 self.err_deleted_deleted_objects(obj)
1967                 error_count += 1
1968
1969         for (dns_part, msg) in self.dns_partitions:
1970             if dn == dns_part and 'repsFrom' in obj:
1971                 location = "msDS-NC-Replica-Locations"
1972                 if self.samdb.am_rodc():
1973                     location = "msDS-NC-RO-Replica-Locations"
1974
1975                 if location not in msg:
1976                     # There are no replica locations!
1977                     self.err_replica_locations(obj, msg.dn, location)
1978                     error_count += 1
1979                     continue
1980
1981                 found = False
1982                 for loc in msg[location]:
1983                     if loc == self.samdb.get_dsServiceName():
1984                         found = True
1985                 if not found:
1986                     # This DC is not in the replica locations
1987                     self.err_replica_locations(obj, msg.dn, location)
1988                     error_count += 1
1989
1990         if dn == self.server_ref_dn:
1991             # Check we have a valid RID Set
1992             if "*" in attrs or "rIDSetReferences" in attrs:
1993                 if "rIDSetReferences" not in obj:
1994                     # NO RID SET reference
1995                     # We are RID master, allocate it.
1996                     error_count += 1
1997
1998                     if self.is_rid_master:
1999                         # Allocate a RID Set
2000                         if self.confirm_all('Allocate the missing RID set for RID master?',
2001                                             'fix_missing_rid_set_master'):
2002
2003                             # We don't have auto-transaction logic on
2004                             # extended operations, so we have to do it
2005                             # here.
2006
2007                             self.samdb.transaction_start()
2008
2009                             try:
2010                                 self.samdb.create_own_rid_set()
2011
2012                             except:
2013                                 self.samdb.transaction_cancel()
2014                                 raise
2015
2016                             self.samdb.transaction_commit()
2017
2018
2019                     elif not self.samdb.am_rodc():
2020                         self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2021
2022
2023         # Check some details of our own RID Set
2024         if dn == self.rid_set_dn:
2025             res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2026                                     attrs=["rIDAllocationPool",
2027                                            "rIDPreviousAllocationPool",
2028                                            "rIDUsedPool",
2029                                            "rIDNextRID"])
2030             if "rIDAllocationPool" not in res[0]:
2031                 self.report("No rIDAllocationPool found in %s" % dn)
2032                 error_count += 1
2033             else:
2034                 next_pool = int(res[0]["rIDAllocationPool"][0])
2035
2036                 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2037                 low = 0x00000000FFFFFFFF & next_pool
2038
2039                 if high <= low:
2040                     self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2041                     error_count += 1
2042
2043                 if "rIDNextRID" in res[0]:
2044                     next_free_rid = int(res[0]["rIDNextRID"][0])
2045                 else:
2046                     next_free_rid = 0
2047
2048                 if next_free_rid == 0:
2049                     next_free_rid = low
2050                 else:
2051                     next_free_rid += 1
2052
2053                 # Check the remainder of this pool for conflicts.  If
2054                 # ridalloc_allocate_rid() moves to a new pool, this
2055                 # will be above high, so we will stop.
2056                 while next_free_rid <= high:
2057                     sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2058                     try:
2059                         res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2060                                                 attrs=[])
2061                     except ldb.LdbError, (enum, estr):
2062                         if enum != ldb.ERR_NO_SUCH_OBJECT:
2063                             raise
2064                         res = None
2065                     if res is not None:
2066                         self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2067                         error_count += 1
2068
2069                         if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2070                                             % (sid, dn),
2071                                             'fix_sid_rid_set_conflict'):
2072                             self.samdb.transaction_start()
2073
2074                             # This will burn RIDs, which will move
2075                             # past the conflict.  We then check again
2076                             # to see if the new RID conflicts, until
2077                             # the end of the current pool.  We don't
2078                             # look at the next pool to avoid burning
2079                             # all RIDs in one go in some strange
2080                             # failure case.
2081                             try:
2082                                 while True:
2083                                     allocated_rid = self.samdb.allocate_rid()
2084                                     if allocated_rid >= next_free_rid:
2085                                         next_free_rid = allocated_rid + 1
2086                                         break
2087                             except:
2088                                 self.samdb.transaction_cancel()
2089                                 raise
2090
2091                             self.samdb.transaction_commit()
2092                         else:
2093                             break
2094                     else:
2095                         next_free_rid += 1
2096
2097
2098         return error_count
2099
2100     ################################################################
2101     # check special @ROOTDSE attributes
2102     def check_rootdse(self):
2103         '''check the @ROOTDSE special object'''
2104         dn = ldb.Dn(self.samdb, '@ROOTDSE')
2105         if self.verbose:
2106             self.report("Checking object %s" % dn)
2107         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2108         if len(res) != 1:
2109             self.report("Object %s disappeared during check" % dn)
2110             return 1
2111         obj = res[0]
2112         error_count = 0
2113
2114         # check that the dsServiceName is in GUID form
2115         if not 'dsServiceName' in obj:
2116             self.report('ERROR: dsServiceName missing in @ROOTDSE')
2117             return error_count+1
2118
2119         if not obj['dsServiceName'][0].startswith('<GUID='):
2120             self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2121             error_count += 1
2122             if not self.confirm('Change dsServiceName to GUID form?'):
2123                 return error_count
2124             res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
2125                                     scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2126             guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2127             m = ldb.Message()
2128             m.dn = dn
2129             m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2130                                                     ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2131             if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2132                 self.report("Changed dsServiceName to GUID form")
2133         return error_count
2134
2135
2136     ###############################################
2137     # re-index the database
2138     def reindex_database(self):
2139         '''re-index the whole database'''
2140         m = ldb.Message()
2141         m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2142         m['add']    = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2143         m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2144         return self.do_modify(m, [], 're-indexed database', validate=False)
2145
2146     ###############################################
2147     # reset @MODULES
2148     def reset_modules(self):
2149         '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2150         m = ldb.Message()
2151         m.dn = ldb.Dn(self.samdb, "@MODULES")
2152         m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2153         return self.do_modify(m, [], 'reset @MODULES on database', validate=False)