python: bulk replace dict.iteritems to items for py3
[kai/samba-autobuild/.git] / python / samba / provision / __init__.py
index b001c544fb9a65e429348fbea70dee715b278773..1709e75184e74f3e1064f249dddb5ae2138b8aa3 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
@@ -27,7 +27,9 @@
 __docformat__ = "restructuredText"
 
 from base64 import b64encode
+import errno
 import os
+import stat
 import re
 import pwd
 import grp
@@ -56,6 +58,7 @@ from samba import (
     substitute_var,
     valid_netbios_name,
     version,
+    is_heimdal_built,
     )
 from samba.dcerpc import security, misc
 from samba.dcerpc.misc import (
@@ -97,6 +100,7 @@ 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,
@@ -118,13 +122,16 @@ import samba.registry
 from samba.schema import Schema
 from samba.samdb import SamDB
 from samba.dbchecker import dbcheck
-from samba.provision.kerberos import make_kdcconf
+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"
 DEFAULTSITE = "Default-First-Site-Name"
 LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
 
+DEFAULT_MIN_PWD_LENGTH = 7
+
 
 class ProvisionPaths(object):
 
@@ -144,6 +151,7 @@ class ProvisionPaths(object):
         self.dns = None
         self.winsdb = None
         self.private_dir = None
+        self.binddns_dir = None
         self.state_dir = None
 
 
@@ -421,7 +429,8 @@ def get_last_provision_usn(sam):
         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):
+    except ldb.LdbError as e1:
+        (ecode, emsg) = e1.args
         if ecode == ldb.ERR_NO_SUCH_OBJECT:
             return None
         raise
@@ -474,7 +483,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)
@@ -530,6 +539,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
@@ -542,15 +552,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"
@@ -640,6 +656,22 @@ 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
 
@@ -670,7 +702,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
 
 def make_smbconf(smbconf, hostname, domain, realm, targetdir,
                  serverrole=None, eadb=False, use_ntvfs=False, lp=None,
-                 global_param=None, kdcconfdir=None):
+                 global_param=None):
     """Create a new smb.conf file based on a couple of basic settings.
     """
     assert smbconf is not None
@@ -731,11 +763,6 @@ def make_smbconf(smbconf, hostname, domain, realm, targetdir,
                 statedir = lp.get("state directory")
             lp.set("xattr_tdb:file", os.path.abspath(os.path.join(statedir, "xattr.tdb")))
 
-    make_kdcconf(realm, domain, kdcconfdir, os.path.dirname(lp.get("log file")))
-    if kdcconfdir is not None:
-        kdcconf = "%s/kdc.conf" % kdcconfdir
-        lp.set("mit kdc config", kdcconf)
-
     shares = {}
     if serverrole == "active directory domain controller":
         shares["sysvol"] = os.path.join(lp.get("state directory"), "sysvol")
@@ -747,11 +774,11 @@ def make_smbconf(smbconf, hostname, domain, realm, targetdir,
     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")
@@ -788,7 +815,8 @@ def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
 
 def setup_samdb_partitions(samdb_path, logger, lp, session_info,
                            provision_backend, names, serverrole,
-                           erase=False):
+                           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.
@@ -817,17 +845,37 @@ 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")
@@ -898,7 +946,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]]
@@ -945,6 +996,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)
@@ -995,6 +1050,30 @@ def setup_privileges(path, session_info, lp):
     privilege_ldb.erase()
     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, 'w') as f:
