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