s4-selftest: silence warnings about bind chown
[samba.git] / source4 / scripting / python / samba / provision.py
index 0a2483790fba90011643dc5bc95643ae188792c9..96e7aa502fee9aba4aca47eafe6c57702cc7e30d 100644 (file)
@@ -1,8 +1,8 @@
-#
+
 # Unix SMB/CIFS implementation.
 # backend code for provisioning a Samba4 server
 
-# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
 #
 
 from base64 import b64encode
 import os
+import re
 import pwd
 import grp
+import logging
 import time
 import uuid
 import socket
-import param
-import registry
 import urllib
 import shutil
 
 import ldb
 
 from samba.auth import system_session, admin_session
-from samba import glue, version, Ldb, substitute_var, valid_netbios_name
+import samba
+from samba import version, Ldb, substitute_var, valid_netbios_name
 from samba import check_all_substituted, read_and_sub_file, setup_file
-from samba import DS_DOMAIN_FUNCTION_2003, DS_DC_FUNCTION_2008
+from samba.dsdb import DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008_R2, ENC_ALL_TYPES
 from samba.dcerpc import security
 from samba.dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
 from samba.idmap import IDmapDB
+from samba.ms_display_specifiers import read_ms_ldif
 from samba.ntacls import setntacl, dsacl2fsacl
 from samba.ndr import ndr_pack,ndr_unpack
+from samba.provisionbackend import (
+    ExistingBackend,
+    FDSBackend,
+    LDBBackend,
+    OpenLDAPBackend,
+    )
+import samba.param
+import samba.registry
 from samba.schema import Schema
-from ms_display_specifiers import read_ms_ldif
-from samba.provisionbackend import LDBBackend, ExistingBackend, FDSBackend, OpenLDAPBackend
-from provisionexceptions import ProvisioningError, InvalidNetbiosName
+from samba.samdb import SamDB
 
 __docformat__ = "restructuredText"
+DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
+DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9"
 
 def find_setup_dir():
     """Find the setup directory used by provision."""
-    dirname = os.path.dirname(__file__)
-    if "/site-packages/" in dirname:
-        prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
+    import sys
+    for prefix in [sys.prefix,
+            os.path.join(os.path.dirname(__file__), "../../../..")]:
         for suffix in ["share/setup", "share/samba/setup", "setup"]:
             ret = os.path.join(prefix, suffix)
             if os.path.isdir(ret):
                 return ret
     # In source tree
+    dirname = os.path.dirname(__file__)
     ret = os.path.join(dirname, "../../../setup")
     if os.path.isdir(ret):
         return ret
@@ -74,6 +85,7 @@ def find_setup_dir():
 # hard coded at this point, but will probably be changed when
 # we enable different fsmo roles
 
+
 def get_config_descriptor(domain_sid):
     sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
@@ -147,10 +159,10 @@ def get_domain_descriptor(domain_sid):
     return ndr_pack(sec)
 
 DEFAULTSITE = "Default-First-Site-Name"
-
-# Exception classes
+LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
 
 class ProvisionPaths(object):
+
     def __init__(self):
         self.shareconf = None
         self.hklm = None
@@ -170,6 +182,7 @@ class ProvisionPaths(object):
 
 
 class ProvisionNames(object):
+
     def __init__(self):
         self.rootdn = None
         self.domaindn = None
@@ -183,15 +196,109 @@ class ProvisionNames(object):
         self.hostname = None
         self.sitename = None
         self.smbconf = None
