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