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