samba_dnsupdate: make rodc_dns_update() more robust against timing problems
[samba.git] / source4 / scripting / bin / samba_upgradeprovision
index b249b4e32dfcbbd44d68eff3122b53c513874828..8433b4ddd9c6edf208b34a6c6918e307201046cf 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # vim: expandtab
 #
 # Copyright (C) Matthieu Patou <mat@matws.net> 2009 - 2010
@@ -36,36 +36,20 @@ sys.path.insert(0, "bin/python")
 import ldb
 import samba
 import samba.getopt as options
+from samba.samdb import get_default_backend_store
 
 from base64 import b64encode
 from samba.credentials import DONT_USE_KERBEROS
 from samba.auth import system_session, admin_session
 from samba import tdb_util
+from samba import mdb_util
 from ldb import (SCOPE_SUBTREE, SCOPE_BASE,
                 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,
                 MessageElement, Message, Dn, LdbError)
 from samba import param, dsdb, Ldb
 from samba.common import confirm
+from samba.descriptor import get_wellknown_sds, get_empty_descriptor, get_diff_sds
 from samba.provision import (find_provision_key_parameters,
-                            get_empty_descriptor,
-                            get_config_descriptor,
-                            get_config_partitions_descriptor,
-                            get_config_sites_descriptor,
-                            get_config_ntds_quotas_descriptor,
-                            get_config_delete_protected1_descriptor,
-                            get_config_delete_protected1wd_descriptor,
-                            get_config_delete_protected2_descriptor,
-                            get_domain_descriptor,
-                            get_domain_infrastructure_descriptor,
-                            get_domain_builtin_descriptor,
-                            get_domain_computers_descriptor,
-                            get_domain_users_descriptor,
-                            get_domain_controllers_descriptor,
-                            get_domain_delete_protected1_descriptor,
-                            get_domain_delete_protected2_descriptor,
-                            get_dns_partition_descriptor,
-                            get_dns_forest_microsoft_dns_descriptor,
-                            get_dns_domain_microsoft_dns_descriptor,
                             ProvisioningError, get_last_provision_usn,
                             get_max_usn, update_provision_usn, setup_path)
 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
