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