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