python: bulk replace dict.iteritems to items for py3
[kai/samba-autobuild/.git] / python / samba / provision / __init__.py
index 940bb1b4e020196a196d70ab97627bb5640d6e12..1709e75184e74f3e1064f249dddb5ae2138b8aa3 100644 (file)
@@ -1,5 +1,5 @@
 # Unix SMB/CIFS implementation.
 # 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
 
 # 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
 __docformat__ = "restructuredText"
 
 from base64 import b64encode
+import errno
 import os
 import os
+import stat
 import re
 import pwd
 import grp
 import re
 import pwd
 import grp
@@ -38,6 +40,7 @@ import socket
 import urllib
 import string
 import tempfile
 import urllib
 import string
 import tempfile
+import samba.dsdb
 
 import ldb
 
 
 import ldb
 
@@ -55,6 +58,7 @@ from samba import (
     substitute_var,
     valid_netbios_name,
     version,
     substitute_var,
     valid_netbios_name,
     version,
+    is_heimdal_built,
     )
 from samba.dcerpc import security, misc
 from samba.dcerpc.misc import (
     )
 from samba.dcerpc import security, misc
 from samba.dcerpc.misc import (
@@ -96,6 +100,7 @@ from samba.descriptor import (
     get_dns_partition_descriptor,
     get_dns_forest_microsoft_dns_descriptor,
     get_dns_domain_microsoft_dns_descriptor,
     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,
     )
 from samba.provision.common import (
     setup_path,
@@ -117,13 +122,16 @@ import samba.registry
 from samba.schema import Schema
 from samba.samdb import SamDB
 from samba.dbchecker import dbcheck
 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_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"
 
 DEFAULTSITE = "Default-First-Site-Name"
 LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
 
+DEFAULT_MIN_PWD_LENGTH = 7
+
 
 class ProvisionPaths(object):
 
 
 class ProvisionPaths(object):
 
@@ -143,6 +151,7 @@ class ProvisionPaths(object):
         self.dns = None
         self.winsdb = None
         self.private_dir = None
         self.dns = None
         self.winsdb = None
         self.private_dir = None
+        self.binddns_dir = None
         self.state_dir = None
 
 
         self.state_dir = None
 
 
@@ -420,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"])
         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
         if ecode == ldb.ERR_NO_SUCH_OBJECT:
             return None
         raise
@@ -473,7 +483,7 @@ class ProvisionResult(object):
     def report_logger(self, logger):
         """Report this provision result to a logger."""
         logger.info(
     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)
             "be ready to use")
         if self.adminpass_generated:
             logger.info("Admin password:        %s", self.adminpass)
@@ -529,6 +539,7 @@ def provision_paths_from_lp(lp, dnsdomain):
     """
     paths = ProvisionPaths()
     paths.private_dir = lp.get("private dir")
     """
     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
     paths.state_dir = lp.get("state directory")
 
     # This is stored without path prefix for the "privateKeytab" attribute in
@@ -541,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.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.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.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.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"
     paths.hklm = "hklm.ldb"
     paths.hkcr = "hkcr.ldb"
     paths.hkcu = "hkcu.ldb"
@@ -639,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 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
 
     if rootdn is None:
        rootdn = domaindn
 
@@ -667,7 +700,6 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
 
     return names
 
 
     return names
 
-
 def make_smbconf(smbconf, hostname, domain, realm, targetdir,
                  serverrole=None, eadb=False, use_ntvfs=False, lp=None,
                  global_param=None):
 def make_smbconf(smbconf, hostname, domain, realm, targetdir,
                  serverrole=None, eadb=False, use_ntvfs=False, lp=None,
                  global_param=None):
@@ -742,11 +774,11 @@ def make_smbconf(smbconf, hostname, domain, realm, targetdir,
     f = open(smbconf, 'w')
     try:
         f.write("[globals]\n")
     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")
 
             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")
             f.write("[%s]\n" % name)
             f.write("\tpath = %s\n" % path)
             f.write("\tread only = no\n")
@@ -759,11 +791,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
     # 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,
 
 
 def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
@@ -786,8 +814,9 @@ def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
 
 
 def setup_samdb_partitions(samdb_path, logger, lp, session_info,
 
 
 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.
     """Setup the partitions for the SAM database.
 
     Alternatively, provision() may call this, and then populate the database.
@@ -816,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
 
     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"), {
     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,
         })
 
 
         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")
                 })
 
         logger.info("Setting up sam.ldb rootDSE")
@@ -876,7 +925,7 @@ def secretsdb_self_join(secretsdb, domain,
         msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
         msg["privateKeytab"] = ["secrets.keytab"]
 
         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:
     msg["samAccountName"] = ["%s$" % netbiosname]
     msg["secureChannelType"] = [str(secure_channel_type)]
     if domainsid is not None:
@@ -897,7 +946,10 @@ def secretsdb_self_join(secretsdb, domain,
 
     if len(res) == 1:
         msg["priorSecret"] = [res[0]["secret"][0]]
 
     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]]
 
         try:
             msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
@@ -928,7 +980,7 @@ def secretsdb_self_join(secretsdb, domain,
 def setup_secretsdb(paths, session_info, backend_credentials, lp):
     """Setup the secrets database.
 
 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.
        it's up to the caller to do this job.
 
     :param path: Path to the secrets database.
@@ -944,6 +996,10 @@ def setup_secretsdb(paths, session_info, backend_credentials, lp):
     if os.path.exists(keytab_path):
         os.unlink(keytab_path)
 
     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)
     dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
     if os.path.exists(dns_keytab_path):
         os.unlink(dns_keytab_path)
@@ -994,6 +1050,30 @@ def setup_privileges(path, session_info, lp):
     privilege_ldb.erase()
     privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
 
     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.
 
 def setup_registry(path, session_info, lp):
     """Setup the registry.
@@ -1151,7 +1231,7 @@ def getpolicypath(sysvolpath, dnsdomain, guid):
 
 def create_gpo_struct(policy_path):
     if not os.path.exists(policy_path):
 
 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")
     f = open(os.path.join(policy_path, "GPT.INI"), 'w')
     try:
         f.write("[General]\r\nVersion=0")
@@ -1159,10 +1239,10 @@ def create_gpo_struct(policy_path):
         f.close()
     p = os.path.join(policy_path, "MACHINE")
     if not os.path.exists(p):
         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):
     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):
 
 
 def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
@@ -1181,7 +1261,8 @@ def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
 
 
 def setup_samdb(path, session_info, provision_backend, lp, names,
 
 
 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!
     """Setup a complete SAM Database.
 
     :note: This will wipe the main SAM database file!
@@ -1190,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,
     # 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)
+        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
 
     # Load the database, but don's load the global schema and don't connect
     # quite yet
@@ -1211,7 +1293,8 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
     # DB
     try:
         samdb.connect(path)
     # 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:
         if (num == ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS):
             raise ProvisioningError("Permission denied connecting to %s, are you running as root?" % path)
         else:
@@ -1228,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,
 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
 
     if next_rid is None:
         next_rid = 1000
@@ -1299,7 +1383,8 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
         "CONFIGDN": names.configdn,
         "POLICYGUID": policyguid,
         "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
         "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
         })
 
     # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