+
+
+def update_provision_usn(samdb, low, high, replace=False):
+    """Update the field provisionUSN in sam.ldb
+
+    This field is used to track range of USN modified by provision and 
+    upgradeprovision.
+    This value is used afterward by next provision to figure out if 
+    the field have been modified since last provision.
+
+    :param samdb: An LDB object connect to sam.ldb
+    :param low: The lowest USN modified by this upgrade
+    :param high: The highest USN modified by this upgrade
+    :param replace: A boolean indicating if the range should replace any 
+                    existing one or appended (default)
+    """
+
+    tab = []
+    if not replace:
+        entry = samdb.search(expression="(&(dn=@PROVISION)(%s=*))" % \
+                                LAST_PROVISION_USN_ATTRIBUTE, base="", 
+                                scope=ldb.SCOPE_SUBTREE,
+                                attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
+        for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
+            tab.append(str(e))
+
+    tab.append("%s-%s" % (low, high))
+    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)
+    samdb.modify(delta)
+
+
+def set_provision_usn(samdb, low, high):
+    """Set the field provisionUSN in sam.ldb
+    This field is used to track range of USN modified by provision and
+    upgradeprovision.
+    This value is used afterward by next provision to figure out if
+    the field have been modified since last provision.
+
+    :param samdb: An LDB object connect to sam.ldb
+    :param low: The lowest USN modified by this upgrade
+    :param high: The highest USN modified by this upgrade"""
+    tab = []
+    tab.append("%s-%s" % (low, high))
+    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)
+    samdb.add(delta)
+
+
+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
+    :param basedn: A string containing the base DN of the provision
+                    (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"])
+    return res[0]["uSNChanged"]
     
+def get_last_provision_usn(sam):
+    """Get the lastest USN modified by a provision or an upgradeprovision
+
+    :param sam: An LDB object pointing to the sam.ldb
+    :return an integer corresponding to the highest USN modified by 
+            (upgrade)provision, 0 is this value is unknown"""
+
+    entry = sam.search(expression="(&(dn=@PROVISION)(%s=*))" % \
+                        LAST_PROVISION_USN_ATTRIBUTE,
+                        base="", scope=ldb.SCOPE_SUBTREE,
+                        attrs=[LAST_PROVISION_USN_ATTRIBUTE])
+    if len(entry):
+        range = []
+        idx = 0
+        p = re.compile(r'-')
+        for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
+            tab = p.split(str(r))
+            range.append(tab[0])
+            range.append(tab[1])
+            idx = idx + 1
+        return range
+    else:
+        return None
 
 class ProvisionResult(object):
+
     def __init__(self):
         self.paths = None
         self.domaindn = None
         self.lp = None
         self.samdb = None
 
+
 def check_install(lp, session_info, credentials):
     """Check whether the current install seems ok.
     
@@ -201,9 +308,9 @@ def check_install(lp, session_info, credentials):
     """
     if lp.get("realm") == "":
         raise Exception("Realm empty")
-    ldb = Ldb(lp.get("sam database"), session_info=session_info, 
+    samdb = Ldb(lp.get("sam database"), session_info=session_info, 
             credentials=credentials, lp=lp)
-    if len(ldb.search("(cn=Administrator)")) != 1:
+    if len(samdb.search("(cn=Administrator)")) != 1:
         raise ProvisioningError("No administrator account found")
 
 
@@ -219,7 +326,7 @@ def findnss(nssfn, names):
             return nssfn(name)
         except KeyError:
             pass
-    raise KeyError("Unable to find user/group %r" % names)
+    raise KeyError("Unable to find user/group in %r" % names)
 
 
 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
@@ -236,10 +343,10 @@ def setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
     """
     assert isinstance(ldif_path, str)
     data = read_and_sub_file(ldif_path, subst_vars)
-    ldb.add_ldif(data,controls)
+    ldb.add_ldif(data, controls)
 
 
-def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
+def setup_modify_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
     """Modify a ldb in the private dir.
     
     :param ldb: LDB object.
@@ -247,8 +354,7 @@ def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
     :param subst_vars: Optional dictionary with substitution variables.
     """
     data = read_and_sub_file(ldif_path, subst_vars)
-
-    ldb.modify_ldif(data)
+    ldb.modify_ldif(data, controls)
 
 
 def setup_ldb(ldb, ldif_path, subst_vars):
@@ -267,7 +373,8 @@ def setup_ldb(ldb, ldif_path, subst_vars):
     except:
         ldb.transaction_cancel()
         raise
-    ldb.transaction_commit()
+    else:
+        ldb.transaction_commit()
 
 
 def provision_paths_from_lp(lp, dnsdomain):
@@ -278,7 +385,11 @@ def provision_paths_from_lp(lp, dnsdomain):
     """
     paths = ProvisionPaths()
     paths.private_dir = lp.get("private dir")
+
+    # This is stored without path prefix for the "privateKeytab" attribute in
+    # "secrets_dns.ldif".
     paths.dns_keytab = "dns.keytab"
+    paths.keytab = "secrets.keytab"
 
     paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
     paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
@@ -287,6 +398,7 @@ def provision_paths_from_lp(lp, dnsdomain):
     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")
@@ -301,13 +413,9 @@ def provision_paths_from_lp(lp, dnsdomain):
     paths.hku = "hku.ldb"
     paths.hkpd = "hkpd.ldb"
     paths.hkpt = "hkpt.ldb"
-
     paths.sysvol = lp.get("path", "sysvol")
-
     paths.netlogon = lp.get("path", "netlogon")
-
     paths.smbconf = lp.configfile
-
     return paths
 
 
@@ -359,7 +467,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
         domain = domain.upper()
 
         if lp.get("workgroup").upper() != domain:
-            raise ProvisioningError("guess_names: Workgroup '%s' in %s must match chosen domain '%s'!  Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile))
+            raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'!  Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile))
 
         if domaindn is None:
             domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
@@ -407,7 +515,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
     
 
 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
-                 targetdir, sid_generator,eadb):
+                 targetdir, sid_generator="internal", eadb=False):
     """Create a new smb.conf file based on a couple of basic settings.
     """
     assert smbconf is not None
@@ -435,7 +543,7 @@ def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
     assert realm is not None
     realm = realm.upper()
 
-    default_lp = param.LoadParm()
+    default_lp = samba.param.LoadParm()
     #Load non-existant file
     if os.path.exists(smbconf):
         default_lp.load(smbconf)
@@ -444,7 +552,7 @@ def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
             privdir = os.path.join(targetdir, "private")
         else:
             privdir = default_lp.get("private dir")
-        posixeadb_line = "posix:eadb = " + os.path.abspath(os.path.join(privdir,"eadb.tdb"))
+        posixeadb_line = "posix:eadb = " + os.path.abspath(os.path.join(privdir, "eadb.tdb"))
     else:
         posixeadb_line = ""
 
@@ -462,6 +570,13 @@ def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
     else:
         sid_generator_line = "sid generator = " + sid_generator
 
+    used_setup_dir = setup_path("")
+    default_setup_dir = default_lp.get("setup directory")
+    setupdir_line = ""
+    if used_setup_dir != default_setup_dir:
+        setupdir_line = "setup directory = %s" % used_setup_dir
+        default_lp.set("setup directory", used_setup_dir)
+
     sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
     netlogon = os.path.join(sysvol, realm.lower(), "scripts")
 
@@ -473,10 +588,11 @@ def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
             "SERVERROLE": serverrole,
             "NETLOGONPATH": netlogon,
             "SYSVOLPATH": sysvol,
+            "SETUPDIRECTORY_LINE": setupdir_line,
             "SIDGENERATOR_LINE": sid_generator_line,
             "PRIVATEDIR_LINE": privatedir_line,
             "LOCKDIR_LINE": lockdir_line,
-                       "POSIXEADB_LINE": posixeadb_line
+            "POSIXEADB_LINE": posixeadb_line
             })
 
 
@@ -492,16 +608,15 @@ def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
     :param nobody_uid: uid of the UNIX nobody user.
     :param users_gid: gid of the UNIX users group.
     :param wheel_gid: gid of the UNIX wheel group."""
-
     idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
     idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
     
     idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
     idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
 
-def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, 
-                           provision_backend, names, schema,
-                           serverrole, 
+
+def setup_samdb_partitions(samdb_path, setup_path, logger, lp, session_info, 
+                           provision_backend, names, schema, serverrole, 
                            erase=False):
     """Setup the partitions for the SAM database. 
     
@@ -533,7 +648,7 @@ def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
 
     samdb.transaction_start()
     try:
-        message("Setting up sam.ldb partitions and settings")
+        logger.info("Setting up sam.ldb partitions and settings")
         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
                 "SCHEMADN": ldb.Dn(schema.ldb, names.schemadn).get_casefold(), 
                 "CONFIGDN": ldb.Dn(schema.ldb, names.configdn).get_casefold(),
@@ -547,14 +662,13 @@ def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
                 "SERVER_ROLE": serverrole
                 })
 
-        message("Setting up sam.ldb rootDSE")
+        logger.info("Setting up sam.ldb rootDSE")
         setup_samdb_rootdse(samdb, setup_path, names)
-
     except:
         samdb.transaction_cancel()
         raise
-
-    samdb.transaction_commit()
+    else:
+        samdb.transaction_commit()
 
         
 def secretsdb_self_join(secretsdb, domain, 
@@ -574,57 +688,77 @@ def secretsdb_self_join(secretsdb, domain,
            "priorChanged",
            "krb5Keytab",
            "privateKeytab"]
-    
 
-    msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
-    msg["secureChannelType"] = str(secure_channel_type)
-    msg["flatname"] = [domain]
-    msg["objectClass"] = ["top", "primaryDomain"]
     if realm is not None:
       if dnsdomain is None:
-        dnsdomain = realm.lower()
+          dnsdomain = realm.lower()
+      dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower())
+    else:
+      dnsname = None
+    shortname = netbiosname.lower()
+    
+    #We don't need to set msg["flatname"] here, because rdn_name will handle it, and it causes problems for modifies anyway
+    msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
+    msg["secureChannelType"] = [str(secure_channel_type)]
+    msg["objectClass"] = ["top", "primaryDomain"]
+    if dnsname is not None:
       msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
-      msg["realm"] = realm
-      msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
+      msg["realm"] = [realm]
+      msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())]
       msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
       msg["privateKeytab"] = ["secrets.keytab"]
 
-
     msg["secret"] = [machinepass]
     msg["samAccountName"] = ["%s$" % netbiosname]
     msg["secureChannelType"] = [str(secure_channel_type)]
     if domainsid is not None:
         msg["objectSid"] = [ndr_pack(domainsid)]
     
+    # This complex expression tries to ensure that we don't have more
+    # than one record for this SID, realm or netbios domain at a time,
+    # 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))" % (domain, realm, str(domainsid))), 
+                           expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
                            scope=ldb.SCOPE_ONELEVEL)
     
     for del_msg in res:
-      if del_msg.dn is not msg.dn:
         secretsdb.delete(del_msg.dn)
 
     res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
 
     if len(res) == 1:
-      msg["priorSecret"] = res[0]["secret"]
-      msg["priorWhenChanged"] = res[0]["whenChanged"]
+      msg["priorSecret"] = [res[0]["secret"][0]]
+      msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
 
-      if res["privateKeytab"] is not None:
-        msg["privateKeytab"] = res[0]["privateKeytab"]
+      try:
+        msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
+      except KeyError:
+        pass
 
-      if res["krb5Keytab"] is not None:
-        msg["krb5Keytab"] = res[0]["krb5Keytab"]
+      try:
+        msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]]
+      except KeyError:
+        pass
 
       for el in msg:
-        el.set_flags(ldb.FLAG_MOD_REPLACE)
-        secretsdb.modify(msg)
+          if el != 'dn':
+              msg[el].set_flags(ldb.FLAG_MOD_REPLACE)
+      secretsdb.modify(msg)
+      secretsdb.rename(res[0].dn, msg.dn)
     else:
+      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 ])
+      msg["servicePrincipalName"] = spn
+
       secretsdb.add(msg)
 
 
-def secretsdb_setup_dns(secretsdb, setup_path, private_dir,
+def secretsdb_setup_dns(secretsdb, setup_path, names, private_dir,
                         realm, dnsdomain,
                         dns_keytab_path, dnspass):
     """Add DNS specific bits to a secrets database.
@@ -643,12 +777,17 @@ def secretsdb_setup_dns(secretsdb, setup_path, private_dir,
             "DNSDOMAIN": dnsdomain,
             "DNS_KEYTAB": dns_keytab_path,
             "DNSPASS_B64": b64encode(dnspass),
+            "HOSTNAME": names.hostname,
+            "DNSNAME" : '%s.%s' % (names.netbiosname.lower(), names.dnsdomain.lower())
             })
 
 
-def setup_secretsdb(path, setup_path, session_info, backend_credentials, lp):
+def setup_secretsdb(paths, setup_path, session_info, backend_credentials, lp):
     """Setup the secrets database.
 
+   :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.
     :param setup_path: Get the path to a setup file.
     :param session_info: Session info.
@@ -656,8 +795,19 @@ def setup_secretsdb(path, setup_path, session_info, backend_credentials, lp):
     :param lp: Loadparm context
     :return: LDB handle for the created secrets database
     """
-    if os.path.exists(path):
-        os.unlink(path)
+    if os.path.exists(paths.secrets):
+        os.unlink(paths.secrets)
+
+    keytab_path = os.path.join(paths.private_dir, paths.keytab)
+    if os.path.exists(keytab_path):
+        os.unlink(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)
+
+    path = paths.secrets
+
     secrets_ldb = Ldb(path, session_info=session_info, 
                       lp=lp)
     secrets_ldb.erase()
@@ -665,22 +815,26 @@ def setup_secretsdb(path, setup_path, session_info, backend_credentials, lp):
     secrets_ldb = Ldb(path, session_info=session_info, 
                       lp=lp)
     secrets_ldb.transaction_start()
-    secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
-
-    if backend_credentials is not None and 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())
-                    })
-        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())
-                    })
-
-    return secrets_ldb
+    try:
+        secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
+
+        if backend_credentials is not None and 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())
+                        })
+            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())
+                        })
+
+        return secrets_ldb
+    except:
+        secrets_ldb.transaction_cancel()
+        raise
 
 def setup_privileges(path, setup_path, session_info, lp):
     """Setup the privileges database.
@@ -708,10 +862,10 @@ def setup_registry(path, setup_path, session_info, lp):
     :param credentials: Credentials
     :param lp: Loadparm context
     """
-    reg = registry.Registry()
-    hive = registry.open_ldb(path, session_info=session_info, 
+    reg = samba.registry.Registry()
+    hive = samba.registry.open_ldb(path, session_info=session_info, 
                          lp_ctx=lp)
-    reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
+    reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
     provision_reg = setup_path("provision.reg")
     assert os.path.exists(provision_reg)
     reg.diff_apply(provision_reg)
@@ -745,10 +899,6 @@ def setup_samdb_rootdse(samdb, setup_path, names):
     """
     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
         "SCHEMADN": names.schemadn, 
-        "NETBIOSNAME": names.netbiosname,
-        "DNSDOMAIN": names.dnsdomain,
-        "REALM": names.realm,
-        "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
         "DOMAINDN": names.domaindn,
         "ROOTDN": names.rootdn,
         "CONFIGDN": names.configdn,
@@ -758,7 +908,7 @@ def setup_samdb_rootdse(samdb, setup_path, names):
 
 def setup_self_join(samdb, names,
                     machinepass, dnspass, 
-                    domainsid, invocationid, setup_path,
+                    domainsid, next_rid, invocationid, setup_path,
                     policyguid, policyguid_dc, domainControllerFunctionality,
                     ntdsguid):
     """Join a host to its own domain."""
@@ -780,6 +930,7 @@ def setup_self_join(samdb, names,
               "REALM": names.realm,
               "DOMAIN": names.domain,
               "DOMAINSID": str(domainsid),
+              "DCRID": str(next_rid),
               "DNSDOMAIN": names.dnsdomain,
               "SAMBA_VERSION_STRING": version,
               "NTDSGUID": ntdsguid_line,
@@ -793,7 +944,7 @@ def setup_self_join(samdb, names,
               "DOMAINDN": names.domaindn})
     
     # add the NTDSGUID based SPNs
-    ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
+    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)
@@ -809,72 +960,116 @@ def setup_self_join(samdb, names,
               "SERVERDN": names.serverdn,
               "NETBIOSNAME": names.netbiosname,
               "NTDSGUID": names.ntdsguid,
+              "RIDALLOCATIONSTART": str(next_rid + 100),
+              "RIDALLOCATIONEND": str(next_rid + 100 + 499),
+              })
+
+    # This is partially Samba4 specific and should be replaced by the correct
+    # DNS AD-style setup
+    setup_add_ldif(samdb, setup_path("provision_dns_add.ldif"), {
+              "DNSDOMAIN": names.dnsdomain,
+              "DOMAINDN": names.domaindn,
               "DNSPASS_B64": b64encode(dnspass),
+              "HOSTNAME" : names.hostname,
+              "DNSNAME" : '%s.%s' % (names.netbiosname.lower(), names.dnsdomain.lower())
               })
 
+def getpolicypath(sysvolpath, dnsdomain, guid):
+    """Return the physical path of policy given its guid.
+
+    :param sysvolpath: Path to the sysvol folder
+    :param dnsdomain: DNS name of the AD domain
+    :param guid: The GUID of the policy
+    :return: A string with the complete path to the policy folder
+    """
+
+    if guid[0] != "{":
+        guid = "{%s}" % guid
+    policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
+    return policy_path
 
