provision: fix string formatting (number of args)
[nivanova/samba-autobuild/.git] / python / samba / provision / __init__.py
index 151444d156e825842d95c967f5638296641b0ee9..1b7762eb12b17113b8f04152c1c2d01fc2a2f452 100644 (file)
@@ -1,5 +1,5 @@
 # Unix SMB/CIFS implementation.
-# backend code for provisioning a Samba4 server
+# backend code for provisioning a Samba AD server
 
 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2012
 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
 
 __docformat__ = "restructuredText"
 
+from samba.compat import urllib_quote
+from samba.compat import string_types
+from samba.compat import binary_type
 from base64 import b64encode
+import errno
 import os
+import stat
 import re
 import pwd
 import grp
@@ -35,14 +40,14 @@ import logging
 import time
 import uuid
 import socket
-import urllib
-import string
 import tempfile
+import samba.dsdb
 
 import ldb
 
 from samba.auth import system_session, admin_session
 import samba
+from samba import auth
 from samba.samba3 import smbd, passdb
 from samba.samba3 import param as s3param
 from samba.dsdb import DS_DOMAIN_FUNCTION_2000
@@ -55,27 +60,27 @@ from samba import (
     substitute_var,
     valid_netbios_name,
     version,
-    )
+    is_heimdal_built,
+)
 from samba.dcerpc import security, misc
 from samba.dcerpc.misc import (
     SEC_CHAN_BDC,
     SEC_CHAN_WKSTA,
-    )
+)
 from samba.dsdb import (
     DS_DOMAIN_FUNCTION_2003,
     DS_DOMAIN_FUNCTION_2008_R2,
     ENC_ALL_TYPES,
-    )
+)
 from samba.idmap import IDmapDB
 from samba.ms_display_specifiers import read_ms_ldif
 from samba.ntacls import setntacl, getntacl, dsacl2fsacl
 from samba.ndr import ndr_pack, ndr_unpack
 from samba.provision.backend import (
-    ExistingBackend,
     FDSBackend,
     LDBBackend,
     OpenLDAPBackend,
-    )
+)
 from samba.descriptor import (
     get_empty_descriptor,
     get_config_descriptor,
@@ -96,7 +101,8 @@ from samba.descriptor import (
     get_dns_partition_descriptor,
     get_dns_forest_microsoft_dns_descriptor,
     get_dns_domain_microsoft_dns_descriptor,
-    )
+    get_managed_service_accounts_descriptor,
+)
 from samba.provision.common import (
     setup_path,
     setup_add_ldif,
@@ -110,20 +116,23 @@ from samba.provision.sambadns import (
     get_dnsadmins_sid,
     setup_ad_dns,
     create_dns_update_list
-    )
+)
 
 import samba.param
 import samba.registry
 from samba.schema import Schema
 from samba.samdb import SamDB
 from samba.dbchecker import dbcheck
-
+from samba.provision.kerberos import create_kdc_conf
+from samba.samdb import get_default_backend_store
 
 DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
-DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9"
+DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04FB984F9"
 DEFAULTSITE = "Default-First-Site-Name"
 LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
 
+DEFAULT_MIN_PWD_LENGTH = 7
+
 
 class ProvisionPaths(object):
 
@@ -143,6 +152,7 @@ class ProvisionPaths(object):
         self.dns = None
         self.winsdb = None
         self.private_dir = None
+        self.binddns_dir = None
         self.state_dir = None
 
 
@@ -164,11 +174,14 @@ class ProvisionNames(object):
         self.hostname = None
         self.sitename = None
         self.smbconf = None
+        self.domainsid = None
+        self.forestsid = None
+        self.domainguid = None
         self.name_map = {}
 
 
 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf,
-        lp):
+                                  lp):
     """Get key provision parameters (realm, domain, ...) from a given provision
 
     :param samdb: An LDB object connected to the sam.ldb file
@@ -183,45 +196,45 @@ def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf,
     names.adminpass = None
 
     # NT domain, kerberos realm, root dn, domain dn, domain dns name
-    names.domain = string.upper(lp.get("workgroup"))
+    names.domain = lp.get("workgroup").upper()
     names.realm = lp.get("realm")
     names.dnsdomain = names.realm.lower()
     basedn = samba.dn_from_dns_name(names.dnsdomain)
-    names.realm = string.upper(names.realm)
+    names.realm = names.realm.upper()
     # netbiosname
     # Get the netbiosname first (could be obtained from smb.conf in theory)
     res = secretsdb.search(expression="(flatname=%s)" %
-                            names.domain,base="CN=Primary Domains",
-                            scope=ldb.SCOPE_SUBTREE, attrs=["sAMAccountName"])
-    names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
+                           names.domain, base="CN=Primary Domains",
+                           scope=ldb.SCOPE_SUBTREE, attrs=["sAMAccountName"])
+    names.netbiosname = str(res[0]["sAMAccountName"]).replace("$", "")
 
     names.smbconf = smbconf
 
     # That's a bit simplistic but it's ok as long as we have only 3
     # partitions
     current = samdb.search(expression="(objectClass=*)",
-        base="", scope=ldb.SCOPE_BASE,
-        attrs=["defaultNamingContext", "schemaNamingContext",
-               "configurationNamingContext","rootDomainNamingContext",
-               "namingContexts"])
+                           base="", scope=ldb.SCOPE_BASE,
+                           attrs=["defaultNamingContext", "schemaNamingContext",
+                                  "configurationNamingContext", "rootDomainNamingContext",
+                                  "namingContexts"])
 
-    names.configdn = current[0]["configurationNamingContext"][0]
-    names.schemadn = current[0]["schemaNamingContext"][0]
+    names.configdn = str(current[0]["configurationNamingContext"][0])
+    names.schemadn = str(current[0]["schemaNamingContext"][0])
     if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
-                                       current[0]["defaultNamingContext"][0]))):
+                                             current[0]["defaultNamingContext"][0].decode('utf8')))):
         raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
                                  "is not the same ..." % (paths.samdb,
-                                    str(current[0]["defaultNamingContext"][0]),
-                                    paths.smbconf, basedn)))
+                                                          str(current[0]["defaultNamingContext"][0].decode('utf8')),
+                                                          paths.smbconf, basedn)))
 
-    names.domaindn=current[0]["defaultNamingContext"][0]
-    names.rootdn=current[0]["rootDomainNamingContext"][0]
-    names.ncs=current[0]["namingContexts"]
+    names.domaindn = str(current[0]["defaultNamingContext"][0])
+    names.rootdn = str(current[0]["rootDomainNamingContext"][0])
+    names.ncs = current[0]["namingContexts"]
     names.dnsforestdn = None
     names.dnsdomaindn = None
 
     for i in range(0, len(names.ncs)):
-        nc = names.ncs[i]
+        nc = str(names.ncs[i])
 
         dnsforestdn = "DC=ForestDnsZones,%s" % (str(names.rootdn))
         if nc == dnsforestdn:
@@ -235,52 +248,55 @@ def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf,
 
     # default site name
     res3 = samdb.search(expression="(objectClass=site)",
-        base="CN=Sites," + names.configdn, scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
+                        base="CN=Sites," + str(names.configdn), scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
     names.sitename = str(res3[0]["cn"])
 
     # dns hostname and server dn
     res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
-                            base="OU=Domain Controllers,%s" % basedn,
-                            scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"])
+                        base="OU=Domain Controllers,%s" % basedn,
+                        scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"])
+    if len(res4) == 0:
+        raise ProvisioningError("Unable to find DC called CN=%s under OU=Domain Controllers,%s" % (names.netbiosname, basedn))
+
     names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain, "")
 
     server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
-                                attrs=[], base=names.configdn)
+                              attrs=[], base=names.configdn)
     names.serverdn = str(server_res[0].dn)
 
     # invocation id/objectguid
     res5 = samdb.search(expression="(objectClass=*)",
-            base="CN=NTDS Settings,%s" % str(names.serverdn),
-            scope=ldb.SCOPE_BASE,
-            attrs=["invocationID", "objectGUID"])
+                        base="CN=NTDS Settings,%s" % str(names.serverdn),
+                        scope=ldb.SCOPE_BASE,
+                        attrs=["invocationID", "objectGUID"])
     names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
     names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
 
     # domain guid/sid
     res6 = samdb.search(expression="(objectClass=*)", base=basedn,
-            scope=ldb.SCOPE_BASE, attrs=["objectGUID",
-                "objectSid","msDS-Behavior-Version" ])
+                        scope=ldb.SCOPE_BASE, attrs=["objectGUID",
+                                                     "objectSid", "msDS-Behavior-Version"])
     names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
-    names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
+    names.domainsid = ndr_unpack(security.dom_sid, res6[0]["objectSid"][0])
+    names.forestsid = ndr_unpack(security.dom_sid, res6[0]["objectSid"][0])
     if res6[0].get("msDS-Behavior-Version") is None or \
-        int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
+            int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
         names.domainlevel = DS_DOMAIN_FUNCTION_2000
     else:
         names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
 
     # policy guid
-    res7 = samdb.search(expression="(displayName=Default Domain Policy)",
+    res7 = samdb.search(expression="(name={%s})" % DEFAULT_POLICY_GUID,
                         base="CN=Policies,CN=System," + basedn,
-                        scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
-    names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
+                        scope=ldb.SCOPE_ONELEVEL, attrs=["cn", "displayName"])
+    names.policyid = str(res7[0]["cn"]).replace("{", "").replace("}", "")
     # dc policy guid
-    res8 = samdb.search(expression="(displayName=Default Domain Controllers"
-                                   " Policy)",
-                            base="CN=Policies,CN=System," + basedn,
-                            scope=ldb.SCOPE_ONELEVEL,
-                            attrs=["cn","displayName"])
+    res8 = samdb.search(expression="(name={%s})" % DEFAULT_DC_POLICY_GUID,
+                        base="CN=Policies,CN=System," + basedn,
+                        scope=ldb.SCOPE_ONELEVEL,
+                        attrs=["cn", "displayName"])
     if len(res8) == 1:
-        names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
+        names.policyid_dc = str(res8[0]["cn"]).replace("{", "").replace("}", "")
     else:
         names.policyid_dc = None
 
@@ -289,8 +305,8 @@ def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf,
                           attrs=["xidNumber", "type"])
     if len(res9) != 1:
         raise ProvisioningError("Unable to find uid/gid for Domain Admins rid (%s-%s" % (str(names.domainsid), security.DOMAIN_RID_ADMINISTRATOR))
-    if res9[0]["type"][0] == "ID_TYPE_BOTH":
-        names.root_gid = res9[0]["xidNumber"][0]
+    if str(res9[0]["type"][0]) == "ID_TYPE_BOTH":
+        names.root_gid = int(res9[0]["xidNumber"][0])
     else:
         names.root_gid = pwd.getpwuid(int(res9[0]["xidNumber"][0])).pw_gid
 
@@ -348,15 +364,17 @@ def update_provision_usn(samdb, low, high, id, replace=False):
                              scope=ldb.SCOPE_BASE,
                              attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
         for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
-            if not re.search(';', e):
-                e = "%s;%s" % (e, id)
+            if not re.search(';', str(e)):
+                e = "%s;%s" % (str(e), id)
             tab.append(str(e))
 
     tab.append("%s-%s;%s" % (low, high, id))
     delta = ldb.Message()
     delta.dn = ldb.Dn(samdb, "@PROVISION")
-    delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
-        ldb.FLAG_MOD_REPLACE, LAST_PROVISION_USN_ATTRIBUTE)
+    delta[LAST_PROVISION_USN_ATTRIBUTE] = \
+        ldb.MessageElement(tab,
+                           ldb.FLAG_MOD_REPLACE,
+                           LAST_PROVISION_USN_ATTRIBUTE)
     entry = samdb.search(expression='provisionnerID=*',
                          base="@PROVISION", scope=ldb.SCOPE_BASE,
                          attrs=["provisionnerID"])
@@ -382,12 +400,14 @@ def set_provision_usn(samdb, low, high, id):
 
     delta = ldb.Message()
     delta.dn = ldb.Dn(samdb, "@PROVISION")
-    delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
-        ldb.FLAG_MOD_ADD, LAST_PROVISION_USN_ATTRIBUTE)
+    delta[LAST_PROVISION_USN_ATTRIBUTE] = \
+        ldb.MessageElement(tab,
+                           ldb.FLAG_MOD_ADD,
+                           LAST_PROVISION_USN_ATTRIBUTE)
     samdb.add(delta)
 
 
-def get_max_usn(samdb,basedn):
+def get_max_usn(samdb, basedn):
     """ This function return the biggest USN present in the provision
 
     :param samdb: A LDB object pointing to the sam.ldb
