from samba.auth import system_session, admin_session
from ldb import (SCOPE_SUBTREE, SCOPE_BASE,
FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,
- MessageElement, Message, Dn)
+ MessageElement, Message, Dn, LdbError)
from samba import param, dsdb, Ldb
+from samba.common import confirm
from samba.provision import (get_domain_descriptor, find_provision_key_parameters,
get_config_descriptor,
ProvisioningError, get_last_provision_usn,
# This is most probably because they are populated automatcally when object is
# created
# This also apply to imported object from reference provision
-hashAttrNotCopied = { "dn": 1, "whenCreated": 1, "whenChanged": 1,
- "objectGUID": 1, "uSNCreated": 1,
- "replPropertyMetaData": 1, "uSNChanged": 1,
- "parentGUID": 1, "objectCategory": 1,
- "distinguishedName": 1, "nTMixedDomain": 1,
- "showInAdvancedViewOnly": 1, "instanceType": 1,
- "msDS-Behavior-Version":1, "nextRid":1, "cn": 1,
- "lmPwdHistory":1, "pwdLastSet": 1,
- "ntPwdHistory":1, "unicodePwd":1,"dBCSPwd":1,
- "supplementalCredentials":1, "gPCUserExtensionNames":1,
- "gPCMachineExtensionNames":1,"maxPwdAge":1, "secret":1,
- "possibleInferiors":1, "privilege":1,
- "sAMAccountType":1 }
+replAttrNotCopied = [ "dn", "whenCreated", "whenChanged", "objectGUID",
+ "parentGUID", "objectCategory", "distinguishedName",
+ "instanceType", "cn",
+ "lmPwdHistory", "pwdLastSet", "ntPwdHistory",
+ "unicodePwd", "dBCSPwd", "supplementalCredentials",
+ "gPCUserExtensionNames", "gPCMachineExtensionNames",
+ "maxPwdAge", "secret", "possibleInferiors", "privilege",
+ "sAMAccountType", "oEMInformation", "creationTime" ]
+nonreplAttrNotCopied = ["uSNCreated", "replPropertyMetaData", "uSNChanged",
+ "nextRid" ,"rIDNextRID", "rIDPreviousAllocationPool"]
+
+nonDSDBAttrNotCopied = ["msDS-KeyVersionNumber", "priorSecret", "priorWhenChanged"]
+
+
+attrNotCopied = replAttrNotCopied
+attrNotCopied.extend(nonreplAttrNotCopied)
+attrNotCopied.extend(nonDSDBAttrNotCopied)
# Usually for an object that already exists we do not overwrite attributes as
# they might have been changed for good reasons. Anyway for a few of them it's
# mandatory to replace them otherwise the provision will be broken somehow.
"wellKnownObjects":replace, "privilege":never,
"defaultSecurityDescriptor": replace,
"rIDAvailablePool": never,
+ "versionNumber" : add,
"rIDNextRID": add, "rIDUsedPool": never,
"defaultSecurityDescriptor": replace + add,
"isMemberOfPartialAttributeSet": delete,
"attributeDisplayNames": replace + add,
"versionNumber": add}
+dnNotToRecalculate = []
+dnToRecalculate = []
backlinked = []
forwardlinked = set()
dn_syntax_att = []
str(schemadn)), scope=SCOPE_SUBTREE,
attrs=["lDAPDisplayName"])
for elem in res:
- not_replicated.append(elem["lDAPDisplayName"])
+ not_replicated.append(str(elem["lDAPDisplayName"]))
+
def populate_dnsyntax(samdb, schemadn):
"""Populate an array with all the attributes that have DN synthax
if (att == "servicePrincipalName" and flag == FLAG_MOD_REPLACE):
hash = {}
newval = []
- changeDelta=0
+ changeDelta = 0
for elem in old[0][att]:
hash[str(elem)]=1
newval.append(str(elem))
for elem in new[0][att]:
if not hash.has_key(str(elem)):
- changeDelta=1
+ changeDelta = 1
newval.append(str(elem))
if changeDelta == 1:
delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
m = re.match(r".*-(\d+)$", sid)
if m and int(m.group(1))>999:
delta.remove("objectSid")
- for att in hashAttrNotCopied.keys():
+ for att in attrNotCopied:
delta.remove(att)
for att in backlinked:
delta.remove(att)
delta = samdb.msg_diff(empty, reference[0])
delta.dn = Dn(samdb, str(reference[0]["dn"]))
- for att in hashAttrNotCopied.keys():
+ for att in attrNotCopied:
delta.remove(att)
modcontrols = ["relax:0", "provision:0"]
dn = current[0].dn
for att in list(delta):
- defSDmodified = True
msgElt = delta.get(att)
if att == "nTSecurityDescriptor":
+ defSDmodified = True
delta.remove(att)
continue
:return: The modified message diff.
"""
global defSDmodified
- isFirst = False
+ isFirst = True
txt = ""
dn = current[0].dn
for att in list(delta):
+ if att in ["dn", "objectSid"]:
+ delta.remove(att)
+ continue
+
# We have updated by provision usn information so let's exploit
# replMetadataProperties
if att in forwardlinked:
continue
if isFirst and len(delta.items())>1:
- isFirst = True
+ isFirst = False
txt = "%s\n" % (str(dn))
- keptAttr = ["dn", "rIDAvailablePool", "objectSid", "creationTime", "oEMInformation", "msDs-KeyVersionNumber"]
- if att in keptAttr:
- delta.remove(att)
- continue
-
if handle_special_case(att, delta, reference, current, True, None, None):
# This attribute is "complicated" to handle and handling
# was done in handle_special_case
# attrUSN will be -1
if isReplicated(att):
continue
- elif att in hashAttrNotCopied.keys():
- delta.remove(att)
else:
+ message(CHANGE, "Non replicated attribute %s changed" % att)
continue
- if attrUSN is None:
- delta.remove(att)
- continue
if att == "nTSecurityDescriptor":
cursd = ndr_unpack(security.descriptor,
str(current[0]["nTSecurityDescriptor"]))
str(reference[0]["nTSecurityDescriptor"]))
refsddl = refsd.as_sddl(names.domainsid)
- if get_diff_sddls(refsddl, cursddl) == "":
- message(CHANGE, "sd are identical")
+ diff = get_diff_sddls(refsddl, cursddl)
+ if diff == "":
+ # FIXME find a way to have it only with huge huge verbose mode
+ # message(CHANGE, "%ssd are identical" % txt)
+ # txt = ""
+ delta.remove(att)
+ continue
else:
- message(CHANGE, "sd are not identical")
+ delta.remove(att)
+ message(CHANGESD, "%ssd are not identical:\n%s" % (txt, diff))
+ txt = ""
+ if attrUSN == -1:
+ message(CHANGESD, "But the SD has been changed by someonelse "\
+ "so it's impossible to know if the difference"\
+ " cames from the modification or from a previous bug")
+ dnNotToRecalculate.append(str(dn))
+ else:
+ dnToRecalculate.append(str(dn))
+ continue
+
if attrUSN == -1:
# This attribute was last modified by another DC forget
# about it
raise ProvisioningError(msg)
changed = 0
- controls = ["search_options:1:2", "sd_flags:1:2"]
+ controls = ["search_options:1:2", "sd_flags:1:0"]
if usns is not None:
message(CHANGE, "Using replPropertyMetadata for change selection")
for dn in listPresent:
for att in backlinked:
delta.remove(att)
+ for att in attrNotCopied:
+ delta.remove(att)
+
delta.remove("name")
+ if len(delta.items()) == 1:
+ continue
+
if len(delta.items()) > 1 and usns is not None:
# Fetch the replPropertyMetaData
res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
current, hash_attr_usn,
basedn, usns, samdb)
else:
- for att in hashAttrNotCopied.keys():
- delta.remove(att)
delta = checkKeepAttributeOldMtd(delta, att, reference, current, basedn, samdb)
delta.dn = dn
if len(delta.items()) >1:
- attributes=", ".join(delta.keys())
+ # Skip dn as the value is not really changed ...
+ attributes=", ".join(delta.keys()[1:])
modcontrols = []
relaxedatt = ['iscriticalsystemobject', 'grouptype']
# Let's try to reduce as much as possible the use of relax control
- #for checkedatt in relaxedatt:
for attr in delta.keys():
if attr.lower() in relaxedatt:
modcontrols = ["relax:0", "provision:0"]
:param names: List of key provision parameters
"""
- current = samdb.search(expression="objectClass=*", base=str(names.schemadn),
+ schemadn = str(names.schemadn)
+ current = samdb.search(expression="objectClass=*", base=schemadn,
scope=SCOPE_SUBTREE)
schema_ldif = ""
prefixmap_data = ""
prefixmap_data = b64encode(prefixmap_data)
# We don't actually add this ldif, just parse it
- prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap_data
+ prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
- dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif)
+ dsdb._dsdb_set_schema_from_ldif(samdb, prefixmap_ldif, schema_ldif, schemadn)
def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, prereloadfunc):
:param basedn: String value of the DN of the partition
:param names: List of key provision parameters
:param schema: A Schema object
- :param provisionUSNs: The USNs modified by provision/upgradeprovision
- last time
+ :param provisionUSNs: A dictionnary with range of USN modified during provision
+ or upgradeprovision. Ranges are grouped by invocationID.
:param prereloadfunc: A function that must be executed just before the reload
of the schema
"""
str(current[i]["nTSecurityDescriptor"]))
sddl = cursd.as_sddl(names.domainsid)
if sddl != hash[key]:
- txt = get_diff_sddls(hash[key], sddl)
+ txt = get_diff_sddls(hash[key], sddl, False)
if txt != "":
message(CHANGESD, "On object %s ACL is different"
" \n%s" % (current[i]["dn"], txt))
:param samdb: An LDB object pointing to the sam of the current provision
:param names: A list of key provision parameters
"""
+ alwaysRecalculate = False
+ if len(dnToRecalculate) == 0 and len(dnNotToRecalculate) == 0:
+ alwaysRecalculate = True
+
+
+ # NC's DN can't be both in dnToRecalculate and dnNotToRecalculate
# First update the SD for the rootdn
- res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
- scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
- controls=["search_options:1:2"])
- delta = Message()
- delta.dn = Dn(samdb, str(res[0]["dn"]))
- descr = get_domain_descriptor(names.domainsid)
- delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
- "nTSecurityDescriptor")
- samdb.modify(delta)
+ if alwaysRecalculate or str(names.rootdn) in dnToRecalculate:
+ delta = Message()
+ delta.dn = Dn(samdb, str(names.rootdn))
+ descr = get_domain_descriptor(names.domainsid)
+ delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
+ "nTSecurityDescriptor")
+ samdb.modify(delta)
+
# Then the config dn
- res = samdb.search(expression="objectClass=*", base=str(names.configdn),
- scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
- controls=["search_options:1:2"])
- delta = Message()
- delta.dn = Dn(samdb, str(res[0]["dn"]))
- descr = get_config_descriptor(names.domainsid)
- delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
- "nTSecurityDescriptor" )
- samdb.modify(delta)
- # Then the schema dn
- res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
- scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
- controls=["search_options:1:2"])
+ if alwaysRecalculate or str(names.configdn) in dnToRecalculate:
+ delta = Message()
+ delta.dn = Dn(samdb, str(names.configdn))
+ descr = get_config_descriptor(names.domainsid)
+ delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
+ "nTSecurityDescriptor" )
+ samdb.modify(delta)
- delta = Message()
- delta.dn = Dn(samdb, str(res[0]["dn"]))
- descr = get_schema_descriptor(names.domainsid)
- delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
- "nTSecurityDescriptor" )
- samdb.modify(delta)
+ # Then the schema dn
+ if alwaysRecalculate or str(names.schemadn) in dnToRecalculate:
+ delta = Message()
+ delta.dn = Dn(samdb, str(names.schemadn))
+ descr = get_schema_descriptor(names.domainsid)
+ delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
+ "nTSecurityDescriptor" )
+ samdb.modify(delta)
def rebuild_sd(samdb, names):
"""Rebuild security descriptor of the current provision from scratch
:param names: List of key provision parameters"""
+ fix_partition_sd(samdb, names)
+ # List of namming contexts
+ listNC = [str(names.rootdn), str(names.configdn), str(names.schemadn)]
hash = {}
- res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
+ if len(dnToRecalculate) == 0:
+ res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
controls=["search_options:1:2"])
- for obj in res:
- if not (str(obj["dn"]) == str(names.rootdn) or
- str(obj["dn"]) == str(names.configdn) or
- str(obj["dn"]) == str(names.schemadn)):
- hash[str(obj["dn"])] = obj["whenCreated"]
-
- listkeys = hash.keys()
- listkeys.sort(dn_sort)
-
- for key in listkeys:
+ for obj in res:
+ hash[str(obj["dn"])] = obj["whenCreated"]
+ else:
+ for dn in dnToRecalculate:
+ if hash.has_key(dn):
+ continue
+ # fetch each dn to recalculate and their child within the same partition
+ res = samdb.search(expression="objectClass=*", base=dn,
+ scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"])
+ for obj in res:
+ hash[str(obj["dn"])] = obj["whenCreated"]
+
+ listKeys = list(set(hash.keys()))
+ listKeys.sort(dn_sort)
+
+ if len(dnToRecalculate) != 0:
+ message(CHANGESD, "%d DNs have been marked as needed to be recalculated"\
+ ", recalculating %d due to inheritance"
+ % (len(dnToRecalculate), len(listKeys)))
+
+ for key in listKeys:
+ if (key in listNC or
+ key in dnNotToRecalculate):
+ continue
+ delta = Message()
+ delta.dn = Dn(samdb, key)
try:
- delta = Message()
- delta.dn = Dn(samdb, key)
delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
"whenCreated" )
- samdb.modify(delta, ["recalculate_sd:0"])
- except:
- # XXX: We should always catch an explicit exception.
- # What could go wrong here?
+ samdb.modify(delta, ["recalculate_sd:0","relax:0"])
+ except LdbError, e:
samdb.transaction_cancel()
res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
scope=SCOPE_SUBTREE,
controls=["search_options:1:2"])
badsd = ndr_unpack(security.descriptor,
str(res[0]["nTSecurityDescriptor"]))
- print "bad stuff %s" % badsd.as_sddl(names.domainsid)
+ message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
return
+def hasATProvision(samdb):
+ entry = samdb.search(expression="dn=@PROVISION", base = "",
+ scope=SCOPE_BASE,
+ attrs=["dn"])
+
+ if entry != None and len(entry) == 1:
+ return True
+ else:
+ return False
+
def removeProvisionUSN(samdb):
attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
entry = samdb.search(expression="dn=@PROVISION", base = "",
- scope=SCOPE_SUBTREE,
- controls=["search_options:1:2"],
+ scope=SCOPE_BASE,
attrs=attrs)
empty = Message()
empty.dn = entry[0].dn
os.path.join(cur_private_path, "privilege.ldb"))
-def update_samdb(ref_samdb, samdb, names, highestUSN, schema, prereloadfunc):
+def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
"""Upgrade the SAM DB contents for all the provision partitions
:param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
:param samdb: An LDB object connected to the sam.ldb of the update
provision
:param names: List of key provision parameters
- :param highestUSN: The highest USN modified by provision/upgradeprovision
- last time
+ :param provisionUSNs: A dictionnary with range of USN modified during provision
+ or upgradeprovision. Ranges are grouped by invocationID.
:param schema: A Schema object that represent the schema of the provision
:param prereloadfunc: A function that must be executed just before the reload
of the schema
message(SIMPLE, "Starting update of samdb")
ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
- schema, highestUSN, prereloadfunc)
+ schema, provisionUSNs, prereloadfunc)
if ret:
message(SIMPLE, "Update of samdb finished")
return 1
# This resulting object is filtered to remove all the back link attribute
# (ie. memberOf) as they will be created by the other linked object (ie.
# the one with the member attribute)
-# All attributes specified in the hashAttrNotCopied associative array are
+# All attributes specified in the attrNotCopied array are
# also removed it's most of the time generated attributes
# After missing entries have been added the update_partition function will
message(CHANGE, "Old style for usn ranges used")
lastProvisionUSNs[str(names.invocation)] = lastProvisionUSNs["default"]
del lastProvisionUSNs["default"]
+ else:
+ message(SIMPLE, "Your provision lacks provision range information")
+ if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
+ ldbs.groupedRollback()
+ os.system("%s %s %s %s %s" % (os.path.join(os.path.dirname(sys.argv[0]),
+ "findprovisionusnranges"),
+ "--storedir",
+ paths.private_dir,
+ "-s",
+ smbconf))
+ message(SIMPLE, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
+ sys.exit(0)
+
# Objects will be created with the admin session
# (not anymore system session)
adm_session = admin_session(lp, str(names.domainsid))
new_ldbs = get_ldbs(newpaths, creds, session, lp)
new_ldbs.startTransactions()
+ populateNotReplicated(new_ldbs.sam, names.schemadn)
# 8) Populate some associative array to ease the update process
# List of attribute which are link and backlink
populate_links(new_ldbs.sam, names.schemadn)
new_ldbs.groupedCommit()
deltaattr = None
# 11)
- if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
+ message(GUESS, oem)
+ if oem is None or hasATProvision(ldbs.sam) or re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
# 11) A
# Starting from alpha9 we can consider that the structure is quite ok
# and that we should do only dela
doit = True
if doit:
deltaattr.remove("dn")
- for att in deltaattr:
- if att.lower() == "dn":
- continue
- if (deltaattr.get(att) is not None
- and deltaattr.get(att).flags() != FLAG_MOD_ADD):
- doit = False
- elif deltaattr.get(att) is None:
- doit = False
+ for att in deltaattr:
+ if att.lower() == "dn":
+ continue
+ if (deltaattr.get(att) is not None
+ and deltaattr.get(att).flags() != FLAG_MOD_ADD):
+ doit = False
+ elif deltaattr.get(att) is None:
+ doit = False
if doit:
message(CHANGE, "Applying delta to @ATTRIBUTES")
deltaattr.dn = ldb.Dn(basesam, "@ATTRIBUTES")
message(SIMPLE, "Update machine account")
update_machine_account_password(ldbs.sam, ldbs.secrets, names)
+ dnToRecalculate.sort(dn_sort)
# 16) SD should be created with admin but as some previous acl were so wrong
# that admin can't modify them we have first to recreate them with the good
# form but with system account and then give the ownership to admin ...
- if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
- message(SIMPLE, "Fixing old povision SD")
- fix_partition_sd(ldbs.sam, names)
+ if str(oem) != "" and not re.match(r'.*alpha(9|\d\d+)', str(oem)):
+ message(SIMPLE, "Fixing very old provision SD")
rebuild_sd(ldbs.sam, names)
# We calculate the max USN before recalculating the SD because we might
# 17)
maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
- # 18) We rebuild SD only if defaultSecurityDescriptor is modified
- # But in fact we should do it also if one object has its SD modified as
- # child might need rebuild
- if defSDmodified:
- message(SIMPLE, "Updating SD")
+ # 18) We rebuild SD if a we have a list of DN to recalculate or if the
+ # defSDmodified is set.
+ if defSDmodified or len(dnToRecalculate) >0:
+ message(SIMPLE, "Some defaultSecurityDescriptors and/or"
+ "securityDescriptor have changed, recalculating SD ")
ldbs.sam.set_session_info(adm_session)
- # Alpha10 was a bit broken still
- if re.match(r'.*alpha(\d|10)', str(oem)):
- fix_partition_sd(ldbs.sam, names)
- rebuild_sd(ldbs.sam, names)
+ rebuild_sd(ldbs.sam, names)
# 19)
# Now we are quite confident in the recalculate process of the SD, we make
- # it optional.
+ # it optional. And we don't do it if there is DN that we must touch
+ # as we are assured that on this DNs we will have differences !
# Also the check must be done in a clever way as for the moment we just
# compare SDDL
- if opts.debugchangesd:
+ if len(dnNotToRecalculate) == 0 and (opts.debugchangesd or opts.debugall):
+ message(CHANGESD, "Checking recalculated SDs")
check_updated_sd(new_ldbs.sam, ldbs.sam, names)
# 20)