-def setup_gpo(paths,names,samdb,policyguid,policyguid_dc,domainsid):
-    policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
-                               "{" + policyguid + "}")
-    os.makedirs(policy_path, 0755)
+def create_gpo_struct(policy_path):
+    if not os.path.exists(policy_path):
+        os.makedirs(policy_path, 0775)
     open(os.path.join(policy_path, "GPT.INI"), 'w').write(
-                      "[General]\r\nVersion=65543")
-    os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
-    os.makedirs(os.path.join(policy_path, "USER"), 0755)
-
-    policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
-                                  "{" + policyguid_dc + "}")
-    os.makedirs(policy_path_dc, 0755)
-    open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
-                      "[General]\r\nVersion=2")
-    os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
-    os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
-
-
-def setup_samdb(path, setup_path, session_info, provision_backend, lp, 
-                names, message, 
-                domainsid, domainguid, policyguid, policyguid_dc,
-                fill, adminpass, krbtgtpass, 
-                machinepass, invocationid, dnspass, ntdsguid,
-                serverrole, dom_for_fun_level=None,
-                schema=None):
+                      "[General]\r\nVersion=0")
+    p = os.path.join(policy_path, "MACHINE")
+    if not os.path.exists(p):
+        os.makedirs(p, 0775)
+    p = os.path.join(policy_path, "USER")
+    if not os.path.exists(p):
+        os.makedirs(p, 0775)
+
+
+def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
+    """Create the default GPO for a domain
+
+        :param sysvolpath: Physical path for the sysvol folder
+        :param dnsdomain: DNS domain name of the AD domain
+        :param policyguid: GUID of the default domain policy
+        :param policyguid_dc: GUID of the default domain controler policy
+    """
+
+    policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
+    create_gpo_struct(policy_path)
+
+    policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
+    create_gpo_struct(policy_path)
+
+
+def setup_samdb(path, setup_path, session_info, provision_backend, lp, names,
+        logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
+        adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
+        serverrole, am_rodc=False, dom_for_fun_level=None, schema=None,
+        next_rid=1000):
     """Setup a complete SAM Database.
     
     :note: This will wipe the main SAM database file!
     """
 
+
+    # Provision does not make much sense values larger than 1000000000
+    # as the upper range of the rIDAvailablePool is 1073741823 and
+    # we don't want to create a domain that cannot allocate rids.
+    if next_rid < 1000 or next_rid > 1000000000:
+        error = "You want to run SAMBA 4 with a next_rid of %u, " % (next_rid)
+        error += "the valid range is %u-%u. The default is %u." % (1000, 1000000000, 1000)
+        raise ProvisioningError(error)
+
     # ATTENTION: Do NOT change these default values without discussion with the
     # team and/or release manager. They have a big impact on the whole program!
-    domainControllerFunctionality = DS_DC_FUNCTION_2008
+    domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2
 
     if dom_for_fun_level is None:
         dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
-    if dom_for_fun_level < DS_DOMAIN_FUNCTION_2003:
-        message("You want to run SAMBA 4 on a domain and forest function level lower than Windows 2003 (Native). This is not recommended")
 
     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). This won't work!")
+        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!")
 
     domainFunctionality = dom_for_fun_level
     forestFunctionality = dom_for_fun_level
 
     # Also wipes the database
-    setup_samdb_partitions(path, setup_path, message=message, lp=lp,
-                           provision_backend=provision_backend, session_info=session_info,
-                           names=names, 
-                           serverrole=serverrole, schema=schema)
+    setup_samdb_partitions(path, setup_path, logger=logger, lp=lp,
+        provision_backend=provision_backend, session_info=session_info,
+        names=names, serverrole=serverrole, schema=schema)
 
-    if (schema == None):
+    if schema is None:
         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn)
 
-    # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
-    samdb = Ldb(session_info=session_info, 
-                credentials=provision_backend.credentials, lp=lp)
+    # Load the database, but don's load the global schema and don't connect quite yet
+    samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
+                  credentials=provision_backend.credentials, lp=lp, global_schema=False,
+                  am_rodc=am_rodc)
 
-    message("Pre-loading the Samba 4 and AD schema")
+    logger.info("Pre-loading the Samba 4 and AD schema")
 
     # Load the schema from the one we computed earlier
-    samdb.set_schema_from_ldb(schema.ldb)
+    samdb.set_schema(schema)
+
+    # Set the NTDS settings DN manually - in order to have it already around
+    # before the provisioned tree exists and we connect
+    samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
 
     # And now we can connect to the DB - the schema won't be loaded from the DB
     samdb.connect(path)
@@ -897,7 +1092,7 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
         samdb.set_domain_sid(str(domainsid))
         samdb.set_invocation_id(invocationid)
 
-        message("Adding DomainDN: %s" % names.domaindn)
+        logger.info("Adding DomainDN: %s" % names.domaindn)
 
 #impersonate domain admin
         admin_session_info = admin_session(lp, str(domainsid))
@@ -918,6 +1113,7 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
             "DOMAINSID": str(domainsid),
+            "NEXTRID": str(next_rid),
             "SCHEMADN": names.schemadn, 
             "NETBIOSNAME": names.netbiosname,
             "DEFAULTSITE": names.sitename,
@@ -929,7 +1125,7 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
             "SAMBA_VERSION_STRING": version
             })
 
-        message("Adding configuration container")
+        logger.info("Adding configuration container")
         descr = b64encode(get_config_descriptor(domainsid))
         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
             "CONFIGDN": names.configdn, 
@@ -937,7 +1133,7 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
             })
 
         # The LDIF here was created when the Schema object was constructed
-        message("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.write_prefixes_from_schema()
@@ -945,15 +1141,28 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
                        {"SCHEMADN": names.schemadn})
 
-        message("Reopening sam.ldb with new schema")
+        logger.info("Reopening sam.ldb with new schema")
+    except:
+        samdb.transaction_cancel()
+        raise
+    else:
         samdb.transaction_commit()
-        samdb = Ldb(session_info=admin_session_info,
-                    credentials=provision_backend.credentials, lp=lp)
-        samdb.connect(path)
-        samdb.transaction_start()
-        samdb.set_invocation_id(invocationid)
 
-        message("Setting up sam.ldb configuration data")
+    samdb = SamDB(session_info=admin_session_info, auto_connect=False,
+                credentials=provision_backend.credentials, lp=lp,
+                global_schema=False, am_rodc=am_rodc)
+
+    # Set the NTDS settings DN manually - in order to have it already around
+    # before the provisioned tree exists and we connect
+    samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
+
+    samdb.connect(path)
+
+    samdb.transaction_start()
+    try:
+        samdb.invocation_id = invocationid
+
+        logger.info("Setting up sam.ldb configuration data")
         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
             "CONFIGDN": names.configdn,
             "NETBIOSNAME": names.netbiosname,
@@ -963,28 +1172,29 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
             "SCHEMADN": names.schemadn,
             "DOMAINDN": names.domaindn,
             "SERVERDN": names.serverdn,
-            "FOREST_FUNCTIONALALITY": str(forestFunctionality)
+            "FOREST_FUNCTIONALITY": str(forestFunctionality),
+            "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
             })
 
-        message("Setting up display specifiers")
+        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)
 
-        message("Adding users container")
+        logger.info("Adding users container")
         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
                 "DOMAINDN": names.domaindn})
-        message("Modifying users container")
+        logger.info("Modifying users container")
         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
                 "DOMAINDN": names.domaindn})
-        message("Adding computers container")
+        logger.info("Adding computers container")
         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
                 "DOMAINDN": names.domaindn})
-        message("Modifying computers container")
+        logger.info("Modifying computers container")
         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
                 "DOMAINDN": names.domaindn})
