dbcheck: Check for and remove duplicate values in attributes
[nivanova/samba-autobuild/.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
35
36 class dbcheck(object):
37     """check a SAM database for errors"""
38
39     def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
40                  yes=False, quiet=False, in_transaction=False,
41                  reset_well_known_acls=False):
42         self.samdb = samdb
43         self.dict_oid_name = None
44         self.samdb_schema = (samdb_schema or samdb)
45         self.verbose = verbose
46         self.fix = fix
47         self.yes = yes
48         self.quiet = quiet
49         self.remove_all_unknown_attributes = False
50         self.remove_all_empty_attributes = False
51         self.fix_all_normalisation = False
52         self.fix_all_duplicates = False
53         self.fix_all_DN_GUIDs = False
54         self.fix_all_binary_dn = False
55         self.remove_all_deleted_DN_links = False
56         self.fix_all_target_mismatch = False
57         self.fix_all_metadata = False
58         self.fix_time_metadata = False
59         self.fix_all_missing_backlinks = False
60         self.fix_all_orphaned_backlinks = False
61         self.fix_rmd_flags = False
62         self.fix_ntsecuritydescriptor = False
63         self.fix_ntsecuritydescriptor_owner_group = False
64         self.seize_fsmo_role = False
65         self.move_to_lost_and_found = False
66         self.fix_instancetype = False
67         self.fix_replmetadata_zero_invocationid = False
68         self.fix_replmetadata_duplicate_attid = False
69         self.fix_replmetadata_wrong_attid = False
70         self.fix_replmetadata_unsorted_attid = False
71         self.fix_deleted_deleted_objects = False
72         self.fix_dn = False
73         self.fix_base64_userparameters = False
74         self.fix_utf8_userparameters = False
75         self.fix_doubled_userparameters = False
76         self.reset_well_known_acls = reset_well_known_acls
77         self.reset_all_well_known_acls = False
78         self.in_transaction = in_transaction
79         self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
80         self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
81         self.schema_dn = samdb.get_schema_basedn()
82         self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
83         self.ntds_dsa = ldb.Dn(samdb, samdb.get_dsServiceName())
84         self.class_schemaIDGUID = {}
85         self.wellknown_sds = get_wellknown_sds(self.samdb)
86         self.fix_all_missing_objectclass = False
87
88         self.dn_set = set()
89
90         self.name_map = {}
91         try:
92             res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE,
93                            attrs=["objectSid"])
94             dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
95             self.name_map['DnsAdmins'] = str(dnsadmins_sid)
96         except ldb.LdbError, (enum, estr):
97             if enum != ldb.ERR_NO_SUCH_OBJECT:
98                 raise
99             pass
100
101         self.system_session_info = system_session()
102         self.admin_session_info = admin_session(None, samdb.get_domain_sid())
103
104         res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs', 'hasMasterNCs'])
105         if "msDS-hasMasterNCs" in res[0]:
106             self.write_ncs = res[0]["msDS-hasMasterNCs"]
107         else:
108             # If the Forest Level is less than 2003 then there is no
109             # msDS-hasMasterNCs, so we fall back to hasMasterNCs
110             # no need to merge as all the NCs that are in hasMasterNCs must
111             # also be in msDS-hasMasterNCs (but not the opposite)
112             if "hasMasterNCs" in res[0]:
113                 self.write_ncs = res[0]["hasMasterNCs"]
114             else:
115                 self.write_ncs = None
116
117         res = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts'])
118         try:
119             ncs = res[0]["namingContexts"]
120             self.deleted_objects_containers = []
121             for nc in ncs:
122                 try:
123                     dn = self.samdb.get_wellknown_dn(ldb.Dn(self.samdb, nc),
124                                                      dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
125                     self.deleted_objects_containers.append(dn)
126                 except KeyError:
127                     pass
128         except KeyError:
129             pass
130         except IndexError:
131             pass
132
133     def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
134         '''perform a database check, returning the number of errors found'''
135         res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
136         self.report('Checking %u objects' % len(res))
137         error_count = 0
138
139         for object in res:
140             self.dn_set.add(str(object.dn))
141             error_count += self.check_object(object.dn, attrs=attrs)
142
143         if DN is None:
144             error_count += self.check_rootdse()
145
146         if error_count != 0 and not self.fix:
147             self.report("Please use --fix to fix these errors")
148
149         self.report('Checked %u objects (%u errors)' % (len(res), error_count))
150         return error_count
151
152     def report(self, msg):
153         '''print a message unless quiet is set'''
154         if not self.quiet:
155             print(msg)
156
157     def confirm(self, msg, allow_all=False, forced=False):
158         '''confirm a change'''
159         if not self.fix:
160             return False
161         if self.quiet:
162             return self.yes
163         if self.yes:
164             forced = True
165         return common.confirm(msg, forced=forced, allow_all=allow_all)
166
167     ################################################################
168     # a local confirm function with support for 'all'
169     def confirm_all(self, msg, all_attr):
170         '''confirm a change with support for "all" '''
171         if not self.fix:
172             return False
173         if self.quiet:
174             return self.yes
175         if getattr(self, all_attr) == 'NONE':
176             return False
177         if getattr(self, all_attr) == 'ALL':
178             forced = True
179         else:
180             forced = self.yes
181         c = common.confirm(msg, forced=forced, allow_all=True)
182         if c == 'ALL':
183             setattr(self, all_attr, 'ALL')
184             return True
185         if c == 'NONE':
186             setattr(self, all_attr, 'NONE')
187             return False
188         return c
189
190     def do_delete(self, dn, controls, msg):
191         '''delete dn with optional verbose output'''
192         if self.verbose:
193             self.report("delete DN %s" % dn)
194         try:
195             controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
196             self.samdb.delete(dn, controls=controls)
197         except Exception, err:
198             self.report("%s : %s" % (msg, err))
199             return False
200         return True
201
202     def do_modify(self, m, controls, msg, validate=True):
203         '''perform a modify with optional verbose output'''
204         if self.verbose:
205             self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
206         try:
207             controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
208             self.samdb.modify(m, controls=controls, validate=validate)
209         except Exception, err:
210             self.report("%s : %s" % (msg, err))
211             return False
212         return True
213
214     def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
215         '''perform a modify with optional verbose output'''
216         if self.verbose:
217             self.report("""dn: %s
218 changeType: modrdn
219 newrdn: %s
220 deleteOldRdn: 1
221 newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
222         try:
223             to_dn = to_rdn + to_base
224             controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
225             self.samdb.rename(from_dn, to_dn, controls=controls)
226         except Exception, err:
227             self.report("%s : %s" % (msg, err))
228             return False
229         return True
230
231     def err_empty_attribute(self, dn, attrname):
232         '''fix empty attributes'''
233         self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
234         if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
235             self.report("Not fixing empty attribute %s" % attrname)
236             return
237
238         m = ldb.Message()
239         m.dn = dn
240         m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
241         if self.do_modify(m, ["relax:0", "show_recycled:1"],
242                           "Failed to remove empty attribute %s" % attrname, validate=False):
243             self.report("Removed empty attribute %s" % attrname)
244
245     def err_normalise_mismatch(self, dn, attrname, values):
246         '''fix attribute normalisation errors'''
247         self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
248         mod_list = []
249         for val in values:
250             normalised = self.samdb.dsdb_normalise_attributes(
251                 self.samdb_schema, attrname, [val])
252             if len(normalised) != 1:
253                 self.report("Unable to normalise value '%s'" % val)
254                 mod_list.append((val, ''))
255             elif (normalised[0] != val):
256                 self.report("value '%s' should be '%s'" % (val, normalised[0]))
257                 mod_list.append((val, normalised[0]))
258         if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
259             self.report("Not fixing attribute %s" % attrname)
260             return
261
262         m = ldb.Message()
263         m.dn = dn
264         for i in range(0, len(mod_list)):
265             (val, nval) = mod_list[i]
266             m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
267             if nval != '':
268                 m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
269                     attrname)
270
271         if self.do_modify(m, ["relax:0", "show_recycled:1"],
272                           "Failed to normalise attribute %s" % attrname,
273                           validate=False):
274             self.report("Normalised attribute %s" % attrname)
275
276     def err_normalise_mismatch_replace(self, dn, attrname, values):
277         '''fix attribute normalisation errors'''
278         normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
279         self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
280         self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
281         if list(normalised) == values:
282             return
283         if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
284             self.report("Not fixing attribute '%s'" % attrname)
285             return
286
287         m = ldb.Message()
288         m.dn = dn
289         m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
290
291         if self.do_modify(m, ["relax:0", "show_recycled:1"],
292                           "Failed to normalise attribute %s" % attrname,
293                           validate=False):
294             self.report("Normalised attribute %s" % attrname)
295
296     def err_duplicate_values(self, dn, attrname, dup_values, values):
297         '''fix attribute normalisation errors'''
298         self.report("ERROR: Duplicate values for attribute '%s' in '%s'" % (attrname, dn))
299         self.report("Values contain a duplicate: [%s]/[%s]!" % (','.join(dup_values), ','.join(values)))
300         if not self.confirm_all("Fix duplicates for '%s' from '%s'?" % (attrname, dn), 'fix_all_duplicates'):
301             self.report("Not fixing attribute '%s'" % attrname)
302             return
303
304         m = ldb.Message()
305         m.dn = dn
306         m[attrname] = ldb.MessageElement(values, ldb.FLAG_MOD_REPLACE, attrname)
307
308         if self.do_modify(m, ["relax:0", "show_recycled:1"],
309                           "Failed to remove duplicate value on attribute %s" % attrname,
310                           validate=False):
311             self.report("Removed duplicate value on attribute %s" % attrname)
312
313     def is_deleted_objects_dn(self, dsdb_dn):
314         '''see if a dsdb_Dn is the special Deleted Objects DN'''
315         return dsdb_dn.prefix == "B:32:%s:" % dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER
316
317     def err_missing_objectclass(self, dn):
318         """handle object without objectclass"""
319         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)))
320         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'):
321             self.report("Not deleting object with missing objectclass '%s'" % dn)
322             return
323         if self.do_delete(dn, ["relax:0"],
324                           "Failed to remove DN %s" % dn):
325             self.report("Removed DN %s" % dn)
326
327     def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn):
328         """handle a DN pointing to a deleted object"""
329         self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
330         self.report("Target GUID points at deleted DN %s" % correct_dn)
331         if not self.confirm_all('Remove DN link?', 'remove_all_deleted_DN_links'):
332             self.report("Not removing")
333             return
334         m = ldb.Message()
335         m.dn = dn
336         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
337         if self.do_modify(m, ["show_recycled:1", "local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK],
338                           "Failed to remove deleted DN attribute %s" % attrname):
339             self.report("Removed deleted DN on attribute %s" % attrname)
340
341     def err_missing_dn_GUID(self, dn, attrname, val, dsdb_dn):
342         """handle a missing target DN (both GUID and DN string form are missing)"""
343         # check if its a backlink
344         linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
345         if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
346             self.report("Not removing dangling forward link")
347             return
348         self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn)
349
350     def err_incorrect_dn_GUID(self, dn, attrname, val, dsdb_dn, errstr):
351         """handle a missing GUID extended DN component"""
352         self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
353         controls=["extended_dn:1:1", "show_recycled:1"]
354         try:
355             res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
356                                     attrs=[], controls=controls)
357         except ldb.LdbError, (enum, estr):
358             self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
359             self.err_missing_dn_GUID(dn, attrname, val, dsdb_dn)
360             return
361         if len(res) == 0:
362             self.report("unable to find object for DN %s" % dsdb_dn.dn)
363             self.err_missing_dn_GUID(dn, attrname, val, dsdb_dn)
364             return
365         dsdb_dn.dn = res[0].dn
366
367         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
368             self.report("Not fixing %s" % errstr)
369             return
370         m = ldb.Message()
371         m.dn = dn
372         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
373         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
374
375         if self.do_modify(m, ["show_recycled:1"],
376                           "Failed to fix %s on attribute %s" % (errstr, attrname)):
377             self.report("Fixed %s on attribute %s" % (errstr, attrname))
378
379     def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
380         """handle an incorrect binary DN component"""
381         self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
382         controls=["extended_dn:1:1", "show_recycled:1"]
383
384         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
385             self.report("Not fixing %s" % errstr)
386             return
387         m = ldb.Message()
388         m.dn = dn
389         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
390         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
391
392         if self.do_modify(m, ["show_recycled:1"],
393                           "Failed to fix %s on attribute %s" % (errstr, attrname)):
394             self.report("Fixed %s on attribute %s" % (errstr, attrname))
395
396     def err_dn_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, errstr):
397         """handle a DN string being incorrect"""
398         self.report("ERROR: incorrect DN string component for %s in object %s - %s" % (attrname, dn, val))
399         dsdb_dn.dn = correct_dn
400
401         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_target_mismatch'):
402             self.report("Not fixing %s" % errstr)
403             return
404         m = ldb.Message()
405         m.dn = dn
406         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
407         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
408         if self.do_modify(m, ["show_recycled:1"],
409                           "Failed to fix incorrect DN string on attribute %s" % attrname):
410             self.report("Fixed incorrect DN string on attribute %s" % (attrname))
411
412     def err_unknown_attribute(self, obj, attrname):
413         '''handle an unknown attribute error'''
414         self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
415         if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
416             self.report("Not removing %s" % attrname)
417             return
418         m = ldb.Message()
419         m.dn = obj.dn
420         m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
421         if self.do_modify(m, ["relax:0", "show_recycled:1"],
422                           "Failed to remove unknown attribute %s" % attrname):
423             self.report("Removed unknown attribute %s" % (attrname))
424
425     def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
426         '''handle a missing backlink value'''
427         self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
428         if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
429             self.report("Not fixing missing backlink %s" % backlink_name)
430             return
431         m = ldb.Message()
432         m.dn = obj.dn
433         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
434         m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, attrname)
435         if self.do_modify(m, ["show_recycled:1"],
436                           "Failed to fix missing backlink %s" % backlink_name):
437             self.report("Fixed missing backlink %s" % (backlink_name))
438
439     def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
440         '''handle a incorrect RMD_FLAGS value'''
441         rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
442         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()))
443         if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
444             self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
445             return
446         m = ldb.Message()
447         m.dn = obj.dn
448         m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
449         if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
450                           "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
451             self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
452
453     def err_orphaned_backlink(self, obj, attrname, val, link_name, target_dn):
454         '''handle a orphaned backlink value'''
455         self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (attrname, obj.dn, link_name, target_dn))
456         if not self.confirm_all('Remove orphaned backlink %s' % link_name, 'fix_all_orphaned_backlinks'):
457             self.report("Not removing orphaned backlink %s" % link_name)
458             return
459         m = ldb.Message()
460         m.dn = obj.dn
461         m['value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
462         if self.do_modify(m, ["show_recycled:1", "relax:0"],
463                           "Failed to fix orphaned backlink %s" % link_name):
464             self.report("Fixed orphaned backlink %s" % (link_name))
465
466     def err_no_fsmoRoleOwner(self, obj):
467         '''handle a missing fSMORoleOwner'''
468         self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
469         res = self.samdb.search("",
470                                 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
471         assert len(res) == 1
472         serviceName = res[0]["dsServiceName"][0]
473         if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
474             self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
475             return
476         m = ldb.Message()
477         m.dn = obj.dn
478         m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
479         if self.do_modify(m, [],
480                           "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
481             self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
482
483     def err_missing_parent(self, obj):
484         '''handle a missing parent'''
485         self.report("ERROR: parent object not found for %s" % (obj.dn))
486         if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
487             self.report('Not moving object %s into LostAndFound' % (obj.dn))
488             return
489
490         keep_transaction = True
491         self.samdb.transaction_start()
492         try:
493             nc_root = self.samdb.get_nc_root(obj.dn);
494             lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
495             new_dn = ldb.Dn(self.samdb, str(obj.dn))
496             new_dn.remove_base_components(len(new_dn) - 1)
497             if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
498                               "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
499                 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
500
501                 m = ldb.Message()
502                 m.dn = obj.dn
503                 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
504
505                 if self.do_modify(m, [],
506                                   "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
507                     self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
508                     keep_transaction = True
509         except:
510             self.samdb.transaction_cancel()
511             raise
512
513         if keep_transaction:
514             self.samdb.transaction_commit()
515         else:
516             self.samdb.transaction_cancel()
517
518     def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
519         '''handle a wrong dn'''
520
521         new_rdn = ldb.Dn(self.samdb, str(new_dn))
522         new_rdn.remove_base_components(len(new_rdn) - 1)
523         new_parent = new_dn.parent()
524
525         attributes = ""
526         if rdn_val != name_val:
527             attributes += "%s=%r " % (rdn_attr, rdn_val)
528         attributes += "name=%r" % (name_val)
529
530         self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
531         if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
532             self.report("Not renaming %s to %s" % (obj.dn, new_dn))
533             return
534
535         if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
536                           "Failed to rename object %s into %s" % (obj.dn, new_dn)):
537             self.report("Renamed %s into %s" % (obj.dn, new_dn))
538
539     def err_wrong_instancetype(self, obj, calculated_instancetype):
540         '''handle a wrong instanceType'''
541         self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
542         if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
543             self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
544             return
545
546         m = ldb.Message()
547         m.dn = obj.dn
548         m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
549         if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
550                           "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
551             self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
552
553     def err_short_userParameters(self, obj, attrname, value):
554         # This is a truncated userParameters due to a pre 4.1 replication bug
555         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)))
556
557     def err_base64_userParameters(self, obj, attrname, value):
558         '''handle a wrong userParameters'''
559         self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
560         if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
561             self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
562             return
563
564         m = ldb.Message()
565         m.dn = obj.dn
566         m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
567         if self.do_modify(m, [],
568                           "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
569             self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
570
571     def err_utf8_userParameters(self, obj, attrname, value):
572         '''handle a wrong userParameters'''
573         self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
574         if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
575             self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
576             return
577
578         m = ldb.Message()
579         m.dn = obj.dn
580         m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
581                                         ldb.FLAG_MOD_REPLACE, 'userParameters')
582         if self.do_modify(m, [],
583                           "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
584             self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
585
586     def err_doubled_userParameters(self, obj, attrname, value):
587         '''handle a wrong userParameters'''
588         self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
589         if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
590             self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
591             return
592
593         m = ldb.Message()
594         m.dn = obj.dn
595         m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
596                                         ldb.FLAG_MOD_REPLACE, 'userParameters')
597         if self.do_modify(m, [],
598                           "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
599             self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
600
601     def err_odd_userParameters(self, obj, attrname):
602         # This is a truncated userParameters due to a pre 4.1 replication bug
603         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)))
604
605     def find_revealed_link(self, dn, attrname, guid):
606         '''return a revealed link in an object'''
607         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
608                                 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
609         syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
610         for val in res[0][attrname]:
611             dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
612             guid2 = dsdb_dn.dn.get_extended_component("GUID")
613             if guid == guid2:
614                 return dsdb_dn
615         return None
616
617     def check_dn(self, obj, attrname, syntax_oid):
618         '''check a DN attribute for correctness'''
619         error_count = 0
620         for val in obj[attrname]:
621             dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
622
623             # all DNs should have a GUID component
624             guid = dsdb_dn.dn.get_extended_component("GUID")
625             if guid is None:
626                 error_count += 1
627                 self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn,
628                     "missing GUID")
629                 continue
630
631             guidstr = str(misc.GUID(guid))
632
633             attrs = ['isDeleted']
634
635             if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
636                 fixing_msDS_HasInstantiatedNCs = True
637                 attrs.append("instanceType")
638             else:
639                 fixing_msDS_HasInstantiatedNCs = False
640
641             linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
642             reverse_link_name = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
643             if reverse_link_name is not None:
644                 attrs.append(reverse_link_name)
645
646             # check its the right GUID
647             try:
648                 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
649                                         attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1"])
650             except ldb.LdbError, (enum, estr):
651                 error_count += 1
652                 self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn, "incorrect GUID")
653                 continue
654
655             if fixing_msDS_HasInstantiatedNCs:
656                 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
657                 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
658
659                 if str(dsdb_dn) != val:
660                     error_count +=1
661                     self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
662                     continue
663
664             # now we have two cases - the source object might or might not be deleted
665             is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
666             target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
667
668             # the target DN is not allowed to be deleted, unless the target DN is the
669             # special Deleted Objects container
670             if target_is_deleted and not is_deleted and not self.is_deleted_objects_dn(dsdb_dn):
671                 error_count += 1
672                 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn)
673                 continue
674
675             # check the DN matches in string form
676             if res[0].dn.extended_str() != dsdb_dn.dn.extended_str():
677                 error_count += 1
678                 self.err_dn_target_mismatch(obj.dn, attrname, val, dsdb_dn,
679                                             res[0].dn, "incorrect string version of DN")
680                 continue
681
682             if is_deleted and not target_is_deleted and reverse_link_name is not None:
683                 revealed_dn = self.find_revealed_link(obj.dn, attrname, guid)
684                 rmd_flags = revealed_dn.dn.get_extended_component("RMD_FLAGS")
685                 if rmd_flags is not None and (int(rmd_flags) & 1) == 0:
686                     # the RMD_FLAGS for this link should be 1, as the target is deleted
687                     self.err_incorrect_rmd_flags(obj, attrname, revealed_dn)
688                     continue
689
690             # check the reverse_link is correct if there should be one
691             if reverse_link_name is not None:
692                 match_count = 0
693                 if reverse_link_name in res[0]:
694                     for v in res[0][reverse_link_name]:
695                         if v == obj.dn.extended_str():
696                             match_count += 1
697                 if match_count != 1:
698                     error_count += 1
699                     if linkID & 1:
700                         self.err_orphaned_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
701                     else:
702                         self.err_missing_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
703                     continue
704
705         return error_count
706
707
708     def get_originating_time(self, val, attid):
709         '''Read metadata properties and return the originating time for
710            a given attributeId.
711
712            :return: the originating time or 0 if not found
713         '''
714
715         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
716         obj = repl.ctr
717
718         for o in repl.ctr.array:
719             if o.attid == attid:
720                 return o.originating_change_time
721
722         return 0
723
724     def process_metadata(self, dn, val):
725         '''Read metadata properties and list attributes in it.
726            raises KeyError if the attid is unknown.'''
727
728         set_att = set()
729         wrong_attids = set()
730         list_attid = []
731         in_schema_nc = dn.is_child_of(self.schema_dn)
732
733         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
734         obj = repl.ctr
735
736         for o in repl.ctr.array:
737             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
738             set_att.add(att.lower())
739             list_attid.append(o.attid)
740             correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
741                                                                              is_schema_nc=in_schema_nc)
742             if correct_attid != o.attid:
743                 wrong_attids.add(o.attid)
744
745         return (set_att, list_attid, wrong_attids)
746
747
748     def fix_metadata(self, dn, attr):
749         '''re-write replPropertyMetaData elements for a single attribute for a
750         object. This is used to fix missing replPropertyMetaData elements'''
751         res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
752                                 controls = ["search_options:1:2", "show_recycled:1"])
753         msg = res[0]
754         nmsg = ldb.Message()
755         nmsg.dn = dn
756         nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
757         if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
758                           "Failed to fix metadata for attribute %s" % attr):
759             self.report("Fixed metadata for attribute %s" % attr)
760
761     def ace_get_effective_inherited_type(self, ace):
762         if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
763             return None
764
765         check = False
766         if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
767             check = True
768         elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
769             check = True
770         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
771             check = True
772         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
773             check = True
774
775         if not check:
776             return None
777
778         if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
779             return None
780
781         return str(ace.object.inherited_type)
782
783     def lookup_class_schemaIDGUID(self, cls):
784         if cls in self.class_schemaIDGUID:
785             return self.class_schemaIDGUID[cls]
786
787         flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
788         res = self.samdb.search(base=self.schema_dn,
789                                 expression=flt,
790                                 attrs=["schemaIDGUID"])
791         t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
792
793         self.class_schemaIDGUID[cls] = t
794         return t
795
796     def process_sd(self, dn, obj):
797         sd_attr = "nTSecurityDescriptor"
798         sd_val = obj[sd_attr]
799
800         sd = ndr_unpack(security.descriptor, str(sd_val))
801
802         is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
803         if is_deleted:
804             # we don't fix deleted objects
805             return (sd, None)
806
807         sd_clean = security.descriptor()
808         sd_clean.owner_sid = sd.owner_sid
809         sd_clean.group_sid = sd.group_sid
810         sd_clean.type = sd.type
811         sd_clean.revision = sd.revision
812
813         broken = False
814         last_inherited_type = None
815
816         aces = []
817         if sd.sacl is not None:
818             aces = sd.sacl.aces
819         for i in range(0, len(aces)):
820             ace = aces[i]
821
822             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
823                 sd_clean.sacl_add(ace)
824                 continue
825
826             t = self.ace_get_effective_inherited_type(ace)
827             if t is None:
828                 continue
829
830             if last_inherited_type is not None:
831                 if t != last_inherited_type:
832                     # if it inherited from more than
833                     # one type it's very likely to be broken
834                     #
835                     # If not the recalculation will calculate
836                     # the same result.
837                     broken = True
838                 continue
839
840             last_inherited_type = t
841
842         aces = []
843         if sd.dacl is not None:
844             aces = sd.dacl.aces
845         for i in range(0, len(aces)):
846             ace = aces[i]
847
848             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
849                 sd_clean.dacl_add(ace)
850                 continue
851
852             t = self.ace_get_effective_inherited_type(ace)
853             if t is None:
854                 continue
855
856             if last_inherited_type is not None:
857                 if t != last_inherited_type:
858                     # if it inherited from more than
859                     # one type it's very likely to be broken
860                     #
861                     # If not the recalculation will calculate
862                     # the same result.
863                     broken = True
864                 continue
865
866             last_inherited_type = t
867
868         if broken:
869             return (sd_clean, sd)
870
871         if last_inherited_type is None:
872             # ok
873             return (sd, None)
874
875         cls = None
876         try:
877             cls = obj["objectClass"][-1]
878         except KeyError, e:
879             pass
880
881         if cls is None:
882             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
883                                     attrs=["isDeleted", "objectClass"],
884                                     controls=["show_recycled:1"])
885             o = res[0]
886             is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
887             if is_deleted:
888                 # we don't fix deleted objects
889                 return (sd, None)
890             cls = o["objectClass"][-1]
891
892         t = self.lookup_class_schemaIDGUID(cls)
893
894         if t != last_inherited_type:
895             # broken
896             return (sd_clean, sd)
897
898         # ok
899         return (sd, None)
900
901     def err_wrong_sd(self, dn, sd, sd_broken):
902         '''re-write the SD due to incorrect inherited ACEs'''
903         sd_attr = "nTSecurityDescriptor"
904         sd_val = ndr_pack(sd)
905         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
906
907         if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
908             self.report('Not fixing %s on %s\n' % (sd_attr, dn))
909             return
910
911         nmsg = ldb.Message()
912         nmsg.dn = dn
913         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
914         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
915                           "Failed to fix attribute %s" % sd_attr):
916             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
917
918     def err_wrong_default_sd(self, dn, sd, sd_old, diff):
919         '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
920         sd_attr = "nTSecurityDescriptor"
921         sd_val = ndr_pack(sd)
922         sd_old_val = ndr_pack(sd_old)
923         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
924         if sd.owner_sid is not None:
925             sd_flags |= security.SECINFO_OWNER
926         if sd.group_sid is not None:
927             sd_flags |= security.SECINFO_GROUP
928
929         if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
930             self.report('Not resetting %s on %s\n' % (sd_attr, dn))
931             return
932
933         m = ldb.Message()
934         m.dn = dn
935         m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
936         if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
937                           "Failed to reset attribute %s" % sd_attr):
938             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
939
940     def err_missing_sd_owner(self, dn, sd):
941         '''re-write the SD due to a missing owner or group'''
942         sd_attr = "nTSecurityDescriptor"
943         sd_val = ndr_pack(sd)
944         sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
945
946         if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
947             self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
948             return
949
950         nmsg = ldb.Message()
951         nmsg.dn = dn
952         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
953
954         # By setting the session_info to admin_session_info and
955         # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
956         # flags we cause the descriptor module to set the correct
957         # owner and group on the SD, replacing the None/NULL values
958         # for owner_sid and group_sid currently present.
959         #
960         # The admin_session_info matches that used in provision, and
961         # is the best guess we can make for an existing object that
962         # hasn't had something specifically set.
963         #
964         # This is important for the dns related naming contexts.
965         self.samdb.set_session_info(self.admin_session_info)
966         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
967                           "Failed to fix metadata for attribute %s" % sd_attr):
968             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
969         self.samdb.set_session_info(self.system_session_info)
970
971
972     def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
973         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
974                           str(repl_meta_data))
975         ctr = repl.ctr
976         found = False
977         for o in ctr.array:
978             # Search for a zero invocationID
979             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
980                 continue
981
982             found = True
983             self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
984                            version %d changed at %s is 00000000-0000-0000-0000-000000000000,
985                            but should be non-zero.  Proposed fix is to set to our invocationID (%s).'''
986                         % (dn, o.attid, o.version,
987                            time.ctime(samba.nttime2unix(o.originating_change_time)),
988                            self.samdb.get_invocation_id()))
989
990         return found
991
992
993     def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
994         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
995                           str(repl_meta_data))
996         ctr = repl.ctr
997         now = samba.unix2nttime(int(time.time()))
998         found = False
999         for o in ctr.array:
1000             # Search for a zero invocationID
1001             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1002                 continue
1003
1004             found = True
1005             seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1006             o.version = o.version + 1
1007             o.originating_change_time = now
1008             o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1009             o.originating_usn = seq
1010             o.local_usn = seq
1011
1012         if found:
1013             replBlob = ndr_pack(repl)
1014             msg = ldb.Message()
1015             msg.dn = dn
1016
1017             if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1018                                     % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1019                 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1020                 return
1021
1022             nmsg = ldb.Message()
1023             nmsg.dn = dn
1024             nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1025             if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1026                                      "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1027                               "Failed to fix attribute %s" % attr):
1028                 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1029
1030
1031     def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1032         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1033                           str(repl_meta_data))
1034         ctr = repl.ctr
1035         for o in ctr.array:
1036             # Search for an invalid attid
1037             try:
1038                 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1039             except KeyError:
1040                 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1041                 return
1042
1043
1044     def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1045         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1046                           str(repl_meta_data))
1047         fix = False
1048
1049         set_att = set()
1050         remove_attid = set()
1051         hash_att = {}
1052
1053         in_schema_nc = dn.is_child_of(self.schema_dn)
1054
1055         ctr = repl.ctr
1056         # Sort the array, except for the last element.  This strange
1057         # construction, creating a new list, due to bugs in samba's
1058         # array handling in IDL generated objects.
1059         ctr.array = sorted(ctr.array[:-1], key=lambda o: o.attid) + [ctr.array[-1]]
1060         # Now walk it in reverse, so we see the low (and so incorrect,
1061         # the correct values are above 0x80000000) values first and
1062         # remove the 'second' value we see.
1063         for o in reversed(ctr.array):
1064             print "%s: 0x%08x" % (dn, o.attid)
1065             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1066             if att.lower() in set_att:
1067                 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1068                 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1069                                         % (attr, dn, o.attid, att, hash_att[att].attid),
1070                                         'fix_replmetadata_duplicate_attid'):
1071                     self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1072                                 % (o.attid, att, attr, dn))
1073                     return
1074                 fix = True
1075                 remove_attid.add(o.attid)
1076                 # We want to set the metadata for the most recent
1077                 # update to have been applied locally, that is the metadata
1078                 # matching the (eg string) value in the attribute
1079                 if o.local_usn > hash_att[att].local_usn:
1080                     # This is always what we would have sent over DRS,
1081                     # because the DRS server will have sent the
1082                     # msDS-IntID, but with the values from both
1083                     # attribute entries.
1084                     hash_att[att].version = o.version
1085                     hash_att[att].originating_change_time = o.originating_change_time
1086                     hash_att[att].originating_invocation_id = o.originating_invocation_id
1087                     hash_att[att].originating_usn = o.originating_usn
1088                     hash_att[att].local_usn = o.local_usn
1089
1090                 # Do not re-add the value to the set or overwrite the hash value
1091                 continue
1092
1093             hash_att[att] = o
1094             set_att.add(att.lower())
1095
1096         # Generate a real list we can sort on properly
1097         new_list = [o for o in ctr.array if o.attid not in remove_attid]
1098
1099         if (len(wrong_attids) > 0):
1100             for o in new_list:
1101                 if o.attid in wrong_attids:
1102                     att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1103                     correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1104                     self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1105                     if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1106                                             % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1107                         self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1108                                     % (o.attid, correct_attid, att, attr, dn))
1109                         return
1110                     fix = True
1111                     o.attid = correct_attid
1112             if fix:
1113                 # Sort the array, except for the last element (we changed
1114                 # the value so must re-sort)
1115                 new_list[:-1] = sorted(new_list[:-1], key=lambda o: o.attid)
1116
1117         # If we did not already need to fix it, then ask about sorting
1118         if not fix:
1119             self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1120             if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1121                                     % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1122                 self.report('Not fixing %s on %s\n' % (attr, dn))
1123                 return
1124
1125             # The actual sort done is done at the top of the function
1126
1127         ctr.count = len(new_list)
1128         ctr.array = new_list
1129         replBlob = ndr_pack(repl)
1130
1131         nmsg = ldb.Message()
1132         nmsg.dn = dn
1133         nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1134         if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1135                              "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1136                              "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1137                       "Failed to fix attribute %s" % attr):
1138             self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1139
1140
1141     def is_deleted_deleted_objects(self, obj):
1142         faulty = False
1143         if "description" not in obj:
1144             self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1145             faulty = True
1146         if "showInAdvancedViewOnly" not in obj:
1147             self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1148             faulty = True
1149         if "objectCategory" not in obj:
1150             self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1151             faulty = True
1152         if "isCriticalSystemObject" not in obj:
1153             self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1154             faulty = True
1155         if "isRecycled" in obj:
1156             self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1157             faulty = True
1158         return faulty
1159
1160
1161     def err_deleted_deleted_objects(self, obj):
1162         nmsg = ldb.Message()
1163         nmsg.dn = dn = obj.dn
1164
1165         if "description" not in obj:
1166             nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1167         if "showInAdvancedViewOnly" not in obj:
1168             nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1169         if "objectCategory" not in obj:
1170             nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1171         if "isCriticalSystemObject" not in obj:
1172             nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1173         if "isRecycled" in obj:
1174             nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1175
1176         if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1177                                 % (dn), 'fix_deleted_deleted_objects'):
1178             self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1179             return
1180
1181         if self.do_modify(nmsg, ["relax:0"],
1182                           "Failed to fix Deleted Objects container  %s" % dn):
1183             self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1184
1185
1186     def is_fsmo_role(self, dn):
1187         if dn == self.samdb.domain_dn:
1188             return True
1189         if dn == self.infrastructure_dn:
1190             return True
1191         if dn == self.naming_dn:
1192             return True
1193         if dn == self.schema_dn:
1194             return True
1195         if dn == self.rid_dn:
1196             return True
1197
1198         return False
1199
1200     def calculate_instancetype(self, dn):
1201         instancetype = 0
1202         nc_root = self.samdb.get_nc_root(dn)
1203         if dn == nc_root:
1204             instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1205             try:
1206                 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1207             except ldb.LdbError, (enum, estr):
1208                 if enum != ldb.ERR_NO_SUCH_OBJECT:
1209                     raise
1210             else:
1211                 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1212
1213         if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1214             instancetype |= dsdb.INSTANCE_TYPE_WRITE
1215
1216         return instancetype
1217
1218     def get_wellknown_sd(self, dn):
1219         for [sd_dn, descriptor_fn] in self.wellknown_sds:
1220             if dn == sd_dn:
1221                 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1222                 return ndr_unpack(security.descriptor,
1223                                   descriptor_fn(domain_sid,
1224                                                 name_map=self.name_map))
1225
1226         raise KeyError
1227
1228     def check_object(self, dn, attrs=['*']):
1229         '''check one object'''
1230         if self.verbose:
1231             self.report("Checking object %s" % dn)
1232         if "dn" in map(str.lower, attrs):
1233             attrs.append("name")
1234         if "distinguishedname" in map(str.lower, attrs):
1235             attrs.append("name")
1236         if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1237             attrs.append("name")
1238         if 'name' in map(str.lower, attrs):
1239             attrs.append(dn.get_rdn_name())
1240             attrs.append("isDeleted")
1241             attrs.append("systemFlags")
1242         if '*' in attrs:
1243             attrs.append("replPropertyMetaData")
1244
1245         try:
1246             sd_flags = 0
1247             sd_flags |= security.SECINFO_OWNER
1248             sd_flags |= security.SECINFO_GROUP
1249             sd_flags |= security.SECINFO_DACL
1250             sd_flags |= security.SECINFO_SACL
1251
1252             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1253                                     controls=[
1254                                         "extended_dn:1:1",
1255                                         "show_recycled:1",
1256                                         "show_deleted:1",
1257                                         "sd_flags:1:%d" % sd_flags,
1258                                     ],
1259                                     attrs=attrs)
1260         except ldb.LdbError, (enum, estr):
1261             if enum == ldb.ERR_NO_SUCH_OBJECT:
1262                 if self.in_transaction:
1263                     self.report("ERROR: Object %s disappeared during check" % dn)
1264                     return 1
1265                 return 0
1266             raise
1267         if len(res) != 1:
1268             self.report("ERROR: Object %s failed to load during check" % dn)
1269             return 1
1270         obj = res[0]
1271         error_count = 0
1272         set_attrs_from_md = set()
1273         set_attrs_seen = set()
1274         got_repl_property_meta_data = False
1275         got_objectclass = False
1276
1277         nc_dn = self.samdb.get_nc_root(obj.dn)
1278         try:
1279             deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
1280                                                  samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
1281         except KeyError, e:
1282             deleted_objects_dn = ldb.Dn(self.samdb, "CN=Deleted Objects,%s" % nc_dn)
1283
1284         object_rdn_attr = None
1285         object_rdn_val = None
1286         name_val = None
1287         isDeleted = False
1288         systemFlags = 0
1289
1290         for attrname in obj:
1291             if attrname == 'dn':
1292                 continue
1293
1294             if str(attrname).lower() == 'objectclass':
1295                 got_objectclass = True
1296
1297             if str(attrname).lower() == "name":
1298                 if len(obj[attrname]) != 1:
1299                     error_count += 1
1300                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1301                                 (len(obj[attrname]), attrname, str(obj.dn)))
1302                 else:
1303                     name_val = obj[attrname][0]
1304
1305             if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
1306                 object_rdn_attr = attrname
1307                 if len(obj[attrname]) != 1:
1308                     error_count += 1
1309                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1310                                 (len(obj[attrname]), attrname, str(obj.dn)))
1311                 else:
1312                     object_rdn_val = obj[attrname][0]
1313
1314             if str(attrname).lower() == 'isdeleted':
1315                 if obj[attrname][0] != "FALSE":
1316                     isDeleted = True
1317
1318             if str(attrname).lower() == 'systemflags':
1319                 systemFlags = int(obj[attrname][0])
1320
1321             if str(attrname).lower() == 'replpropertymetadata':
1322                 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
1323                     error_count += 1
1324                     self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
1325                     # We don't continue, as we may also have other fixes for this attribute
1326                     # based on what other attributes we see.
1327
1328                 try:
1329                     (set_attrs_from_md, list_attid_from_md, wrong_attids) \
1330                         = self.process_metadata(dn, obj[attrname])
1331                 except KeyError:
1332                     error_count += 1
1333                     self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
1334                     continue
1335
1336                 if len(set_attrs_from_md) < len(list_attid_from_md) \
1337                    or len(wrong_attids) > 0 \
1338                    or sorted(list_attid_from_md[:-1]) != list_attid_from_md[:-1]:
1339                     error_count +=1
1340                     self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname], wrong_attids)
1341
1342                 else:
1343                     # Here we check that the first attid is 0
1344                     # (objectClass) and that the last on is the RDN
1345                     # from the DN.
1346                     rdn_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(dn.get_rdn_name())
1347                     if list_attid_from_md[-1] != rdn_attid:
1348                         error_count += 1
1349                         self.report("ERROR: Not fixing incorrect final attributeID in '%s' on '%s', it should match the RDN %s" %
1350                                     (attrname, str(dn), dn.get_rdn_name()))
1351
1352                     if list_attid_from_md[0] != 0:
1353                         error_count += 1
1354                         self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
1355                                     (attrname, str(dn)))
1356
1357                 got_repl_property_meta_data = True
1358                 continue
1359
1360             if str(attrname).lower() == 'ntsecuritydescriptor':
1361                 (sd, sd_broken) = self.process_sd(dn, obj)
1362                 if sd_broken is not None:
1363                     self.err_wrong_sd(dn, sd, sd_broken)
1364                     error_count += 1
1365                     continue
1366
1367                 if sd.owner_sid is None or sd.group_sid is None:
1368                     self.err_missing_sd_owner(dn, sd)
1369                     error_count += 1
1370                     continue
1371
1372                 if self.reset_well_known_acls:
1373                     try:
1374                         well_known_sd = self.get_wellknown_sd(dn)
1375                     except KeyError:
1376                         continue
1377
1378                     current_sd = ndr_unpack(security.descriptor,
1379                                             str(obj[attrname][0]))
1380
1381                     diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
1382                     if diff != "":
1383                         self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
1384                         error_count += 1
1385                         continue
1386                 continue
1387
1388             if str(attrname).lower() == 'objectclass':
1389                 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
1390                 # Do not consider the attribute incorrect if:
1391                 #  - The sorted (alphabetically) list is the same, inclding case
1392                 #  - The first and last elements are the same
1393                 #
1394                 # This avoids triggering an error due to
1395                 # non-determinism in the sort routine in (at least)
1396                 # 4.3 and earlier, and the fact that any AUX classes
1397                 # in these attributes are also not sorted when
1398                 # imported from Windows (they are just in the reverse
1399                 # order of last set)
1400                 if sorted(normalised) != sorted(obj[attrname]) \
1401                    or normalised[0] != obj[attrname][0] \
1402                    or normalised[-1] != obj[attrname][-1]:
1403                     self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
1404                     error_count += 1
1405                 continue
1406
1407             if str(attrname).lower() == 'userparameters':
1408                 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
1409                     error_count += 1
1410                     self.err_short_userParameters(obj, attrname, obj[attrname])
1411                     continue
1412
1413                 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1414                     # This is the correct, normal prefix
1415                     continue
1416
1417                 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1418                     # this is the typical prefix from a windows migration
1419                     error_count += 1
1420                     self.err_base64_userParameters(obj, attrname, obj[attrname])
1421                     continue
1422
1423                 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':
1424                     # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1425                     error_count += 1
1426                     self.err_utf8_userParameters(obj, attrname, obj[attrname])
1427                     continue
1428
1429                 elif len(obj[attrname][0]) % 2 != 0:
1430                     # This is a value that isn't even in length
1431                     error_count += 1
1432                     self.err_odd_userParameters(obj, attrname, obj[attrname])
1433                     continue
1434
1435                 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':
1436                     # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
1437                     error_count += 1
1438                     self.err_doubled_userParameters(obj, attrname, obj[attrname])
1439                     continue
1440
1441             # check for empty attributes
1442             for val in obj[attrname]:
1443                 if val == '':
1444                     self.err_empty_attribute(dn, attrname)
1445                     error_count += 1
1446                     continue
1447
1448             # get the syntax oid for the attribute, so we can can have
1449             # special handling for some specific attribute types
1450             try:
1451                 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1452             except Exception, msg:
1453                 self.err_unknown_attribute(obj, attrname)
1454                 error_count += 1
1455                 continue
1456
1457             flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
1458             if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
1459                 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
1460                 and not self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)):
1461                 set_attrs_seen.add(str(attrname).lower())
1462
1463             if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
1464                                dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
1465                 # it's some form of DN, do specialised checking on those
1466                 error_count += self.check_dn(obj, attrname, syntax_oid)
1467
1468             values = set()
1469             # check for incorrectly normalised attributes
1470             for val in obj[attrname]:
1471                 values.add(str(val))
1472
1473                 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
1474                 if len(normalised) != 1 or normalised[0] != val:
1475                     self.err_normalise_mismatch(dn, attrname, obj[attrname])
1476                     error_count += 1
1477                     break
1478
1479             if len(obj[attrname]) != len(values):
1480                    self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
1481                    error_count += 1
1482                    break
1483
1484             if str(attrname).lower() == "instancetype":
1485                 calculated_instancetype = self.calculate_instancetype(dn)
1486                 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
1487                     error_count += 1
1488                     self.err_wrong_instancetype(obj, calculated_instancetype)
1489
1490         if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
1491             error_count += 1
1492             self.err_missing_objectclass(dn)
1493
1494         if ("*" in attrs or "name" in map(str.lower, attrs)):
1495             if name_val is None:
1496                 error_count += 1
1497                 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
1498             if object_rdn_attr is None:
1499                 error_count += 1
1500                 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
1501
1502         if name_val is not None:
1503             parent_dn = None
1504             if isDeleted:
1505                 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
1506                     parent_dn = deleted_objects_dn
1507             if parent_dn is None:
1508                 parent_dn = obj.dn.parent()
1509             expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
1510             expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
1511
1512             if obj.dn == deleted_objects_dn:
1513                 expected_dn = obj.dn
1514
1515             if expected_dn != obj.dn:
1516                 error_count += 1
1517                 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
1518             elif obj.dn.get_rdn_value() != object_rdn_val:
1519                 error_count += 1
1520                 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
1521
1522         show_dn = True
1523         if got_repl_property_meta_data:
1524             if obj.dn == deleted_objects_dn:
1525                 isDeletedAttId = 131120
1526                 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
1527
1528                 expectedTimeDo = 2650466015990000000
1529                 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
1530                 if originating != expectedTimeDo:
1531                     if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
1532                         nmsg = ldb.Message()
1533                         nmsg.dn = dn
1534                         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1535                         error_count += 1
1536                         self.samdb.modify(nmsg, controls=["provision:0"])
1537
1538                     else:
1539                         self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
1540
1541             for att in set_attrs_seen.difference(set_attrs_from_md):
1542                 if show_dn:
1543                     self.report("On object %s" % dn)
1544                     show_dn = False
1545                 error_count += 1
1546                 self.report("ERROR: Attribute %s not present in replication metadata" % att)
1547                 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
1548                     self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
1549                     continue
1550                 self.fix_metadata(dn, att)
1551
1552         if self.is_fsmo_role(dn):
1553             if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
1554                 self.err_no_fsmoRoleOwner(obj)
1555                 error_count += 1
1556
1557         try:
1558             if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
1559                 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
1560                                         controls=["show_recycled:1", "show_deleted:1"])
1561         except ldb.LdbError, (enum, estr):
1562             if enum == ldb.ERR_NO_SUCH_OBJECT:
1563                 self.err_missing_parent(obj)
1564                 error_count += 1
1565             else:
1566                 raise
1567
1568         if dn in self.deleted_objects_containers and '*' in attrs:
1569             if self.is_deleted_deleted_objects(obj):
1570                 self.err_deleted_deleted_objects(obj)
1571                 error_count += 1
1572
1573         return error_count
1574
1575     ################################################################
1576     # check special @ROOTDSE attributes
1577     def check_rootdse(self):
1578         '''check the @ROOTDSE special object'''
1579         dn = ldb.Dn(self.samdb, '@ROOTDSE')
1580         if self.verbose:
1581             self.report("Checking object %s" % dn)
1582         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
1583         if len(res) != 1:
1584             self.report("Object %s disappeared during check" % dn)
1585             return 1
1586         obj = res[0]
1587         error_count = 0
1588
1589         # check that the dsServiceName is in GUID form
1590         if not 'dsServiceName' in obj:
1591             self.report('ERROR: dsServiceName missing in @ROOTDSE')
1592             return error_count+1
1593
1594         if not obj['dsServiceName'][0].startswith('<GUID='):
1595             self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
1596             error_count += 1
1597             if not self.confirm('Change dsServiceName to GUID form?'):
1598                 return error_count
1599             res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
1600                                     scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
1601             guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
1602             m = ldb.Message()
1603             m.dn = dn
1604             m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
1605                                                     ldb.FLAG_MOD_REPLACE, 'dsServiceName')
1606             if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
1607                 self.report("Changed dsServiceName to GUID form")
1608         return error_count
1609
1610
1611     ###############################################
1612     # re-index the database
1613     def reindex_database(self):
1614         '''re-index the whole database'''
1615         m = ldb.Message()
1616         m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
1617         m['add']    = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
1618         m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
1619         return self.do_modify(m, [], 're-indexed database', validate=False)
1620
1621     ###############################################
1622     # reset @MODULES
1623     def reset_modules(self):
1624         '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
1625         m = ldb.Message()
1626         m.dn = ldb.Dn(self.samdb, "@MODULES")
1627         m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
1628         return self.do_modify(m, [], 'reset @MODULES on database', validate=False)