@@ -395,11 +415,11 @@ def get_max_usn(samdb,basedn):
                     (ie. DC=foo, DC=bar)
     :return: The biggest USN in the provision"""
 
-    res = samdb.search(expression="objectClass=*",base=basedn,
-                         scope=ldb.SCOPE_SUBTREE,attrs=["uSNChanged"],
-                         controls=["search_options:1:2",
-                                   "server_sort:1:1:uSNChanged",
-                                   "paged_results:1:1"])
+    res = samdb.search(expression="objectClass=*", base=basedn,
+                       scope=ldb.SCOPE_SUBTREE, attrs=["uSNChanged"],
+                       controls=["search_options:1:2",
+                                 "server_sort:1:1:uSNChanged",
+                                 "paged_results:1:1"])
     return res[0]["uSNChanged"]
 
 
@@ -412,9 +432,10 @@ def get_last_provision_usn(sam):
     """
     try:
         entry = sam.search(expression="%s=*" % LAST_PROVISION_USN_ATTRIBUTE,
-                       base="@PROVISION", scope=ldb.SCOPE_BASE,
-                       attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
-    except ldb.LdbError, (ecode, emsg):
+                           base="@PROVISION", scope=ldb.SCOPE_BASE,
+                           attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
+    except ldb.LdbError as e1:
+        (ecode, emsg) = e1.args
         if ecode == ldb.ERR_NO_SUCH_OBJECT:
             return None
         raise
@@ -467,7 +488,7 @@ class ProvisionResult(object):
     def report_logger(self, logger):
         """Report this provision result to a logger."""
         logger.info(
-            "Once the above files are installed, your Samba4 server will "
+            "Once the above files are installed, your Samba AD server will "
             "be ready to use")
         if self.adminpass_generated:
             logger.info("Admin password:        %s", self.adminpass)
@@ -491,7 +512,7 @@ def check_install(lp, session_info, credentials):
     if lp.get("realm") == "":
         raise Exception("Realm empty")
     samdb = Ldb(lp.samdb_url(), session_info=session_info,
-            credentials=credentials, lp=lp)
+                credentials=credentials, lp=lp)
     if len(samdb.search("(cn=Administrator)")) != 1:
         raise ProvisioningError("No administrator account found")
 
@@ -511,8 +532,12 @@ def findnss(nssfn, names):
     raise KeyError("Unable to find user/group in %r" % names)
 
 
-findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
-findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
+def findnss_uid(names):
+    return findnss(pwd.getpwnam, names)[2]
+
+
+def findnss_gid(names):
+    return findnss(grp.getgrnam, names)[2]
 
 
 def provision_paths_from_lp(lp, dnsdomain):
@@ -523,6 +548,7 @@ def provision_paths_from_lp(lp, dnsdomain):
     """
     paths = ProvisionPaths()
     paths.private_dir = lp.get("private dir")
+    paths.binddns_dir = lp.get("binddns dir")
     paths.state_dir = lp.get("state directory")
 
     # This is stored without path prefix for the "privateKeytab" attribute in
@@ -535,15 +561,21 @@ def provision_paths_from_lp(lp, dnsdomain):
     paths.idmapdb = os.path.join(paths.private_dir, "idmap.ldb")
     paths.secrets = os.path.join(paths.private_dir, "secrets.ldb")
     paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
-    paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
     paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
     paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
-    paths.namedconf = os.path.join(paths.private_dir, "named.conf")
-    paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
-    paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
     paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
+    paths.kdcconf = os.path.join(paths.private_dir, "kdc.conf")
     paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
     paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
+    paths.encrypted_secrets_key_path = os.path.join(
+        paths.private_dir,
+        "encrypted_secrets.key")
+
+    paths.dns = os.path.join(paths.binddns_dir, "dns", dnsdomain + ".zone")
+    paths.namedconf = os.path.join(paths.binddns_dir, "named.conf")
+    paths.namedconf_update = os.path.join(paths.binddns_dir, "named.conf.update")
+    paths.namedtxt = os.path.join(paths.binddns_dir, "named.txt")
+
     paths.hklm = "hklm.ldb"
     paths.hkcr = "hkcr.ldb"
     paths.hkcu = "hkcu.ldb"
@@ -582,7 +614,9 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
     if dnsdomain is None:
         dnsdomain = lp.get("realm")
         if dnsdomain is None or dnsdomain == "":
-            raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile)
+            raise ProvisioningError(
+                "guess_names: 'realm' not specified in supplied %s!" %
+                lp.configfile)
 
     dnsdomain = dnsdomain.lower()
 
@@ -633,8 +667,24 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
     if domain == realm and not domain_names_forced:
         raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
 
+    if serverrole != "active directory domain controller":
+        #
+        # This is the code path for a domain member
+        # where we provision the database as if we where
+        # on a domain controller, so we should not use
+        # the same dnsdomain as the domain controllers
+        # of our primary domain.
+        #
+        # This will be important if we start doing
+        # SID/name filtering and reject the local
+        # sid and names if they come from a domain
+        # controller.
+        #
+        realm = netbiosname
+        dnsdomain = netbiosname.lower()
+
     if rootdn is None:
-       rootdn = domaindn
+        rootdn = domaindn
 
     if configdn is None:
         configdn = "CN=Configuration," + rootdn
@@ -688,11 +738,11 @@ def make_smbconf(smbconf, hostname, domain, realm, targetdir,
         "workgroup": domain,
         "realm": realm,
         "server role": serverrole,
-        }
+    }
 
     if lp is None:
         lp = samba.param.LoadParm()
-    #Load non-existent file
+    # Load non-existent file
     if os.path.exists(smbconf):
         lp.load(smbconf)
 
@@ -706,10 +756,12 @@ def make_smbconf(smbconf, hostname, domain, realm, targetdir,
         global_settings["lock dir"] = os.path.abspath(targetdir)
         global_settings["state directory"] = os.path.abspath(os.path.join(targetdir, "state"))
         global_settings["cache directory"] = os.path.abspath(os.path.join(targetdir, "cache"))
+        global_settings["binddns dir"] = os.path.abspath(os.path.join(targetdir, "bind-dns"))
 
         lp.set("lock dir", os.path.abspath(targetdir))
-        lp.set("state directory",  global_settings["state directory"])
+        lp.set("state directory", global_settings["state directory"])
         lp.set("cache directory", global_settings["cache directory"])
+        lp.set("binddns dir", global_settings["binddns dir"])
 
     if eadb:
         if use_ntvfs and not lp.get("posix:eadb"):
@@ -729,18 +781,18 @@ def make_smbconf(smbconf, hostname, domain, realm, targetdir,
     if serverrole == "active directory domain controller":
         shares["sysvol"] = os.path.join(lp.get("state directory"), "sysvol")
         shares["netlogon"] = os.path.join(shares["sysvol"], realm.lower(),
-            "scripts")
+                                          "scripts")
     else:
         global_settings["passdb backend"] = "samba_dsdb"
 
     f = open(smbconf, 'w')
     try:
         f.write("[globals]\n")
-        for key, val in global_settings.iteritems():
+        for key, val in global_settings.items():
             f.write("\t%s = %s\n" % (key, val))
         f.write("\n")
 
-        for name, path in shares.iteritems():
+        for name, path in shares.items():
             f.write("[%s]\n" % name)
             f.write("\tpath = %s\n" % path)
             f.write("\tread only = no\n")
@@ -753,11 +805,7 @@ def make_smbconf(smbconf, hostname, domain, realm, targetdir,
     # and dump it without any values that are the default
     # this ensures that any smb.conf parameters that were set
     # on the provision/join command line are set in the resulting smb.conf
-    f = open(smbconf, mode='w')
-    try:
-        lp.dump(f, False)
-    finally:
-        f.close()
+    lp.dump(False, smbconf)
 
 
 def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
@@ -780,8 +828,9 @@ def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
 
 
 def setup_samdb_partitions(samdb_path, logger, lp, session_info,
-                           provision_backend, names, schema, serverrole,
-                           erase=False):
+                           provision_backend, names, serverrole,
+                           erase=False, plaintext_secrets=False,
+                           backend_store=None):
     """Setup the partitions for the SAM database.
 
     Alternatively, provision() may call this, and then populate the database.
@@ -810,17 +859,36 @@ def setup_samdb_partitions(samdb_path, logger, lp, session_info,
     if provision_backend.type != "ldb":
         ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri
 
+    required_features = None
+    if not plaintext_secrets:
+        required_features = "requiredFeatures: encryptedSecrets"
+
+    if backend_store is None:
+        backend_store = get_default_backend_store()
+    backend_store_line = "backendStore: %s" % backend_store
+
+    if backend_store == "mdb":
+        if required_features is not None:
+            required_features += "\n"
+        else:
+            required_features = ""
+        required_features += "requiredFeatures: lmdbLevelOne"
+
+    if required_features is None:
+        required_features = "# No required features"
+
     samdb.transaction_start()
     try:
         logger.info("Setting up sam.ldb partitions and settings")
         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
-                "LDAP_BACKEND_LINE": ldap_backend_line
+                "LDAP_BACKEND_LINE": ldap_backend_line,
+                "BACKEND_STORE": backend_store_line
         })
 
-
         setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
                 "BACKEND_TYPE": provision_backend.type,
-                "SERVER_ROLE": serverrole
+                "SERVER_ROLE": serverrole,
+                "REQUIRED_FEATURES": required_features
                 })
 
         logger.info("Setting up sam.ldb rootDSE")