+        key = samba.generate_random_bytes(16)
+        f.write(key)
+
 
 def setup_registry(path, session_info, lp):
     """Setup the registry.
@@ -1152,7 +1231,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")
@@ -1160,10 +1239,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):
@@ -1182,7 +1261,8 @@ def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
 
 
 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!
@@ -1191,7 +1271,8 @@ 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)
+        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
@@ -1212,7 +1293,8 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
     # DB
     try:
         samdb.connect(path)
-    except ldb.LdbError, (num, string_error):
+    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:
@@ -1229,7 +1311,8 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
 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):
+        dom_for_fun_level=None, schema=None, next_rid=None, dc_rid=None,
+        backend_store=None):
 
     if next_rid is None:
         next_rid = 1000
@@ -1300,7 +1383,8 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
         "CONFIGDN": names.configdn,
         "POLICYGUID": policyguid,
         "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
-        "SAMBA_VERSION_STRING": version
+        "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
@@ -1343,6 +1427,12 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
         protected1wd_descr = b64encode(get_config_delete_protected1wd_descriptor(names.domainsid))
         protected2_descr = b64encode(get_config_delete_protected2_descriptor(names.domainsid))
 
+        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,
@@ -1364,6 +1454,11 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
                 "SITES_DESCRIPTOR": sites_descr,
                 })
 
+        setup_add_ldif(samdb, setup_path("extended-rights.ldif"), {
+                "CONFIGDN": names.configdn,
+                "INC2012" : incl_2012,
+                })
+
         logger.info("Setting up display specifiers")
         display_specifiers_ldif = read_ms_ldif(
             setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
@@ -1372,7 +1467,7 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
         check_all_substituted(display_specifiers_ldif)
         samdb.add_ldif(display_specifiers_ldif)
 
-        logger.info("Modifying display specifiers")
+        logger.info("Modifying display specifiers and extended rights")
         setup_modify_ldif(samdb,
             setup_path("provision_configuration_modify.ldif"), {
             "CONFIGDN": names.configdn,
@@ -1422,6 +1517,7 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
 
     # 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))
         setup_modify_ldif(samdb,
                           setup_path("provision_configuration_references.ldif"), {
                 "CONFIGDN": names.configdn,
@@ -1432,12 +1528,14 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
         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})
+                          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"), {
@@ -1445,7 +1543,7 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
             "DOMAINSID": str(names.domainsid),
             "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')),
             "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le'))
-            })
+            }, controls=["relax:0", "provision:0"])
 
         logger.info("Setting up self join")
         setup_self_join(samdb, admin_session_info, names=names, fill=fill,
@@ -1536,7 +1634,7 @@ def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain,
         file = tempfile.NamedTemporaryFile(dir=os.path.abspath(sysvol))
         try:
             try:
-                smbd.set_simple_acl(file.name, 0755, gid)
+                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
@@ -1752,7 +1850,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"
@@ -1784,7 +1883,8 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
                        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)
+                       next_rid=next_rid, dc_rid=dc_rid,
+                       backend_store=backend_store)
 
         # Set up group policies (domain policy and domain controller
         # policy)
@@ -1825,7 +1925,8 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
                 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
@@ -1833,7 +1934,8 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
         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, fill_level=samdb_fill)
+                     targetdir=targetdir, fill_level=samdb_fill,
+                     backend_store=backend_store)
 
         domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
                                      attribute="objectGUID")
@@ -1928,8 +2030,17 @@ def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain,
     else:
         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 provision(logger, session_info, smbconf=None, kdcconfdir=None,
+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,
@@ -1942,7 +2053,9 @@ def provision(logger, session_info, smbconf=None, kdcconfdir=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):
+        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!
@@ -1959,6 +2072,8 @@ def provision(logger, session_info, smbconf=None, kdcconfdir=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()
@@ -2013,13 +2128,11 @@ def provision(logger, session_info, smbconf=None, kdcconfdir=None,
             make_smbconf(smbconf, hostname, domain, realm,
                          targetdir, serverrole=serverrole,
                          eadb=useeadb, use_ntvfs=use_ntvfs,
-                         lp=lp, global_param=global_param,
-                         kdcconfdir=kdcconfdir)
+                         lp=lp, global_param=global_param)
     else:
         make_smbconf(smbconf, hostname, domain, realm, targetdir,
                      serverrole=serverrole,
-                     eadb=useeadb, use_ntvfs=use_ntvfs, lp=lp, global_param=global_param,
-                     kdcconfdir=kdcconfdir)
+                     eadb=useeadb, use_ntvfs=use_ntvfs, lp=lp, global_param=global_param)
 
     if lp is None:
         lp = samba.param.LoadParm()
@@ -2066,20 +2179,20 @@ def provision(logger, session_info, smbconf=None, kdcconfdir=None,
     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)
+        os.makedirs(paths.sysvol, 0o775)
 
     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,
@@ -2143,7 +2256,9 @@ def provision(logger, session_info, smbconf=None, kdcconfdir=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:
@@ -2153,7 +2268,7 @@ def provision(logger, session_info, smbconf=None, kdcconfdir=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)
@@ -2174,13 +2289,22 @@ def provision(logger, session_info, smbconf=None, kdcconfdir=None,
                     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)
+                    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 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)
@@ -2195,16 +2319,42 @@ def provision(logger, session_info, smbconf=None, kdcconfdir=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 not os.environ.has_key('SAMBA_SELFTEST'):
+                    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 not os.environ.has_key('SAMBA_SELFTEST'):
+                    logger.info("Failed to chown %s to bind gid %u",
+                                bind_dns_keytab_path, paths.bind_gid)
 
     result = ProvisionResult()
     result.server_role = serverrole