@@ -1312,13 +1397,17 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
                 })
 
         # The LDIF here was created when the Schema object was constructed
                 })
 
         # 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")
         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.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.write_prefixes_from_schema()
-        samdb.add_ldif(schema.schema_data, controls=["relax:0"])
+        samdb.add_ldif(schema.schema_data, controls=["relax:0", ignore_checks_oid])
         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
-                       {"SCHEMADN": names.schemadn})
+                       {"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))
 
     # Now register this container in the root of the forest
     msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
@@ -1338,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))
 
         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,
         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
                 "CONFIGDN": names.configdn,
                 "NETBIOSNAME": names.netbiosname,
@@ -1359,6 +1454,11 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
                 "SITES_DESCRIPTOR": sites_descr,
                 })
 
                 "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'))
         logger.info("Setting up display specifiers")
         display_specifiers_ldif = read_ms_ldif(
             setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
@@ -1367,7 +1467,7 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
         check_all_substituted(display_specifiers_ldif)
         samdb.add_ldif(display_specifiers_ldif)
 
         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,
         setup_modify_ldif(samdb,
             setup_path("provision_configuration_modify.ldif"), {
             "CONFIGDN": names.configdn,
@@ -1417,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:
 
     # 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,
         setup_modify_ldif(samdb,
                           setup_path("provision_configuration_references.ldif"), {
                 "CONFIGDN": names.configdn,
@@ -1427,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,
         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,
 
     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"), {
 
         logger.info("Setting up sam.ldb users and groups")
         setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
@@ -1440,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'))
             "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,
 
         logger.info("Setting up self join")
         setup_self_join(samdb, admin_session_info, names=names, fill=fill,
@@ -1531,7 +1634,7 @@ def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain,
         file = tempfile.NamedTemporaryFile(dir=os.path.abspath(sysvol))
         try:
             try:
         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
             except OSError:
                 if not smbd.have_posix_acls():
                     # This clue is only strictly correct for RPM and
@@ -1747,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,
                    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"
     # create/adapt the group policy GUIDs
     # Default GUID for default policy are described at
     # "How Core Group Policy Works"
@@ -1763,9 +1867,9 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
         invocationid = str(uuid.uuid4())
 
     if krbtgtpass is None:
         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:
     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)
 
     if dnspass is None:
         dnspass = samba.generate_random_password(128, 255)
 
@@ -1779,8 +1883,14 @@ 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,
                        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)
+        if serverrole == "active directory domain controller":
+            create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
+                               policyguid_dc)
     except:
         samdb.transaction_cancel()
         raise
     except:
         samdb.transaction_cancel()
         raise
@@ -1788,11 +1898,8 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
         samdb.transaction_commit()
 
     if serverrole == "active directory domain controller":
         samdb.transaction_commit()
 
     if serverrole == "active directory domain controller":
-
-        # Set up group policies (domain policy and domain controller
-        # policy)
-        create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
-                           policyguid_dc)
+        # 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, names.domainsid, names.dnsdomain,
         if not skip_sysvolacl:
             setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.root_uid,
                          paths.root_gid, names.domainsid, names.dnsdomain,
@@ -1818,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)
                 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
             if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
                 # It might be that this attribute does not exist in this schema
                 raise
@@ -1826,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,
         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")
 
         domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
                                      attribute="objectGUID")