@@ -844,11 +912,11 @@ def secretsdb_self_join(secretsdb, domain,
     :param machinepass: Machine password
     """
     attrs = ["whenChanged",
-           "secret",
-           "priorSecret",
-           "priorChanged",
-           "krb5Keytab",
-           "privateKeytab"]
+             "secret",
+             "priorSecret",
+             "priorChanged",
+             "krb5Keytab",
+             "privateKeytab"]
 
     if realm is not None:
         if dnsdomain is None:
@@ -870,7 +938,7 @@ def secretsdb_self_join(secretsdb, domain,
         msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
         msg["privateKeytab"] = ["secrets.keytab"]
 
-    msg["secret"] = [machinepass]
+    msg["secret"] = [machinepass.encode('utf-8')]
     msg["samAccountName"] = ["%s$" % netbiosname]
     msg["secureChannelType"] = [str(secure_channel_type)]
     if domainsid is not None:
@@ -881,8 +949,8 @@ def secretsdb_self_join(secretsdb, domain,
     # but we don't delete the old record that we are about to modify,
     # because that would delete the keytab and previous password.
     res = secretsdb.search(base="cn=Primary Domains", attrs=attrs,
-        expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(distinguishedName=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
-        scope=ldb.SCOPE_ONELEVEL)
+                           expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(distinguishedName=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
+                           scope=ldb.SCOPE_ONELEVEL)
 
     for del_msg in res:
         secretsdb.delete(del_msg.dn)
@@ -891,7 +959,10 @@ def secretsdb_self_join(secretsdb, domain,
 
     if len(res) == 1:
         msg["priorSecret"] = [res[0]["secret"][0]]
-        msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
+        try:
+            msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
+        except KeyError:
+            pass
 
         try:
             msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
@@ -909,11 +980,11 @@ def secretsdb_self_join(secretsdb, domain,
         secretsdb.modify(msg)
         secretsdb.rename(res[0].dn, msg.dn)
     else:
-        spn = [ 'HOST/%s' % shortname ]
+        spn = ['HOST/%s' % shortname]
         if secure_channel_type == SEC_CHAN_BDC and dnsname is not None:
             # we are a domain controller then we add servicePrincipalName
             # entries for the keytab code to update.
-            spn.extend([ 'HOST/%s' % dnsname ])
+            spn.extend(['HOST/%s' % dnsname])
         msg["servicePrincipalName"] = spn
 
         secretsdb.add(msg)
@@ -922,7 +993,7 @@ def secretsdb_self_join(secretsdb, domain,
 def setup_secretsdb(paths, session_info, backend_credentials, lp):
     """Setup the secrets database.
 
-   :note: This function does not handle exceptions and transaction on purpose,
+    :note: This function does not handle exceptions and transaction on purpose,
        it's up to the caller to do this job.
 
     :param path: Path to the secrets database.
@@ -938,6 +1009,10 @@ def setup_secretsdb(paths, session_info, backend_credentials, lp):
     if os.path.exists(keytab_path):
         os.unlink(keytab_path)
 
+    bind_dns_keytab_path = os.path.join(paths.binddns_dir, paths.dns_keytab)
+    if os.path.exists(bind_dns_keytab_path):
+        os.unlink(bind_dns_keytab_path)
+
     dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
     if os.path.exists(dns_keytab_path):
         os.unlink(dns_keytab_path)
@@ -956,17 +1031,17 @@ def setup_secretsdb(paths, session_info, backend_credentials, lp):
             backend_credentials.authentication_requested()):
             if backend_credentials.get_bind_dn() is not None:
                 setup_add_ldif(secrets_ldb,
-                    setup_path("secrets_simple_ldap.ldif"), {
-                        "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
-                        "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
-                        })
+                               setup_path("secrets_simple_ldap.ldif"), {
+                                   "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
+                                   "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password()).decode('utf8')
+                               })
             else:
                 setup_add_ldif(secrets_ldb,
-                    setup_path("secrets_sasl_ldap.ldif"), {
-                        "LDAPADMINUSER": backend_credentials.get_username(),
-                        "LDAPADMINREALM": backend_credentials.get_realm(),
-                        "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
-                        })
+                               setup_path("secrets_sasl_ldap.ldif"), {
+                                   "LDAPADMINUSER": backend_credentials.get_username(),
+                                   "LDAPADMINREALM": backend_credentials.get_realm(),
+                                   "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password()).decode('utf8')
+                               })
     except:
         secrets_ldb.transaction_cancel()
         raise
@@ -989,6 +1064,31 @@ def setup_privileges(path, session_info, lp):
     privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
 
 
+def setup_encrypted_secrets_key(path):
+    """Setup the encrypted secrets key file.
+
+    Any existing key file will be deleted and a new random key generated.
+
+    :param path: Path to the secrets key file.
+
+    """
+    if os.path.exists(path):
+        os.unlink(path)
+
+    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
+    mode = stat.S_IRUSR | stat.S_IWUSR
+
+    umask_original = os.umask(0)
+    try:
+        fd = os.open(path, flags, mode)
+    finally:
+        os.umask(umask_original)
+
+    with os.fdopen(fd, 'wb') as f:
+        key = samba.generate_random_bytes(16)
+        f.write(key)
+
+
 def setup_registry(path, session_info, lp):
     """Setup the registry.
 
@@ -1030,20 +1130,20 @@ def setup_samdb_rootdse(samdb, names):
     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
         "SCHEMADN": names.schemadn,
         "DOMAINDN": names.domaindn,
-        "ROOTDN"  : names.rootdn,
+        "ROOTDN": names.rootdn,
         "CONFIGDN": names.configdn,
         "SERVERDN": names.serverdn,
-        })
+    })
 
 
 def setup_self_join(samdb, admin_session_info, names, fill, machinepass,
-        dns_backend, dnspass, domainsid, next_rid, invocationid,
-        policyguid, policyguid_dc,
-        domainControllerFunctionality, ntdsguid=None, dc_rid=None):
+                    dns_backend, dnspass, domainsid, next_rid, invocationid,
+                    policyguid, policyguid_dc,
+                    domainControllerFunctionality, ntdsguid=None, dc_rid=None):
     """Join a host to its own domain."""
     assert isinstance(invocationid, str)
     if ntdsguid is not None:
-        ntdsguid_line = "objectGUID: %s\n"%ntdsguid
+        ntdsguid_line = "objectGUID: %s\n" % ntdsguid
     else:
         ntdsguid_line = ""
 
@@ -1058,7 +1158,7 @@ def setup_self_join(samdb, admin_session_info, names, fill, machinepass,
               "INVOCATIONID": invocationid,
               "NETBIOSNAME": names.netbiosname,
               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
-              "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
+              "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')).decode('utf8'),
               "DOMAINSID": str(domainsid),
               "DCRID": str(dc_rid),
               "SAMBA_VERSION_STRING": version,
@@ -1085,7 +1185,7 @@ def setup_self_join(samdb, admin_session_info, names, fill, machinepass,
                 "INVOCATIONID": invocationid,
                 "NETBIOSNAME": names.netbiosname,
                 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
-                "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
+                "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')).decode('utf8'),
                 "DOMAINSID": str(domainsid),
                 "DCRID": str(dc_rid),
                 "SAMBA_VERSION_STRING": version,
@@ -1095,13 +1195,13 @@ def setup_self_join(samdb, admin_session_info, names, fill, machinepass,
 
     # Setup fSMORoleOwner entries to point at the newly created DC entry
         setup_modify_ldif(samdb,
-            setup_path("provision_self_join_modify_config.ldif"), {
-                "CONFIGDN": names.configdn,
-                "SCHEMADN": names.schemadn,
-                "DEFAULTSITE": names.sitename,
-                "NETBIOSNAME": names.netbiosname,
-                "SERVERDN": names.serverdn,
-                })
+                          setup_path("provision_self_join_modify_config.ldif"), {
+                              "CONFIGDN": names.configdn,
+                              "SCHEMADN": names.schemadn,
+                              "DEFAULTSITE": names.sitename,
+                              "NETBIOSNAME": names.netbiosname,
+                              "SERVERDN": names.serverdn,
+                          })
 
     system_session_info = system_session()
     samdb.set_session_info(system_session_info)
@@ -1122,9 +1222,9 @@ def setup_self_join(samdb, admin_session_info, names, fill, machinepass,
         setup_add_ldif(samdb, setup_path("provision_dns_add_samba.ldif"), {
               "DNSDOMAIN": names.dnsdomain,
               "DOMAINDN": names.domaindn,
-              "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')),
-              "HOSTNAME" : names.hostname,
-              "DNSNAME" : '%s.%s' % (
+              "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')).decode('utf8'),
+              "HOSTNAME": names.hostname,
+              "DNSNAME": '%s.%s' % (
                   names.netbiosname.lower(), names.dnsdomain.lower())
               })
 
@@ -1145,7 +1245,7 @@ def getpolicypath(sysvolpath, dnsdomain, guid):
 
 def create_gpo_struct(policy_path):
     if not os.path.exists(policy_path):
-        os.makedirs(policy_path, 0775)
+        os.makedirs(policy_path, 0o775)
     f = open(os.path.join(policy_path, "GPT.INI"), 'w')
     try:
         f.write("[General]\r\nVersion=0")
@@ -1153,10 +1253,10 @@ def create_gpo_struct(policy_path):
         f.close()
     p = os.path.join(policy_path, "MACHINE")
     if not os.path.exists(p):
-        os.makedirs(p, 0775)
+        os.makedirs(p, 0o775)
     p = os.path.join(policy_path, "USER")
     if not os.path.exists(p):
-        os.makedirs(p, 0775)
+        os.makedirs(p, 0o775)
 
 
 def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
@@ -1167,15 +1267,16 @@ def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
     :param policyguid: GUID of the default domain policy
     :param policyguid_dc: GUID of the default domain controler policy
     """
-    policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
+    policy_path = getpolicypath(sysvolpath, dnsdomain, policyguid)
     create_gpo_struct(policy_path)
 
