libds:common Remove DS_DC_* domain functionality flags
[kai/samba-autobuild/.git] / source4 / scripting / python / samba / provision.py
index 1e1bf480f53259e9273a4c8cc1d8a51cbfd4c9cd..1b7cfca64d4d84f84919c3d25846ebb53afb5b80 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, DS_DC_FUNCTION_2008_R2
+from samba.dsdb import DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008
 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"
 
 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 +83,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 +157,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
@@ -167,19 +177,10 @@ class ProvisionPaths(object):
         self.dns = None
         self.winsdb = None
         self.private_dir = None
-        self.ldapdir = None
-        self.slapdconf = None
-        self.modulesconf = None
-        self.memberofconf = None
-        self.olmmron = None
-        self.olmmrserveridsconf = None
-        self.olmmrsyncreplconf = None
-        self.olcdir = None
-        self.olslapd = None
-        self.olcseedldif = None
 
 
 class ProvisionNames(object):
+
     def __init__(self):
         self.rootdn = None
         self.domaindn = None
@@ -193,15 +194,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.
     
@@ -211,9 +306,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")
 
 
@@ -229,7 +324,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]
@@ -246,7 +341,7 @@ 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):
@@ -257,7 +352,6 @@ 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)
 
 
@@ -277,7 +371,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):
@@ -288,6 +383,9 @@ 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.shareconf = os.path.join(paths.private_dir, "share.ldb")
@@ -296,6 +394,8 @@ def provision_paths_from_lp(lp, dnsdomain):
     paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
     paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
     paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
+    paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
+    paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
     paths.namedconf = os.path.join(paths.private_dir, "named.conf")
     paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
     paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
@@ -304,37 +404,15 @@ def provision_paths_from_lp(lp, dnsdomain):
     paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
     paths.phpldapadminconfig = os.path.join(paths.private_dir, 
                                             "phpldapadmin-config.php")
-    paths.ldapdir = os.path.join(paths.private_dir, 
-                                 "ldap")
-    paths.slapdconf = os.path.join(paths.ldapdir, 
-                                   "slapd.conf")
-    paths.slapdpid = os.path.join(paths.ldapdir, 
-                                   "slapd.pid")
-    paths.modulesconf = os.path.join(paths.ldapdir, 
-                                     "modules.conf")
-    paths.memberofconf = os.path.join(paths.ldapdir, 
-                                      "memberof.conf")
-    paths.olmmrserveridsconf = os.path.join(paths.ldapdir, 
-                                            "mmr_serverids.conf")
-    paths.olmmrsyncreplconf = os.path.join(paths.ldapdir, 
-                                           "mmr_syncrepl.conf")
-    paths.olcdir = os.path.join(paths.ldapdir, 
-                                 "slapd.d")
-    paths.olcseedldif = os.path.join(paths.ldapdir, 
-                                 "olc_seed.ldif")
     paths.hklm = "hklm.ldb"
     paths.hkcr = "hkcr.ldb"
     paths.hkcu = "hkcu.ldb"
     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
 
 
@@ -357,34 +435,36 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
     if dnsdomain is None:
         dnsdomain = lp.get("realm")
         if dnsdomain is None or dnsdomain == "":
-            raise ProvisioningError("guess_names: 'realm' not specified in supplied smb.conf!")
+            raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile)
 
     dnsdomain = dnsdomain.lower()
 
     if serverrole is None:
         serverrole = lp.get("server role")
         if serverrole is None:
-            raise ProvisioningError("guess_names: 'server role' not specified in supplied smb.conf!")
+            raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile)
 
     serverrole = serverrole.lower()
 
     realm = dnsdomain.upper()
 
+    if lp.get("realm") == "":
+        raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s.  Please remove the smb.conf file and let provision generate it" % lp.configfile)
+
     if lp.get("realm").upper() != realm:
-        raise ProvisioningError("guess_names: Realm '%s' in smb.conf must match chosen realm '%s'!", lp.get("realm").upper(), realm)
+        raise ProvisioningError("guess_names: 'realm=%s' in %s must match chosen realm '%s'!  Please remove the smb.conf file and let provision generate it" % (lp.get("realm").upper(), realm, lp.configfile))
 
     if lp.get("server role").lower() != serverrole:
-        raise ProvisioningError("guess_names: server role '%s' in smb.conf must match chosen server role '%s'!", lp.get("server role").upper(), serverrole)
+        raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'!  Please remove the smb.conf file and let provision generate it" % (lp.get("server role").upper(), serverrole, lp.configfile))
 
     if serverrole == "domain controller":
         if domain is None:
+            # This will, for better or worse, default to 'WORKGROUP'
             domain = lp.get("workgroup")
-        if domain is None:
-            raise ProvisioningError("guess_names: 'workgroup' not specified in supplied smb.conf!")
         domain = domain.upper()
 
         if lp.get("workgroup").upper() != domain:
-            raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'!", 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))
 
         if domaindn is None:
             domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
@@ -397,11 +477,11 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
         raise InvalidNetbiosName(domain)
         
     if hostname.upper() == realm:
-        raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!", realm, hostname)
+        raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname))
     if netbiosname == realm:
-        raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!", realm, netbiosname)
+        raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname))
     if domain == realm:
-        raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!", realm, domain)
+        raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
 
     if rootdn is None:
        rootdn = domaindn
@@ -432,7 +512,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
@@ -460,7 +540,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)
@@ -469,7 +549,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 = ""
 
@@ -501,7 +581,7 @@ def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
             "SIDGENERATOR_LINE": sid_generator_line,
             "PRIVATEDIR_LINE": privatedir_line,
             "LOCKDIR_LINE": lockdir_line,
-                       "POSIXEADB_LINE": posixeadb_line
+            "POSIXEADB_LINE": posixeadb_line
             })
 
 
@@ -517,16 +597,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. 
     
@@ -541,9 +620,6 @@ def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
     """
     assert session_info is not None
 
-    old_partitions = None
-    new_partitions = None
-
     # We use options=["modules:"] to stop the modules loading - we
     # just want to wipe and re-initialise the database, not start it up
 
@@ -561,7 +637,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(),
@@ -575,14 +651,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, 
@@ -604,7 +679,7 @@ def secretsdb_self_join(secretsdb, domain,
            "privateKeytab"]
     
 
-    msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain));
+    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"]
@@ -615,7 +690,7 @@ def secretsdb_self_join(secretsdb, domain,
       msg["realm"] = realm
       msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
       msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
-      msg["privateKeytab"] = ["secrets.keytab"];
+      msg["privateKeytab"] = ["secrets.keytab"]
 
 
     msg["secret"] = [machinepass]
@@ -677,6 +752,9 @@ def secretsdb_setup_dns(secretsdb, setup_path, private_dir,
 def setup_secretsdb(path, 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.
@@ -693,22 +771,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.
@@ -736,10 +818,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)
@@ -821,7 +903,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,CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration,%s" % (names.hostname, names.sitename, names.domaindn)
     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
                                      expression="", scope=ldb.SCOPE_BASE)
     assert isinstance(names.ntdsguid, str)
@@ -840,32 +922,32 @@ def setup_self_join(samdb, names,
               "DNSPASS_B64": b64encode(dnspass),
               })
 
+def getpolicypath(sysvolpath, dnsdomain, guid):
+    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 + "}")
+def create_gpo_struct(policy_path):
     os.makedirs(policy_path, 0755)
     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):
+
+def setup_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
+    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):
     """Setup a complete SAM Database.
     
     :note: This will wipe the main SAM database file!
@@ -873,12 +955,10 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
 
     # 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
 
     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!")
@@ -887,22 +967,23 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
     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):
-        schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn)
+    if schema is None:
+        schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
+                        am_rodc=am_rodc)
 
-    # 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)
 
     # And now we can connect to the DB - the schema won't be loaded from the DB
     samdb.connect(path)
@@ -924,8 +1005,9 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
 
         samdb.set_domain_sid(str(domainsid))
         samdb.set_invocation_id(invocationid)
+        samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
 
-        message("Adding DomainDN: %s" % names.domaindn)
+        logger.info("Adding DomainDN: %s" % names.domaindn)
 
 #impersonate domain admin
         admin_session_info = admin_session(lp, str(domainsid))
@@ -957,7 +1039,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, 
@@ -965,7 +1047,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()
@@ -973,15 +1055,22 @@ 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,
+                credentials=provision_backend.credentials, lp=lp,
+                global_schema=False, am_rodc=am_rodc)
+    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,
@@ -991,28 +1080,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,
@@ -1030,7 +1120,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),
@@ -1039,7 +1129,7 @@ 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,
@@ -1049,17 +1139,16 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
                             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,CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration,%s" % (names.hostname, names.sitename, names.domaindn)
             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"