-        message("Setting up sam.ldb data")
+        logger.info("Setting up sam.ldb data")
         setup_add_ldif(samdb, setup_path("provision.ldif"), {
             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
             "DOMAINDN": names.domaindn,
@@ -992,6 +1202,7 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
             "DEFAULTSITE": names.sitename,
             "CONFIGDN": names.configdn,
             "SERVERDN": names.serverdn,
+            "RIDAVAILABLESTART": str(next_rid + 600),
             "POLICYGUID_DC": policyguid_dc
             })
 
@@ -1002,7 +1213,7 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
                 "CONFIGDN": names.configdn,
                 "SCHEMADN": names.schemadn})
         if fill == FILL_FULL:
-            message("Setting up sam.ldb users and groups")
+            logger.info("Setting up sam.ldb users and groups")
             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
                 "DOMAINDN": names.domaindn,
                 "DOMAINSID": str(domainsid),
@@ -1011,27 +1222,28 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
                 })
 
-            message("Setting up self join")
+            logger.info("Setting up self join")
             setup_self_join(samdb, names=names, invocationid=invocationid,
                             dnspass=dnspass,
                             machinepass=machinepass,
-                            domainsid=domainsid, policyguid=policyguid,
+                            domainsid=domainsid,
+                            next_rid=next_rid,
+                            policyguid=policyguid,
                             policyguid_dc=policyguid_dc,
                             setup_path=setup_path,
                             domainControllerFunctionality=domainControllerFunctionality,
                             ntdsguid=ntdsguid)
 
-            ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
+            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
-
-    samdb.transaction_commit()
-    return samdb
+    else:
+        samdb.transaction_commit()
+        return samdb
 
 
 FILL_FULL = "FULL"
@@ -1040,53 +1252,86 @@ FILL_DRS = "DRS"
 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)"
 
