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