@@ -75,7 +59,7 @@ from samba.dcerpc.security import (
 from samba.ndr import ndr_unpack
 from samba.upgradehelpers import (dn_sort, get_paths, newprovision,
                                  get_ldbs, findprovisionrange,
-                                 usn_in_range, identic_rename, get_diff_sddls,
+                                 usn_in_range, identic_rename,
                                  update_secrets, CHANGE, ERROR, SIMPLE,
                                  CHANGEALL, GUESS, CHANGESD, PROVISION,
                                  updateOEMInfo, getOEMInfo, update_gpo,
@@ -86,6 +70,7 @@ from samba.upgradehelpers import (dn_sort, get_paths, newprovision,
                                  increment_calculated_keyversion_number,
                                  print_provision_ranges)
 from samba.xattr import copytree_with_xattrs
+from samba.compat import cmp_to_key_fn
 
 # make sure the script dies immediately when hitting control-C,
 # rather than raising KeyboardInterrupt. As we do all database
@@ -112,7 +97,7 @@ __docformat__ = "restructuredText"
 # created
 # This also apply to imported object from reference provision
 replAttrNotCopied = [   "dn", "whenCreated", "whenChanged", "objectGUID",
-                        "parentGUID", "objectCategory", "distinguishedName",
+                        "parentGUID", "distinguishedName",
                         "instanceType", "cn",
                         "lmPwdHistory", "pwdLastSet", "ntPwdHistory",
                         "unicodePwd", "dBCSPwd", "supplementalCredentials",
@@ -140,7 +125,6 @@ hashOverwrittenAtt = {  "prefixMap": replace, "systemMayContain": replace,
                         "description":replace, "operatingSystemVersion":replace,
                         "adminPropertyPages":replace, "groupType":replace,
                         "wellKnownObjects":replace, "privilege":never,
-                        "defaultSecurityDescriptor": replace,
                         "rIDAvailablePool": never,
                         "versionNumber" : add,
                         "rIDNextRID": add, "rIDUsedPool": never,
@@ -149,7 +133,7 @@ hashOverwrittenAtt = {  "prefixMap": replace, "systemMayContain": replace,
                         "attributeDisplayNames": replace + add,
                         "versionNumber": add}
 
-dnNotToRecalculate = []
+dnNotToRecalculateFound = False
 dnToRecalculate = []
 backlinked = []
 forwardlinked = set()
@@ -191,6 +175,8 @@ parser.add_option("--db_backup_only", action="store_true",
                   help="Do the backup of the database in the provision, skip the sysvol / netlogon shares")
 parser.add_option("--full", action="store_true",
                   help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
+parser.add_option("--very-old-pre-alpha9", action="store_true",
+                  help="Perform additional forced SD resets required for a database from before Samba 4.0.0alpha9.")
 
 opts = parser.parse_args()[0]
 
@@ -223,7 +209,7 @@ creds.set_kerberos_state(DONT_USE_KERBEROS)
 
 
 
-def check_for_DNS(refprivate, private, dns_backend):
+def check_for_DNS(refprivate, private, refbinddns_dir, binddns_dir, dns_backend):
     """Check if the provision has already the requirement for dynamic dns
 
     :param refprivate: The path to the private directory of the reference
@@ -240,22 +226,25 @@ def check_for_DNS(refprivate, private, dns_backend):
     if not os.path.exists(dnsfile):
         shutil.copy("%s/dns_update_list" % refprivate, "%s" % dnsfile)
 
+    if not os.path.exists(binddns_dir):
+        os.mkdir(binddns_dir)
+
     if dns_backend not in ['BIND9_DLZ', 'BIND9_FLATFILE']:
        return
 
     namedfile = lp.get("dnsupdate:path")
     if not namedfile:
-       namedfile = "%s/named.conf.update" % private
+       namedfile = "%s/named.conf.update" % binddns_dir
     if not os.path.exists(namedfile):
-        destdir = "%s/new_dns" % private
-        dnsdir = "%s/dns" % private
+        destdir = "%s/new_dns" % binddns_dir
+        dnsdir = "%s/dns" % binddns_dir
 
         if not os.path.exists(destdir):
             os.mkdir(destdir)
         if not os.path.exists(dnsdir):
             os.mkdir(dnsdir)
-        shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
-        shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
+        shutil.copy("%s/named.conf" % refbinddns_dir, "%s/named.conf" % destdir)
+        shutil.copy("%s/named.txt" % refbinddns_dir, "%s/named.txt" % destdir)
         message(SIMPLE, "It seems that your provision did not integrate "
                 "new rules for dynamic dns update of domain related entries")
         message(SIMPLE, "A copy of the new bind configuration files and "
@@ -320,13 +309,13 @@ def sanitychecks(samdb, names):
                          scope=SCOPE_SUBTREE, attrs=["dn"],
                          controls=["search_options:1:2"])
     if len(res) == 0:
-        print "No DC found. Your provision is most probably broken!"
+        print("No DC found. Your provision is most probably broken!")
         return False
     elif len(res) != 1:
-        print "Found %d domain controllers. For the moment " \
+        print("Found %d domain controllers. For the moment " \
               "upgradeprovision is not able to handle an upgrade on a " \
               "domain with more than one DC. Please demote the other " \
-              "DC(s) before upgrading" % len(res)
+              "DC(s) before upgrading") % len(res)
         return False
     else:
         return True
@@ -388,7 +377,7 @@ def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
                             (int(str(old[0][att])), int(str(new[0][att]))))
             return False
         if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
-            if (long(str(old[0][att])) == 0):
+            if (int(str(old[0][att])) == 0):
                 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
             return True
 
@@ -401,7 +390,7 @@ def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
                 newval.append(str(elem))
 
             for elem in new[0][att]:
-                if not hash.has_key(str(elem).lower()):
+                if not str(elem).lower() in hash:
                     changeDelta=1
                     newval.append(str(elem))
             if changeDelta == 1:
@@ -456,7 +445,7 @@ def handle_special_case(att, delta, new, old, useReplMetadata, basedn, aldb):
             newval.append(str(elem))
 
         for elem in new[0][att]:
-            if not hash.has_key(str(elem)):
+            if not str(elem) in hash:
                 changeDelta = 1
                 newval.append(str(elem))
         if changeDelta == 1:
@@ -594,7 +583,7 @@ def check_dn_nottobecreated(hash, index, listdn):
         return None
     for dn in listdn:
         key = str(dn).lower()
-        if hash.has_key(key) and hash[key] > index:
+        if key in hash and hash[key] > index:
             return str(dn)
     return None
 
@@ -641,7 +630,7 @@ def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
                     skip = True
     finally:
         if delta.get("objectSid"):
-            sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
+            sid = str(ndr_unpack(security.dom_sid, reference[0]["objectSid"][0]))
             m = re.match(r".*-(\d+)$", sid)
             if m and int(m.group(1))>999:
                 delta.remove("objectSid")
@@ -649,7 +638,6 @@ def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
             delta.remove(att)
         for att in backlinked:
             delta.remove(att)
-        depend_on_yettobecreated = None
         for att in dn_syntax_att:
             depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
                                                                 delta.get(str(att)))
@@ -811,7 +799,7 @@ def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
     # Also this function in fact just accept add not removal
 
     for e in res[0][att]:
-        if not hash.has_key(e):
+        if not e in hash:
             # We put in the blacklist all the element that are in the "revealed"
             # result and not in the "standard" result
             # This element are links that were removed before and so that
@@ -819,7 +807,7 @@ def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
             blacklist[e] = 1
 
     for e in ref_value:
-        if not blacklist.has_key(e) and not hash.has_key(e):
+        if not e in blacklist and not e in hash:
             newlinklist.append(str(e))
             changed = True
     if changed:
@@ -830,68 +818,6 @@ def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
     return delta
 
 
-msg_elt_flag_strs = {
-    ldb.FLAG_MOD_ADD: "MOD_ADD",
-    ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
-    ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
-
-def checkKeepAttributeOldMtd(delta, att, reference, current,
-                                    basedn, samdb):
-    """ Check if we should keep the attribute modification or not.
-        This function didn't use replicationMetadata to take a decision.
-
-        :param delta: A message diff object
-        :param att: An attribute
-        :param reference: A message object for the current entry comming from
-                            the reference provision.
-        :param current: A message object for the current entry commin from
-                            the current provision.
-        :param basedn: The DN of the partition
-        :param samdb: A ldb connection to the sam database of the current provision.
-
-        :return: The modified message diff.
-    """
-    # Old school way of handling things for pre alpha12 upgrade
-    global defSDmodified
-    isFirst = False
-    txt = ""
-    dn = current[0].dn
-
-    for att in list(delta):
-        msgElt = delta.get(att)
-
-        if att == "nTSecurityDescriptor":
-            defSDmodified = True
-            delta.remove(att)
-            continue
-
-        if att == "dn":
-            continue
-
-        if not hashOverwrittenAtt.has_key(att):
-            if msgElt.flags() != FLAG_MOD_ADD:
-                if not handle_special_case(att, delta, reference, current,
-                                            False, basedn, samdb):
-                    if opts.debugchange or opts.debugall:
-                        try:
-                            dump_denied_change(dn, att,
-                                msg_elt_flag_strs[msgElt.flags()],
-                                current[0][att], reference[0][att])
-                        except KeyError:
-                            dump_denied_change(dn, att,
-                                msg_elt_flag_strs[msgElt.flags()],
-                                current[0][att], None)
-                    delta.remove(att)
-                continue
-        else:
-            if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
-                continue
-            elif hashOverwrittenAtt.get(att) == never:
-                delta.remove(att)
-                continue
-
-    return delta
-
 def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
                                     hash_attr_usn, basedn, usns, samdb):
     """ Check if we should keep the attribute modification or not
@@ -960,13 +886,11 @@ def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
 
         if att == "nTSecurityDescriptor":
             cursd = ndr_unpack(security.descriptor,
-                str(current[0]["nTSecurityDescriptor"]))
-            cursddl = cursd.as_sddl(names.domainsid)
+                current[0]["nTSecurityDescriptor"][0])
             refsd = ndr_unpack(security.descriptor,
-                str(reference[0]["nTSecurityDescriptor"]))
-            refsddl = refsd.as_sddl(names.domainsid)
+                reference[0]["nTSecurityDescriptor"][0])
 
-            diff = get_diff_sddls(refsddl, cursddl)
+            diff = get_diff_sds(refsd, cursd, names.domainsid)
             if diff == "":
                 # FIXME find a way to have it only with huge huge verbose mode
                 # message(CHANGE, "%ssd are identical" % txt)
@@ -981,9 +905,10 @@ def checkKeepAttributeWithMetadata(delta, att, message, reference, current,
                     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))
+                    global dnNotToRecalculateFound
+                    dnNotToRecalculateFound = True
                 else:
-                    dnToRecalculate.append(str(dn))
+                    dnToRecalculate.append(dn)
                 continue
 
         if attrUSN == -1:
@@ -1051,8 +976,7 @@ def update_present(ref_samdb, samdb, basedn, listPresent, usns):
     changed = 0
     sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
     controls = ["search_options:1:2", "sd_flags:1:%d" % sd_flags]
-    if usns is not None:
-            message(CHANGE, "Using replPropertyMetadata for change selection")
+    message(CHANGE, "Using replPropertyMetadata for change selection")
     for dn in listPresent:
         reference = ref_samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
                                         scope=SCOPE_SUBTREE,
@@ -1087,13 +1011,13 @@ def update_present(ref_samdb, samdb, basedn, listPresent, usns):
         if nb_items == 1:
             continue
 
-        if nb_items > 1 and usns is not None:
+        if nb_items > 1:
             # Fetch the replPropertyMetaData
             res = samdb.search(expression="(distinguishedName=%s)" % (str(dn)), base=basedn,
                                 scope=SCOPE_SUBTREE, controls=controls,
                                 attrs=["replPropertyMetaData"])
             ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
-                                str(res[0]["replPropertyMetaData"])).ctr
+                                res[0]["replPropertyMetaData"][0]).ctr
 
             hash_attr_usn = {}
             for o in ctr.array:
@@ -1105,12 +1029,9 @@ def update_present(ref_samdb, samdb, basedn, listPresent, usns):
                 else:
                     hash_attr_usn[att] = [-1, None]
 
-        if usns is not None:
-            delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
-                                                    current, hash_attr_usn,
-                                                    basedn, usns, samdb)
-        else:
-            delta =  checkKeepAttributeOldMtd(delta, att, reference, current, basedn, samdb)
+        delta = checkKeepAttributeWithMetadata(delta, att, message, reference,
+                                               current, hash_attr_usn,
+                                               basedn, usns, samdb)
 
         delta.dn = dn
 
@@ -1148,8 +1069,8 @@ def reload_full_schema(samdb, names):
     for ent in current:
         schema_ldif += samdb.write_ldif(ent, ldb.CHANGETYPE_NONE)
 
-    prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read()
-    prefixmap_data = b64encode(prefixmap_data)
+    prefixmap_data = open(setup_path("prefixMap.txt"), 'rb').read()
+    prefixmap_data = b64encode(prefixmap_data).decode('utf8')
 
     # We don't actually add this ldif, just parse it
     prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (schemadn, prefixmap_data)
@@ -1205,7 +1126,7 @@ def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, pre
 
 
     for k in hash_new.keys():
-        if not hash.has_key(k):
+        if not k in hash:
             if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
                 listMissing.append(hash_new[k])
         else:
@@ -1213,8 +1134,8 @@ def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, pre
 
     # Sort the missing object in order to have object of the lowest level
     # first (which can be containers for higher level objects)
-    listMissing.sort(dn_sort)
-    listPresent.sort(dn_sort)
+    listMissing.sort(key=cmp_to_key_fn(dn_sort))
+    listPresent.sort(key=cmp_to_key_fn(dn_sort))
 
     # The following lines is to load the up to
     # date schema into our current LDB
@@ -1239,7 +1160,7 @@ def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs, pre
         message(SIMPLE, "There are %d changed objects" % (changed))
         return 1
 
-    except StandardError, err:
+    except Exception as err:
         message(ERROR, "Exception during upgrade of samdb:")
         (typ, val, tb) = sys.exc_info()
         traceback.print_exception(typ, val, tb)
@@ -1265,19 +1186,20 @@ def check_updated_sd(ref_sam, cur_sam, names):
                                 controls=["search_options:1:2"])
     hash = {}
     for i in range(0, len(reference)):
-        refsd = ndr_unpack(security.descriptor,
-                    str(reference[i]["nTSecurityDescriptor"]))
-        hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
+        refsd_blob = reference[i]["nTSecurityDescriptor"][0]
+        hash[str(reference[i]["dn"]).lower()] = refsd_blob
 
 
     for i in range(0, len(current)):
         key = str(current[i]["dn"]).lower()
-        if hash.has_key(key):
+        if key in hash:
+            cursd_blob = current[i]["nTSecurityDescriptor"][0]
             cursd = ndr_unpack(security.descriptor,
-                        str(current[i]["nTSecurityDescriptor"]))
-            sddl = cursd.as_sddl(names.domainsid)
-            if sddl != hash[key]:
-                txt = get_diff_sddls(hash[key], sddl, False)
+                               cursd_blob)
+            if cursd_blob != hash[key]:
+                refsd = ndr_unpack(security.descriptor,
+                                   hash[key])
+                txt = get_diff_sds(refsd, cursd, names.domainsid, False)
                 if txt != "":
                     message(CHANGESD, "On object %s ACL is different"
                                       " \n%s" % (current[i]["dn"], txt))
@@ -1291,70 +1213,16 @@ def fix_wellknown_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
 
     list_wellknown_dns = []
 
