upgradeprovision: treat provision without oem attribute as quite recent, it's provisi...
[ddiss/samba.git] / source4 / scripting / bin / upgradeprovision
index 7f11e404ab0fc0573adc6e311bbf82463c890c1e..35e469693c4631ee2914d7740e6f50774e6a1a12 100755 (executable)
@@ -42,8 +42,9 @@ from samba.credentials import DONT_USE_KERBEROS
 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,
@@ -81,20 +82,24 @@ __docformat__ = "restructuredText"
 # 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.
@@ -108,12 +113,15 @@ hashOverwrittenAtt = {  "prefixMap": replace, "systemMayContain": replace,
                         "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 = []
@@ -257,7 +265,8 @@ def populateNotReplicated(samdb, schemadn):
                         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
@@ -412,14 +421,14 @@ def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
     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)
@@ -606,7 +615,7 @@ def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
             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)
@@ -673,7 +682,7 @@ def add_deletedobj_containers(ref_samdb, samdb, names):
             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"]
@@ -817,10 +826,10 @@ def checkKeepAttributeOldMtd(delta, att, reference, current,
     dn = current[0].dn
 
     for att in list(delta):
-        defSDmodified = True
         msgElt = delta.get(att)
 
         if att == "nTSecurityDescriptor":
+            defSDmodified = True
             delta.remove(att)
             continue
 
@@ -872,11 +881,15 @@ def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
         :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:
@@ -887,14 +900,9 @@ def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
             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
@@ -913,14 +921,10 @@ def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
             # 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"]))
@@ -929,10 +933,26 @@ def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
                 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
@@ -996,7 +1016,7 @@ def update_present(ref_samdb, samdb, basedn, listPresent, usns):
         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:
@@ -1023,8 +1043,14 @@ def update_present(ref_samdb, samdb, basedn, listPresent, usns):
         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,
@@ -1048,17 +1074,15 @@ def update_present(ref_samdb, samdb, basedn, listPresent, usns):
                                                     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"]
@@ -1077,7 +1101,8 @@ def reload_full_schema(samdb, names):
     :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 = ""
@@ -1089,9 +1114,9 @@ def reload_full_schema(samdb, names):
     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):
@@ -1109,8 +1134,8 @@ def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, pre
     :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
     """
@@ -1214,7 +1239,7 @@ def check_updated_sd(ref_sam, cur_sam, names):
                         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))
@@ -1228,37 +1253,38 @@ def fix_partition_sd(samdb, names):
     :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
@@ -1271,30 +1297,46 @@ def rebuild_sd(samdb, names):
 
     :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,
@@ -1302,14 +1344,23 @@ def rebuild_sd(samdb, names):
                                 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
@@ -1379,7 +1430,7 @@ def update_privilege(ref_private_path, cur_private_path):
                 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
@@ -1387,8 +1438,8 @@ def update_samdb(ref_samdb, samdb, names, highestUSN, schema, prereloadfunc):
     :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
@@ -1396,7 +1447,7 @@ def update_samdb(ref_samdb, samdb, names, highestUSN, schema, prereloadfunc):
 
     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
@@ -1613,7 +1664,7 @@ def sync_calculated_attributes(samdb, names):
 # 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
@@ -1690,6 +1741,19 @@ if __name__ == '__main__':
                 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))
@@ -1725,6 +1789,7 @@ if __name__ == '__main__':
             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)
@@ -1739,7 +1804,8 @@ if __name__ == '__main__':
             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
@@ -1770,14 +1836,14 @@ if __name__ == '__main__':
                     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")
@@ -1834,12 +1900,12 @@ if __name__ == '__main__':
             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
@@ -1850,23 +1916,22 @@ if __name__ == '__main__':
             # 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)