-def set_gpo_acl(path,acl,lp,domsid):
-       setntacl(lp,path,acl,domsid)
-       for root, dirs, files in os.walk(path, topdown=False):
-               for name in files:
-                       setntacl(lp,os.path.join(root, name),acl,domsid)
-               for name in dirs:
-                       setntacl(lp,os.path.join(root, name),acl,domsid)
-
-def setsysvolacl(samdb,names,netlogon,sysvol,gid,domainsid,lp):
-       canchown = 1
-       try:
-               os.chown(sysvol,-1,gid)
-       except:
-               canchown = 0
-
-       setntacl(lp,sysvol,SYSVOL_ACL,str(domainsid))
-       for root, dirs, files in os.walk(sysvol, topdown=False):
-               for name in files:
-                       if canchown:
-                               os.chown(os.path.join(root, name),-1,gid)
-                       setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
-               for name in dirs:
-                       if canchown:
-                               os.chown(os.path.join(root, name),-1,gid)
-                       setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
-
-       # Set ACL for GPO
-       policy_path = os.path.join(sysvol, names.dnsdomain, "Policies")
-       set_gpo_acl(policy_path,dsacl2fsacl(POLICIES_ACL,str(domainsid)),lp,str(domainsid))
-       res = samdb.search(base="CN=Policies,CN=System,%s"%(names.domaindn),
-                                               attrs=["cn","nTSecurityDescriptor"],
-                                               expression="", scope=ldb.SCOPE_ONELEVEL)
-       for policy in res:
-               acl = ndr_unpack(security.descriptor,str(policy["nTSecurityDescriptor"])).as_sddl()
-               policy_path = os.path.join(sysvol, names.dnsdomain, "Policies",
-                                                                        str(policy["cn"]))
-               set_gpo_acl(policy_path,dsacl2fsacl(acl,str(domainsid)),lp,str(domainsid))
-
-
-
-def provision(setup_dir, message, session_info, 
+def set_dir_acl(path, acl, lp, domsid):
+    setntacl(lp, path, acl, domsid)
+    for root, dirs, files in os.walk(path, topdown=False):
+        for name in files:
+            setntacl(lp, os.path.join(root, name), acl, domsid)
+        for name in dirs:
+            setntacl(lp, os.path.join(root, name), acl, domsid)
+
+
+def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
+    """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
+    folders beneath.
+
+    :param sysvol: Physical path for the sysvol folder
+    :param dnsdomain: The DNS name of the domain
+    :param domainsid: The SID of the domain
+    :param domaindn: The DN of the domain (ie. DC=...)
+    :param samdb: An LDB object on the SAM db
+    :param lp: an LP object
+    """
+
+    # Set ACL for GPO root folder
+    root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
+    setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid))
+
+    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_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
+        set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp, 
+                    str(domainsid))
+
+def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
+    lp):
+    """Set the ACL for the sysvol share and the subfolders
+
+    :param samdb: An LDB object on the SAM db
+    :param netlogon: Physical path for the netlogon folder
+    :param sysvol: Physical path for the sysvol folder
+    :param gid: The GID of the "Domain adminstrators" group
+    :param domainsid: The SID of the domain
+    :param dnsdomain: The DNS name of the domain
+    :param domaindn: The DN of the domain (ie. DC=...)
+    """
+
+    try:
+        os.chown(sysvol,-1,gid)
+    except:
+        canchown = False
+    else:
+        canchown = True
+
+    # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
+    setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid))
+    for root, dirs, files in os.walk(sysvol, topdown=False):
+        for name in files:
+            if canchown:
+                os.chown(os.path.join(root, name), -1, gid)
+            setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
+        for name in dirs:
+            if canchown:
+                os.chown(os.path.join(root, name), -1, gid)
+            setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
+
+    # Set acls on Policy folder and policies folders
+    set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
+
+
+def provision(setup_dir, logger, session_info, 
               credentials, 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, adminpass=None, ldapadminpass=None, 
+              domainsid=None, next_rid=1000,
+              adminpass=None, ldapadminpass=None,
               krbtgtpass=None, domainguid=None, 
               policyguid=None, policyguid_dc=None, invocationid=None,
               machinepass=None, ntdsguid=None,
@@ -1097,7 +1342,7 @@ def provision(setup_dir, message, session_info,
               sitename=None,
               ol_mmr_urls=None, ol_olc=None, 
               setup_ds_path=None, slapd_path=None, nosync=False,
-              ldap_dryrun_mode=False,useeadb=False):
+              ldap_dryrun_mode=False, useeadb=False, am_rodc=False):
     """Provision samba4
     
     :note: caution, this wipes all existing data!
@@ -1112,24 +1357,27 @@ def provision(setup_dir, message, session_info,
       domainsid = security.dom_sid(domainsid)
 
     # create/adapt the group policy GUIDs
+    # Default GUID for default policy are described at
+    # "How Core Group Policy Works"
+    # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx
     if policyguid is None:
-        policyguid = str(uuid.uuid4())
+        policyguid = DEFAULT_POLICY_GUID
     policyguid = policyguid.upper()
     if policyguid_dc is None:
-        policyguid_dc = str(uuid.uuid4())
+        policyguid_dc = DEFAULT_DC_POLICY_GUID
     policyguid_dc = policyguid_dc.upper()
 
     if adminpass is None:
-        adminpass = glue.generate_random_password(12, 32)
+        adminpass = samba.generate_random_password(12, 32)
     if krbtgtpass is None:
-        krbtgtpass = glue.generate_random_password(128, 255)
+        krbtgtpass = samba.generate_random_password(128, 255)
     if machinepass is None:
-        machinepass  = glue.generate_random_password(128, 255)
+        machinepass  = samba.generate_random_password(128, 255)
     if dnspass is None:
-        dnspass = glue.generate_random_password(128, 255)
+        dnspass = samba.generate_random_password(128, 255)
     if ldapadminpass is None:
         #Make a new, random password between Samba and it's LDAP server
-        ldapadminpass=glue.generate_random_password(128, 255)
+        ldapadminpass=samba.generate_random_password(128, 255)
 
     if backend_type is None:
         backend_type = "ldb"
@@ -1140,7 +1388,7 @@ def provision(setup_dir, message, session_info,
 
     root_uid = findnss_uid([root or "root"])
     nobody_uid = findnss_uid([nobody or "nobody"])
-    users_gid = findnss_gid([users or "users"])
+    users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
     if wheel is None:
         wheel_gid = findnss_gid(["wheel", "adm"])
     else:
@@ -1151,11 +1399,11 @@ def provision(setup_dir, message, session_info,
         bind_gid = None
 
     if targetdir is not None:
-        if (not os.path.exists(os.path.join(targetdir, "etc"))):
-            os.makedirs(os.path.join(targetdir, "etc"))
         smbconf = os.path.join(targetdir, "etc", "smb.conf")
     elif smbconf is None:
-        smbconf = param.default_path()
+        smbconf = samba.param.default_path()
+    if not os.path.exists(os.path.dirname(smbconf)):
+        os.makedirs(os.path.dirname(smbconf))
 
     # only install a new smb.conf if there isn't one there already
     if os.path.exists(smbconf):
@@ -1165,13 +1413,13 @@ def provision(setup_dir, message, session_info,
         data = open(smbconf, 'r').read()
         data = data.lstrip()
         if data is None or data == "":
-            make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
-                         targetdir, sid_generator, useeadb)
+            make_smbconf(smbconf, setup_path, hostname, domain, realm,
+                         serverrole, targetdir, sid_generator, useeadb)
     else: 
         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
                      targetdir, sid_generator, useeadb)
 
-    lp = param.LoadParm()
+    lp = samba.param.LoadParm()
     lp.load(smbconf)
 
     names = guess_names(lp=lp, hostname=hostname, domain=domain,
@@ -1184,14 +1432,14 @@ def provision(setup_dir, message, session_info,
     paths.bind_gid = bind_gid
 
     if hostip is None:
-        hostips = glue.interface_ips(lp, False)
+        hostips = samba.interface_ips(lp, False)
         if len(hostips) == 0:
-            message("No external IPv4 address has been found: I use the loopback.")
+            logger.warning("No external IPv4 address has been found. Using loopback.")
             hostip = '127.0.0.1'
         else:
             hostip = hostips[0]
             if len(hostips) > 1:
-                message("More than one IPv4 address found: I use " + hostip + ".")
+                logger.warning("More than one IPv4 address found. Using %s.", hostip)
 
     if hostip6 is None:
         try:
@@ -1212,32 +1460,33 @@ def provision(setup_dir, message, session_info,
 
     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.mkdir(os.path.join(paths.private_dir,"tls"))
+    if not os.path.exists(os.path.join(paths.private_dir, "tls")):
+        os.mkdir(os.path.join(paths.private_dir, "tls"))
 
     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
  
-    schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn)
-    
+    schema = Schema(setup_path, domainsid, invocationid=invocationid, schemadn=names.schemadn,
+                    serverdn=names.serverdn)
+
     if backend_type == "ldb":
         provision_backend = LDBBackend(backend_type,
                                          paths=paths, setup_path=setup_path,
                                          lp=lp, credentials=credentials, 
                                          names=names,
-                                         message=message)
+                                         logger=logger)
     elif backend_type == "existing":
         provision_backend = ExistingBackend(backend_type,
                                          paths=paths, setup_path=setup_path,
                                          lp=lp, credentials=credentials, 
                                          names=names,
-                                         message=message,
+                                         logger=logger,
                                          ldapi_url=ldapi_url)
     elif backend_type == "fedora-ds":
         provision_backend = FDSBackend(backend_type,
                                          paths=paths, setup_path=setup_path,
                                          lp=lp, credentials=credentials, 
                                          names=names,
-                                         message=message,
+                                         logger=logger,
                                          domainsid=domainsid,
                                          schema=schema,
                                          hostname=hostname,
@@ -1252,7 +1501,7 @@ def provision(setup_dir, message, session_info,
                                          paths=paths, setup_path=setup_path,
                                          lp=lp, credentials=credentials, 
                                          names=names,
-                                         message=message,
+                                         logger=logger,
                                          domainsid=domainsid,
                                          schema=schema,
                                          hostname=hostname,
@@ -1263,127 +1512,155 @@ def provision(setup_dir, message, session_info,
                                          ol_mmr_urls=ol_mmr_urls, 
                                          nosync=nosync)
     else:
-        raise ProvisioningError("Unknown LDAP backend type selected")
+        raise ValueError("Unknown LDAP backend type selected")
 
     provision_backend.init()
     provision_backend.start()
 
     # only install a new shares config db if there is none
     if not os.path.exists(paths.shareconf):
-        message("Setting up share.ldb")
+        logger.info("Setting up share.ldb")
         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
                         lp=lp)
         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
 
-     
-    message("Setting up secrets.ldb")
-    secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
-                                  session_info=session_info, 
-                                  backend_credentials=provision_backend.secrets_credentials, lp=lp)
-
-    message("Setting up the registry")
-    setup_registry(paths.hklm, setup_path, session_info, 
-                   lp=lp)
-
-    message("Setting up the privileges database")
-    setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
-
-    message("Setting up idmap db")
-    idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
-                          lp=lp)
-
-    message("Setting up SAM db")
-    samdb = setup_samdb(paths.samdb, setup_path, session_info, 
-                        provision_backend, lp, names,
-                        message, 
-                        domainsid=domainsid, 
-                        schema=schema, domainguid=domainguid,
-                        policyguid=policyguid, policyguid_dc=policyguid_dc,
-                        fill=samdb_fill, 
-                        adminpass=adminpass, krbtgtpass=krbtgtpass,
-                        invocationid=invocationid, 
-                        machinepass=machinepass, dnspass=dnspass, 
-                        ntdsguid=ntdsguid, serverrole=serverrole,
-                        dom_for_fun_level=dom_for_fun_level)
+    logger.info("Setting up secrets.ldb")
+    secrets_ldb = setup_secretsdb(paths, setup_path,
+        session_info=session_info,
+        backend_credentials=provision_backend.secrets_credentials, lp=lp)
 
-    if serverrole == "domain controller":
-        if paths.netlogon is None:
-            message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
-            message("Please either remove %s or see the template at %s" % 
-                    ( paths.smbconf, setup_path("provision.smb.conf.dc")))
-            assert(paths.netlogon is not None)
-
-        if paths.sysvol is None:
-            message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
-            message("Please either remove %s or see the template at %s" % 
-                    (paths.smbconf, setup_path("provision.smb.conf.dc")))
-            assert(paths.sysvol is not None)            
-            
-
-        if not os.path.isdir(paths.netlogon):
-            os.makedirs(paths.netlogon, 0755)
-
-    if samdb_fill == FILL_FULL:
-        setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
-                            root_uid=root_uid, nobody_uid=nobody_uid,
-                            users_gid=users_gid, wheel_gid=wheel_gid)
+    try:
+        logger.info("Setting up the registry")
+        setup_registry(paths.hklm, setup_path, session_info, 
+                       lp=lp)
 
-        if serverrole == "domain controller":
-            # Set up group policies (domain policy and domain controller policy)
-            setup_gpo(paths,names,samdb,policyguid,policyguid_dc,domainsid)
-            setsysvolacl(samdb,names,paths.netlogon,paths.sysvol,wheel_gid,domainsid,lp)
+        logger.info("Setting up the privileges database")
+        setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
 
-        message("Setting up sam.ldb rootDSE marking as synchronized")
-        setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
+        logger.info("Setting up idmap db")
+        idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
+                              lp=lp)
 
-        secretsdb_self_join(secrets_ldb, domain=names.domain,
-                            realm=names.realm,
-                            dnsdomain=names.dnsdomain,
-                            netbiosname=names.netbiosname,
+        logger.info("Setting up SAM db")
+        samdb = setup_samdb(paths.samdb, setup_path, session_info, 
+                            provision_backend, lp, names,
+                            logger=logger, 
                             domainsid=domainsid, 
-                            machinepass=machinepass,
-                            secure_channel_type=SEC_CHAN_BDC)
+                            schema=schema, domainguid=domainguid,
+                            policyguid=policyguid, policyguid_dc=policyguid_dc,
+                            fill=samdb_fill, 
+                            adminpass=adminpass, krbtgtpass=krbtgtpass,
+                            invocationid=invocationid, 
+                            machinepass=machinepass, dnspass=dnspass, 
+                            ntdsguid=ntdsguid, serverrole=serverrole,
+                            dom_for_fun_level=dom_for_fun_level,
+                            am_rodc=am_rodc, next_rid=next_rid)
 
         if serverrole == "domain controller":
-            secretsdb_setup_dns(secrets_ldb, setup_path,
-                                paths.private_dir,
-                                realm=names.realm, dnsdomain=names.dnsdomain,
-                                dns_keytab_path=paths.dns_keytab,
-                                dnspass=dnspass)
-
-            domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
-            assert isinstance(domainguid, str)
-
-            # Only make a zone file on the first DC, it should be replicated
-            # with DNS replication
-            create_zone_file(lp, message, paths, targetdir, setup_path, dnsdomain=names.dnsdomain,
-                             hostip=hostip,
-                             hostip6=hostip6, hostname=names.hostname,
-                             realm=names.realm,
-                             domainguid=domainguid, ntdsguid=names.ntdsguid)
-
-            create_named_conf(paths, setup_path, realm=names.realm,
-                              dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
-
-            create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
-                              dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
-                              keytab_name=paths.dns_keytab)
-            message("See %s for an example configuration include file for BIND" % paths.namedconf)
-            message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
-
-            create_krb5_conf(paths.krb5conf, setup_path,
-                             dnsdomain=names.dnsdomain, hostname=names.hostname,
-                             realm=names.realm)
-            message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
+            if paths.netlogon is None:
+                logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
+                logger.info("Please either remove %s or see the template at %s" % 
+                        (paths.smbconf, setup_path("provision.smb.conf.dc")))
+                assert paths.netlogon is not None
+
+            if paths.sysvol is None:
+                logger.info("Existing smb.conf does not have a [sysvol] share, but you"
+                        " are configuring a DC.")
+                logger.info("Please either remove %s or see the template at %s" % 
+                        (paths.smbconf, setup_path("provision.smb.conf.dc")))
+                assert paths.sysvol is not None
+
+            if not os.path.isdir(paths.netlogon):
+                os.makedirs(paths.netlogon, 0755)
+
+        if samdb_fill == FILL_FULL:
+            setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
+                                root_uid=root_uid, nobody_uid=nobody_uid,
+                                users_gid=users_gid, wheel_gid=wheel_gid)
+
+            if serverrole == "domain controller":
+                # Set up group policies (domain policy and domain controller policy)
+                create_default_gpo(paths.sysvol, names.dnsdomain, policyguid, policyguid_dc)
+                setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid, 
+                             domainsid, names.dnsdomain, names.domaindn, lp)
+
+            logger.info("Setting up sam.ldb rootDSE marking as synchronized")
+            setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
+
+            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)
+
+            # Now set up the right msDS-SupportedEncryptionTypes into the DB
+            # In future, this might be determined from some configuration
+            kerberos_enctypes = str(ENC_ALL_TYPES)
+
+            try:
+                msg = ldb.Message(ldb.Dn(samdb, samdb.searchone("distinguishedName", expression="samAccountName=%s$" % names.netbiosname, scope=ldb.SCOPE_SUBTREE)))
+                msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(elements=kerberos_enctypes, 
+                                                                          flags=ldb.FLAG_MOD_REPLACE, 
+                                                                          name="msDS-SupportedEncryptionTypes")
+                samdb.modify(msg)
+            except ldb.LdbError, (ldb.ERR_NO_SUCH_ATTRIBUTE, _):
+                # It might be that this attribute does not exist in this schema
+                pass
+
+
+            if serverrole == "domain controller":
+                secretsdb_setup_dns(secrets_ldb, setup_path, names,
+                                    paths.private_dir,
+                                    realm=names.realm, dnsdomain=names.dnsdomain,
+                                    dns_keytab_path=paths.dns_keytab,
+                                    dnspass=dnspass)
+
+                domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
+                assert isinstance(domainguid, str)
+
+                # Only make a zone file on the first DC, it should be replicated
+                # with DNS replication
+                create_zone_file(lp, logger, paths, targetdir, setup_path,
+                    dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
+                    hostname=names.hostname, realm=names.realm, 
+                    domainguid=domainguid, ntdsguid=names.ntdsguid)
+
+                create_named_conf(paths, setup_path, realm=names.realm,
+                                  dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
+
+                create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
+                                  dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
+                                  keytab_name=paths.dns_keytab)
+                logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
+                logger.info("and %s for further documentation required for secure DNS "
+                        "updates", paths.namedtxt)
+
+            lastProvisionUSNs = get_last_provision_usn(samdb)
+            maxUSN = get_max_usn(samdb, str(names.rootdn))
+            if lastProvisionUSNs is not None:
+                update_provision_usn(samdb, 0, maxUSN, 1)
+            else:
+                set_provision_usn(samdb, 0, maxUSN)
+
+        create_krb5_conf(paths.krb5conf, setup_path,
+                         dnsdomain=names.dnsdomain, hostname=names.hostname,
+                         realm=names.realm)
+        logger.info("A Kerberos configuration suitable for Samba 4 has been "
+                    "generated at %s", paths.krb5conf)
 
-    if serverrole == "domain controller":
-        create_dns_update_list(lp, message, paths, setup_path)
+        if serverrole == "domain controller":
+            create_dns_update_list(lp, logger, paths, setup_path)
 
-    provision_backend.post_setup()
-    provision_backend.shutdown()
-    
-    create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
-                               ldapi_url)
+        provision_backend.post_setup()
+        provision_backend.shutdown()
+
+        create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
+                                   ldapi_url)
+    except:
+        secrets_ldb.transaction_cancel()
+        raise
 
     #Now commit the secrets.ldb to disk
     secrets_ldb.transaction_commit()
@@ -1395,33 +1672,36 @@ def provision(setup_dir, message, session_info,
             os.chmod(dns_keytab_path, 0640)
             os.chown(dns_keytab_path, -1, paths.bind_gid)
         except OSError:
-            message("Failed to chown %s to bind gid %u" % (dns_keytab_path, paths.bind_gid))
+            if not os.environ.has_key('SAMBA_SELFTEST'):
+                logger.info("Failed to chown %s to bind gid %u", dns_keytab_path,
+                            paths.bind_gid)
 
 
-    message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
+    logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php",
+            paths.phpldapadminconfig)
 
-    message("Once the above files are installed, your Samba4 server will be ready to use")
-    message("Server Role:           %s" % serverrole)
-    message("Hostname:              %s" % names.hostname)
-    message("NetBIOS Domain:        %s" % names.domain)
-    message("DNS Domain:            %s" % names.dnsdomain)
-    message("DOMAIN SID:            %s" % str(domainsid))
+    logger.info("Once the above files are installed, your Samba4 server will be ready to use")
+    logger.info("Server Role:           %s" % serverrole)
+    logger.info("Hostname:              %s" % names.hostname)
+    logger.info("NetBIOS Domain:        %s" % names.domain)
+    logger.info("DNS Domain:            %s" % names.dnsdomain)
+    logger.info("DOMAIN SID:            %s" % str(domainsid))
     if samdb_fill == FILL_FULL:
-        message("Admin password:        %s" % adminpass)
+        logger.info("Admin password:        %s" % adminpass)
     if provision_backend.type is not "ldb":
         if provision_backend.credentials.get_bind_dn() is not None:
-            message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
+            logger.info("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
         else:
-            message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
+            logger.info("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
 
-        message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
+        logger.info("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
 
         if provision_backend.slapd_command_escaped is not None:
             # now display slapd_command_file.txt to show how slapd must be started next time
-            message("Use later the following commandline to start slapd, then Samba:")
-            message(provision_backend.slapd_command_escaped)
-            message("This slapd-Commandline is also stored under: " + provision_backend.ldapdir + "/ldap_backend_startup.sh")
-
+            logger.info("Use later the following commandline to start slapd, then Samba:")
+            logger.info(provision_backend.slapd_command_escaped)
+            logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh", 
+                    provision_backend.ldapdir)
 
     result = ProvisionResult()
     result.domaindn = domaindn
@@ -1431,7 +1711,6 @@ def provision(setup_dir, message, session_info,
     return result
 
 
-
 def provision_become_dc(setup_dir=None,
                         smbconf=None, targetdir=None, realm=None, 
                         rootdn=None, domaindn=None, schemadn=None,
@@ -1445,13 +1724,10 @@ def provision_become_dc(setup_dir=None,
                         ldap_backend=None, ldap_backend_type=None,
                         sitename=None, debuglevel=1):
 
-    def message(text):
-        """print a message if quiet is not set."""
-        print text
+    logger = logging.getLogger("provision")
+    samba.set_debug_level(debuglevel)
 
-    glue.set_debug_level(debuglevel)
-
-    return provision(setup_dir, message, system_session(), None,
+    return provision(setup_dir, logger, system_session(), None,
               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
               configdn=configdn, serverdn=serverdn, domain=domain,
@@ -1470,7 +1746,7 @@ def create_phpldapadmin_config(path, setup_path, ldapi_uri):
             {"S4_LDAPI_URI": ldapi_uri})
 
 
-def create_zone_file(lp, message, paths, targetdir, setup_path, dnsdomain,
+def create_zone_file(lp, logger, paths, targetdir, setup_path, dnsdomain,
                      hostip, hostip6, hostname, realm, domainguid,
                      ntdsguid):
     """Write out a DNS zone file, from the info in the current database.
