3fcfbc05639a68502ac40856fb4ad6efe1e2f710
[amitay/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_target_dn_or_GUID(self, dn, attrname, val, dsdb_dn):
520         """handle a missing target DN (if specified, GUID form can't be found,
521         and otherwise DN string form can't be found)"""
522         # check if its a backlink
523         linkID, _ = self.get_attr_linkID_and_reverse_name(attrname)
524         if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1:
525             self.report("Not removing dangling forward link")
526             return
527         self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn, False)
528
529     def err_missing_dn_GUID_component(self, dn, attrname, val, dsdb_dn, errstr):
530         """handle a missing GUID extended DN component"""
531         self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
532         controls=["extended_dn:1:1", "show_recycled:1"]
533         try:
534             res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
535                                     attrs=[], controls=controls)
536         except ldb.LdbError, (enum, estr):
537             self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
538             if enum != ldb.ERR_NO_SUCH_OBJECT:
539                 raise
540             self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
541             return
542         if len(res) == 0:
543             self.report("unable to find object for DN %s" % dsdb_dn.dn)
544             self.err_missing_target_dn_or_GUID(dn, attrname, val, dsdb_dn)
545             return
546         dsdb_dn.dn = res[0].dn
547
548         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
549             self.report("Not fixing %s" % errstr)
550             return
551         m = ldb.Message()
552         m.dn = dn
553         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
554         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
555
556         if self.do_modify(m, ["show_recycled:1"],
557                           "Failed to fix %s on attribute %s" % (errstr, attrname)):
558             self.report("Fixed %s on attribute %s" % (errstr, attrname))
559
560     def err_incorrect_binary_dn(self, dn, attrname, val, dsdb_dn, errstr):
561         """handle an incorrect binary DN component"""
562         self.report("ERROR: %s binary component for %s in object %s - %s" % (errstr, attrname, dn, val))
563         controls=["extended_dn:1:1", "show_recycled:1"]
564
565         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_binary_dn'):
566             self.report("Not fixing %s" % errstr)
567             return
568         m = ldb.Message()
569         m.dn = dn
570         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
571         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
572
573         if self.do_modify(m, ["show_recycled:1"],
574                           "Failed to fix %s on attribute %s" % (errstr, attrname)):
575             self.report("Fixed %s on attribute %s" % (errstr, attrname))
576
577     def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type):
578         """handle a DN string being incorrect"""
579         self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val))
580         dsdb_dn.dn = correct_dn
581
582         if not self.confirm_all('Change DN to %s?' % str(dsdb_dn),
583                                 'fix_all_%s_dn_component_mismatch' % mismatch_type):
584             self.report("Not fixing %s component mismatch" % mismatch_type)
585             return
586         m = ldb.Message()
587         m.dn = dn
588         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
589         m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
590         if self.do_modify(m, ["show_recycled:1"],
591                           "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)):
592             self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname))
593
594     def err_unknown_attribute(self, obj, attrname):
595         '''handle an unknown attribute error'''
596         self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
597         if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
598             self.report("Not removing %s" % attrname)
599             return
600         m = ldb.Message()
601         m.dn = obj.dn
602         m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
603         if self.do_modify(m, ["relax:0", "show_recycled:1"],
604                           "Failed to remove unknown attribute %s" % attrname):
605             self.report("Removed unknown attribute %s" % (attrname))
606
607     def err_undead_linked_attribute(self, obj, attrname, val):
608         '''handle a link that should not be there on a deleted object'''
609         self.report("ERROR: linked attribute '%s' to '%s' is present on "
610                     "deleted object %s" % (attrname, val, obj.dn))
611         if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'):
612             self.report("Not removing linked attribute %s" % attrname)
613             return
614         m = ldb.Message()
615         m.dn = obj.dn
616         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
617
618         if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0",
619                               "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS],
620                           "Failed to delete forward link %s" % attrname):
621             self.report("Fixed undead forward link %s" % (attrname))
622
623     def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
624         '''handle a missing backlink value'''
625         self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
626         if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
627             self.report("Not fixing missing backlink %s" % backlink_name)
628             return
629         m = ldb.Message()
630         m.dn = obj.dn
631         m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
632         m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, attrname)
633         if self.do_modify(m, ["show_recycled:1"],
634                           "Failed to fix missing backlink %s" % backlink_name):
635             self.report("Fixed missing backlink %s" % (backlink_name))
636
637     def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
638         '''handle a incorrect RMD_FLAGS value'''
639         rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
640         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()))
641         if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
642             self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
643             return
644         m = ldb.Message()
645         m.dn = obj.dn
646         m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
647         if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
648                           "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
649             self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
650
651     def err_orphaned_backlink(self, obj, attrname, val, link_name, target_dn):
652         '''handle a orphaned backlink value'''
653         self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (attrname, obj.dn, link_name, target_dn))
654         if not self.confirm_all('Remove orphaned backlink %s' % attrname, 'fix_all_orphaned_backlinks'):
655             self.report("Not removing orphaned backlink %s" % attrname)
656             return
657         m = ldb.Message()
658         m.dn = obj.dn
659         m['value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
660         if self.do_modify(m, ["show_recycled:1", "relax:0"],
661                           "Failed to fix orphaned backlink %s" % attrname):
662             self.report("Fixed orphaned backlink %s" % (attrname))
663
664     def err_no_fsmoRoleOwner(self, obj):
665         '''handle a missing fSMORoleOwner'''
666         self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
667         res = self.samdb.search("",
668                                 scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
669         assert len(res) == 1
670         serviceName = res[0]["dsServiceName"][0]
671         if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
672             self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
673             return
674         m = ldb.Message()
675         m.dn = obj.dn
676         m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
677         if self.do_modify(m, [],
678                           "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
679             self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
680
681     def err_missing_parent(self, obj):
682         '''handle a missing parent'''
683         self.report("ERROR: parent object not found for %s" % (obj.dn))
684         if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
685             self.report('Not moving object %s into LostAndFound' % (obj.dn))
686             return
687
688         keep_transaction = False
689         self.samdb.transaction_start()
690         try:
691             nc_root = self.samdb.get_nc_root(obj.dn);
692             lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
693             new_dn = ldb.Dn(self.samdb, str(obj.dn))
694             new_dn.remove_base_components(len(new_dn) - 1)
695             if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
696                               "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
697                 self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
698
699                 m = ldb.Message()
700                 m.dn = obj.dn
701                 m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
702
703                 if self.do_modify(m, [],
704                                   "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
705                     self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
706                     keep_transaction = True
707         except:
708             self.samdb.transaction_cancel()
709             raise
710
711         if keep_transaction:
712             self.samdb.transaction_commit()
713         else:
714             self.samdb.transaction_cancel()
715
716     def err_wrong_dn(self, obj, new_dn, rdn_attr, rdn_val, name_val):
717         '''handle a wrong dn'''
718
719         new_rdn = ldb.Dn(self.samdb, str(new_dn))
720         new_rdn.remove_base_components(len(new_rdn) - 1)
721         new_parent = new_dn.parent()
722
723         attributes = ""
724         if rdn_val != name_val:
725             attributes += "%s=%r " % (rdn_attr, rdn_val)
726         attributes += "name=%r" % (name_val)
727
728         self.report("ERROR: wrong dn[%s] %s new_dn[%s]" % (obj.dn, attributes, new_dn))
729         if not self.confirm_all("Rename %s to %s?" % (obj.dn, new_dn), 'fix_dn'):
730             self.report("Not renaming %s to %s" % (obj.dn, new_dn))
731             return
732
733         if self.do_rename(obj.dn, new_rdn, new_parent, ["show_recycled:1", "relax:0"],
734                           "Failed to rename object %s into %s" % (obj.dn, new_dn)):
735             self.report("Renamed %s into %s" % (obj.dn, new_dn))
736
737     def err_wrong_instancetype(self, obj, calculated_instancetype):
738         '''handle a wrong instanceType'''
739         self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
740         if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
741             self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
742             return
743
744         m = ldb.Message()
745         m.dn = obj.dn
746         m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
747         if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
748                           "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
749             self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
750
751     def err_short_userParameters(self, obj, attrname, value):
752         # This is a truncated userParameters due to a pre 4.1 replication bug
753         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)))
754
755     def err_base64_userParameters(self, obj, attrname, value):
756         '''handle a wrong userParameters'''
757         self.report("ERROR: wrongly formatted userParameters %s on %s, should not be base64-encoded" % (value, obj.dn))
758         if not self.confirm_all('Convert userParameters from base64 encoding on %s?' % (obj.dn), 'fix_base64_userparameters'):
759             self.report('Not changing userParameters from base64 encoding on %s' % (obj.dn))
760             return
761
762         m = ldb.Message()
763         m.dn = obj.dn
764         m['value'] = ldb.MessageElement(b64decode(obj[attrname][0]), ldb.FLAG_MOD_REPLACE, 'userParameters')
765         if self.do_modify(m, [],
766                           "Failed to correct base64-encoded userParameters on %s by converting from base64" % (obj.dn)):
767             self.report("Corrected base64-encoded userParameters on %s by converting from base64" % (obj.dn))
768
769     def err_utf8_userParameters(self, obj, attrname, value):
770         '''handle a wrong userParameters'''
771         self.report("ERROR: wrongly formatted userParameters on %s, should not be psudo-UTF8 encoded" % (obj.dn))
772         if not self.confirm_all('Convert userParameters from UTF8 encoding on %s?' % (obj.dn), 'fix_utf8_userparameters'):
773             self.report('Not changing userParameters from UTF8 encoding on %s' % (obj.dn))
774             return
775
776         m = ldb.Message()
777         m.dn = obj.dn
778         m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf8').encode('utf-16-le'),
779                                         ldb.FLAG_MOD_REPLACE, 'userParameters')
780         if self.do_modify(m, [],
781                           "Failed to correct psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn)):
782             self.report("Corrected psudo-UTF8 encoded userParameters on %s by converting from UTF8" % (obj.dn))
783
784     def err_doubled_userParameters(self, obj, attrname, value):
785         '''handle a wrong userParameters'''
786         self.report("ERROR: wrongly formatted userParameters on %s, should not be double UTF16 encoded" % (obj.dn))
787         if not self.confirm_all('Convert userParameters from doubled UTF-16 encoding on %s?' % (obj.dn), 'fix_doubled_userparameters'):
788             self.report('Not changing userParameters from doubled UTF-16 encoding on %s' % (obj.dn))
789             return
790
791         m = ldb.Message()
792         m.dn = obj.dn
793         m['value'] = ldb.MessageElement(obj[attrname][0].decode('utf-16-le').decode('utf-16-le').encode('utf-16-le'),
794                                         ldb.FLAG_MOD_REPLACE, 'userParameters')
795         if self.do_modify(m, [],
796                           "Failed to correct doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn)):
797             self.report("Corrected doubled-UTF16 encoded userParameters on %s by converting" % (obj.dn))
798
799     def err_odd_userParameters(self, obj, attrname):
800         # This is a truncated userParameters due to a pre 4.1 replication bug
801         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)))
802
803     def find_revealed_link(self, dn, attrname, guid):
804         '''return a revealed link in an object'''
805         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
806                                 controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
807         syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
808         for val in res[0][attrname]:
809             dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
810             guid2 = dsdb_dn.dn.get_extended_component("GUID")
811             if guid == guid2:
812                 return dsdb_dn
813         return None
814
815     def check_dn(self, obj, attrname, syntax_oid):
816         '''check a DN attribute for correctness'''
817         error_count = 0
818         obj_guid = obj['objectGUID'][0]
819
820         for val in obj[attrname]:
821             dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
822
823             # all DNs should have a GUID component
824             guid = dsdb_dn.dn.get_extended_component("GUID")
825             if guid is None:
826                 error_count += 1
827                 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
828                     "missing GUID")
829                 continue
830
831             guidstr = str(misc.GUID(guid))
832             attrs = ['isDeleted', 'replPropertyMetaData']
833
834             if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
835                 fixing_msDS_HasInstantiatedNCs = True
836                 attrs.append("instanceType")
837             else:
838                 fixing_msDS_HasInstantiatedNCs = False
839
840             linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
841             if reverse_link_name is not None:
842                 attrs.append(reverse_link_name)
843
844             # check its the right GUID
845             try:
846                 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
847                                         attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
848                                                                "reveal_internals:0"
849                                         ])
850             except ldb.LdbError, (enum, estr):
851                 error_count += 1
852                 self.report("ERROR: no target object found for GUID component for %s in object %s - %s" % (attrname, obj.dn, val))
853                 if enum != ldb.ERR_NO_SUCH_OBJECT:
854                     raise
855
856                 self.err_missing_target_dn_or_GUID(obj.dn, attrname, val, dsdb_dn)
857                 continue
858
859             if fixing_msDS_HasInstantiatedNCs:
860                 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
861                 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
862
863                 if str(dsdb_dn) != val:
864                     error_count +=1
865                     self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
866                     continue
867
868             # now we have two cases - the source object might or might not be deleted
869             is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
870             target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
871
872
873             if is_deleted and not obj.dn in self.deleted_objects_containers and linkID:
874                 # A fully deleted object should not have any linked
875                 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
876                 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
877                 # Requirements)
878                 self.err_undead_linked_attribute(obj, attrname, val)
879                 error_count += 1
880                 continue
881             elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
882                 # the target DN is not allowed to be deleted, unless the target DN is the
883                 # special Deleted Objects container
884                 error_count += 1
885                 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
886                 if local_usn:
887                     if 'replPropertyMetaData' in res[0]:
888                         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
889                                           str(res[0]['replPropertyMetadata']))
890                         found_data = False
891                         for o in repl.ctr.array:
892                             if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
893                                 deleted_usn = o.local_usn
894                                 if deleted_usn >= int(local_usn):
895                                     # If the object was deleted after the link
896                                     # was last modified then, clean it up here
897                                     found_data = True
898                                     break
899
900                         if found_data:
901                             self.err_deleted_dn(obj.dn, attrname,
902                                                 val, dsdb_dn, res[0].dn, True)
903                             continue
904
905                 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
906                 continue
907
908             # We should not check for incorrect
909             # components on deleted links, as these are allowed to
910             # go stale (we just need the GUID, not the name)
911             rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
912             if rmd_blob is not None:
913                 rmd_flags = int(rmd_blob)
914                 if rmd_flags & 1:
915                     continue
916
917             # check the DN matches in string form
918             if str(res[0].dn) != str(dsdb_dn.dn):
919                 error_count += 1
920                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
921                                                       res[0].dn, "string")
922                 continue
923
924             if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
925                 error_count += 1
926                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
927                                                       res[0].dn, "GUID")
928                 continue
929
930             if res[0].dn.get_extended_component("SID") != dsdb_dn.dn.get_extended_component("SID"):
931                 error_count += 1
932                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
933                                                       res[0].dn, "SID")
934                 continue
935
936
937             # check the reverse_link is correct if there should be one
938             if reverse_link_name is not None:
939                 match_count = 0
940                 if reverse_link_name in res[0]:
941                     for v in res[0][reverse_link_name]:
942                         v_guid = dsdb_Dn(self.samdb, v).dn.get_extended_component("GUID")
943                         if v_guid == obj_guid:
944                             match_count += 1
945                 if match_count != 1:
946                     if target_is_deleted:
947                         error_count += 1
948                         if linkID & 1:
949                             self.err_missing_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
950                         else:
951                             self.err_orphaned_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
952                     continue
953
954
955
956
957         return error_count
958
959
960     def get_originating_time(self, val, attid):
961         '''Read metadata properties and return the originating time for
962            a given attributeId.
963
964            :return: the originating time or 0 if not found
965         '''
966
967         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
968         obj = repl.ctr
969
970         for o in repl.ctr.array:
971             if o.attid == attid:
972                 return o.originating_change_time
973
974         return 0
975
976     def process_metadata(self, dn, val):
977         '''Read metadata properties and list attributes in it.
978            raises KeyError if the attid is unknown.'''
979
980         set_att = set()
981         wrong_attids = set()
982         list_attid = []
983         in_schema_nc = dn.is_child_of(self.schema_dn)
984
985         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
986         obj = repl.ctr
987
988         for o in repl.ctr.array:
989             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
990             set_att.add(att.lower())
991             list_attid.append(o.attid)
992             correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
993                                                                              is_schema_nc=in_schema_nc)
994             if correct_attid != o.attid:
995                 wrong_attids.add(o.attid)
996
997         return (set_att, list_attid, wrong_attids)
998
999
1000     def fix_metadata(self, dn, attr):
1001         '''re-write replPropertyMetaData elements for a single attribute for a
1002         object. This is used to fix missing replPropertyMetaData elements'''
1003         res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
1004                                 controls = ["search_options:1:2", "show_recycled:1"])
1005         msg = res[0]
1006         nmsg = ldb.Message()
1007         nmsg.dn = dn
1008         nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1009         if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1010                           "Failed to fix metadata for attribute %s" % attr):
1011             self.report("Fixed metadata for attribute %s" % attr)
1012
1013     def ace_get_effective_inherited_type(self, ace):
1014         if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1015             return None
1016
1017         check = False
1018         if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1019             check = True
1020         elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1021             check = True
1022         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1023             check = True
1024         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1025             check = True
1026
1027         if not check:
1028             return None
1029
1030         if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1031             return None
1032
1033         return str(ace.object.inherited_type)
1034
1035     def lookup_class_schemaIDGUID(self, cls):
1036         if cls in self.class_schemaIDGUID:
1037             return self.class_schemaIDGUID[cls]
1038
1039         flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1040         res = self.samdb.search(base=self.schema_dn,
1041                                 expression=flt,
1042                                 attrs=["schemaIDGUID"])
1043         t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1044
1045         self.class_schemaIDGUID[cls] = t
1046         return t
1047
1048     def process_sd(self, dn, obj):
1049         sd_attr = "nTSecurityDescriptor"
1050         sd_val = obj[sd_attr]
1051
1052         sd = ndr_unpack(security.descriptor, str(sd_val))
1053
1054         is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1055         if is_deleted:
1056             # we don't fix deleted objects
1057             return (sd, None)
1058
1059         sd_clean = security.descriptor()
1060         sd_clean.owner_sid = sd.owner_sid
1061         sd_clean.group_sid = sd.group_sid
1062         sd_clean.type = sd.type
1063         sd_clean.revision = sd.revision
1064
1065         broken = False
1066         last_inherited_type = None
1067
1068         aces = []
1069         if sd.sacl is not None:
1070             aces = sd.sacl.aces
1071         for i in range(0, len(aces)):
1072             ace = aces[i]
1073
1074             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1075                 sd_clean.sacl_add(ace)
1076                 continue
1077
1078             t = self.ace_get_effective_inherited_type(ace)
1079             if t is None:
1080                 continue
1081
1082             if last_inherited_type is not None:
1083                 if t != last_inherited_type:
1084                     # if it inherited from more than
1085                     # one type it's very likely to be broken
1086                     #
1087                     # If not the recalculation will calculate
1088                     # the same result.
1089                     broken = True
1090                 continue
1091
1092             last_inherited_type = t
1093
1094         aces = []
1095         if sd.dacl is not None:
1096             aces = sd.dacl.aces
1097         for i in range(0, len(aces)):
1098             ace = aces[i]
1099
1100             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1101                 sd_clean.dacl_add(ace)
1102                 continue
1103
1104             t = self.ace_get_effective_inherited_type(ace)
1105             if t is None:
1106                 continue
1107
1108             if last_inherited_type is not None:
1109                 if t != last_inherited_type:
1110                     # if it inherited from more than
1111                     # one type it's very likely to be broken
1112                     #
1113                     # If not the recalculation will calculate
1114                     # the same result.
1115                     broken = True
1116                 continue
1117
1118             last_inherited_type = t
1119
1120         if broken:
1121             return (sd_clean, sd)
1122
1123         if last_inherited_type is None:
1124             # ok
1125             return (sd, None)
1126
1127         cls = None
1128         try:
1129             cls = obj["objectClass"][-1]
1130         except KeyError, e:
1131             pass
1132
1133         if cls is None:
1134             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1135                                     attrs=["isDeleted", "objectClass"],
1136                                     controls=["show_recycled:1"])
1137             o = res[0]
1138             is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
1139             if is_deleted:
1140                 # we don't fix deleted objects
1141                 return (sd, None)
1142             cls = o["objectClass"][-1]
1143
1144         t = self.lookup_class_schemaIDGUID(cls)
1145
1146         if t != last_inherited_type:
1147             # broken
1148             return (sd_clean, sd)
1149
1150         # ok
1151         return (sd, None)
1152
1153     def err_wrong_sd(self, dn, sd, sd_broken):
1154         '''re-write the SD due to incorrect inherited ACEs'''
1155         sd_attr = "nTSecurityDescriptor"
1156         sd_val = ndr_pack(sd)
1157         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1158
1159         if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1160             self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1161             return
1162
1163         nmsg = ldb.Message()
1164         nmsg.dn = dn
1165         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1166         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1167                           "Failed to fix attribute %s" % sd_attr):
1168             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1169
1170     def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1171         '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1172         sd_attr = "nTSecurityDescriptor"
1173         sd_val = ndr_pack(sd)
1174         sd_old_val = ndr_pack(sd_old)
1175         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1176         if sd.owner_sid is not None:
1177             sd_flags |= security.SECINFO_OWNER
1178         if sd.group_sid is not None:
1179             sd_flags |= security.SECINFO_GROUP
1180
1181         if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1182             self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1183             return
1184
1185         m = ldb.Message()
1186         m.dn = dn
1187         m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1188         if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1189                           "Failed to reset attribute %s" % sd_attr):
1190             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1191
1192     def err_missing_sd_owner(self, dn, sd):
1193         '''re-write the SD due to a missing owner or group'''
1194         sd_attr = "nTSecurityDescriptor"
1195         sd_val = ndr_pack(sd)
1196         sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1197
1198         if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1199             self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1200             return
1201
1202         nmsg = ldb.Message()
1203         nmsg.dn = dn
1204         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1205
1206         # By setting the session_info to admin_session_info and
1207         # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1208         # flags we cause the descriptor module to set the correct
1209         # owner and group on the SD, replacing the None/NULL values
1210         # for owner_sid and group_sid currently present.
1211         #
1212         # The admin_session_info matches that used in provision, and
1213         # is the best guess we can make for an existing object that
1214         # hasn't had something specifically set.
1215         #
1216         # This is important for the dns related naming contexts.
1217         self.samdb.set_session_info(self.admin_session_info)
1218         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1219                           "Failed to fix metadata for attribute %s" % sd_attr):
1220             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1221         self.samdb.set_session_info(self.system_session_info)
1222
1223
1224     def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1225         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1226                           str(repl_meta_data))
1227         ctr = repl.ctr
1228         found = False
1229         for o in ctr.array:
1230             # Search for a zero invocationID
1231             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1232                 continue
1233
1234             found = True
1235             self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1236                            version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1237                            but should be non-zero.  Proposed fix is to set to our invocationID (%s).'''
1238                         % (dn, o.attid, o.version,
1239                            time.ctime(samba.nttime2unix(o.originating_change_time)),
1240                            self.samdb.get_invocation_id()))
1241
1242         return found
1243
1244
1245     def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1246         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1247                           str(repl_meta_data))
1248         ctr = repl.ctr
1249         now = samba.unix2nttime(int(time.time()))
1250         found = False
1251         for o in ctr.array:
1252             # Search for a zero invocationID
1253             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1254                 continue
1255
1256             found = True
1257             seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1258             o.version = o.version + 1
1259             o.originating_change_time = now
1260             o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1261             o.originating_usn = seq
1262             o.local_usn = seq
1263
1264         if found:
1265             replBlob = ndr_pack(repl)
1266             msg = ldb.Message()
1267             msg.dn = dn
1268
1269             if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1270                                     % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1271                 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1272                 return
1273
1274             nmsg = ldb.Message()
1275             nmsg.dn = dn
1276             nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1277             if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1278                                      "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1279                               "Failed to fix attribute %s" % attr):
1280                 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1281
1282
1283     def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1284         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1285                           str(repl_meta_data))
1286         ctr = repl.ctr
1287         for o in ctr.array:
1288             # Search for an invalid attid
1289             try:
1290                 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1291             except KeyError:
1292                 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1293                 return
1294
1295
1296     def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1297         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1298                           str(repl_meta_data))
1299         fix = False
1300
1301         set_att = set()
1302         remove_attid = set()
1303         hash_att = {}
1304
1305         in_schema_nc = dn.is_child_of(self.schema_dn)
1306
1307         ctr = repl.ctr
1308         # Sort the array, except for the last element.  This strange
1309         # construction, creating a new list, due to bugs in samba's
1310         # array handling in IDL generated objects.
1311         ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1312         # Now walk it in reverse, so we see the low (and so incorrect,
1313         # the correct values are above 0x80000000) values first and
1314         # remove the 'second' value we see.
1315         for o in reversed(ctr.array):
1316             print "%s: 0x%08x" % (dn, o.attid)
1317             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1318             if att.lower() in set_att:
1319                 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1320                 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1321                                         % (attr, dn, o.attid, att, hash_att[att].attid),
1322                                         'fix_replmetadata_duplicate_attid'):
1323                     self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1324                                 % (o.attid, att, attr, dn))
1325                     return
1326                 fix = True
1327                 remove_attid.add(o.attid)
1328                 # We want to set the metadata for the most recent
1329                 # update to have been applied locally, that is the metadata
1330                 # matching the (eg string) value in the attribute
1331                 if o.local_usn > hash_att[att].local_usn:
1332                     # This is always what we would have sent over DRS,
1333                     # because the DRS server will have sent the
1334                     # msDS-IntID, but with the values from both
1335                     # attribute entries.
1336                     hash_att[att].version = o.version
1337                     hash_att[att].originating_change_time = o.originating_change_time
1338                     hash_att[att].originating_invocation_id = o.originating_invocation_id
1339                     hash_att[att].originating_usn = o.originating_usn
1340                     hash_att[att].local_usn = o.local_usn
1341
1342                 # Do not re-add the value to the set or overwrite the hash value
1343                 continue
1344
1345             hash_att[att] = o
1346             set_att.add(att.lower())
1347
1348         # Generate a real list we can sort on properly
1349         new_list = [o for o in ctr.array if o.attid not in remove_attid]
1350
1351         if (len(wrong_attids) > 0):
1352             for o in new_list:
1353                 if o.attid in wrong_attids:
1354                     att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1355                     correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1356                     self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1357                     if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1358                                             % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1359                         self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1360                                     % (o.attid, correct_attid, att, attr, dn))
1361                         return
1362                     fix = True
1363                     o.attid = correct_attid
1364             if fix:
1365                 # Sort the array, (we changed the value so must re-sort)
1366                 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1367
1368         # If we did not already need to fix it, then ask about sorting
1369         if not fix:
1370             self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1371             if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1372                                     % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1373                 self.report('Not fixing %s on %s\n' % (attr, dn))
1374                 return
1375
1376             # The actual sort done is done at the top of the function
1377
1378         ctr.count = len(new_list)
1379         ctr.array = new_list
1380         replBlob = ndr_pack(repl)
1381
1382         nmsg = ldb.Message()
1383         nmsg.dn = dn
1384         nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1385         if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1386                              "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1387                              "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1388                       "Failed to fix attribute %s" % attr):
1389             self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1390
1391
1392     def is_deleted_deleted_objects(self, obj):
1393         faulty = False
1394         if "description" not in obj:
1395             self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1396             faulty = True
1397         if "showInAdvancedViewOnly" not in obj or obj['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1398             self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1399             faulty = True
1400         if "objectCategory" not in obj:
1401             self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1402             faulty = True
1403         if "isCriticalSystemObject" not in obj or obj['isCriticalSystemObject'][0].upper() == 'FALSE':
1404             self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1405             faulty = True
1406         if "isRecycled" in obj:
1407             self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1408             faulty = True
1409         if "isDeleted" in obj and obj['isDeleted'][0].upper() == 'FALSE':
1410             self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1411             faulty = True
1412         if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1413                                         obj['objectClass'][0] != 'top' or
1414                                         obj['objectClass'][1] != 'container'):
1415             self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1416             faulty = True
1417         if "systemFlags" not in obj or obj['systemFlags'][0] != '-1946157056':
1418             self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1419             faulty = True
1420         return faulty
1421
1422     def err_deleted_deleted_objects(self, obj):
1423         nmsg = ldb.Message()
1424         nmsg.dn = dn = obj.dn
1425
1426         if "description" not in obj:
1427             nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1428         if "showInAdvancedViewOnly" not in obj:
1429             nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1430         if "objectCategory" not in obj:
1431             nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1432         if "isCriticalSystemObject" not in obj:
1433             nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1434         if "isRecycled" in obj:
1435             nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1436
1437         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1438         nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1439         nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1440
1441         if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1442                                 % (dn), 'fix_deleted_deleted_objects'):
1443             self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1444             return
1445
1446         if self.do_modify(nmsg, ["relax:0"],
1447                           "Failed to fix Deleted Objects container  %s" % dn):
1448             self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1449
1450     def err_replica_locations(self, obj, cross_ref, attr):
1451         nmsg = ldb.Message()
1452         nmsg.dn = cross_ref
1453         target = self.samdb.get_dsServiceName()
1454
1455         if self.samdb.am_rodc():
1456             self.report('Not fixing %s for the RODC' % (attr, obj.dn))
1457             return
1458
1459         if not self.confirm_all('Add yourself to the replica locations for %s?'
1460                                 % (obj.dn), 'fix_replica_locations'):
1461             self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1462             return
1463
1464         nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1465         if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1466             self.report("Fixed %s for %s" % (attr, obj.dn))
1467
1468     def is_fsmo_role(self, dn):
1469         if dn == self.samdb.domain_dn:
1470             return True
1471         if dn == self.infrastructure_dn:
1472             return True
1473         if dn == self.naming_dn:
1474             return True
1475         if dn == self.schema_dn:
1476             return True
1477         if dn == self.rid_dn:
1478             return True
1479
1480         return False
1481
1482     def calculate_instancetype(self, dn):
1483         instancetype = 0
1484         nc_root = self.samdb.get_nc_root(dn)
1485         if dn == nc_root:
1486             instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1487             try:
1488                 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1489             except ldb.LdbError, (enum, estr):
1490                 if enum != ldb.ERR_NO_SUCH_OBJECT:
1491                     raise
1492             else:
1493                 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1494
1495         if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1496             instancetype |= dsdb.INSTANCE_TYPE_WRITE
1497
1498         return instancetype
1499
1500     def get_wellknown_sd(self, dn):
1501         for [sd_dn, descriptor_fn] in self.wellknown_sds:
1502             if dn == sd_dn:
1503                 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1504                 return ndr_unpack(security.descriptor,
1505                                   descriptor_fn(domain_sid,
1506                                                 name_map=self.name_map))
1507
1508         raise KeyError
1509
1510     def check_object(self, dn, attrs=['*']):
1511         '''check one object'''
1512         if self.verbose:
1513             self.report("Checking object %s" % dn)
1514
1515         # If we modify the pass-by-reference attrs variable, then we get a
1516         # replPropertyMetadata for every object that we check.
1517         attrs = list(attrs)
1518         if "dn" in map(str.lower, attrs):
1519             attrs.append("name")
1520         if "distinguishedname" in map(str.lower, attrs):
1521             attrs.append("name")
1522         if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1523             attrs.append("name")
1524         if 'name' in map(str.lower, attrs):
1525             attrs.append(dn.get_rdn_name())
1526             attrs.append("isDeleted")
1527             attrs.append("systemFlags")
1528         if '*' in attrs:
1529             attrs.append("replPropertyMetaData")
1530         else:
1531             attrs.append("objectGUID")
1532
1533         try:
1534             sd_flags = 0
1535             sd_flags |= security.SECINFO_OWNER
1536             sd_flags |= security.SECINFO_GROUP
1537             sd_flags |= security.SECINFO_DACL
1538             sd_flags |= security.SECINFO_SACL
1539
1540             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1541                                     controls=[
1542                                         "extended_dn:1:1",
1543                                         "show_recycled:1",
1544                                         "show_deleted:1",
1545                                         "sd_flags:1:%d" % sd_flags,
1546                                         "reveal_internals:0",
1547                                     ],
1548                                     attrs=attrs)
1549         except ldb.LdbError, (enum, estr):
1550             if enum == ldb.ERR_NO_SUCH_OBJECT:
1551                 if self.in_transaction:
1552                     self.report("ERROR: Object %s disappeared during check" % dn)
1553                     return 1
1554                 return 0
1555             raise
1556         if len(res) != 1:
1557             self.report("ERROR: Object %s failed to load during check" % dn)
1558             return 1
1559         obj = res[0]
1560         error_count = 0
1561         set_attrs_from_md = set()
1562         set_attrs_seen = set()
1563         got_repl_property_meta_data = False
1564         got_objectclass = False
1565
1566         nc_dn = self.samdb.get_nc_root(obj.dn)
1567         try:
1568             deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
1569                                                              samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
1570         except KeyError:
1571             # We have no deleted objects DN for schema, and we check for this above for the other
1572             # NCs
1573             deleted_objects_dn = None
1574
1575
1576         object_rdn_attr = None
1577         object_rdn_val = None
1578         name_val = None
1579         isDeleted = False
1580         systemFlags = 0
1581
1582         for attrname in obj:
1583             if attrname == 'dn' or attrname == "distinguishedName":
1584                 continue
1585
1586             if str(attrname).lower() == 'objectclass':
1587                 got_objectclass = True
1588
1589             if str(attrname).lower() == "name":
1590                 if len(obj[attrname]) != 1:
1591                     error_count += 1
1592                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1593                                 (len(obj[attrname]), attrname, str(obj.dn)))
1594                 else:
1595                     name_val = obj[attrname][0]
1596
1597             if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
1598                 object_rdn_attr = attrname
1599                 if len(obj[attrname]) != 1:
1600                     error_count += 1
1601                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1602                                 (len(obj[attrname]), attrname, str(obj.dn)))
1603                 else:
1604                     object_rdn_val = obj[attrname][0]
1605
1606             if str(attrname).lower() == 'isdeleted':
1607                 if obj[attrname][0] != "FALSE":
1608                     isDeleted = True
1609
1610             if str(attrname).lower() == 'systemflags':
1611                 systemFlags = int(obj[attrname][0])
1612
1613             if str(attrname).lower() == 'replpropertymetadata':
1614                 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
1615                     error_count += 1
1616                     self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
1617                     # We don't continue, as we may also have other fixes for this attribute
1618                     # based on what other attributes we see.
1619
1620                 try:
1621                     (set_attrs_from_md, list_attid_from_md, wrong_attids) \
1622                         = self.process_metadata(dn, obj[attrname])
1623                 except KeyError:
1624                     error_count += 1
1625                     self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
1626                     continue
1627
1628                 if len(set_attrs_from_md) < len(list_attid_from_md) \
1629                    or len(wrong_attids) > 0 \
1630                    or sorted(list_attid_from_md) != list_attid_from_md:
1631                     error_count +=1
1632                     self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname], wrong_attids)
1633
1634                 else:
1635                     # Here we check that the first attid is 0
1636                     # (objectClass).
1637                     if list_attid_from_md[0] != 0:
1638                         error_count += 1
1639                         self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
1640                                     (attrname, str(dn)))
1641
1642                 got_repl_property_meta_data = True
1643                 continue
1644
1645             if str(attrname).lower() == 'ntsecuritydescriptor':
1646                 (sd, sd_broken) = self.process_sd(dn, obj)
1647                 if sd_broken is not None:
1648                     self.err_wrong_sd(dn, sd, sd_broken)
1649                     error_count += 1
1650                     continue
1651
1652                 if sd.owner_sid is None or sd.group_sid is None:
1653                     self.err_missing_sd_owner(dn, sd)
1654                     error_count += 1
1655                     continue
1656
1657                 if self.reset_well_known_acls:
1658                     try:
1659                         well_known_sd = self.get_wellknown_sd(dn)
1660                     except KeyError:
1661                         continue
1662
1663                     current_sd = ndr_unpack(security.descriptor,
1664                                             str(obj[attrname][0]))
1665
1666                     diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
1667                     if diff != "":
1668                         self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
1669                         error_count += 1
1670                         continue
1671                 continue
1672
1673             if str(attrname).lower() == 'objectclass':
1674                 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
1675                 # Do not consider the attribute incorrect if:
1676                 #  - The sorted (alphabetically) list is the same, inclding case
1677                 #  - The first and last elements are the same
1678                 #
1679                 # This avoids triggering an error due to
1680                 # non-determinism in the sort routine in (at least)
1681                 # 4.3 and earlier, and the fact that any AUX classes
1682                 # in these attributes are also not sorted when
1683                 # imported from Windows (they are just in the reverse
1684                 # order of last set)
1685                 if sorted(normalised) != sorted(obj[attrname]) \
1686                    or normalised[0] != obj[attrname][0] \
1687                    or normalised[-1] != obj[attrname][-1]:
1688                     self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
1689                     error_count += 1
1690                 continue
1691
1692             if str(attrname).lower() == 'userparameters':
1693                 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
1694                     error_count += 1
1695                     self.err_short_userParameters(obj, attrname, obj[attrname])
1696                     continue
1697
1698                 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1699                     # This is the correct, normal prefix
1700                     continue
1701
1702                 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1703                     # this is the typical prefix from a windows migration
1704                     error_count += 1
1705                     self.err_base64_userParameters(obj, attrname, obj[attrname])
1706                     continue
1707
1708                 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':
1709                     # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1710                     error_count += 1
1711                     self.err_utf8_userParameters(obj, attrname, obj[attrname])
1712                     continue
1713
1714                 elif len(obj[attrname][0]) % 2 != 0:
1715                     # This is a value that isn't even in length
1716                     error_count += 1
1717                     self.err_odd_userParameters(obj, attrname, obj[attrname])
1718                     continue
1719
1720                 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':
1721                     # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
1722                     error_count += 1
1723                     self.err_doubled_userParameters(obj, attrname, obj[attrname])
1724                     continue
1725
1726             if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
1727                 if obj[attrname][0] in self.attribute_or_class_ids:
1728                     error_count += 1
1729                     self.report('Error: %s %s on %s already exists as an attributeId or governsId'
1730                                 % (attrname, obj.dn, obj[attrname][0]))
1731                 else:
1732                     self.attribute_or_class_ids.add(obj[attrname][0])
1733
1734             # check for empty attributes
1735             for val in obj[attrname]:
1736                 if val == '':
1737                     self.err_empty_attribute(dn, attrname)
1738                     error_count += 1
1739                     continue
1740
1741             # get the syntax oid for the attribute, so we can can have
1742             # special handling for some specific attribute types
1743             try:
1744                 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
1745             except Exception, msg:
1746                 self.err_unknown_attribute(obj, attrname)
1747                 error_count += 1
1748                 continue
1749
1750             linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
1751
1752             flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
1753             if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
1754                 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
1755                 and not linkID):
1756                 set_attrs_seen.add(str(attrname).lower())
1757
1758             if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
1759                                dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
1760                 # it's some form of DN, do specialised checking on those
1761                 error_count += self.check_dn(obj, attrname, syntax_oid)
1762             else:
1763
1764                 values = set()
1765                 # check for incorrectly normalised attributes
1766                 for val in obj[attrname]:
1767                     values.add(str(val))
1768
1769                     normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
1770                     if len(normalised) != 1 or normalised[0] != val:
1771                         self.err_normalise_mismatch(dn, attrname, obj[attrname])
1772                         error_count += 1
1773                         break
1774
1775                 if len(obj[attrname]) != len(values):
1776                     self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
1777                     error_count += 1
1778                     break
1779
1780             if str(attrname).lower() == "instancetype":
1781                 calculated_instancetype = self.calculate_instancetype(dn)
1782                 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
1783                     error_count += 1
1784                     self.err_wrong_instancetype(obj, calculated_instancetype)
1785
1786         if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
1787             error_count += 1
1788             self.err_missing_objectclass(dn)
1789
1790         if ("*" in attrs or "name" in map(str.lower, attrs)):
1791             if name_val is None:
1792                 error_count += 1
1793                 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
1794             if object_rdn_attr is None:
1795                 error_count += 1
1796                 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
1797
1798         if name_val is not None:
1799             parent_dn = None
1800             if isDeleted:
1801                 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
1802                     parent_dn = deleted_objects_dn
1803             if parent_dn is None:
1804                 parent_dn = obj.dn.parent()
1805             expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
1806             expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
1807
1808             if obj.dn == deleted_objects_dn:
1809                 expected_dn = obj.dn
1810
1811             if expected_dn != obj.dn:
1812                 error_count += 1
1813                 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
1814             elif obj.dn.get_rdn_value() != object_rdn_val:
1815                 error_count += 1
1816                 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
1817
1818         show_dn = True
1819         if got_repl_property_meta_data:
1820             if obj.dn == deleted_objects_dn:
1821                 isDeletedAttId = 131120
1822                 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
1823
1824                 expectedTimeDo = 2650466015990000000
1825                 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
1826                 if originating != expectedTimeDo:
1827                     if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
1828                         nmsg = ldb.Message()
1829                         nmsg.dn = dn
1830                         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1831                         error_count += 1
1832                         self.samdb.modify(nmsg, controls=["provision:0"])
1833
1834                     else:
1835                         self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
1836
1837             for att in set_attrs_seen.difference(set_attrs_from_md):
1838                 if show_dn:
1839                     self.report("On object %s" % dn)
1840                     show_dn = False
1841                 error_count += 1
1842                 self.report("ERROR: Attribute %s not present in replication metadata" % att)
1843                 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
1844                     self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
1845                     continue
1846                 self.fix_metadata(dn, att)
1847
1848         if self.is_fsmo_role(dn):
1849             if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
1850                 self.err_no_fsmoRoleOwner(obj)
1851                 error_count += 1
1852
1853         try:
1854             if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
1855                 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
1856                                         controls=["show_recycled:1", "show_deleted:1"])
1857         except ldb.LdbError, (enum, estr):
1858             if enum == ldb.ERR_NO_SUCH_OBJECT:
1859                 self.err_missing_parent(obj)
1860                 error_count += 1
1861             else:
1862                 raise
1863
1864         if dn in self.deleted_objects_containers and '*' in attrs:
1865             if self.is_deleted_deleted_objects(obj):
1866                 self.err_deleted_deleted_objects(obj)
1867                 error_count += 1
1868
1869         for (dns_part, msg) in self.dns_partitions:
1870             if dn == dns_part and 'repsFrom' in obj:
1871                 location = "msDS-NC-Replica-Locations"
1872                 if self.samdb.am_rodc():
1873                     location = "msDS-NC-RO-Replica-Locations"
1874
1875                 if location not in msg:
1876                     # There are no replica locations!
1877                     self.err_replica_locations(obj, msg.dn, location)
1878                     error_count += 1
1879                     continue
1880
1881                 found = False
1882                 for loc in msg[location]:
1883                     if loc == self.samdb.get_dsServiceName():
1884                         found = True
1885                 if not found:
1886                     # This DC is not in the replica locations
1887                     self.err_replica_locations(obj, msg.dn, location)
1888                     error_count += 1
1889
1890         if dn == self.server_ref_dn:
1891             # Check we have a valid RID Set
1892             if "*" in attrs or "rIDSetReferences" in attrs:
1893                 if "rIDSetReferences" not in obj:
1894                     # NO RID SET reference
1895                     # We are RID master, allocate it.
1896                     error_count += 1
1897
1898                     if self.is_rid_master:
1899                         # Allocate a RID Set
1900                         if self.confirm_all('Allocate the missing RID set for RID master?',
1901                                             'fix_missing_rid_set_master'):
1902
1903                             # We don't have auto-transaction logic on
1904                             # extended operations, so we have to do it
1905                             # here.
1906
1907                             self.samdb.transaction_start()
1908
1909                             try:
1910                                 self.samdb.create_own_rid_set()
1911
1912                             except:
1913                                 self.samdb.transaction_cancel()
1914                                 raise
1915
1916                             self.samdb.transaction_commit()
1917
1918
1919                     elif not self.samdb.am_rodc():
1920                         self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
1921
1922
1923         # Check some details of our own RID Set
1924         if dn == self.rid_set_dn:
1925             res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
1926                                     attrs=["rIDAllocationPool",
1927                                            "rIDPreviousAllocationPool",
1928                                            "rIDUsedPool",
1929                                            "rIDNextRID"])
1930             if "rIDAllocationPool" not in res[0]:
1931                 self.report("No rIDAllocationPool found in %s" % dn)
1932                 error_count += 1
1933             else:
1934                 next_pool = int(res[0]["rIDAllocationPool"][0])
1935
1936                 high = (0xFFFFFFFF00000000 & next_pool) >> 32
1937                 low = 0x00000000FFFFFFFF & next_pool
1938
1939                 if high <= low:
1940                     self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
1941                     error_count += 1
1942
1943                 if "rIDNextRID" in res[0]:
1944                     next_free_rid = int(res[0]["rIDNextRID"][0])
1945                 else:
1946                     next_free_rid = 0
1947
1948                 if next_free_rid == 0:
1949                     next_free_rid = low
1950                 else:
1951                     next_free_rid += 1
1952
1953                 # Check the remainder of this pool for conflicts.  If
1954                 # ridalloc_allocate_rid() moves to a new pool, this
1955                 # will be above high, so we will stop.
1956                 while next_free_rid <= high:
1957                     sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
1958                     try:
1959                         res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
1960                                                 attrs=[])
1961                     except ldb.LdbError, (enum, estr):
1962                         if enum != ldb.ERR_NO_SUCH_OBJECT:
1963                             raise
1964                         res = None
1965                     if res is not None:
1966                         self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
1967                         error_count += 1
1968
1969                         if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
1970                                             % (sid, dn),
1971                                             'fix_sid_rid_set_conflict'):
1972                             self.samdb.transaction_start()
1973
1974                             # This will burn RIDs, which will move
1975                             # past the conflict.  We then check again
1976                             # to see if the new RID conflicts, until
1977                             # the end of the current pool.  We don't
1978                             # look at the next pool to avoid burning
1979                             # all RIDs in one go in some strange
1980                             # failure case.
1981                             try:
1982                                 while True:
1983                                     allocated_rid = self.samdb.allocate_rid()
1984                                     if allocated_rid >= next_free_rid:
1985                                         next_free_rid = allocated_rid + 1
1986                                         break
1987                             except:
1988                                 self.samdb.transaction_cancel()
1989                                 raise
1990
1991                             self.samdb.transaction_commit()
1992                         else:
1993                             break
1994                     else:
1995                         next_free_rid += 1
1996
1997
1998         return error_count
1999
2000     ################################################################
2001     # check special @ROOTDSE attributes
2002     def check_rootdse(self):
2003         '''check the @ROOTDSE special object'''
2004         dn = ldb.Dn(self.samdb, '@ROOTDSE')
2005         if self.verbose:
2006             self.report("Checking object %s" % dn)
2007         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2008         if len(res) != 1:
2009             self.report("Object %s disappeared during check" % dn)
2010             return 1
2011         obj = res[0]
2012         error_count = 0
2013
2014         # check that the dsServiceName is in GUID form
2015         if not 'dsServiceName' in obj:
2016             self.report('ERROR: dsServiceName missing in @ROOTDSE')
2017             return error_count+1
2018
2019         if not obj['dsServiceName'][0].startswith('<GUID='):
2020             self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2021             error_count += 1
2022             if not self.confirm('Change dsServiceName to GUID form?'):
2023                 return error_count
2024             res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
2025                                     scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2026             guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2027             m = ldb.Message()
2028             m.dn = dn
2029             m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2030                                                     ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2031             if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2032                 self.report("Changed dsServiceName to GUID form")
2033         return error_count
2034
2035
2036     ###############################################
2037     # re-index the database
2038     def reindex_database(self):
2039         '''re-index the whole database'''
2040         m = ldb.Message()
2041         m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2042         m['add']    = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2043         m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2044         return self.do_modify(m, [], 're-indexed database', validate=False)
2045
2046     ###############################################
2047     # reset @MODULES
2048     def reset_modules(self):
2049         '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2050         m = ldb.Message()
2051         m.dn = ldb.Dn(self.samdb, "@MODULES")
2052         m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2053         return self.do_modify(m, [], 'reset @MODULES on database', validate=False)