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