@@ -1491,16 +1767,20 @@ def create_zone_file(lp, message, paths, targetdir, setup_path, dnsdomain,
     if hostip6 is not None:
         hostip6_base_line = "            IN AAAA    " + hostip6
         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
+        gc_msdcs_ip6_line = "gc._msdcs               IN AAAA    " + hostip6
     else:
         hostip6_base_line = ""
         hostip6_host_line = ""
+        gc_msdcs_ip6_line = ""
 
     if hostip is not None:
         hostip_base_line = "            IN A    " + hostip
         hostip_host_line = hostname + "        IN A    " + hostip
+        gc_msdcs_ip_line = "gc._msdcs               IN A    " + hostip
     else:
         hostip_base_line = ""
         hostip_host_line = ""
+        gc_msdcs_ip_line = ""
 
     dns_dir = os.path.dirname(paths.dns)
 
@@ -1528,12 +1808,17 @@ def create_zone_file(lp, message, paths, targetdir, setup_path, dnsdomain,
             "NTDSGUID": ntdsguid,
             "HOSTIP6_BASE_LINE": hostip6_base_line,
             "HOSTIP6_HOST_LINE": hostip6_host_line,
+            "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
+            "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
         })
 
     # note that we use no variable substitution on this file
     # the substitution is done at runtime by samba_dnsupdate
     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
 