-    policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
+    policy_path = getpolicypath(sysvolpath, dnsdomain, policyguid_dc)
     create_gpo_struct(policy_path)
 
 
 def setup_samdb(path, session_info, provision_backend, lp, names,
-        logger, fill, serverrole, schema, am_rodc=False):
+                logger, fill, serverrole, schema, am_rodc=False,
+                plaintext_secrets=False, backend_store=None):
     """Setup a complete SAM Database.
 
     :note: This will wipe the main SAM database file!
@@ -1183,8 +1284,9 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
 
     # Also wipes the database
     setup_samdb_partitions(path, logger=logger, lp=lp,
-        provision_backend=provision_backend, session_info=session_info,
-        names=names, serverrole=serverrole, schema=schema)
+                           provision_backend=provision_backend, session_info=session_info,
+                           names=names, serverrole=serverrole, plaintext_secrets=plaintext_secrets,
+                           backend_store=backend_store)
 
     # Load the database, but don's load the global schema and don't connect
     # quite yet
@@ -1203,7 +1305,14 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
 
     # And now we can connect to the DB - the schema won't be loaded from the
     # DB
-    samdb.connect(path)
+    try:
+        samdb.connect(path)
+    except ldb.LdbError as e2:
+        (num, string_error) = e2.args
+        if (num == ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS):
+            raise ProvisioningError("Permission denied connecting to %s, are you running as root?" % path)
+        else:
+            raise
 
     # But we have to give it one more kick to have it use the schema
     # during provision - it needs, now that it is connected, to write
@@ -1213,10 +1322,11 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
     return samdb
 
 
-def fill_samdb(samdb, lp, names, logger, domainsid, domainguid, policyguid,
-        policyguid_dc, fill, adminpass, krbtgtpass, machinepass, dns_backend,
-        dnspass, invocationid, ntdsguid, serverrole, am_rodc=False,
-        dom_for_fun_level=None, schema=None, next_rid=None, dc_rid=None):
+def fill_samdb(samdb, lp, names, logger, policyguid,
+               policyguid_dc, fill, adminpass, krbtgtpass, machinepass, dns_backend,
+               dnspass, invocationid, ntdsguid, serverrole, am_rodc=False,
+               dom_for_fun_level=None, schema=None, next_rid=None, dc_rid=None,
+               backend_store=None):
 
     if next_rid is None:
         next_rid = 1000
@@ -1235,7 +1345,7 @@ def fill_samdb(samdb, lp, names, logger, domainsid, domainguid, policyguid,
     domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2
 
     if dom_for_fun_level is None:
-        dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
+        dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
 
     if dom_for_fun_level > domainControllerFunctionality:
         raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level which itself is higher than its actual DC function level (2008_R2). This won't work!")
@@ -1247,238 +1357,244 @@ def fill_samdb(samdb, lp, names, logger, domainsid, domainguid, policyguid,
     # before the provisioned tree exists and we connect
     samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
 
-    samdb.transaction_start()
-    try:
-        # Set the domain functionality levels onto the database.
-        # Various module (the password_hash module in particular) need
-        # to know what level of AD we are emulating.
-
-        # These will be fixed into the database via the database
-        # modifictions below, but we need them set from the start.
-        samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
-        samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
-        samdb.set_opaque_integer("domainControllerFunctionality",
-            domainControllerFunctionality)
-
-        samdb.set_domain_sid(str(domainsid))
-        samdb.set_invocation_id(invocationid)
-
-        logger.info("Adding DomainDN: %s" % names.domaindn)
-
-        # impersonate domain admin
-        admin_session_info = admin_session(lp, str(domainsid))
-        samdb.set_session_info(admin_session_info)
-        if domainguid is not None:
-            domainguid_line = "objectGUID: %s\n-" % domainguid
-        else:
-            domainguid_line = ""
+    # Set the domain functionality levels onto the database.
+    # Various module (the password_hash module in particular) need
+    # to know what level of AD we are emulating.
 
-        descr = b64encode(get_domain_descriptor(domainsid))
-        setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
-                "DOMAINDN": names.domaindn,
-                "DOMAINSID": str(domainsid),
-                "DESCRIPTOR": descr,
-                "DOMAINGUID": domainguid_line
-                })
+    # These will be fixed into the database via the database
+    # modifictions below, but we need them set from the start.
+    samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
+    samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
+    samdb.set_opaque_integer("domainControllerFunctionality",
+                             domainControllerFunctionality)
 
-        setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
-            "DOMAINDN": names.domaindn,
-            "CREATTIME": str(samba.unix2nttime(int(time.time()))),
-            "NEXTRID": str(next_rid),
-            "DEFAULTSITE": names.sitename,
-            "CONFIGDN": names.configdn,
-            "POLICYGUID": policyguid,
-            "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
-            "SAMBA_VERSION_STRING": version
-            })
+    samdb.set_domain_sid(str(names.domainsid))
+    samdb.set_invocation_id(invocationid)
 
-        # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
-        if fill == FILL_FULL:
-            logger.info("Adding configuration container")
-            descr = b64encode(get_config_descriptor(domainsid))
-            setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
-                    "CONFIGDN": names.configdn,
-                    "DESCRIPTOR": descr,
-                    })
-
-            # The LDIF here was created when the Schema object was constructed
-            logger.info("Setting up sam.ldb schema")
-            samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
-            samdb.modify_ldif(schema.schema_dn_modify)
-            samdb.write_prefixes_from_schema()
-            samdb.add_ldif(schema.schema_data, controls=["relax:0"])
-            setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
-                           {"SCHEMADN": names.schemadn})
-
-        # Now register this container in the root of the forest
-        msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
-        msg["subRefs"] = ldb.MessageElement(names.configdn , ldb.FLAG_MOD_ADD,
-                    "subRefs")
+    logger.info("Adding DomainDN: %s" % names.domaindn)
 
-    except:
-        samdb.transaction_cancel()
-        raise
+    # impersonate domain admin
+    admin_session_info = admin_session(lp, str(names.domainsid))
+    samdb.set_session_info(admin_session_info)
+    if names.domainguid is not None:
+        domainguid_line = "objectGUID: %s\n-" % names.domainguid
     else:
-        samdb.transaction_commit()
+        domainguid_line = ""
 
-    samdb.transaction_start()
-    try:
-        samdb.invocation_id = invocationid
-
-        # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
-        if fill == FILL_FULL:
-            logger.info("Setting up sam.ldb configuration data")
-
-            partitions_descr = b64encode(get_config_partitions_descriptor(domainsid))
-            sites_descr = b64encode(get_config_sites_descriptor(domainsid))
-            ntdsquotas_descr = b64encode(get_config_ntds_quotas_descriptor(domainsid))
-            protected1_descr = b64encode(get_config_delete_protected1_descriptor(domainsid))
-            protected1wd_descr = b64encode(get_config_delete_protected1wd_descriptor(domainsid))
-            protected2_descr = b64encode(get_config_delete_protected2_descriptor(domainsid))
-
-            setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
-                    "CONFIGDN": names.configdn,
-                    "NETBIOSNAME": names.netbiosname,
-                    "DEFAULTSITE": names.sitename,
-                    "DNSDOMAIN": names.dnsdomain,
-                    "DOMAIN": names.domain,
-                    "SCHEMADN": names.schemadn,
-                    "DOMAINDN": names.domaindn,
-                    "SERVERDN": names.serverdn,
-                    "FOREST_FUNCTIONALITY": str(forestFunctionality),
-                    "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
-                    "NTDSQUOTAS_DESCRIPTOR": ntdsquotas_descr,
-                    "LOSTANDFOUND_DESCRIPTOR": protected1wd_descr,
-                    "SERVICES_DESCRIPTOR": protected1_descr,
-                    "PHYSICALLOCATIONS_DESCRIPTOR": protected1wd_descr,
-                    "FORESTUPDATES_DESCRIPTOR": protected1wd_descr,
-                    "EXTENDEDRIGHTS_DESCRIPTOR": protected2_descr,
-                    "PARTITIONS_DESCRIPTOR": partitions_descr,
-                    "SITES_DESCRIPTOR": sites_descr,
-                    })
-
-            logger.info("Setting up display specifiers")
-            display_specifiers_ldif = read_ms_ldif(
-                setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
-            display_specifiers_ldif = substitute_var(display_specifiers_ldif,
-                                                     {"CONFIGDN": names.configdn})
-            check_all_substituted(display_specifiers_ldif)
-            samdb.add_ldif(display_specifiers_ldif)
-
-            logger.info("Modifying display specifiers")
-            setup_modify_ldif(samdb,
-                setup_path("provision_configuration_modify.ldif"), {
+    descr = b64encode(get_domain_descriptor(names.domainsid)).decode('utf8')
+    setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
+            "DOMAINDN": names.domaindn,
+            "DOMAINSID": str(names.domainsid),
+            "DESCRIPTOR": descr,
+            "DOMAINGUID": domainguid_line
+            })
+
+    setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
+        "DOMAINDN": names.domaindn,
+        "CREATTIME": str(samba.unix2nttime(int(time.time()))),
+        "NEXTRID": str(next_rid),
+        "DEFAULTSITE": names.sitename,
+        "CONFIGDN": names.configdn,
+        "POLICYGUID": policyguid,
+        "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
+        "SAMBA_VERSION_STRING": version,
+        "MIN_PWD_LENGTH": str(DEFAULT_MIN_PWD_LENGTH)
+    })
+
+    # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
+    if fill == FILL_FULL:
+        logger.info("Adding configuration container")
+        descr = b64encode(get_config_descriptor(names.domainsid)).decode('utf8')
+        setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
                 "CONFIGDN": names.configdn,
-                "DISPLAYSPECIFIERS_DESCRIPTOR": protected2_descr
+                "DESCRIPTOR": descr,
                 })
 
-        logger.info("Adding users container")
-        users_desc = b64encode(get_domain_users_descriptor(domainsid))
-        setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
+        # The LDIF here was created when the Schema object was constructed
+        ignore_checks_oid = "local_oid:%s:0" % samba.dsdb.DSDB_CONTROL_SKIP_DUPLICATES_CHECK_OID
+        logger.info("Setting up sam.ldb schema")
+        samdb.add_ldif(schema.schema_dn_add,
+                       controls=["relax:0", ignore_checks_oid])
+        samdb.modify_ldif(schema.schema_dn_modify,
+                          controls=[ignore_checks_oid])
+        samdb.write_prefixes_from_schema()
+        samdb.add_ldif(schema.schema_data, controls=["relax:0", ignore_checks_oid])
+        setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
+                       {"SCHEMADN": names.schemadn},
+                       controls=["relax:0", ignore_checks_oid])
+
+    # Now register this container in the root of the forest
+    msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
+    msg["subRefs"] = ldb.MessageElement(names.configdn, ldb.FLAG_MOD_ADD,
+                                        "subRefs")
+
+    samdb.invocation_id = invocationid
+
+    # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
+    if fill == FILL_FULL:
+        logger.info("Setting up sam.ldb configuration data")
+
+        partitions_descr = b64encode(get_config_partitions_descriptor(names.domainsid)).decode('utf8')
+        sites_descr = b64encode(get_config_sites_descriptor(names.domainsid)).decode('utf8')
+        ntdsquotas_descr = b64encode(get_config_ntds_quotas_descriptor(names.domainsid)).decode('utf8')
+        protected1_descr = b64encode(get_config_delete_protected1_descriptor(names.domainsid)).decode('utf8')
+        protected1wd_descr = b64encode(get_config_delete_protected1wd_descriptor(names.domainsid)).decode('utf8')
+        protected2_descr = b64encode(get_config_delete_protected2_descriptor(names.domainsid)).decode('utf8')
+
+        if "2008" in schema.base_schema:
+            # exclude 2012-specific changes if we're using a 2008 schema
+            incl_2012 = "#"
+        else:
+            incl_2012 = ""
+
+        setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
+                "CONFIGDN": names.configdn,
+                "NETBIOSNAME": names.netbiosname,
+                "DEFAULTSITE": names.sitename,
+                "DNSDOMAIN": names.dnsdomain,
+                "DOMAIN": names.domain,
+                "SCHEMADN": names.schemadn,
                 "DOMAINDN": names.domaindn,
-                "USERS_DESCRIPTOR": users_desc
+                "SERVERDN": names.serverdn,
+                "FOREST_FUNCTIONALITY": str(forestFunctionality),
+                "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
+                "NTDSQUOTAS_DESCRIPTOR": ntdsquotas_descr,
+                "LOSTANDFOUND_DESCRIPTOR": protected1wd_descr,
+                "SERVICES_DESCRIPTOR": protected1_descr,
+                "PHYSICALLOCATIONS_DESCRIPTOR": protected1wd_descr,
+                "FORESTUPDATES_DESCRIPTOR": protected1wd_descr,
+                "EXTENDEDRIGHTS_DESCRIPTOR": protected2_descr,
+                "PARTITIONS_DESCRIPTOR": partitions_descr,
+                "SITES_DESCRIPTOR": sites_descr,
                 })
-        logger.info("Modifying users container")
-        setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
-                "DOMAINDN": names.domaindn})
-        logger.info("Adding computers container")
-        computers_desc = b64encode(get_domain_computers_descriptor(domainsid))
-        setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
-                "DOMAINDN": names.domaindn,
-                "COMPUTERS_DESCRIPTOR": computers_desc
+
+        setup_add_ldif(samdb, setup_path("extended-rights.ldif"), {
+                "CONFIGDN": names.configdn,
+                "INC2012": incl_2012,
                 })
-        logger.info("Modifying computers container")
+
+        logger.info("Setting up display specifiers")
+        display_specifiers_ldif = read_ms_ldif(
+            setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
+        display_specifiers_ldif = substitute_var(display_specifiers_ldif,
+                                                 {"CONFIGDN": names.configdn})
+        check_all_substituted(display_specifiers_ldif)
+        samdb.add_ldif(display_specifiers_ldif)
+
+        logger.info("Modifying display specifiers and extended rights")
         setup_modify_ldif(samdb,
-            setup_path("provision_computers_modify.ldif"), {
-                "DOMAINDN": names.domaindn})
-        logger.info("Setting up sam.ldb data")
-        infrastructure_desc = b64encode(get_domain_infrastructure_descriptor(domainsid))
-        lostandfound_desc = b64encode(get_domain_delete_protected2_descriptor(domainsid))
-        system_desc = b64encode(get_domain_delete_protected1_descriptor(domainsid))
-        builtin_desc = b64encode(get_domain_builtin_descriptor(domainsid))
-        controllers_desc = b64encode(get_domain_controllers_descriptor(domainsid))
-        setup_add_ldif(samdb, setup_path("provision.ldif"), {
-            "CREATTIME": str(samba.unix2nttime(int(time.time()))),
+                          setup_path("provision_configuration_modify.ldif"), {
+                              "CONFIGDN": names.configdn,
+                              "DISPLAYSPECIFIERS_DESCRIPTOR": protected2_descr
+                          })
+
+    logger.info("Adding users container")
+    users_desc = b64encode(get_domain_users_descriptor(names.domainsid)).decode('utf8')
+    setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
             "DOMAINDN": names.domaindn,
-            "NETBIOSNAME": names.netbiosname,
-            "DEFAULTSITE": names.sitename,
-            "CONFIGDN": names.configdn,
-            "SERVERDN": names.serverdn,
-            "RIDAVAILABLESTART": str(next_rid + 600),
-            "POLICYGUID_DC": policyguid_dc,
-            "INFRASTRUCTURE_DESCRIPTOR": infrastructure_desc,
-            "LOSTANDFOUND_DESCRIPTOR": lostandfound_desc,
-            "SYSTEM_DESCRIPTOR": system_desc,
-            "BUILTIN_DESCRIPTOR": builtin_desc,
-            "DOMAIN_CONTROLLERS_DESCRIPTOR": controllers_desc,
+            "USERS_DESCRIPTOR": users_desc
             })
+    logger.info("Modifying users container")
+    setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
+            "DOMAINDN": names.domaindn})
+    logger.info("Adding computers container")
+    computers_desc = b64encode(get_domain_computers_descriptor(names.domainsid)).decode('utf8')
+    setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
+            "DOMAINDN": names.domaindn,
+            "COMPUTERS_DESCRIPTOR": computers_desc
+            })
+    logger.info("Modifying computers container")
+    setup_modify_ldif(samdb,
+                      setup_path("provision_computers_modify.ldif"), {
+                          "DOMAINDN": names.domaindn})
+    logger.info("Setting up sam.ldb data")
+    infrastructure_desc = b64encode(get_domain_infrastructure_descriptor(names.domainsid)).decode('utf8')
+    lostandfound_desc = b64encode(get_domain_delete_protected2_descriptor(names.domainsid)).decode('utf8')
+    system_desc = b64encode(get_domain_delete_protected1_descriptor(names.domainsid)).decode('utf8')
+    builtin_desc = b64encode(get_domain_builtin_descriptor(names.domainsid)).decode('utf8')
+    controllers_desc = b64encode(get_domain_controllers_descriptor(names.domainsid)).decode('utf8')
+    setup_add_ldif(samdb, setup_path("provision.ldif"), {
+        "CREATTIME": str(samba.unix2nttime(int(time.time()))),
+        "DOMAINDN": names.domaindn,
+        "NETBIOSNAME": names.netbiosname,
+        "DEFAULTSITE": names.sitename,
+        "CONFIGDN": names.configdn,
+        "SERVERDN": names.serverdn,
+        "RIDAVAILABLESTART": str(next_rid + 600),
+        "POLICYGUID_DC": policyguid_dc,
+        "INFRASTRUCTURE_DESCRIPTOR": infrastructure_desc,
+        "LOSTANDFOUND_DESCRIPTOR": lostandfound_desc,
+        "SYSTEM_DESCRIPTOR": system_desc,
+        "BUILTIN_DESCRIPTOR": builtin_desc,
+        "DOMAIN_CONTROLLERS_DESCRIPTOR": controllers_desc,
+    })
+
+    # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
+    if fill == FILL_FULL:
+        managedservice_descr = b64encode(get_managed_service_accounts_descriptor(names.domainsid)).decode('utf8')
+        setup_modify_ldif(samdb,
+                          setup_path("provision_configuration_references.ldif"), {
+                              "CONFIGDN": names.configdn,
+                              "SCHEMADN": names.schemadn})
 
-        # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
-        if fill == FILL_FULL:
-            setup_modify_ldif(samdb,
-                              setup_path("provision_configuration_references.ldif"), {
-                    "CONFIGDN": names.configdn,
-                    "SCHEMADN": names.schemadn})
-
-            logger.info("Setting up well known security principals")
-            protected1wd_descr = b64encode(get_config_delete_protected1wd_descriptor(domainsid))
-            setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), {
-                "CONFIGDN": names.configdn,
-                "WELLKNOWNPRINCIPALS_DESCRIPTOR": protected1wd_descr,
-                })
+        logger.info("Setting up well known security principals")
+        protected1wd_descr = b64encode(get_config_delete_protected1wd_descriptor(names.domainsid)).decode('utf8')
+        setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), {
+            "CONFIGDN": names.configdn,
+            "WELLKNOWNPRINCIPALS_DESCRIPTOR": protected1wd_descr,
+        }, controls=["relax:0", "provision:0"])
 
-        if fill == FILL_FULL or fill == FILL_SUBDOMAIN:
-            setup_modify_ldif(samdb,
-                              setup_path("provision_basedn_references.ldif"),
-                              {"DOMAINDN": names.domaindn})
+    if fill == FILL_FULL or fill == FILL_SUBDOMAIN:
+        setup_modify_ldif(samdb,
+                          setup_path("provision_basedn_references.ldif"), {
+                              "DOMAINDN": names.domaindn,
+                              "MANAGEDSERVICE_DESCRIPTOR": managedservice_descr
+                          })
 
-            logger.info("Setting up sam.ldb users and groups")
-            setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
-                "DOMAINDN": names.domaindn,
-                "DOMAINSID": str(domainsid),
-                "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')),
-                "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le'))
-                })
+        logger.info("Setting up sam.ldb users and groups")
+        setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
+            "DOMAINDN": names.domaindn,
+            "DOMAINSID": str(names.domainsid),
+            "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')).decode('utf8'),
+            "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le')).decode('utf8')
+        }, controls=["relax:0", "provision:0"])
+
+        logger.info("Setting up self join")
+        setup_self_join(samdb, admin_session_info, names=names, fill=fill,
+                        invocationid=invocationid,
+                        dns_backend=dns_backend,
+                        dnspass=dnspass,
+                        machinepass=machinepass,
+                        domainsid=names.domainsid,
+                        next_rid=next_rid,
+                        dc_rid=dc_rid,
+                        policyguid=policyguid,
+                        policyguid_dc=policyguid_dc,
+                        domainControllerFunctionality=domainControllerFunctionality,
+                        ntdsguid=ntdsguid)
+
+        ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
+        names.ntdsguid = samdb.searchone(basedn=ntds_dn,
+                                         attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE).decode('utf8')
+        assert isinstance(names.ntdsguid, string_types)
 
-            logger.info("Setting up self join")
-            setup_self_join(samdb, admin_session_info, names=names, fill=fill,
-                invocationid=invocationid,
-                dns_backend=dns_backend,
-                dnspass=dnspass,
-                machinepass=machinepass,
-                domainsid=domainsid,
-                next_rid=next_rid,
-                dc_rid=dc_rid,
-                policyguid=policyguid,
-                policyguid_dc=policyguid_dc,
-                domainControllerFunctionality=domainControllerFunctionality,
-                ntdsguid=ntdsguid)
-
-            ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
-            names.ntdsguid = samdb.searchone(basedn=ntds_dn,
-                attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
-            assert isinstance(names.ntdsguid, str)
-    except:
-        samdb.transaction_cancel()
-        raise
-    else:
-        samdb.transaction_commit()
-        return samdb
+    return samdb
 
 
 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
 POLICIES_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)(A;OICI;0x001301bf;;;PA)"
-SYSVOL_SERVICE="sysvol"
+SYSVOL_SERVICE = "sysvol"
+
 
 def set_dir_acl(path, acl, lp, domsid, use_ntvfs, passdb, service=SYSVOL_SERVICE):
     setntacl(lp, path, acl, domsid, use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service)
     for root, dirs, files in os.walk(path, topdown=False):
         for name in files:
             setntacl(lp, os.path.join(root, name), acl, domsid,
-                    use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service)
+                     use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service)
         for name in dirs:
             setntacl(lp, os.path.join(root, name), acl, domsid,
-                    use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service)
+                     use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service)
 
 
 def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, passdb):
@@ -1496,15 +1612,15 @@ def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, p
     # Set ACL for GPO root folder
     root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
     setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid),
-            use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=SYSVOL_SERVICE)
+             use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=SYSVOL_SERVICE)
 
-    res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
-                        attrs=["cn", "nTSecurityDescriptor"],
-                        expression="", scope=ldb.SCOPE_ONELEVEL)
+    res = samdb.search(base="CN=Policies,CN=System,%s" %(domaindn),
+                       attrs=["cn", "nTSecurityDescriptor"],
+                       expression="", scope=ldb.SCOPE_ONELEVEL)
 
     for policy in res:
         acl = ndr_unpack(security.descriptor,
-                         str(policy["nTSecurityDescriptor"])).as_sddl()
+                         policy["nTSecurityDescriptor"][0]).as_sddl()
         policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
         set_dir_acl(policy_path, dsacl2fsacl(acl, domainsid), lp,
                     str(domainsid), use_ntvfs,
@@ -1512,7 +1628,7 @@ def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, p
 
 
 def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain,
-        domaindn, lp, use_ntvfs):
+                 domaindn, lp, use_ntvfs):
     """Set the ACL for the sysvol share and the subfolders
 
     :param samdb: An LDB object on the SAM db