-    # Then subcontainers
-    subcontainers = [
-        ("%s" % str(names.domaindn), get_domain_descriptor),
-        ("CN=LostAndFound,%s" % str(names.domaindn), get_domain_delete_protected2_descriptor),
-        ("CN=System,%s" % str(names.domaindn), get_domain_delete_protected1_descriptor),
-        ("CN=Infrastructure,%s" % str(names.domaindn), get_domain_infrastructure_descriptor),
-        ("CN=Builtin,%s" % str(names.domaindn), get_domain_builtin_descriptor),
-        ("CN=Computers,%s" % str(names.domaindn), get_domain_computers_descriptor),
-        ("CN=Users,%s" % str(names.domaindn), get_domain_users_descriptor),
-        ("OU=Domain Controllers,%s" % str(names.domaindn), get_domain_controllers_descriptor),
-        ("CN=MicrosoftDNS,CN=System,%s" % str(names.domaindn), get_dns_domain_microsoft_dns_descriptor),
-
-        ("%s" % str(names.configdn), get_config_descriptor),
-        ("CN=NTDS Quotas,%s" % str(names.configdn), get_config_ntds_quotas_descriptor),
-        ("CN=LostAndFoundConfig,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
-        ("CN=Services,%s" % str(names.configdn), get_config_delete_protected1_descriptor),
-        ("CN=Physical Locations,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
-        ("CN=WellKnown Security Principals,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
-        ("CN=ForestUpdates,%s" % str(names.configdn), get_config_delete_protected1wd_descriptor),
-        ("CN=DisplaySpecifiers,%s" % str(names.configdn), get_config_delete_protected2_descriptor),
-        ("CN=Extended-Rights,%s" % str(names.configdn), get_config_delete_protected2_descriptor),
-        ("CN=Partitions,%s" % str(names.configdn), get_config_partitions_descriptor),
-        ("CN=Sites,%s" % str(names.configdn), get_config_sites_descriptor),
-
-        ("%s" % str(names.schemadn), get_schema_descriptor),
-    ]
-
-    if names.dnsforestdn is not None:
-        c = ("%s" % str(names.dnsforestdn), get_dns_partition_descriptor)
-        subcontainers.append(c)
-        c = ("CN=Infrastructure,%s" % str(names.dnsforestdn),
-             get_domain_delete_protected1_descriptor)
-        subcontainers.append(c)
-        c = ("CN=LostAndFound,%s" % str(names.dnsforestdn),
-             get_domain_delete_protected2_descriptor)
-        subcontainers.append(c)
-        c = ("CN=MicrosoftDNS,%s" % str(names.dnsforestdn),
-             get_dns_forest_microsoft_dns_descriptor)
-        subcontainers.append(c)
-
-    if names.dnsdomaindn is not None:
-        c = ("%s" % str(names.dnsdomaindn), get_dns_partition_descriptor)
-        subcontainers.append(c)
-        c = ("CN=Infrastructure,%s" % str(names.dnsdomaindn),
-             get_domain_delete_protected1_descriptor)
-        subcontainers.append(c)
-        c = ("CN=LostAndFound,%s" % str(names.dnsdomaindn),
-             get_domain_delete_protected2_descriptor)
-        subcontainers.append(c)
-        c = ("CN=MicrosoftDNS,%s" % str(names.dnsdomaindn),
-             get_dns_domain_microsoft_dns_descriptor)
-        subcontainers.append(c)
+    subcontainers = get_wellknown_sds(samdb)
 
     for [dn, descriptor_fn] in subcontainers:
         list_wellknown_dns.append(dn)
-        if alwaysRecalculate or dn in dnToRecalculate:
+        if dn in dnToRecalculate:
             delta = Message()
-            delta.dn = Dn(samdb, str(dn))
+            delta.dn = dn
             descr = descriptor_fn(names.domainsid, name_map=names.name_map)
             delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
                                                             "nTSecurityDescriptor" )
@@ -1366,62 +1234,43 @@ def fix_wellknown_sd(samdb, names):
 def rebuild_sd(samdb, names):
     """Rebuild security descriptor of the current provision from scratch
 
-    During the different pre release of samba4 security descriptors (SD)
-    were notarly broken (up to alpha11 included)
-    This function allow to get them back in order, this function make the
-    assumption that nobody has modified manualy an SD
-    and so SD can be safely recalculated from scratch to get them right.
+    During the different pre release of samba4 security descriptors
+    (SD) were notarly broken (up to alpha11 included)
 
-    :param names: List of key provision parameters"""
+    This function allows one to get them back in order, this function works
+    only after the database comparison that --full mode uses and which
+    populates the dnToRecalculate and dnNotToRecalculate lists.
 
-    listWellknown = fix_wellknown_sd(samdb, names)
+    The idea is that the SD can be safely recalculated from scratch to get it right.
 
-    hash = {}
-    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:
-                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"]
+    :param names: List of key provision parameters"""
 
-    listKeys = list(set(hash.keys()))
-    listKeys.sort(dn_sort)
+    listWellknown = fix_wellknown_sd(samdb, names)
 
     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)))
+                            % (len(dnToRecalculate)))
 
-    for key in listKeys:
-        if key in listWellknown:
-            continue
-        if key in dnNotToRecalculate:
+    for dn in dnToRecalculate:
+        # well known SDs have already been reset
+        if dn in listWellknown:
             continue
         delta = Message()
-        delta.dn = Dn(samdb, key)
+        delta.dn = dn
         sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
         try:
             descr = get_empty_descriptor(names.domainsid)
             delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
                                                     "nTSecurityDescriptor")
             samdb.modify(delta, ["sd_flags:1:%d" % sd_flags,"relax:0","local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK])
-        except LdbError, e:
+        except LdbError as e:
             samdb.transaction_cancel()
             res = samdb.search(expression="objectClass=*", base=str(delta.dn),
                                 scope=SCOPE_BASE,
                                 attrs=["nTSecurityDescriptor"],
                                 controls=["sd_flags:1:%d" % sd_flags])
             badsd = ndr_unpack(security.descriptor,
-                        str(res[0]["nTSecurityDescriptor"]))
+                        res[0]["nTSecurityDescriptor"][0])
             message(ERROR, "On %s bad stuff %s" % (str(delta.dn),badsd.as_sddl(names.domainsid)))
             return
 
@@ -1471,7 +1320,7 @@ def simple_update_basesamdb(newpaths, paths, names):
     :param names: List of key provision parameters"""
 
     message(SIMPLE, "Copy samdb")
-    shutil.copy(newpaths.samdb, paths.samdb)
+    tdb_util.tdb_copy(newpaths.samdb, paths.samdb)
 
     message(SIMPLE, "Update partitions filename if needed")
     schemaldb = os.path.join(paths.private_dir, "schema.ldb")
@@ -1481,33 +1330,21 @@ def simple_update_basesamdb(newpaths, paths, names):
 
     if not os.path.isdir(samldbdir):
         os.mkdir(samldbdir)
-        os.chmod(samldbdir, 0700)
+        os.chmod(samldbdir, 0o700)
     if os.path.isfile(schemaldb):
-        shutil.copy(schemaldb, os.path.join(samldbdir,
+        tdb_util.tdb_copy(schemaldb, os.path.join(samldbdir,
                                             "%s.ldb"%str(names.schemadn).upper()))
         os.remove(schemaldb)
     if os.path.isfile(usersldb):
-        shutil.copy(usersldb, os.path.join(samldbdir,
+        tdb_util.tdb_copy(usersldb, os.path.join(samldbdir,
                                             "%s.ldb"%str(names.rootdn).upper()))
         os.remove(usersldb)
     if os.path.isfile(configldb):
-        shutil.copy(configldb, os.path.join(samldbdir,
+        tdb_util.tdb_copy(configldb, os.path.join(samldbdir,
                                             "%s.ldb"%str(names.configdn).upper()))
         os.remove(configldb)
 
 
-def update_privilege(ref_private_path, cur_private_path):
-    """Update the privilege database
-
-    :param ref_private_path: Path to the private directory of the reference
-                             provision.
-    :param cur_private_path: Path to the private directory of the current
-                             (and to be updated) provision."""
-    message(SIMPLE, "Copy privilege")
-    shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
-                os.path.join(cur_private_path, "privilege.ldb"))
-
-
 def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
     """Upgrade the SAM DB contents for all the provision partitions
 
@@ -1534,7 +1371,7 @@ def update_samdb(ref_samdb, samdb, names, provisionUSNs, schema, prereloadfunc):
         return 0
 
 
-def backup_provision(paths, dir, only_db):
+def backup_provision(samdb, paths, dir, only_db):
     """This function backup the provision files so that a rollback
     is possible
 
@@ -1542,14 +1379,26 @@ def backup_provision(paths, dir, only_db):
     :param dir: Directory where to store the backup
     :param only_db: Skip sysvol for users with big sysvol
     """
+
+    # Currently we default to tdb for the backend store type
+    #
+    backend_store = "tdb"
+    res = samdb.search(base="@PARTITION",
+                       scope=ldb.SCOPE_BASE,
+                       attrs=["backendStore"])
+    if "backendStore" in res[0]:
+        backend_store = str(res[0]["backendStore"][0])
+
+
     if paths.sysvol and not only_db:
         copytree_with_xattrs(paths.sysvol, os.path.join(dir, "sysvol"))
-    shutil.copy2(paths.samdb, dir)
-    shutil.copy2(paths.secrets, dir)
-    shutil.copy2(paths.idmapdb, dir)
-    shutil.copy2(paths.privilege, dir)
+
+    tdb_util.tdb_copy(paths.samdb, os.path.join(dir, os.path.basename(paths.samdb)))
+    tdb_util.tdb_copy(paths.secrets, os.path.join(dir, os.path.basename(paths.secrets)))
+    tdb_util.tdb_copy(paths.idmapdb, os.path.join(dir, os.path.basename(paths.idmapdb)))
+    tdb_util.tdb_copy(paths.privilege, os.path.join(dir, os.path.basename(paths.privilege)))
     if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
-        shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir)
+        tdb_util.tdb_copy(os.path.join(paths.private_dir,"eadb.tdb"), os.path.join(dir, "eadb.tdb"))
     shutil.copy2(paths.smbconf, dir)
     shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
 
@@ -1559,11 +1408,20 @@ def backup_provision(paths, dir, only_db):
         schemaldb = os.path.join(paths.private_dir, "schema.ldb")
         configldb = os.path.join(paths.private_dir, "configuration.ldb")
         usersldb = os.path.join(paths.private_dir, "users.ldb")
-        shutil.copy2(schemaldb, dir)
-        shutil.copy2(usersldb, dir)
-        shutil.copy2(configldb, dir)
+        tdb_util.tdb_copy(schemaldb, os.path.join(dir, "schema.ldb"))
+        tdb_util.tdb_copy(usersldb, os.path.join(dir, "configuration.ldb"))
+        tdb_util.tdb_copy(configldb, os.path.join(dir, "users.ldb"))
     else:
-        shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d"))
+        os.mkdir(os.path.join(dir, "sam.ldb.d"), 0o700)
+
+        for ldb_name in os.listdir(samldbdir):
+            if not ldb_name.endswith("-lock"):
+                if backend_store == "mdb" and ldb_name != "metadata.tdb":
+                    mdb_util.mdb_copy(os.path.join(samldbdir, ldb_name),
+                                      os.path.join(dir, "sam.ldb.d", ldb_name))
+                else:
+                    tdb_util.tdb_copy(os.path.join(samldbdir, ldb_name),
+                                      os.path.join(dir, "sam.ldb.d", ldb_name))
 
 
 def sync_calculated_attributes(samdb, names):
@@ -1578,7 +1436,7 @@ def sync_calculated_attributes(samdb, names):
    """
    listAttrs = ["msDs-KeyVersionNumber"]
    hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
-   if hash.has_key("msDs-KeyVersionNumber"):
+   if "msDs-KeyVersionNumber" in hash:
        increment_calculated_keyversion_number(samdb, names.rootdn,
                                             hash["msDs-KeyVersionNumber"])
 
@@ -1594,13 +1452,13 @@ def sync_calculated_attributes(samdb, names):
 # 6) get reference provision paths
 # 7) open reference provision ldbs
 # 8) setup helpers data that will help the update process
-# 9) update the privilege ldb by copying the one of referecence provision to
-#    the current provision
+# 9) (SKIPPED) we no longer update the privilege ldb by copying the one of referecence provision to
+#    the current provision, because a shutil.copy would break the transaction locks both databases are under
+#    and this database has not changed between 2009 and Samba 4.0.3 in Feb 2013 (at least)
 # 10)get the oemInfo field, this field contains information about the different
 #    provision that have been done
-# 11)Depending  on whether oemInfo has the string "alpha9" or alphaxx (x as an
-#    integer) or none of this the following things are done
-#    A) When alpha9 or alphaxx is present
+# 11)Depending on if the --very-old-pre-alpha9 flag is set the following things are done
+#    A) When alpha9 or alphaxx not specified (default)
 #       The base sam.ldb file is updated by looking at the difference between
 #       referrence one and the current one. Everything is copied with the
 #       exception of lastProvisionUSN attributes.
@@ -1668,7 +1526,7 @@ def sync_calculated_attributes(samdb, names):
 # The object is reappended at the end of the list to be created later
 # (and preferably after all the needed object have been created)
 # The function keeps on looping on the list of object to be created until
-# it's empty or that the number of defered creation is equal to the number
+# it's empty or that the number of deferred creation is equal to the number
 # of object that still needs to be created.
 
 # The function add_missing_object will first check if the object can be created.
@@ -1734,7 +1592,7 @@ if __name__ == '__main__':
     ldbs = get_ldbs(paths, creds, session, lp)
     backupdir = tempfile.mkdtemp(dir=paths.private_dir,
                                     prefix="backupprovision")
-    backup_provision(paths, backupdir, opts.db_backup_only)
+    backup_provision(ldbs.sam, paths, backupdir, opts.db_backup_only)
     try:
         ldbs.startTransactions()
 
@@ -1796,8 +1654,8 @@ if __name__ == '__main__':
         message(SIMPLE, "Creating a reference provision")
         provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
                         prefix="referenceprovision")
-        result = newprovision(names, creds, session, smbconf, provisiondir,
-                provision_logger)
+        result = newprovision(names, session, smbconf, provisiondir,
+                provision_logger, base_schema="2008_R2")
         result.report_logger(provision_logger)
 
         # TODO
@@ -1817,8 +1675,7 @@ if __name__ == '__main__':
         populate_links(new_ldbs.sam, names.schemadn)
         # List of attribute with ASN DN synthax)
         populate_dnsyntax(new_ldbs.sam, names.schemadn)
-        # 9)
-        update_privilege(newpaths.private_dir, paths.private_dir)
+        # 9) (now skipped, was copy of privileges.ldb)
         # 10)
         oem = getOEMInfo(ldbs.sam, str(names.rootdn))
         # Do some modification on sam.ldb
@@ -1827,7 +1684,7 @@ if __name__ == '__main__':
         deltaattr = None
     # 11)
         message(GUESS, oem)
-        if oem is None or hasATProvision(ldbs.sam) or re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
+        if oem is None or hasATProvision(ldbs.sam) or not opts.very_old_pre_alpha9:
             # 11) A
             # Starting from alpha9 we can consider that the structure is quite ok
             # and that we should do only dela
@@ -1922,11 +1779,10 @@ 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 str(oem) != "" and not re.match(r'.*alpha(9|\d\d+)', str(oem)):
+        if opts.very_old_pre_alpha9:
             message(SIMPLE, "Fixing very old provision SD")
             rebuild_sd(ldbs.sam, names)
 
@@ -1940,7 +1796,7 @@ if __name__ == '__main__':
 
         # 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:
+        if opts.full and (defSDmodified or len(dnToRecalculate) >0):
             message(SIMPLE, "Some (default) security descriptors (SDs) have "
                             "changed, recalculating them")
             ldbs.sam.set_session_info(adm_session)
@@ -1952,24 +1808,25 @@ if __name__ == '__main__':
         # 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 len(dnNotToRecalculate) == 0 and (opts.debugchangesd or opts.debugall):
+        if dnNotToRecalculateFound == False and (opts.debugchangesd or opts.debugall):
             message(CHANGESD, "Checking recalculated SDs")
             check_updated_sd(new_ldbs.sam, ldbs.sam, names)
 
         # 20)
         updateOEMInfo(ldbs.sam, str(names.rootdn))
         # 21)
-        check_for_DNS(newpaths.private_dir, paths.private_dir, names.dns_backend)
+        check_for_DNS(newpaths.private_dir, paths.private_dir,
+                      newpaths.binddns_dir, paths.binddns_dir,
+                      names.dns_backend)
         # 22)
-        if lastProvisionUSNs is not None:
-            update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
+        update_provision_usn(ldbs.sam, minUSN, maxUSN, names.invocation)
         if opts.full and (names.policyid is None or names.policyid_dc is None):
             update_policyids(names, ldbs.sam)
 
         if opts.full:
             try:
                 update_gpo(paths, ldbs.sam, names, lp, message)
-            except ProvisioningError, e:
+            except ProvisioningError as e:
                 message(ERROR, "The policy for domain controller is missing. "
                             "You should restart upgradeprovision with --full")
 
@@ -1988,7 +1845,7 @@ if __name__ == '__main__':
         message(SIMPLE, "Reindexing finished")
 
         shutil.rmtree(provisiondir)
-    except StandardError, err:
+    except Exception as err:
         message(ERROR, "A problem occurred while trying to upgrade your "
                    "provision. A full backup is located at %s" % backupdir)
         if opts.debugall or opts.debugchange: