dbcheck: make sure we always ask for the objectGUID attribute explicitly
[amitay/samba.git] / python / samba / dbchecker.py
1 # Samba4 AD database checker
2 #
3 # Copyright (C) Andrew Tridgell 2011
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2011
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import ldb
21 import samba
22 import time
23 from base64 import b64decode
24 from samba import dsdb
25 from samba import common
26 from samba.dcerpc import misc
27 from samba.dcerpc import drsuapi
28 from samba.ndr import ndr_unpack, ndr_pack
29 from samba.dcerpc import drsblobs
30 from samba.common import dsdb_Dn
31 from samba.dcerpc import security
32 from samba.descriptor import get_wellknown_sds, get_diff_sds
33 from samba.auth import system_session, admin_session
34 from samba.netcmd import CommandError
35 from samba.netcmd.fsmo import get_fsmo_roleowner
36
37
38 class dbcheck(object):
39     """check a SAM database for errors"""
40
41     def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
42                  yes=False, quiet=False, in_transaction=False,
43                  reset_well_known_acls=False):
44         self.samdb = samdb
45         self.dict_oid_name = None
46         self.samdb_schema = (samdb_schema or samdb)
47         self.verbose = verbose
48         self.fix = fix
49         self.yes = yes
50         self.quiet = quiet
51         self.remove_all_unknown_attributes = False
52         self.remove_all_empty_attributes = False
53         self.fix_all_normalisation = False
54         self.fix_all_duplicates = False
55         self.fix_all_DN_GUIDs = False
56         self.fix_all_binary_dn = False
57         self.remove_implausible_deleted_DN_links = False
58         self.remove_plausible_deleted_DN_links = False
59         self.fix_all_string_dn_component_mismatch = False
60         self.fix_all_GUID_dn_component_mismatch = False
61         self.fix_all_SID_dn_component_mismatch = False
62         self.fix_all_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 check_dn(self, obj, attrname, syntax_oid):
991         '''check a DN attribute for correctness'''
992         error_count = 0
993         obj_guid = obj['objectGUID'][0]
994
995         linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
996         if reverse_link_name is not None:
997             reverse_syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(reverse_link_name)
998         else:
999             reverse_syntax_oid = None
1000
1001         error_count, duplicate_dict, unique_dict = \
1002             self.check_duplicate_links(obj, attrname, syntax_oid, linkID, reverse_link_name)
1003
1004         if len(duplicate_dict) != 0:
1005             self.report("ERROR: Duplicate forward link values for attribute '%s' in '%s'" % (attrname, obj.dn))
1006             for keystr in duplicate_dict.keys():
1007                 d = duplicate_dict[keystr]
1008                 for dd in d["delete"]:
1009                     self.report("Duplicate link '%s'" % dd)
1010                 self.report("Correct   link '%s'" % d["keep"])
1011
1012             # We now construct the sorted dn values.
1013             # They're sorted by the objectGUID of the target
1014             # See dsdb_Dn.__cmp__()
1015             vals = [str(dn) for dn in sorted(unique_dict.values())]
1016             self.err_recover_forward_links(obj, attrname, vals)
1017             # We should continue with the fixed values
1018             obj[attrname] = ldb.MessageElement(vals, 0, attrname)
1019
1020         for val in obj[attrname]:
1021             dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
1022
1023             # all DNs should have a GUID component
1024             guid = dsdb_dn.dn.get_extended_component("GUID")
1025             if guid is None:
1026                 error_count += 1
1027                 self.err_missing_dn_GUID_component(obj.dn, attrname, val, dsdb_dn,
1028                     "missing GUID")
1029                 continue
1030
1031             guidstr = str(misc.GUID(guid))
1032             attrs = ['isDeleted', 'replPropertyMetaData']
1033
1034             if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa):
1035                 fixing_msDS_HasInstantiatedNCs = True
1036                 attrs.append("instanceType")
1037             else:
1038                 fixing_msDS_HasInstantiatedNCs = False
1039
1040             if reverse_link_name is not None:
1041                 attrs.append(reverse_link_name)
1042
1043             # check its the right GUID
1044             try:
1045                 res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
1046                                         attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1",
1047                                                                "reveal_internals:0"
1048                                         ])
1049             except ldb.LdbError, (enum, estr):
1050                 if enum != ldb.ERR_NO_SUCH_OBJECT:
1051                     raise
1052
1053                 # We don't always want to
1054                 error_count += self.err_missing_target_dn_or_GUID(obj.dn,
1055                                                                   attrname,
1056                                                                   val,
1057                                                                   dsdb_dn)
1058                 continue
1059
1060             if fixing_msDS_HasInstantiatedNCs:
1061                 dsdb_dn.prefix = "B:8:%08X:" % int(res[0]['instanceType'][0])
1062                 dsdb_dn.binary = "%08X" % int(res[0]['instanceType'][0])
1063
1064                 if str(dsdb_dn) != val:
1065                     error_count +=1
1066                     self.err_incorrect_binary_dn(obj.dn, attrname, val, dsdb_dn, "incorrect instanceType part of Binary DN")
1067                     continue
1068
1069             # now we have two cases - the source object might or might not be deleted
1070             is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1071             target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
1072
1073
1074             if is_deleted and not obj.dn in self.deleted_objects_containers and linkID:
1075                 # A fully deleted object should not have any linked
1076                 # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone
1077                 # Requirements and 3.1.1.5.5.1.3 Recycled-Object
1078                 # Requirements)
1079                 self.err_undead_linked_attribute(obj, attrname, val)
1080                 error_count += 1
1081                 continue
1082             elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn) and linkID:
1083                 # the target DN is not allowed to be deleted, unless the target DN is the
1084                 # special Deleted Objects container
1085                 error_count += 1
1086                 local_usn = dsdb_dn.dn.get_extended_component("RMD_LOCAL_USN")
1087                 if local_usn:
1088                     if 'replPropertyMetaData' in res[0]:
1089                         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1090                                           str(res[0]['replPropertyMetadata']))
1091                         found_data = False
1092                         for o in repl.ctr.array:
1093                             if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
1094                                 deleted_usn = o.local_usn
1095                                 if deleted_usn >= int(local_usn):
1096                                     # If the object was deleted after the link
1097                                     # was last modified then, clean it up here
1098                                     found_data = True
1099                                     break
1100
1101                         if found_data:
1102                             self.err_deleted_dn(obj.dn, attrname,
1103                                                 val, dsdb_dn, res[0].dn, True)
1104                             continue
1105
1106                 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn, False)
1107                 continue
1108
1109             # We should not check for incorrect
1110             # components on deleted links, as these are allowed to
1111             # go stale (we just need the GUID, not the name)
1112             rmd_blob = dsdb_dn.dn.get_extended_component("RMD_FLAGS")
1113             rmd_flags = 0
1114             if rmd_blob is not None:
1115                 rmd_flags = int(rmd_blob)
1116
1117             # assert the DN matches in string form, where a reverse
1118             # link exists, otherwise (below) offer to fix it as a non-error.
1119             # The string form is essentially only kept for forensics,
1120             # as we always re-resolve by GUID in normal operations.
1121             if not rmd_flags & 1 and reverse_link_name is not None:
1122                 if str(res[0].dn) != str(dsdb_dn.dn):
1123                     error_count += 1
1124                     self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1125                                                           res[0].dn, "string")
1126                     continue
1127
1128             if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"):
1129                 error_count += 1
1130                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1131                                                       res[0].dn, "GUID")
1132                 continue
1133
1134             if res[0].dn.get_extended_component("SID") != dsdb_dn.dn.get_extended_component("SID"):
1135                 error_count += 1
1136                 self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn,
1137                                                       res[0].dn, "SID")
1138                 continue
1139
1140             # Now we have checked the GUID and SID, offer to fix old
1141             # DN strings as a non-error (for forward links with no
1142             # backlink).  Samba does not maintain this string
1143             # otherwise, so we don't increment error_count.
1144             if reverse_link_name is None:
1145                 if str(res[0].dn) != str(dsdb_dn.dn):
1146                     self.err_dn_string_component_old(obj.dn, attrname, val, dsdb_dn,
1147                                                      res[0].dn)
1148                 continue
1149
1150             # check the reverse_link is correct if there should be one
1151             match_count = 0
1152             if reverse_link_name in res[0]:
1153                 for v in res[0][reverse_link_name]:
1154                     v_dn = dsdb_Dn(self.samdb, v)
1155                     v_guid = v_dn.dn.get_extended_component("GUID")
1156                     v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1157                     v_rmd_flags = 0
1158                     if v_blob is not None:
1159                         v_rmd_flags = int(v_blob)
1160                     if v_rmd_flags & 1:
1161                         continue
1162                     if v_guid == obj_guid:
1163                         match_count += 1
1164
1165             if match_count != 1:
1166                 if syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN or reverse_syntax_oid == dsdb.DSDB_SYNTAX_BINARY_DN:
1167                     if not linkID & 1:
1168                         # Forward binary multi-valued linked attribute
1169                         forward_count = 0
1170                         for w in obj[attrname]:
1171                             w_guid = dsdb_Dn(self.samdb, w).dn.get_extended_component("GUID")
1172                             if w_guid == guid:
1173                                 forward_count += 1
1174
1175                         if match_count == forward_count:
1176                             continue
1177             expected_count = 0
1178             for v in obj[attrname]:
1179                 v_dn = dsdb_Dn(self.samdb, v)
1180                 v_guid = v_dn.dn.get_extended_component("GUID")
1181                 v_blob = v_dn.dn.get_extended_component("RMD_FLAGS")
1182                 v_rmd_flags = 0
1183                 if v_blob is not None:
1184                     v_rmd_flags = int(v_blob)
1185                 if v_rmd_flags & 1:
1186                     continue
1187                 if v_guid == guid:
1188                     expected_count += 1
1189
1190             if match_count == expected_count:
1191                 continue
1192
1193             diff_count = expected_count - match_count
1194
1195             if linkID & 1:
1196                 # If there's a backward link on binary multi-valued linked attribute,
1197                 # let the check on the forward link remedy the value.
1198                 # UNLESS, there is no forward link detected.
1199                 if match_count == 0:
1200                     error_count += 1
1201                     self.err_orphaned_backlink(obj.dn, attrname,
1202                                                val, dsdb_dn.dn,
1203                                                reverse_link_name,
1204                                                reverse_syntax_oid)
1205                     continue
1206                 # Only warn here and let the forward link logic fix it.
1207                 self.report("WARNING: Link (back) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1208                             attrname, expected_count, str(obj.dn),
1209                             reverse_link_name, match_count, str(dsdb_dn.dn)))
1210                 continue
1211
1212             assert not target_is_deleted
1213
1214             self.report("ERROR: Link (forward) mismatch for '%s' (%d) on '%s' to '%s' (%d) on '%s'" % (
1215                         attrname, expected_count, str(obj.dn),
1216                         reverse_link_name, match_count, str(dsdb_dn.dn)))
1217
1218             # Loop until the difference between the forward and
1219             # the backward links is resolved.
1220             while diff_count != 0:
1221                 error_count += 1
1222                 if diff_count > 0:
1223                     if match_count > 0 or diff_count > 1:
1224                         # TODO no method to fix these right now
1225                         self.report("ERROR: Can't fix missing "
1226                                     "multi-valued backlinks on %s" % str(dsdb_dn.dn))
1227                         break
1228                     self.err_missing_backlink(obj, attrname,
1229                                               obj.dn.extended_str(),
1230                                               reverse_link_name,
1231                                               dsdb_dn.dn)
1232                     diff_count -= 1
1233                 else:
1234                     self.err_orphaned_backlink(res[0].dn, reverse_link_name,
1235                                                obj.dn.extended_str(), obj.dn,
1236                                                attrname, syntax_oid)
1237                     diff_count += 1
1238
1239
1240         return error_count
1241
1242
1243     def get_originating_time(self, val, attid):
1244         '''Read metadata properties and return the originating time for
1245            a given attributeId.
1246
1247            :return: the originating time or 0 if not found
1248         '''
1249
1250         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1251         obj = repl.ctr
1252
1253         for o in repl.ctr.array:
1254             if o.attid == attid:
1255                 return o.originating_change_time
1256
1257         return 0
1258
1259     def process_metadata(self, dn, val):
1260         '''Read metadata properties and list attributes in it.
1261            raises KeyError if the attid is unknown.'''
1262
1263         set_att = set()
1264         wrong_attids = set()
1265         list_attid = []
1266         in_schema_nc = dn.is_child_of(self.schema_dn)
1267
1268         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
1269         obj = repl.ctr
1270
1271         for o in repl.ctr.array:
1272             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1273             set_att.add(att.lower())
1274             list_attid.append(o.attid)
1275             correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att,
1276                                                                              is_schema_nc=in_schema_nc)
1277             if correct_attid != o.attid:
1278                 wrong_attids.add(o.attid)
1279
1280         return (set_att, list_attid, wrong_attids)
1281
1282
1283     def fix_metadata(self, obj, attr):
1284         '''re-write replPropertyMetaData elements for a single attribute for a
1285         object. This is used to fix missing replPropertyMetaData elements'''
1286         guid_str = str(ndr_unpack(misc.GUID, obj['objectGUID'][0]))
1287         dn = ldb.Dn(self.samdb, "<GUID=%s>" % guid_str)
1288         res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
1289                                 controls = ["search_options:1:2",
1290                                             "show_recycled:1"])
1291         msg = res[0]
1292         nmsg = ldb.Message()
1293         nmsg.dn = dn
1294         nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
1295         if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
1296                           "Failed to fix metadata for attribute %s" % attr):
1297             self.report("Fixed metadata for attribute %s" % attr)
1298
1299     def ace_get_effective_inherited_type(self, ace):
1300         if ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY:
1301             return None
1302
1303         check = False
1304         if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT:
1305             check = True
1306         elif ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT:
1307             check = True
1308         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT:
1309             check = True
1310         elif ace.type == security.SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT:
1311             check = True
1312
1313         if not check:
1314             return None
1315
1316         if not ace.object.flags & security.SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT:
1317             return None
1318
1319         return str(ace.object.inherited_type)
1320
1321     def lookup_class_schemaIDGUID(self, cls):
1322         if cls in self.class_schemaIDGUID:
1323             return self.class_schemaIDGUID[cls]
1324
1325         flt = "(&(ldapDisplayName=%s)(objectClass=classSchema))" % cls
1326         res = self.samdb.search(base=self.schema_dn,
1327                                 expression=flt,
1328                                 attrs=["schemaIDGUID"])
1329         t = str(ndr_unpack(misc.GUID, res[0]["schemaIDGUID"][0]))
1330
1331         self.class_schemaIDGUID[cls] = t
1332         return t
1333
1334     def process_sd(self, dn, obj):
1335         sd_attr = "nTSecurityDescriptor"
1336         sd_val = obj[sd_attr]
1337
1338         sd = ndr_unpack(security.descriptor, str(sd_val))
1339
1340         is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
1341         if is_deleted:
1342             # we don't fix deleted objects
1343             return (sd, None)
1344
1345         sd_clean = security.descriptor()
1346         sd_clean.owner_sid = sd.owner_sid
1347         sd_clean.group_sid = sd.group_sid
1348         sd_clean.type = sd.type
1349         sd_clean.revision = sd.revision
1350
1351         broken = False
1352         last_inherited_type = None
1353
1354         aces = []
1355         if sd.sacl is not None:
1356             aces = sd.sacl.aces
1357         for i in range(0, len(aces)):
1358             ace = aces[i]
1359
1360             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1361                 sd_clean.sacl_add(ace)
1362                 continue
1363
1364             t = self.ace_get_effective_inherited_type(ace)
1365             if t is None:
1366                 continue
1367
1368             if last_inherited_type is not None:
1369                 if t != last_inherited_type:
1370                     # if it inherited from more than
1371                     # one type it's very likely to be broken
1372                     #
1373                     # If not the recalculation will calculate
1374                     # the same result.
1375                     broken = True
1376                 continue
1377
1378             last_inherited_type = t
1379
1380         aces = []
1381         if sd.dacl is not None:
1382             aces = sd.dacl.aces
1383         for i in range(0, len(aces)):
1384             ace = aces[i]
1385
1386             if not ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE:
1387                 sd_clean.dacl_add(ace)
1388                 continue
1389
1390             t = self.ace_get_effective_inherited_type(ace)
1391             if t is None:
1392                 continue
1393
1394             if last_inherited_type is not None:
1395                 if t != last_inherited_type:
1396                     # if it inherited from more than
1397                     # one type it's very likely to be broken
1398                     #
1399                     # If not the recalculation will calculate
1400                     # the same result.
1401                     broken = True
1402                 continue
1403
1404             last_inherited_type = t
1405
1406         if broken:
1407             return (sd_clean, sd)
1408
1409         if last_inherited_type is None:
1410             # ok
1411             return (sd, None)
1412
1413         cls = None
1414         try:
1415             cls = obj["objectClass"][-1]
1416         except KeyError, e:
1417             pass
1418
1419         if cls is None:
1420             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1421                                     attrs=["isDeleted", "objectClass"],
1422                                     controls=["show_recycled:1"])
1423             o = res[0]
1424             is_deleted = 'isDeleted' in o and o['isDeleted'][0].upper() == 'TRUE'
1425             if is_deleted:
1426                 # we don't fix deleted objects
1427                 return (sd, None)
1428             cls = o["objectClass"][-1]
1429
1430         t = self.lookup_class_schemaIDGUID(cls)
1431
1432         if t != last_inherited_type:
1433             # broken
1434             return (sd_clean, sd)
1435
1436         # ok
1437         return (sd, None)
1438
1439     def err_wrong_sd(self, dn, sd, sd_broken):
1440         '''re-write the SD due to incorrect inherited ACEs'''
1441         sd_attr = "nTSecurityDescriptor"
1442         sd_val = ndr_pack(sd)
1443         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1444
1445         if not self.confirm_all('Fix %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor'):
1446             self.report('Not fixing %s on %s\n' % (sd_attr, dn))
1447             return
1448
1449         nmsg = ldb.Message()
1450         nmsg.dn = dn
1451         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1452         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1453                           "Failed to fix attribute %s" % sd_attr):
1454             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1455
1456     def err_wrong_default_sd(self, dn, sd, sd_old, diff):
1457         '''re-write the SD due to not matching the default (optional mode for fixing an incorrect provision)'''
1458         sd_attr = "nTSecurityDescriptor"
1459         sd_val = ndr_pack(sd)
1460         sd_old_val = ndr_pack(sd_old)
1461         sd_flags = security.SECINFO_DACL | security.SECINFO_SACL
1462         if sd.owner_sid is not None:
1463             sd_flags |= security.SECINFO_OWNER
1464         if sd.group_sid is not None:
1465             sd_flags |= security.SECINFO_GROUP
1466
1467         if not self.confirm_all('Reset %s on %s back to provision default?\n%s' % (sd_attr, dn, diff), 'reset_all_well_known_acls'):
1468             self.report('Not resetting %s on %s\n' % (sd_attr, dn))
1469             return
1470
1471         m = ldb.Message()
1472         m.dn = dn
1473         m[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1474         if self.do_modify(m, ["sd_flags:1:%d" % sd_flags],
1475                           "Failed to reset attribute %s" % sd_attr):
1476             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1477
1478     def err_missing_sd_owner(self, dn, sd):
1479         '''re-write the SD due to a missing owner or group'''
1480         sd_attr = "nTSecurityDescriptor"
1481         sd_val = ndr_pack(sd)
1482         sd_flags = security.SECINFO_OWNER | security.SECINFO_GROUP
1483
1484         if not self.confirm_all('Fix missing owner or group in %s on %s?' % (sd_attr, dn), 'fix_ntsecuritydescriptor_owner_group'):
1485             self.report('Not fixing missing owner or group %s on %s\n' % (sd_attr, dn))
1486             return
1487
1488         nmsg = ldb.Message()
1489         nmsg.dn = dn
1490         nmsg[sd_attr] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_REPLACE, sd_attr)
1491
1492         # By setting the session_info to admin_session_info and
1493         # setting the security.SECINFO_OWNER | security.SECINFO_GROUP
1494         # flags we cause the descriptor module to set the correct
1495         # owner and group on the SD, replacing the None/NULL values
1496         # for owner_sid and group_sid currently present.
1497         #
1498         # The admin_session_info matches that used in provision, and
1499         # is the best guess we can make for an existing object that
1500         # hasn't had something specifically set.
1501         #
1502         # This is important for the dns related naming contexts.
1503         self.samdb.set_session_info(self.admin_session_info)
1504         if self.do_modify(nmsg, ["sd_flags:1:%d" % sd_flags],
1505                           "Failed to fix metadata for attribute %s" % sd_attr):
1506             self.report("Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
1507         self.samdb.set_session_info(self.system_session_info)
1508
1509
1510     def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
1511         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1512                           str(repl_meta_data))
1513         ctr = repl.ctr
1514         found = False
1515         for o in ctr.array:
1516             # Search for a zero invocationID
1517             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1518                 continue
1519
1520             found = True
1521             self.report('''ERROR: on replPropertyMetaData of %s, the instanceType on attribute 0x%08x,
1522                            version %d changed at %s is 00000000-0000-0000-0000-000000000000,
1523                            but should be non-zero.  Proposed fix is to set to our invocationID (%s).'''
1524                         % (dn, o.attid, o.version,
1525                            time.ctime(samba.nttime2unix(o.originating_change_time)),
1526                            self.samdb.get_invocation_id()))
1527
1528         return found
1529
1530
1531     def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
1532         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1533                           str(repl_meta_data))
1534         ctr = repl.ctr
1535         now = samba.unix2nttime(int(time.time()))
1536         found = False
1537         for o in ctr.array:
1538             # Search for a zero invocationID
1539             if o.originating_invocation_id != misc.GUID("00000000-0000-0000-0000-000000000000"):
1540                 continue
1541
1542             found = True
1543             seq = self.samdb.sequence_number(ldb.SEQ_NEXT)
1544             o.version = o.version + 1
1545             o.originating_change_time = now
1546             o.originating_invocation_id = misc.GUID(self.samdb.get_invocation_id())
1547             o.originating_usn = seq
1548             o.local_usn = seq
1549
1550         if found:
1551             replBlob = ndr_pack(repl)
1552             msg = ldb.Message()
1553             msg.dn = dn
1554
1555             if not self.confirm_all('Fix %s on %s by setting originating_invocation_id on some elements to our invocationID %s?'
1556                                     % (attr, dn, self.samdb.get_invocation_id()), 'fix_replmetadata_zero_invocationid'):
1557                 self.report('Not fixing zero originating_invocation_id in %s on %s\n' % (attr, dn))
1558                 return
1559
1560             nmsg = ldb.Message()
1561             nmsg.dn = dn
1562             nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1563             if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1564                                      "local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
1565                               "Failed to fix attribute %s" % attr):
1566                 self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1567
1568
1569     def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
1570         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1571                           str(repl_meta_data))
1572         ctr = repl.ctr
1573         for o in ctr.array:
1574             # Search for an invalid attid
1575             try:
1576                 att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1577             except KeyError:
1578                 self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
1579                 return
1580
1581
1582     def err_replmetadata_incorrect_attid(self, dn, attr, repl_meta_data, wrong_attids):
1583         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1584                           str(repl_meta_data))
1585         fix = False
1586
1587         set_att = set()
1588         remove_attid = set()
1589         hash_att = {}
1590
1591         in_schema_nc = dn.is_child_of(self.schema_dn)
1592
1593         ctr = repl.ctr
1594         # Sort the array, except for the last element.  This strange
1595         # construction, creating a new list, due to bugs in samba's
1596         # array handling in IDL generated objects.
1597         ctr.array = sorted(ctr.array[:], key=lambda o: o.attid)
1598         # Now walk it in reverse, so we see the low (and so incorrect,
1599         # the correct values are above 0x80000000) values first and
1600         # remove the 'second' value we see.
1601         for o in reversed(ctr.array):
1602             print "%s: 0x%08x" % (dn, o.attid)
1603             att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1604             if att.lower() in set_att:
1605                 self.report('ERROR: duplicate attributeID values for %s in %s on %s\n' % (att, attr, dn))
1606                 if not self.confirm_all('Fix %s on %s by removing the duplicate value 0x%08x for %s (keeping 0x%08x)?'
1607                                         % (attr, dn, o.attid, att, hash_att[att].attid),
1608                                         'fix_replmetadata_duplicate_attid'):
1609                     self.report('Not fixing duplicate value 0x%08x for %s in %s on %s\n'
1610                                 % (o.attid, att, attr, dn))
1611                     return
1612                 fix = True
1613                 remove_attid.add(o.attid)
1614                 # We want to set the metadata for the most recent
1615                 # update to have been applied locally, that is the metadata
1616                 # matching the (eg string) value in the attribute
1617                 if o.local_usn > hash_att[att].local_usn:
1618                     # This is always what we would have sent over DRS,
1619                     # because the DRS server will have sent the
1620                     # msDS-IntID, but with the values from both
1621                     # attribute entries.
1622                     hash_att[att].version = o.version
1623                     hash_att[att].originating_change_time = o.originating_change_time
1624                     hash_att[att].originating_invocation_id = o.originating_invocation_id
1625                     hash_att[att].originating_usn = o.originating_usn
1626                     hash_att[att].local_usn = o.local_usn
1627
1628                 # Do not re-add the value to the set or overwrite the hash value
1629                 continue
1630
1631             hash_att[att] = o
1632             set_att.add(att.lower())
1633
1634         # Generate a real list we can sort on properly
1635         new_list = [o for o in ctr.array if o.attid not in remove_attid]
1636
1637         if (len(wrong_attids) > 0):
1638             for o in new_list:
1639                 if o.attid in wrong_attids:
1640                     att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
1641                     correct_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(att, is_schema_nc=in_schema_nc)
1642                     self.report('ERROR: incorrect attributeID values in %s on %s\n' % (attr, dn))
1643                     if not self.confirm_all('Fix %s on %s by replacing incorrect value 0x%08x for %s (new 0x%08x)?'
1644                                             % (attr, dn, o.attid, att, hash_att[att].attid), 'fix_replmetadata_wrong_attid'):
1645                         self.report('Not fixing incorrect value 0x%08x with 0x%08x for %s in %s on %s\n'
1646                                     % (o.attid, correct_attid, att, attr, dn))
1647                         return
1648                     fix = True
1649                     o.attid = correct_attid
1650             if fix:
1651                 # Sort the array, (we changed the value so must re-sort)
1652                 new_list[:] = sorted(new_list[:], key=lambda o: o.attid)
1653
1654         # If we did not already need to fix it, then ask about sorting
1655         if not fix:
1656             self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
1657             if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
1658                                     % (attr, dn), 'fix_replmetadata_unsorted_attid'):
1659                 self.report('Not fixing %s on %s\n' % (attr, dn))
1660                 return
1661
1662             # The actual sort done is done at the top of the function
1663
1664         ctr.count = len(new_list)
1665         ctr.array = new_list
1666         replBlob = ndr_pack(repl)
1667
1668         nmsg = ldb.Message()
1669         nmsg.dn = dn
1670         nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
1671         if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
1672                              "local_oid:1.3.6.1.4.1.7165.4.3.14:0",
1673                              "local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
1674                       "Failed to fix attribute %s" % attr):
1675             self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))
1676
1677
1678     def is_deleted_deleted_objects(self, obj):
1679         faulty = False
1680         if "description" not in obj:
1681             self.report("ERROR: description not present on Deleted Objects container %s" % obj.dn)
1682             faulty = True
1683         if "showInAdvancedViewOnly" not in obj or obj['showInAdvancedViewOnly'][0].upper() == 'FALSE':
1684             self.report("ERROR: showInAdvancedViewOnly not present on Deleted Objects container %s" % obj.dn)
1685             faulty = True
1686         if "objectCategory" not in obj:
1687             self.report("ERROR: objectCategory not present on Deleted Objects container %s" % obj.dn)
1688             faulty = True
1689         if "isCriticalSystemObject" not in obj or obj['isCriticalSystemObject'][0].upper() == 'FALSE':
1690             self.report("ERROR: isCriticalSystemObject not present on Deleted Objects container %s" % obj.dn)
1691             faulty = True
1692         if "isRecycled" in obj:
1693             self.report("ERROR: isRecycled present on Deleted Objects container %s" % obj.dn)
1694             faulty = True
1695         if "isDeleted" in obj and obj['isDeleted'][0].upper() == 'FALSE':
1696             self.report("ERROR: isDeleted not set on Deleted Objects container %s" % obj.dn)
1697             faulty = True
1698         if "objectClass" not in obj or (len(obj['objectClass']) != 2 or
1699                                         obj['objectClass'][0] != 'top' or
1700                                         obj['objectClass'][1] != 'container'):
1701             self.report("ERROR: objectClass incorrectly set on Deleted Objects container %s" % obj.dn)
1702             faulty = True
1703         if "systemFlags" not in obj or obj['systemFlags'][0] != '-1946157056':
1704             self.report("ERROR: systemFlags incorrectly set on Deleted Objects container %s" % obj.dn)
1705             faulty = True
1706         return faulty
1707
1708     def err_deleted_deleted_objects(self, obj):
1709         nmsg = ldb.Message()
1710         nmsg.dn = dn = obj.dn
1711
1712         if "description" not in obj:
1713             nmsg["description"] = ldb.MessageElement("Container for deleted objects", ldb.FLAG_MOD_REPLACE, "description")
1714         if "showInAdvancedViewOnly" not in obj:
1715             nmsg["showInAdvancedViewOnly"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "showInAdvancedViewOnly")
1716         if "objectCategory" not in obj:
1717             nmsg["objectCategory"] = ldb.MessageElement("CN=Container,%s" % self.schema_dn, ldb.FLAG_MOD_REPLACE, "objectCategory")
1718         if "isCriticalSystemObject" not in obj:
1719             nmsg["isCriticalSystemObject"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isCriticalSystemObject")
1720         if "isRecycled" in obj:
1721             nmsg["isRecycled"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_DELETE, "isRecycled")
1722
1723         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
1724         nmsg["systemFlags"] = ldb.MessageElement("-1946157056", ldb.FLAG_MOD_REPLACE, "systemFlags")
1725         nmsg["objectClass"] = ldb.MessageElement(["top", "container"], ldb.FLAG_MOD_REPLACE, "objectClass")
1726
1727         if not self.confirm_all('Fix Deleted Objects container %s by restoring default attributes?'
1728                                 % (dn), 'fix_deleted_deleted_objects'):
1729             self.report('Not fixing missing/incorrect attributes on %s\n' % (dn))
1730             return
1731
1732         if self.do_modify(nmsg, ["relax:0"],
1733                           "Failed to fix Deleted Objects container  %s" % dn):
1734             self.report("Fixed Deleted Objects container '%s'\n" % (dn))
1735
1736     def err_replica_locations(self, obj, cross_ref, attr):
1737         nmsg = ldb.Message()
1738         nmsg.dn = cross_ref
1739         target = self.samdb.get_dsServiceName()
1740
1741         if self.samdb.am_rodc():
1742             self.report('Not fixing %s for the RODC' % (attr, obj.dn))
1743             return
1744
1745         if not self.confirm_all('Add yourself to the replica locations for %s?'
1746                                 % (obj.dn), 'fix_replica_locations'):
1747             self.report('Not fixing missing/incorrect attributes on %s\n' % (obj.dn))
1748             return
1749
1750         nmsg[attr] = ldb.MessageElement(target, ldb.FLAG_MOD_ADD, attr)
1751         if self.do_modify(nmsg, [], "Failed to add %s for %s" % (attr, obj.dn)):
1752             self.report("Fixed %s for %s" % (attr, obj.dn))
1753
1754     def is_fsmo_role(self, dn):
1755         if dn == self.samdb.domain_dn:
1756             return True
1757         if dn == self.infrastructure_dn:
1758             return True
1759         if dn == self.naming_dn:
1760             return True
1761         if dn == self.schema_dn:
1762             return True
1763         if dn == self.rid_dn:
1764             return True
1765
1766         return False
1767
1768     def calculate_instancetype(self, dn):
1769         instancetype = 0
1770         nc_root = self.samdb.get_nc_root(dn)
1771         if dn == nc_root:
1772             instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
1773             try:
1774                 self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
1775             except ldb.LdbError, (enum, estr):
1776                 if enum != ldb.ERR_NO_SUCH_OBJECT:
1777                     raise
1778             else:
1779                 instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
1780
1781         if self.write_ncs is not None and str(nc_root) in self.write_ncs:
1782             instancetype |= dsdb.INSTANCE_TYPE_WRITE
1783
1784         return instancetype
1785
1786     def get_wellknown_sd(self, dn):
1787         for [sd_dn, descriptor_fn] in self.wellknown_sds:
1788             if dn == sd_dn:
1789                 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1790                 return ndr_unpack(security.descriptor,
1791                                   descriptor_fn(domain_sid,
1792                                                 name_map=self.name_map))
1793
1794         raise KeyError
1795
1796     def check_object(self, dn, attrs=['*']):
1797         '''check one object'''
1798         if self.verbose:
1799             self.report("Checking object %s" % dn)
1800
1801         # If we modify the pass-by-reference attrs variable, then we get a
1802         # replPropertyMetadata for every object that we check.
1803         attrs = list(attrs)
1804         if "dn" in map(str.lower, attrs):
1805             attrs.append("name")
1806         if "distinguishedname" in map(str.lower, attrs):
1807             attrs.append("name")
1808         if str(dn.get_rdn_name()).lower() in map(str.lower, attrs):
1809             attrs.append("name")
1810         if 'name' in map(str.lower, attrs):
1811             attrs.append(dn.get_rdn_name())
1812             attrs.append("isDeleted")
1813             attrs.append("systemFlags")
1814         if '*' in attrs:
1815             attrs.append("replPropertyMetaData")
1816         attrs.append("objectGUID")
1817
1818         try:
1819             sd_flags = 0
1820             sd_flags |= security.SECINFO_OWNER
1821             sd_flags |= security.SECINFO_GROUP
1822             sd_flags |= security.SECINFO_DACL
1823             sd_flags |= security.SECINFO_SACL
1824
1825             res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
1826                                     controls=[
1827                                         "extended_dn:1:1",
1828                                         "show_recycled:1",
1829                                         "show_deleted:1",
1830                                         "sd_flags:1:%d" % sd_flags,
1831                                         "reveal_internals:0",
1832                                     ],
1833                                     attrs=attrs)
1834         except ldb.LdbError, (enum, estr):
1835             if enum == ldb.ERR_NO_SUCH_OBJECT:
1836                 if self.in_transaction:
1837                     self.report("ERROR: Object %s disappeared during check" % dn)
1838                     return 1
1839                 return 0
1840             raise
1841         if len(res) != 1:
1842             self.report("ERROR: Object %s failed to load during check" % dn)
1843             return 1
1844         obj = res[0]
1845         error_count = 0
1846         set_attrs_from_md = set()
1847         set_attrs_seen = set()
1848         got_repl_property_meta_data = False
1849         got_objectclass = False
1850
1851         nc_dn = self.samdb.get_nc_root(obj.dn)
1852         try:
1853             deleted_objects_dn = self.samdb.get_wellknown_dn(nc_dn,
1854                                                              samba.dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
1855         except KeyError:
1856             # We have no deleted objects DN for schema, and we check for this above for the other
1857             # NCs
1858             deleted_objects_dn = None
1859
1860
1861         object_rdn_attr = None
1862         object_rdn_val = None
1863         name_val = None
1864         isDeleted = False
1865         systemFlags = 0
1866
1867         for attrname in obj:
1868             if attrname == 'dn' or attrname == "distinguishedName":
1869                 continue
1870
1871             if str(attrname).lower() == 'objectclass':
1872                 got_objectclass = True
1873
1874             if str(attrname).lower() == "name":
1875                 if len(obj[attrname]) != 1:
1876                     error_count += 1
1877                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1878                                 (len(obj[attrname]), attrname, str(obj.dn)))
1879                 else:
1880                     name_val = obj[attrname][0]
1881
1882             if str(attrname).lower() == str(obj.dn.get_rdn_name()).lower():
1883                 object_rdn_attr = attrname
1884                 if len(obj[attrname]) != 1:
1885                     error_count += 1
1886                     self.report("ERROR: Not fixing num_values(%d) for '%s' on '%s'" %
1887                                 (len(obj[attrname]), attrname, str(obj.dn)))
1888                 else:
1889                     object_rdn_val = obj[attrname][0]
1890
1891             if str(attrname).lower() == 'isdeleted':
1892                 if obj[attrname][0] != "FALSE":
1893                     isDeleted = True
1894
1895             if str(attrname).lower() == 'systemflags':
1896                 systemFlags = int(obj[attrname][0])
1897
1898             if str(attrname).lower() == 'replpropertymetadata':
1899                 if self.has_replmetadata_zero_invocationid(dn, obj[attrname]):
1900                     error_count += 1
1901                     self.err_replmetadata_zero_invocationid(dn, attrname, obj[attrname])
1902                     # We don't continue, as we may also have other fixes for this attribute
1903                     # based on what other attributes we see.
1904
1905                 try:
1906                     (set_attrs_from_md, list_attid_from_md, wrong_attids) \
1907                         = self.process_metadata(dn, obj[attrname])
1908                 except KeyError:
1909                     error_count += 1
1910                     self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
1911                     continue
1912
1913                 if len(set_attrs_from_md) < len(list_attid_from_md) \
1914                    or len(wrong_attids) > 0 \
1915                    or sorted(list_attid_from_md) != list_attid_from_md:
1916                     error_count +=1
1917                     self.err_replmetadata_incorrect_attid(dn, attrname, obj[attrname], wrong_attids)
1918
1919                 else:
1920                     # Here we check that the first attid is 0
1921                     # (objectClass).
1922                     if list_attid_from_md[0] != 0:
1923                         error_count += 1
1924                         self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" %
1925                                     (attrname, str(dn)))
1926
1927                 got_repl_property_meta_data = True
1928                 continue
1929
1930             if str(attrname).lower() == 'ntsecuritydescriptor':
1931                 (sd, sd_broken) = self.process_sd(dn, obj)
1932                 if sd_broken is not None:
1933                     self.err_wrong_sd(dn, sd, sd_broken)
1934                     error_count += 1
1935                     continue
1936
1937                 if sd.owner_sid is None or sd.group_sid is None:
1938                     self.err_missing_sd_owner(dn, sd)
1939                     error_count += 1
1940                     continue
1941
1942                 if self.reset_well_known_acls:
1943                     try:
1944                         well_known_sd = self.get_wellknown_sd(dn)
1945                     except KeyError:
1946                         continue
1947
1948                     current_sd = ndr_unpack(security.descriptor,
1949                                             str(obj[attrname][0]))
1950
1951                     diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid()))
1952                     if diff != "":
1953                         self.err_wrong_default_sd(dn, well_known_sd, current_sd, diff)
1954                         error_count += 1
1955                         continue
1956                 continue
1957
1958             if str(attrname).lower() == 'objectclass':
1959                 normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, obj[attrname])
1960                 # Do not consider the attribute incorrect if:
1961                 #  - The sorted (alphabetically) list is the same, inclding case
1962                 #  - The first and last elements are the same
1963                 #
1964                 # This avoids triggering an error due to
1965                 # non-determinism in the sort routine in (at least)
1966                 # 4.3 and earlier, and the fact that any AUX classes
1967                 # in these attributes are also not sorted when
1968                 # imported from Windows (they are just in the reverse
1969                 # order of last set)
1970                 if sorted(normalised) != sorted(obj[attrname]) \
1971                    or normalised[0] != obj[attrname][0] \
1972                    or normalised[-1] != obj[attrname][-1]:
1973                     self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
1974                     error_count += 1
1975                 continue
1976
1977             if str(attrname).lower() == 'userparameters':
1978                 if len(obj[attrname][0]) == 1 and obj[attrname][0][0] == '\x20':
1979                     error_count += 1
1980                     self.err_short_userParameters(obj, attrname, obj[attrname])
1981                     continue
1982
1983                 elif obj[attrname][0][:16] == '\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00':
1984                     # This is the correct, normal prefix
1985                     continue
1986
1987                 elif obj[attrname][0][:20] == 'IAAgACAAIAAgACAAIAAg':
1988                     # this is the typical prefix from a windows migration
1989                     error_count += 1
1990                     self.err_base64_userParameters(obj, attrname, obj[attrname])
1991                     continue
1992
1993                 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':
1994                     # This is a prefix that is not in UTF-16 format for the space or munged dialback prefix
1995                     error_count += 1
1996                     self.err_utf8_userParameters(obj, attrname, obj[attrname])
1997                     continue
1998
1999                 elif len(obj[attrname][0]) % 2 != 0:
2000                     # This is a value that isn't even in length
2001                     error_count += 1
2002                     self.err_odd_userParameters(obj, attrname, obj[attrname])
2003                     continue
2004
2005                 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':
2006                     # This is a prefix that would happen if a SAMR-written value was replicated from a Samba 4.1 server to a working server
2007                     error_count += 1
2008                     self.err_doubled_userParameters(obj, attrname, obj[attrname])
2009                     continue
2010
2011             if attrname.lower() == 'attributeid' or attrname.lower() == 'governsid':
2012                 if obj[attrname][0] in self.attribute_or_class_ids:
2013                     error_count += 1
2014                     self.report('Error: %s %s on %s already exists as an attributeId or governsId'
2015                                 % (attrname, obj.dn, obj[attrname][0]))
2016                 else:
2017                     self.attribute_or_class_ids.add(obj[attrname][0])
2018
2019             # check for empty attributes
2020             for val in obj[attrname]:
2021                 if val == '':
2022                     self.err_empty_attribute(dn, attrname)
2023                     error_count += 1
2024                     continue
2025
2026             # get the syntax oid for the attribute, so we can can have
2027             # special handling for some specific attribute types
2028             try:
2029                 syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
2030             except Exception, msg:
2031                 self.err_unknown_attribute(obj, attrname)
2032                 error_count += 1
2033                 continue
2034
2035             linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname)
2036
2037             flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
2038             if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
2039                 and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
2040                 and not linkID):
2041                 set_attrs_seen.add(str(attrname).lower())
2042
2043             if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
2044                                dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
2045                 # it's some form of DN, do specialised checking on those
2046                 error_count += self.check_dn(obj, attrname, syntax_oid)
2047             else:
2048
2049                 values = set()
2050                 # check for incorrectly normalised attributes
2051                 for val in obj[attrname]:
2052                     values.add(str(val))
2053
2054                     normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
2055                     if len(normalised) != 1 or normalised[0] != val:
2056                         self.err_normalise_mismatch(dn, attrname, obj[attrname])
2057                         error_count += 1
2058                         break
2059
2060                 if len(obj[attrname]) != len(values):
2061                     self.err_duplicate_values(dn, attrname, obj[attrname], list(values))
2062                     error_count += 1
2063                     break
2064
2065             if str(attrname).lower() == "instancetype":
2066                 calculated_instancetype = self.calculate_instancetype(dn)
2067                 if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
2068                     error_count += 1
2069                     self.err_wrong_instancetype(obj, calculated_instancetype)
2070
2071         if not got_objectclass and ("*" in attrs or "objectclass" in map(str.lower, attrs)):
2072             error_count += 1
2073             self.err_missing_objectclass(dn)
2074
2075         if ("*" in attrs or "name" in map(str.lower, attrs)):
2076             if name_val is None:
2077                 error_count += 1
2078                 self.report("ERROR: Not fixing missing 'name' on '%s'" % (str(obj.dn)))
2079             if object_rdn_attr is None:
2080                 error_count += 1
2081                 self.report("ERROR: Not fixing missing '%s' on '%s'" % (obj.dn.get_rdn_name(), str(obj.dn)))
2082
2083         if name_val is not None:
2084             parent_dn = None
2085             if isDeleted:
2086                 if not (systemFlags & samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE):
2087                     parent_dn = deleted_objects_dn
2088             if parent_dn is None:
2089                 parent_dn = obj.dn.parent()
2090             expected_dn = ldb.Dn(self.samdb, "RDN=RDN,%s" % (parent_dn))
2091             expected_dn.set_component(0, obj.dn.get_rdn_name(), name_val)
2092
2093             if obj.dn == deleted_objects_dn:
2094                 expected_dn = obj.dn
2095
2096             if expected_dn != obj.dn:
2097                 error_count += 1
2098                 self.err_wrong_dn(obj, expected_dn, object_rdn_attr, object_rdn_val, name_val)
2099             elif obj.dn.get_rdn_value() != object_rdn_val:
2100                 error_count += 1
2101                 self.report("ERROR: Not fixing %s=%r on '%s'" % (object_rdn_attr, object_rdn_val, str(obj.dn)))
2102
2103         show_dn = True
2104         if got_repl_property_meta_data:
2105             if obj.dn == deleted_objects_dn:
2106                 isDeletedAttId = 131120
2107                 # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
2108
2109                 expectedTimeDo = 2650466015990000000
2110                 originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
2111                 if originating != expectedTimeDo:
2112                     if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
2113                         nmsg = ldb.Message()
2114                         nmsg.dn = dn
2115                         nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
2116                         error_count += 1
2117                         self.samdb.modify(nmsg, controls=["provision:0"])
2118
2119                     else:
2120                         self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
2121
2122             for att in set_attrs_seen.difference(set_attrs_from_md):
2123                 if show_dn:
2124                     self.report("On object %s" % dn)
2125                     show_dn = False
2126                 error_count += 1
2127                 self.report("ERROR: Attribute %s not present in replication metadata" % att)
2128                 if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
2129                     self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
2130                     continue
2131                 self.fix_metadata(obj, att)
2132
2133         if self.is_fsmo_role(dn):
2134             if "fSMORoleOwner" not in obj and ("*" in attrs or "fsmoroleowner" in map(str.lower, attrs)):
2135                 self.err_no_fsmoRoleOwner(obj)
2136                 error_count += 1
2137
2138         try:
2139             if dn != self.samdb.get_root_basedn() and str(dn.parent()) not in self.dn_set:
2140                 res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
2141                                         controls=["show_recycled:1", "show_deleted:1"])
2142         except ldb.LdbError, (enum, estr):
2143             if enum == ldb.ERR_NO_SUCH_OBJECT:
2144                 self.err_missing_parent(obj)
2145                 error_count += 1
2146             else:
2147                 raise
2148
2149         if dn in self.deleted_objects_containers and '*' in attrs:
2150             if self.is_deleted_deleted_objects(obj):
2151                 self.err_deleted_deleted_objects(obj)
2152                 error_count += 1
2153
2154         for (dns_part, msg) in self.dns_partitions:
2155             if dn == dns_part and 'repsFrom' in obj:
2156                 location = "msDS-NC-Replica-Locations"
2157                 if self.samdb.am_rodc():
2158                     location = "msDS-NC-RO-Replica-Locations"
2159
2160                 if location not in msg:
2161                     # There are no replica locations!
2162                     self.err_replica_locations(obj, msg.dn, location)
2163                     error_count += 1
2164                     continue
2165
2166                 found = False
2167                 for loc in msg[location]:
2168                     if loc == self.samdb.get_dsServiceName():
2169                         found = True
2170                 if not found:
2171                     # This DC is not in the replica locations
2172                     self.err_replica_locations(obj, msg.dn, location)
2173                     error_count += 1
2174
2175         if dn == self.server_ref_dn:
2176             # Check we have a valid RID Set
2177             if "*" in attrs or "rIDSetReferences" in attrs:
2178                 if "rIDSetReferences" not in obj:
2179                     # NO RID SET reference
2180                     # We are RID master, allocate it.
2181                     error_count += 1
2182
2183                     if self.is_rid_master:
2184                         # Allocate a RID Set
2185                         if self.confirm_all('Allocate the missing RID set for RID master?',
2186                                             'fix_missing_rid_set_master'):
2187
2188                             # We don't have auto-transaction logic on
2189                             # extended operations, so we have to do it
2190                             # here.
2191
2192                             self.samdb.transaction_start()
2193
2194                             try:
2195                                 self.samdb.create_own_rid_set()
2196
2197                             except:
2198                                 self.samdb.transaction_cancel()
2199                                 raise
2200
2201                             self.samdb.transaction_commit()
2202
2203
2204                     elif not self.samdb.am_rodc():
2205                         self.report("No RID Set found for this server: %s, and we are not the RID Master (so can not self-allocate)" % dn)
2206
2207
2208         # Check some details of our own RID Set
2209         if dn == self.rid_set_dn:
2210             res = self.samdb.search(base=self.rid_set_dn, scope=ldb.SCOPE_BASE,
2211                                     attrs=["rIDAllocationPool",
2212                                            "rIDPreviousAllocationPool",
2213                                            "rIDUsedPool",
2214                                            "rIDNextRID"])
2215             if "rIDAllocationPool" not in res[0]:
2216                 self.report("No rIDAllocationPool found in %s" % dn)
2217                 error_count += 1
2218             else:
2219                 next_pool = int(res[0]["rIDAllocationPool"][0])
2220
2221                 high = (0xFFFFFFFF00000000 & next_pool) >> 32
2222                 low = 0x00000000FFFFFFFF & next_pool
2223
2224                 if high <= low:
2225                     self.report("Invalid RID set %d-%s, %d > %d!" % (low, high, low, high))
2226                     error_count += 1
2227
2228                 if "rIDNextRID" in res[0]:
2229                     next_free_rid = int(res[0]["rIDNextRID"][0])
2230                 else:
2231                     next_free_rid = 0
2232
2233                 if next_free_rid == 0:
2234                     next_free_rid = low
2235                 else:
2236                     next_free_rid += 1
2237
2238                 # Check the remainder of this pool for conflicts.  If
2239                 # ridalloc_allocate_rid() moves to a new pool, this
2240                 # will be above high, so we will stop.
2241                 while next_free_rid <= high:
2242                     sid = "%s-%d" % (self.samdb.get_domain_sid(), next_free_rid)
2243                     try:
2244                         res = self.samdb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
2245                                                 attrs=[])
2246                     except ldb.LdbError, (enum, estr):
2247                         if enum != ldb.ERR_NO_SUCH_OBJECT:
2248                             raise
2249                         res = None
2250                     if res is not None:
2251                         self.report("SID %s for %s conflicts with our current RID set in %s" % (sid, res[0].dn, dn))
2252                         error_count += 1
2253
2254                         if self.confirm_all('Fix conflict between SID %s and RID pool in %s by allocating a new RID?'
2255                                             % (sid, dn),
2256                                             'fix_sid_rid_set_conflict'):
2257                             self.samdb.transaction_start()
2258
2259                             # This will burn RIDs, which will move
2260                             # past the conflict.  We then check again
2261                             # to see if the new RID conflicts, until
2262                             # the end of the current pool.  We don't
2263                             # look at the next pool to avoid burning
2264                             # all RIDs in one go in some strange
2265                             # failure case.
2266                             try:
2267                                 while True:
2268                                     allocated_rid = self.samdb.allocate_rid()
2269                                     if allocated_rid >= next_free_rid:
2270                                         next_free_rid = allocated_rid + 1
2271                                         break
2272                             except:
2273                                 self.samdb.transaction_cancel()
2274                                 raise
2275
2276                             self.samdb.transaction_commit()
2277                         else:
2278                             break
2279                     else:
2280                         next_free_rid += 1
2281
2282
2283         return error_count
2284
2285     ################################################################
2286     # check special @ROOTDSE attributes
2287     def check_rootdse(self):
2288         '''check the @ROOTDSE special object'''
2289         dn = ldb.Dn(self.samdb, '@ROOTDSE')
2290         if self.verbose:
2291             self.report("Checking object %s" % dn)
2292         res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
2293         if len(res) != 1:
2294             self.report("Object %s disappeared during check" % dn)
2295             return 1
2296         obj = res[0]
2297         error_count = 0
2298
2299         # check that the dsServiceName is in GUID form
2300         if not 'dsServiceName' in obj:
2301             self.report('ERROR: dsServiceName missing in @ROOTDSE')
2302             return error_count+1
2303
2304         if not obj['dsServiceName'][0].startswith('<GUID='):
2305             self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
2306             error_count += 1
2307             if not self.confirm('Change dsServiceName to GUID form?'):
2308                 return error_count
2309             res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
2310                                     scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
2311             guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
2312             m = ldb.Message()
2313             m.dn = dn
2314             m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
2315                                                     ldb.FLAG_MOD_REPLACE, 'dsServiceName')
2316             if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
2317                 self.report("Changed dsServiceName to GUID form")
2318         return error_count
2319
2320
2321     ###############################################
2322     # re-index the database
2323     def reindex_database(self):
2324         '''re-index the whole database'''
2325         m = ldb.Message()
2326         m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
2327         m['add']    = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
2328         m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
2329         return self.do_modify(m, [], 're-indexed database', validate=False)
2330
2331     ###############################################
2332     # reset @MODULES
2333     def reset_modules(self):
2334         '''reset @MODULES to that needed for current sam.ldb (to read a very old database)'''
2335         m = ldb.Message()
2336         m.dn = ldb.Dn(self.samdb, "@MODULES")
2337         m['@LIST'] = ldb.MessageElement('samba_dsdb', ldb.FLAG_MOD_REPLACE, '@LIST')
2338         return self.do_modify(m, [], 'reset @MODULES on database', validate=False)