@@ -1862,6 +1971,9 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
                                   'ipsecISAKMPReference',
                                   'ipsecNegotiationPolicyReference',
                                   'ipsecNFAReference'])
                                   '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
     except:
         samdb.transaction_cancel()
         raise
@@ -1918,6 +2030,15 @@ def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain,
     else:
         samdb.transaction_commit()
 
     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,
         targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
 
 def provision(logger, session_info, smbconf=None,
         targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
@@ -1932,7 +2053,9 @@ def provision(logger, session_info, smbconf=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,
         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!
     """Provision samba4
 
     :note: caution, this wipes all existing data!
@@ -1949,6 +2072,8 @@ def provision(logger, session_info, smbconf=None,
 
     if backend_type is None:
         backend_type = "ldb"
 
     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()
 
     if domainsid is None:
         domainsid = security.random_sid()
@@ -2054,20 +2179,20 @@ def provision(logger, session_info, smbconf=None,
     if serverrole is None:
         serverrole = lp.get("server role")
 
     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):
 
     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,
 
     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,
 
     if backend_type == "ldb":
         provision_backend = LDBBackend(backend_type, paths=paths,
@@ -2131,7 +2256,9 @@ def provision(logger, session_info, smbconf=None,
         samdb = setup_samdb(paths.samdb, session_info,
                             provision_backend, lp, names, logger=logger,
                             serverrole=serverrole,
         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:
 
         if serverrole == "active directory domain controller":
             if paths.netlogon is None:
@@ -2141,7 +2268,7 @@ def provision(logger, session_info, smbconf=None,
                 raise MissingShareError("sysvol", paths.smbconf)
 
             if not os.path.isdir(paths.netlogon):
                 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)
 
         if adminpass is None:
             adminpass = samba.generate_random_password(12, 32)
@@ -2162,13 +2289,22 @@ def provision(logger, session_info, smbconf=None,
                     dnspass=dnspass, serverrole=serverrole,
                     dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
                     lp=lp, use_ntvfs=use_ntvfs,
                     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)
 
         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)
                     "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)
 
         if serverrole == "active directory domain controller":
             create_dns_update_list(lp, logger, paths)
@@ -2183,16 +2319,42 @@ def provision(logger, session_info, smbconf=None,
     # Now commit the secrets.ldb to disk
     secrets_ldb.transaction_commit()
 
     # 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:
         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
 
     result = ProvisionResult()
     result.server_role = serverrole