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