@@ -1068,47 +1157,53 @@ 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_gpo_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
+    # Set ACL for GPO
+    policy_path = os.path.join(sysvol, dnsdomain, "Policies")
+    set_dir_acl(policy_path,dsacl2fsacl(POLICIES_ACL, str(domainsid)), 
+        lp, 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):
+    try:
+        os.chown(sysvol,-1,gid)
+    except:
+        canchown = False
+    else:
+        canchown = True
+
+    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_gpo_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, 
@@ -1125,7 +1220,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!
@@ -1148,16 +1243,16 @@ def provision(setup_dir, message, session_info,
     policyguid_dc = policyguid_dc.upper()
 
     if adminpass is None:
-        adminpass = glue.generate_random_str(12)
+        adminpass = samba.generate_random_password(12, 32)
     if krbtgtpass is None:
-        krbtgtpass = glue.generate_random_str(12)
+        krbtgtpass = samba.generate_random_password(128, 255)
     if machinepass is None:
-        machinepass  = glue.generate_random_str(12)
+        machinepass  = samba.generate_random_password(128, 255)
     if dnspass is None:
-        dnspass = glue.generate_random_str(12)
+        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_str(12)        
+        ldapadminpass=samba.generate_random_password(128, 255)
 
     if backend_type is None:
         backend_type = "ldb"
@@ -1168,7 +1263,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:
@@ -1179,11 +1274,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):
@@ -1193,13 +1288,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,
@@ -1212,14 +1307,22 @@ def provision(setup_dir, message, session_info,
     paths.bind_gid = bind_gid
 
     if hostip is None:
-        try:
-            hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
-        except socket.gaierror, (socket.EAI_NODATA, msg):
-            hostip = None
+        hostips = samba.interface_ips(lp, False)
+        if len(hostips) == 0:
+            logger.warning("No external IPv4 address has been found. Using loopback.")
+            hostip = '127.0.0.1'
+        else:
+            hostip = hostips[0]
+            if len(hostips) > 1:
+                logger.warning("More than one IPv4 address found. Using %s.", hostip)
 
     if hostip6 is None:
         try:
-            hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
+            for ip in socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP):
+                if hostip6 is None:
+                    hostip6 = ip[-1][0]
+                if hostip6 == '::1' and ip[-1][0] != '::1':
+                    hostip6 = ip[-1][0]
         except socket.gaierror, (socket.EAI_NODATA, msg): 
             hostip6 = None
 
@@ -1232,31 +1335,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, am_rodc=am_rodc)
+
     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,
@@ -1271,7 +1376,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,
@@ -1282,124 +1387,140 @@ 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")
+    logger.info("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)
-
-    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)
+        session_info=session_info,
+        backend_credentials=provision_backend.secrets_credentials, lp=lp)
 
-    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)
 
         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)
-
-    provision_backend.post_setup()
-    provision_backend.shutdown()
-    
-    create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
-                               ldapi_url)
+            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)
+                setup_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)
+
+            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, 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)
+
+                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)
+
+            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)
+
+        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)
+    except:
+        secrets_ldb.transaction_cancel()
+        raise
 
     #Now commit the secrets.ldb to disk
     secrets_ldb.transaction_commit()
@@ -1411,33 +1532,35 @@ 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))
+            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: " + paths.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
@@ -1447,7 +1570,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,
@@ -1461,13 +1583,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
-
-    glue.set_debug_level(debuglevel)
+    logger = logging.getLogger("provision")
+    samba.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,
@@ -1486,7 +1605,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.
@@ -1507,16 +1626,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)
 
@@ -1529,7 +1652,7 @@ def create_zone_file(lp, message, paths, targetdir, setup_path, dnsdomain,
 
     # we need to freeze the zone while we update the contents
     if targetdir is None:
-        rndc = lp.get("rndc command")
+        rndc = ' '.join(lp.get("rndc command"))
         os.system(rndc + " freeze " + lp.get("realm"))
 
     setup_file(setup_path("provision.zone"), paths.dns, {
@@ -1544,8 +1667,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)
@@ -1554,12 +1686,20 @@ 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))
+            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, 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,
                       private_dir):
     """Write out a file containing zone statements suitable for inclusion in a
@@ -1584,6 +1724,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
@@ -1605,6 +1746,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).
@@ -1615,7 +1757,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,
@@ -1623,3 +1764,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)