@@ -1527,6 +1643,31 @@ def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain,
     s4_passdb = None
 
     if not use_ntvfs:
+        s3conf = s3param.get_context()
+        s3conf.load(lp.configfile)
+
+        file = tempfile.NamedTemporaryFile(dir=os.path.abspath(sysvol))
+        try:
+            try:
+                smbd.set_simple_acl(file.name, 0o755, gid)
+            except OSError:
+                if not smbd.have_posix_acls():
+                    # This clue is only strictly correct for RPM and
+                    # Debian-like Linux systems, but hopefully other users
+                    # will get enough clue from it.
+                    raise ProvisioningError("Samba was compiled without the posix ACL support that s3fs requires.  "
+                                            "Try installing libacl1-dev or libacl-devel, then re-run configure and make.")
+
+                raise ProvisioningError("Your filesystem or build does not support posix ACLs, which s3fs requires.  "
+                                        "Try the mounting the filesystem with the 'acl' option.")
+            try:
+                smbd.chown(file.name, uid, gid)
+            except OSError:
+                raise ProvisioningError("Unable to chown a file on your filesystem.  "
+                                        "You may not be running provision as root.")
+        finally:
+            file.close()
+
         # This will ensure that the smbd code we are running when setting ACLs
         # is initialised with the smb.conf
         s3conf = s3param.get_context()
