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