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