@@ -1550,7 +1691,6 @@ def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain,
         if domain_info["dns_domain"].upper() != dnsdomain.upper():
             raise ProvisioningError('Realm as seen by pdb_samba_dsdb [%s] does not match Realm as seen by the provision script [%s]!' % (domain_info["dns_domain"].upper(), dnsdomain.upper()))
 
-
     try:
         if use_ntvfs:
             os.chown(sysvol, -1, gid)
@@ -1559,33 +1699,47 @@ def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain,
     else:
         canchown = True
 
+    # use admin sid dn as user dn, since admin should own most of the files,
+    # the operation will be much faster
+    userdn = '<SID={}-{}>'.format(domainsid, security.DOMAIN_RID_ADMINISTRATOR)
+
+    flags = (auth.AUTH_SESSION_INFO_DEFAULT_GROUPS |
+             auth.AUTH_SESSION_INFO_AUTHENTICATED |
+             auth.AUTH_SESSION_INFO_SIMPLE_PRIVILEGES)
+
+    session_info = auth.user_session(samdb, lp_ctx=lp, dn=userdn,
+                                     session_info_flags=flags)
+
+    def _setntacl(path):
+        """A helper to reuse args"""
+        return setntacl(
+            lp, path, SYSVOL_ACL, str(domainsid),
+            use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=s4_passdb,
+            service=SYSVOL_SERVICE, session_info=session_info)
+
     # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
-    setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid), use_ntvfs=use_ntvfs,
-             skip_invalid_chown=True, passdb=s4_passdb,
-             service=SYSVOL_SERVICE)
+    _setntacl(sysvol)
     for root, dirs, files in os.walk(sysvol, topdown=False):
         for name in files:
             if use_ntvfs and canchown:
                 os.chown(os.path.join(root, name), -1, gid)
-            setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid),
-                     use_ntvfs=use_ntvfs, skip_invalid_chown=True,
-                     passdb=s4_passdb, service=SYSVOL_SERVICE)
+            _setntacl(os.path.join(root, name))
         for name in dirs:
             if use_ntvfs and canchown:
                 os.chown(os.path.join(root, name), -1, gid)
-            setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid),
-                     use_ntvfs=use_ntvfs, skip_invalid_chown=True,
-                     passdb=s4_passdb, service=SYSVOL_SERVICE)
+            _setntacl(os.path.join(root, name))
 
     # Set acls on Policy folder and policies folders
     set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, passdb=s4_passdb)
 
+
 def acl_type(direct_db_access):
     if direct_db_access:
         return "DB"
     else:
         return "VFS"
 
+
 def check_dir_acl(path, acl, lp, domainsid, direct_db_access):
     fsacl = getntacl(lp, path, direct_db_access=direct_db_access, service=SYSVOL_SERVICE)
     fsacl_sddl = fsacl.as_sddl(domainsid)
@@ -1597,7 +1751,9 @@ def check_dir_acl(path, acl, lp, domainsid, direct_db_access):
             fsacl = getntacl(lp, os.path.join(root, name),
                              direct_db_access=direct_db_access, service=SYSVOL_SERVICE)
             if fsacl is None:
-                raise ProvisioningError('%s ACL on GPO file %s %s not found!' % (acl_type(direct_db_access), os.path.join(root, name)))
+                raise ProvisioningError('%s ACL on GPO file %s not found!' %
+                                        (acl_type(direct_db_access),
+                                         os.path.join(root, name)))
             fsacl_sddl = fsacl.as_sddl(domainsid)
             if fsacl_sddl != acl:
                 raise ProvisioningError('%s ACL on GPO file %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), os.path.join(root, name), fsacl_sddl, acl))
@@ -1606,14 +1762,16 @@ def check_dir_acl(path, acl, lp, domainsid, direct_db_access):
             fsacl = getntacl(lp, os.path.join(root, name),
                              direct_db_access=direct_db_access, service=SYSVOL_SERVICE)
             if fsacl is None:
-                raise ProvisioningError('%s ACL on GPO directory %s %s not found!' % (acl_type(direct_db_access), os.path.join(root, name)))
+                raise ProvisioningError('%s ACL on GPO directory %s not found!'
+                                        % (acl_type(direct_db_access),
+                                           os.path.join(root, name)))
             fsacl_sddl = fsacl.as_sddl(domainsid)
             if fsacl_sddl != acl:
                 raise ProvisioningError('%s ACL on GPO directory %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), os.path.join(root, name), fsacl_sddl, acl))
 
 
 def check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp,
-        direct_db_access):
+                   direct_db_access):
     """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
     folders beneath.
 
@@ -1634,20 +1792,20 @@ def check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp,
     fsacl_sddl = fsacl.as_sddl(domainsid)
     if fsacl_sddl != POLICIES_ACL:
         raise ProvisioningError('%s ACL on policy root %s %s does not match expected value %s from provision' % (acl_type(direct_db_access), root_policy_path, fsacl_sddl, fsacl))
-    res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
-                        attrs=["cn", "nTSecurityDescriptor"],
-                        expression="", scope=ldb.SCOPE_ONELEVEL)
+    res = samdb.search(base="CN=Policies,CN=System,%s" %(domaindn),
+                       attrs=["cn", "nTSecurityDescriptor"],
+                       expression="", scope=ldb.SCOPE_ONELEVEL)
 
     for policy in res:
         acl = ndr_unpack(security.descriptor,
-                         str(policy["nTSecurityDescriptor"])).as_sddl()
+                         policy["nTSecurityDescriptor"][0]).as_sddl()
         policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
         check_dir_acl(policy_path, dsacl2fsacl(acl, domainsid), lp,
                       domainsid, direct_db_access)
 
 
 def checksysvolacl(samdb, netlogon, sysvol, domainsid, dnsdomain, domaindn,
-    lp):
+                   lp):
     """Set the ACL for the sysvol share and the subfolders
 
     :param samdb: An LDB object on the SAM db
@@ -1692,7 +1850,7 @@ def checksysvolacl(samdb, netlogon, sysvol, domainsid, dnsdomain, domaindn,
 
         # Check acls on Policy folder and policies folders
         check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp,
-                direct_db_access)
+                       direct_db_access)
 
 
 def interface_ips_v4(lp):
@@ -1716,7 +1874,7 @@ def interface_ips_v6(lp):
 
 
 def provision_fill(samdb, secrets_ldb, logger, names, paths,
-                   domainsid, schema=None,
+                   schema=None,
                    targetdir=None, samdb_fill=FILL_FULL,
                    hostip=None, hostip6=None,
                    next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None,
@@ -1724,7 +1882,8 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
                    invocationid=None, machinepass=None, ntdsguid=None,
                    dns_backend=None, dnspass=None,
                    serverrole=None, dom_for_fun_level=None,
-                   am_rodc=False, lp=None, use_ntvfs=False, skip_sysvolacl=False):
+                   am_rodc=False, lp=None, use_ntvfs=False,
+                   skip_sysvolacl=False, backend_store=None):
     # create/adapt the group policy GUIDs
     # Default GUID for default policy are described at
     # "How Core Group Policy Works"
@@ -1740,39 +1899,50 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
         invocationid = str(uuid.uuid4())
 
     if krbtgtpass is None:
-        krbtgtpass = samba.generate_random_password(128, 255)
+        krbtgtpass = samba.generate_random_machine_password(128, 255)
     if machinepass is None:
-        machinepass  = samba.generate_random_password(128, 255)
+        machinepass = samba.generate_random_machine_password(128, 255)
     if dnspass is None:
         dnspass = samba.generate_random_password(128, 255)
 
-    samdb = fill_samdb(samdb, lp, names, logger=logger,
-                   domainsid=domainsid, schema=schema, domainguid=domainguid,
-                   policyguid=policyguid, policyguid_dc=policyguid_dc,
-                   fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
-                   invocationid=invocationid, machinepass=machinepass,
-                   dns_backend=dns_backend, dnspass=dnspass,
-                   ntdsguid=ntdsguid, serverrole=serverrole,
-                   dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
-                   next_rid=next_rid, dc_rid=dc_rid)
-
-    if serverrole == "active directory domain controller":
+    samdb.transaction_start()
+    try:
+        samdb = fill_samdb(samdb, lp, names, logger=logger,
+                           schema=schema,
+                           policyguid=policyguid, policyguid_dc=policyguid_dc,
+                           fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
+                           invocationid=invocationid, machinepass=machinepass,
+                           dns_backend=dns_backend, dnspass=dnspass,
+                           ntdsguid=ntdsguid, serverrole=serverrole,
+                           dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
+                           next_rid=next_rid, dc_rid=dc_rid,
+                           backend_store=backend_store)
 
         # Set up group policies (domain policy and domain controller
         # policy)
-        create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
-                           policyguid_dc)
+        if serverrole == "active directory domain controller":
+            create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
+                               policyguid_dc)
+    except:
+        samdb.transaction_cancel()
+        raise
+    else:
+        samdb.transaction_commit()
+
+    if serverrole == "active directory domain controller":
+        # Continue setting up sysvol for GPO. This appears to require being
+        # outside a transaction.
         if not skip_sysvolacl:
             setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.root_uid,
-                         paths.root_gid, domainsid, names.dnsdomain,
+                         paths.root_gid, names.domainsid, names.dnsdomain,
                          names.domaindn, lp, use_ntvfs)
         else:
             logger.info("Setting acl on sysvol skipped")
 
         secretsdb_self_join(secrets_ldb, domain=names.domain,
-                realm=names.realm, dnsdomain=names.dnsdomain,
-                netbiosname=names.netbiosname, domainsid=domainsid,
-                machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
+                            realm=names.realm, dnsdomain=names.dnsdomain,
+                            netbiosname=names.netbiosname, domainsid=names.domainsid,
+                            machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
 
         # Now set up the right msDS-SupportedEncryptionTypes into the DB
         # In future, this might be determined from some configuration
@@ -1782,24 +1952,26 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
             msg = ldb.Message(ldb.Dn(samdb,
                                      samdb.searchone("distinguishedName",
                                                      expression="samAccountName=%s$" % names.netbiosname,
-                                                     scope=ldb.SCOPE_SUBTREE)))
+                                                     scope=ldb.SCOPE_SUBTREE).decode('utf8')))
             msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(
                 elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE,
                 name="msDS-SupportedEncryptionTypes")
             samdb.modify(msg)