+    # and the SPN update list
+    setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
+
     if paths.bind_gid is not None:
         try:
             os.chown(dns_dir, -1, paths.bind_gid)
@@ -1542,17 +1827,19 @@ def create_zone_file(lp, message, paths, targetdir, setup_path, dnsdomain,
             os.chmod(dns_dir, 0775)
             os.chmod(paths.dns, 0664)
         except OSError:
-            message("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
+            if not os.environ.has_key('SAMBA_SELFTEST'):
+                logger.error("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
 
     if targetdir is None:
         os.system(rndc + " unfreeze " + lp.get("realm"))
 
 
-def create_dns_update_list(lp, message, paths, setup_path):
+def create_dns_update_list(lp, logger, paths, setup_path):
     """Write out a dns_update_list file"""
     # note that we use no variable substitution on this file
     # the substitution is done at runtime by samba_dnsupdate
     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
+    setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
 
 
 def create_named_conf(paths, setup_path, realm, dnsdomain,
@@ -1579,6 +1866,7 @@ def create_named_conf(paths, setup_path, realm, dnsdomain,
 
     setup_file(setup_path("named.conf.update"), paths.namedconf_update)
 
+
 def create_named_txt(path, setup_path, realm, dnsdomain,
                       private_dir, keytab_name):
     """Write out a file containing zone statements suitable for inclusion in a
@@ -1600,6 +1888,7 @@ def create_named_txt(path, setup_path, realm, dnsdomain,
             "PRIVATE_DIR": private_dir
         })
 
+
 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
     """Write out a file containing zone statements suitable for inclusion in a
     named.conf file (including GSS-TSIG configuration).
@@ -1610,7 +1899,6 @@ def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
     :param hostname: Local hostname
     :param realm: Realm name
     """
-
     setup_file(setup_path("krb5.conf"), path, {
             "DNSDOMAIN": dnsdomain,
             "HOSTNAME": hostname,
@@ -1618,3 +1906,17 @@ def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
         })
 
 
+class ProvisioningError(Exception):
+    """A generic provision error."""
+
+    def __init__(self, value):
+        self.value = value
+
+    def __str__(self):
+        return "ProvisioningError: " + self.value
+
+
+class InvalidNetbiosName(Exception):
+    """A specified name was not a valid NetBIOS name."""
+    def __init__(self, name):
+        super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)