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