-        except ldb.LdbError, (enum, estr):
+        except ldb.LdbError as e:
+            (enum, estr) = e.args
             if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
                 # It might be that this attribute does not exist in this schema
                 raise
 
-        setup_ad_dns(samdb, secrets_ldb, domainsid, names, paths, lp, logger,
+        setup_ad_dns(samdb, secrets_ldb, names, paths, lp, logger,
                      hostip=hostip, hostip6=hostip6, dns_backend=dns_backend,
                      dnspass=dnspass, os_level=dom_for_fun_level,
-                     targetdir=targetdir, site=DEFAULTSITE, fill_level=samdb_fill)
+                     targetdir=targetdir, fill_level=samdb_fill,
+                     backend_store=backend_store)
 
         domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
-                                     attribute="objectGUID")
-        assert isinstance(domainguid, str)
+                                     attribute="objectGUID").decode('utf8')
+        assert isinstance(domainguid, string_types)
 
     lastProvisionUSNs = get_last_provision_usn(samdb)
     maxUSN = get_max_usn(samdb, str(names.rootdn))
@@ -1810,12 +1982,12 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
 
     logger.info("Setting up sam.ldb rootDSE marking as synchronized")
     setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
-                      { 'NTDSGUID' : names.ntdsguid })
+                      {'NTDSGUID': names.ntdsguid})
 
     # fix any dangling GUIDs from the provision
     logger.info("Fixing provision GUIDs")
     chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True,
-            quiet=True)
+                  quiet=True)
     samdb.transaction_start()
     try:
         # a small number of GUIDs are missing because of ordering issues in the
@@ -1831,6 +2003,9 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
                                   'ipsecISAKMPReference',
                                   'ipsecNegotiationPolicyReference',
                                   'ipsecNFAReference'])
+        if chk.check_database(DN=names.schemadn, scope=ldb.SCOPE_SUBTREE,
+                              attrs=['attributeId', 'governsId']) != 0:
+            raise ProvisioningError("Duplicate attributeId or governsId in schema. Must be fixed manually!!")
     except:
         samdb.transaction_cancel()
         raise
@@ -1850,7 +2025,7 @@ _ROLES_MAP = {
     "member server": "member server",
     "standalone": "standalone server",
     "standalone server": "standalone server",
-    }
+}
 
 
 def sanitize_server_role(role):
@@ -1868,7 +2043,7 @@ def sanitize_server_role(role):
 
 
 def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain,
-        maxuid, maxgid):
+                            maxuid, maxgid):
     """Create AD entries for the fake ypserver.
 
     This is needed for being able to manipulate posix attrs via ADUC.
@@ -1877,10 +2052,10 @@ def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain,
     try:
         logger.info("Setting up fake yp server settings")
         setup_add_ldif(samdb, setup_path("ypServ30.ldif"), {
-        "DOMAINDN": domaindn,
-        "NETBIOSNAME": netbiosname,
-        "NISDOMAIN": nisdomain,
-         })
+            "DOMAINDN": domaindn,
+            "NETBIOSNAME": netbiosname,
+            "NISDOMAIN": nisdomain,
+        })
     except:
         samdb.transaction_cancel()
         raise
@@ -1888,20 +2063,64 @@ def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain,
         samdb.transaction_commit()
 
 
+def directory_create_or_exists(path, mode=0o755):
+    if not os.path.exists(path):
+        try:
+            os.mkdir(path, mode)
+        except OSError as e:
+            if e.errno in [errno.EEXIST]:
+                pass
+            else:
+                raise ProvisioningError("Failed to create directory %s: %s" % (path, e.strerror))
+
+
+def determine_host_ip(logger, lp, hostip=None):
+    if hostip is None:
+        logger.info("Looking up IPv4 addresses")
+        hostips = interface_ips_v4(lp)
+        if len(hostips) > 0:
+            hostip = hostips[0]
+            if len(hostips) > 1:
+                logger.warning("More than one IPv4 address found. Using %s",
+                               hostip)
+    if hostip == "127.0.0.1":
+        hostip = None
+    if hostip is None:
+        logger.warning("No IPv4 address will be assigned")
+
+    return hostip
+
+
+def determine_host_ip6(logger, lp, hostip6=None):
+    if hostip6 is None:
+        logger.info("Looking up IPv6 addresses")
+        hostips = interface_ips_v6(lp)
+        if hostips:
+            hostip6 = hostips[0]
+        if len(hostips) > 1:
+            logger.warning("More than one IPv6 address found. Using %s", hostip6)
+    if hostip6 is None:
+        logger.warning("No IPv6 address will be assigned")
+
+    return hostip6
+
+
 def provision(logger, session_info, smbconf=None,
-        targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
-        domaindn=None, schemadn=None, configdn=None, serverdn=None,
-        domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
-        next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None,
-        krbtgtpass=None, domainguid=None, policyguid=None, policyguid_dc=None,
-        dns_backend=None, dns_forwarder=None, dnspass=None,
-        invocationid=None, machinepass=None, ntdsguid=None,
-        root=None, nobody=None, users=None, backup=None, aci=None,
-        serverrole=None, dom_for_fun_level=None, backend_type=None,
-        sitename=None, ol_mmr_urls=None, ol_olc=None, slapd_path=None,
-        useeadb=False, am_rodc=False, lp=None, use_ntvfs=False,
-        use_rfc2307=False, maxuid=None, maxgid=None, skip_sysvolacl=True,
-        ldap_backend_forced_uri=None, nosync=False, ldap_dryrun_mode=False, ldap_backend_extra_port=None):
+              targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
+              domaindn=None, schemadn=None, configdn=None, serverdn=None,
+              domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
+              next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None,
+              krbtgtpass=None, domainguid=None, policyguid=None, policyguid_dc=None,
+              dns_backend=None, dns_forwarder=None, dnspass=None,
+              invocationid=None, machinepass=None, ntdsguid=None,
+              root=None, nobody=None, users=None, backup=None, aci=None,
+              serverrole=None, dom_for_fun_level=None, backend_type=None,
+              sitename=None, ol_mmr_urls=None, ol_olc=None, slapd_path=None,
+              useeadb=False, am_rodc=False, lp=None, use_ntvfs=False,
+              use_rfc2307=False, maxuid=None, maxgid=None, skip_sysvolacl=True,
+              ldap_backend_forced_uri=None, nosync=False, ldap_dryrun_mode=False,
+              ldap_backend_extra_port=None, base_schema=None,
+              plaintext_secrets=False, backend_store=None):
     """Provision samba4
 
     :note: caution, this wipes all existing data!
@@ -1918,11 +2137,11 @@ def provision(logger, session_info, smbconf=None,
 
     if backend_type is None:
         backend_type = "ldb"
+    if backend_store is None:
+        backend_store = get_default_backend_store()
 
     if domainsid is None:
         domainsid = security.random_sid()
-    else:
-        domainsid = security.dom_sid(domainsid)
 
     root_uid = findnss_uid([root or "root"])
     nobody_uid = findnss_uid([nobody or "nobody"])
@@ -1984,112 +2203,61 @@ def provision(logger, session_info, smbconf=None,
         lp = samba.param.LoadParm()
     lp.load(smbconf)
     names = guess_names(lp=lp, hostname=hostname, domain=domain,
-        dnsdomain=realm, serverrole=serverrole, domaindn=domaindn,
-        configdn=configdn, schemadn=schemadn, serverdn=serverdn,
-        sitename=sitename, rootdn=rootdn, domain_names_forced=(samdb_fill == FILL_DRS))
+                        dnsdomain=realm, serverrole=serverrole, domaindn=domaindn,
+                        configdn=configdn, schemadn=schemadn, serverdn=serverdn,
+                        sitename=sitename, rootdn=rootdn, domain_names_forced=(samdb_fill == FILL_DRS))
     paths = provision_paths_from_lp(lp, names.dnsdomain)
 
     paths.bind_gid = bind_gid
-    paths.root_uid = root_uid;
+    paths.root_uid = root_uid
     paths.root_gid = root_gid
 
-    if hostip is None:
-        logger.info("Looking up IPv4 addresses")
-        hostips = interface_ips_v4(lp)
-        if len(hostips) > 0:
-            hostip = hostips[0]
-            if len(hostips) > 1:
-                logger.warning("More than one IPv4 address found. Using %s",
-                    hostip)
-    if hostip == "127.0.0.1":
-        hostip = None
-    if hostip is None:
-        logger.warning("No IPv4 address will be assigned")
-
-    if hostip6 is None:
-        logger.info("Looking up IPv6 addresses")
-        hostips = interface_ips_v6(lp)
-        if hostips:
-            hostip6 = hostips[0]
-        if len(hostips) > 1:
-            logger.warning("More than one IPv6 address found. Using %s", hostip6)
-    if hostip6 is None:
-        logger.warning("No IPv6 address will be assigned")
-
+    hostip = determine_host_ip(logger, lp, hostip)
+    hostip6 = determine_host_ip6(logger, lp, hostip6)
     names.hostip = hostip
     names.hostip6 = hostip6
+    names.domainguid = domainguid
+    names.domainsid = domainsid
+    names.forestsid = domainsid
 
     if serverrole is None:
         serverrole = lp.get("server role")
 
-    if not os.path.exists(paths.private_dir):
-        os.mkdir(paths.private_dir)
-    if not os.path.exists(os.path.join(paths.private_dir, "tls")):
-        os.makedirs(os.path.join(paths.private_dir, "tls"), 0700)
-    if not os.path.exists(paths.state_dir):
-        os.mkdir(paths.state_dir)
+    directory_create_or_exists(paths.private_dir, 0o700)
+    directory_create_or_exists(paths.binddns_dir, 0o770)
+    directory_create_or_exists(os.path.join(paths.private_dir, "tls"))
+    directory_create_or_exists(paths.state_dir)
+    if not plaintext_secrets:
+        setup_encrypted_secrets_key(paths.encrypted_secrets_key_path)
 
     if paths.sysvol and not os.path.exists(paths.sysvol):
-        os.makedirs(paths.sysvol, 0775)
-
-    if not use_ntvfs and serverrole == "active directory domain controller":
-        s3conf = s3param.get_context()
-        s3conf.load(lp.configfile)
-
-        if paths.sysvol is None:
-            raise MissingShareError("sysvol", paths.smbconf)
-
-        file = tempfile.NamedTemporaryFile(dir=os.path.abspath(paths.sysvol))
-        try:
-            try:
-                smbd.set_simple_acl(file.name, 0755, root_gid)
-            except Exception:
-                if not smbd.have_posix_acls():
-                    # This clue is only strictly correct for RPM and
-                    # Debian-like Linux systems, but hopefully other users
-                    # will get enough clue from it.
-                    raise ProvisioningError("Samba was compiled without the posix ACL support that s3fs requires.  Try installing libacl1-dev or libacl-devel, then re-run configure and make.")
+        os.makedirs(paths.sysvol, 0o775)
 
-                raise ProvisioningError("Your filesystem or build does not support posix ACLs, which s3fs requires.  Try the mounting the filesystem with the 'acl' option.")
-            try:
-                smbd.chown(file.name, root_uid, root_gid)
-            except Exception:
-                raise ProvisioningError("Unable to chown a file on your filesystem.  You may not be running provision as root.")
-        finally:
-            file.close()
-
-    ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
+    ldapi_url = "ldapi://%s" % urllib_quote(paths.s4_ldapi_path, safe="")
 
     schema = Schema(domainsid, invocationid=invocationid,
-        schemadn=names.schemadn)
+                    schemadn=names.schemadn, base_schema=base_schema)
 
     if backend_type == "ldb":
         provision_backend = LDBBackend(backend_type, paths=paths,
-            lp=lp,
-            names=names, logger=logger)
-    elif backend_type == "existing":
-        # If support for this is ever added back, then the URI will need to be
-        # specified again
-        provision_backend = ExistingBackend(backend_type, paths=paths,
-            lp=lp,
-            names=names, logger=logger,
-            ldap_backend_forced_uri=ldap_backend_forced_uri)
+                                       lp=lp,
+                                       names=names, logger=logger)
     elif backend_type == "fedora-ds":
         provision_backend = FDSBackend(backend_type, paths=paths,
-            lp=lp,
-            names=names, logger=logger, domainsid=domainsid,
-            schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
-            slapd_path=slapd_path,
-            root=root)
+                                       lp=lp,
+                                       names=names, logger=logger, domainsid=domainsid,
+                                       schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
+                                       slapd_path=slapd_path,
+                                       root=root)
     elif backend_type == "openldap":
         provision_backend = OpenLDAPBackend(backend_type, paths=paths,
-            lp=lp,
-            names=names, logger=logger, domainsid=domainsid,
-            schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
-            slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls,
-            ldap_backend_extra_port=ldap_backend_extra_port,
-            ldap_dryrun_mode=ldap_dryrun_mode, nosync=nosync,
-            ldap_backend_forced_uri=ldap_backend_forced_uri)
+                                            lp=lp,
+                                            names=names, logger=logger, domainsid=domainsid,
+                                            schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
+                                            slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls,
+                                            ldap_backend_extra_port=ldap_backend_extra_port,
+                                            ldap_dryrun_mode=ldap_dryrun_mode, nosync=nosync,
+                                            ldap_backend_forced_uri=ldap_backend_forced_uri)
     else:
         raise ValueError("Unknown LDAP backend type selected")
 
