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