@@ -2104,8 +2272,8 @@ def provision(logger, session_info, smbconf=None,
 
     logger.info("Setting up secrets.ldb")
     secrets_ldb = setup_secretsdb(paths,
-        session_info=session_info,
-        backend_credentials=provision_backend.credentials, lp=lp)
+                                  session_info=session_info,
+                                  backend_credentials=provision_backend.credentials, lp=lp)
 
     try:
         logger.info("Setting up the registry")
@@ -2125,7 +2293,9 @@ def provision(logger, session_info, smbconf=None,
         samdb = setup_samdb(paths.samdb, session_info,
                             provision_backend, lp, names, logger=logger,
                             serverrole=serverrole,
-                            schema=schema, fill=samdb_fill, am_rodc=am_rodc)
+                            schema=schema, fill=samdb_fill, am_rodc=am_rodc,
+                            plaintext_secrets=plaintext_secrets,
+                            backend_store=backend_store)
 
         if serverrole == "active directory domain controller":
             if paths.netlogon is None:
@@ -2135,34 +2305,44 @@ def provision(logger, session_info, smbconf=None,
                 raise MissingShareError("sysvol", paths.smbconf)
 
             if not os.path.isdir(paths.netlogon):
-                os.makedirs(paths.netlogon, 0755)
+                os.makedirs(paths.netlogon, 0o755)
 
         if adminpass is None:
             adminpass = samba.generate_random_password(12, 32)
             adminpass_generated = True
         else:
-            adminpass = unicode(adminpass, 'utf-8')
+            if isinstance(adminpass, binary_type):
+                adminpass = adminpass.decode('utf-8')
             adminpass_generated = False
 
         if samdb_fill == FILL_FULL:
             provision_fill(samdb, secrets_ldb, logger, names, paths,
-                    schema=schema, targetdir=targetdir, samdb_fill=samdb_fill,
-                    hostip=hostip, hostip6=hostip6, domainsid=domainsid,
-                    next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass,
-                    krbtgtpass=krbtgtpass, domainguid=domainguid,
-                    policyguid=policyguid, policyguid_dc=policyguid_dc,
-                    invocationid=invocationid, machinepass=machinepass,
-                    ntdsguid=ntdsguid, dns_backend=dns_backend,
-                    dnspass=dnspass, serverrole=serverrole,
-                    dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
-                    lp=lp, use_ntvfs=use_ntvfs,
-                           skip_sysvolacl=skip_sysvolacl)
+                           schema=schema, targetdir=targetdir, samdb_fill=samdb_fill,
+                           hostip=hostip, hostip6=hostip6,
+                           next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass,
+                           krbtgtpass=krbtgtpass,
+                           policyguid=policyguid, policyguid_dc=policyguid_dc,
+                           invocationid=invocationid, machinepass=machinepass,
+                           ntdsguid=ntdsguid, dns_backend=dns_backend,
+                           dnspass=dnspass, serverrole=serverrole,
+                           dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
+                           lp=lp, use_ntvfs=use_ntvfs,
+                           skip_sysvolacl=skip_sysvolacl,
+                           backend_store=backend_store)
+
+        if not is_heimdal_built():
+            create_kdc_conf(paths.kdcconf, realm, domain, os.path.dirname(lp.get("log file")))
+            logger.info("The Kerberos KDC configuration for Samba AD is "
+                        "located at %s", paths.kdcconf)
 
         create_krb5_conf(paths.krb5conf,
                          dnsdomain=names.dnsdomain, hostname=names.hostname,
                          realm=names.realm)
-        logger.info("A Kerberos configuration suitable for Samba 4 has been "
+        logger.info("A Kerberos configuration suitable for Samba AD has been "
                     "generated at %s", paths.krb5conf)
+        logger.info("Merge the contents of this file with your system "
+                    "krb5.conf or replace it with this one. Do not create a "
+                    "symlink!")
 
         if serverrole == "active directory domain controller":
             create_dns_update_list(lp, logger, paths)
@@ -2177,16 +2357,42 @@ def provision(logger, session_info, smbconf=None,
     # Now commit the secrets.ldb to disk
     secrets_ldb.transaction_commit()
 
-    # the commit creates the dns.keytab, now chown it
-    dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
-    if os.path.isfile(dns_keytab_path) and paths.bind_gid is not None:
+    # the commit creates the dns.keytab in the private directory
+    private_dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
+    bind_dns_keytab_path = os.path.join(paths.binddns_dir, paths.dns_keytab)
+
+    if os.path.isfile(private_dns_keytab_path):
+        if os.path.isfile(bind_dns_keytab_path):
+            try:
+                os.unlink(bind_dns_keytab_path)
+            except OSError as e:
+                logger.error("Failed to remove %s: %s" %
+                             (bind_dns_keytab_path, e.strerror))
+
+        # link the dns.keytab to the bind-dns directory
         try:
-            os.chmod(dns_keytab_path, 0640)
-            os.chown(dns_keytab_path, -1, paths.bind_gid)
-        except OSError:
-            if not os.environ.has_key('SAMBA_SELFTEST'):
-                logger.info("Failed to chown %s to bind gid %u",
-                            dns_keytab_path, paths.bind_gid)
+            os.link(private_dns_keytab_path, bind_dns_keytab_path)
+        except OSError as e:
+            logger.error("Failed to create link %s -> %s: %s" %
+                         (private_dns_keytab_path, bind_dns_keytab_path, e.strerror))
+
+        # chown the dns.keytab in the bind-dns directory
+        if paths.bind_gid is not None:
+            try:
+                os.chmod(paths.binddns_dir, 0o770)
+                os.chown(paths.binddns_dir, -1, paths.bind_gid)
+            except OSError:
+                if 'SAMBA_SELFTEST' not in os.environ:
+                    logger.info("Failed to chown %s to bind gid %u",
+                                paths.binddns_dir, paths.bind_gid)
+
+            try:
+                os.chmod(bind_dns_keytab_path, 0o640)
+                os.chown(bind_dns_keytab_path, -1, paths.bind_gid)
+            except OSError:
+                if 'SAMBA_SELFTEST' not in os.environ:
+                    logger.info("Failed to chown %s to bind gid %u",
+                                bind_dns_keytab_path, paths.bind_gid)
 
     result = ProvisionResult()
     result.server_role = serverrole
@@ -2209,33 +2415,33 @@ def provision(logger, session_info, smbconf=None,
 
     if use_rfc2307:
         provision_fake_ypserver(logger=logger, samdb=samdb,
-                domaindn=names.domaindn, netbiosname=names.netbiosname,
-                nisdomain=names.domain.lower(), maxuid=maxuid, maxgid=maxgid)
+                                domaindn=names.domaindn, netbiosname=names.netbiosname,
+                                nisdomain=names.domain.lower(), maxuid=maxuid, maxgid=maxgid)
 
     return result
 
 
 def provision_become_dc(smbconf=None, targetdir=None,
-        realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=None,
-        serverdn=None, domain=None, hostname=None, domainsid=None,
-        adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None,
-        policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None,
-        dns_backend=None, root=None, nobody=None, users=None,
-        backup=None, serverrole=None, ldap_backend=None,
-        ldap_backend_type=None, sitename=None, debuglevel=1, use_ntvfs=False):
+                        realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=None,
+                        serverdn=None, domain=None, hostname=None, domainsid=None,
+                        adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None,
+                        policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None,
+                        dns_backend=None, root=None, nobody=None, users=None,
+                        backup=None, serverrole=None, ldap_backend=None,
+                        ldap_backend_type=None, sitename=None, debuglevel=1, use_ntvfs=False):
 
     logger = logging.getLogger("provision")
     samba.set_debug_level(debuglevel)
 
     res = provision(logger, system_session(),
-        smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
-        realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
-        configdn=configdn, serverdn=serverdn, domain=domain,
-        hostname=hostname, hostip=None, domainsid=domainsid,
-        machinepass=machinepass,
-        serverrole="active directory domain controller",
-        sitename=sitename, dns_backend=dns_backend, dnspass=dnspass,
-        use_ntvfs=use_ntvfs)
+                    smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
+                    realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
+                    configdn=configdn, serverdn=serverdn, domain=domain,
+                    hostname=hostname, hostip=None, domainsid=domainsid,
+                    machinepass=machinepass,
+                    serverrole="active directory domain controller",
+                    sitename=sitename, dns_backend=dns_backend, dnspass=dnspass,
+                    use_ntvfs=use_ntvfs)
     res.lp.set("debuglevel", str(debuglevel))
     return res
 
@@ -2252,7 +2458,7 @@ def create_krb5_conf(path, dnsdomain, hostname, realm):
             "DNSDOMAIN": dnsdomain,
             "HOSTNAME": hostname,
             "REALM": realm,
-        })
+    })
 
 
 class ProvisioningError(Exception):