s4-provision Add support for fixing the DC rid to a particular value
[amitay/samba.git] / source4 / scripting / python / samba / provision / __init__.py
index 5aabd36c1a8e6b6a79bef4d6ff3f0fbd7ea31bcb..1b32a4105904ed14fd57b310d068004ab6ee172b 100644 (file)
@@ -74,10 +74,14 @@ from samba.provision.backend import (
     LDBBackend,
     OpenLDAPBackend,
     )
+from samba.provision.sambadns import setup_ad_dns
+
 import samba.param
 import samba.registry
 from samba.schema import Schema
 from samba.samdb import SamDB
+from samba.dbchecker import dbcheck
+
 
 VALID_NETBIOS_CHARS = " !#$%&'()-.@^_{}~"
 DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
@@ -327,10 +331,9 @@ def update_provision_usn(samdb, low, high, id, replace=False):
 
     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"])
+        entry = samdb.search(base="@PROVISION",
+                             scope=ldb.SCOPE_BASE,
+                             attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
         for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
             if not re.search(';', e):
                 e = "%s;%s" % (e, id)
@@ -341,9 +344,9 @@ def update_provision_usn(samdb, low, high, id, replace=False):
     delta.dn = ldb.Dn(samdb, "@PROVISION")
     delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
         ldb.FLAG_MOD_REPLACE, LAST_PROVISION_USN_ATTRIBUTE)
-    entry = samdb.search(expression="(&(dn=@PROVISION)(provisionnerID=*))",
-                            base="", scope=ldb.SCOPE_SUBTREE,
-                            attrs=["provisionnerID"])
+    entry = samdb.search(expression='provisionnerID=*',
+                         base="@PROVISION", scope=ldb.SCOPE_BASE,
+                         attrs=["provisionnerID"])
     if len(entry) == 0 or len(entry[0]) == 0:
         delta["provisionnerID"] = ldb.MessageElement(id, ldb.FLAG_MOD_ADD, "provisionnerID")
     samdb.modify(delta)
@@ -394,10 +397,14 @@ def get_last_provision_usn(sam):
     :return: a dictionnary which keys are invocation id and values are an array
              of integer representing the different ranges
     """
-    entry = sam.search(expression="(&(dn=@PROVISION)(%s=*))" %
-                        LAST_PROVISION_USN_ATTRIBUTE,
-                        base="", scope=ldb.SCOPE_SUBTREE,
-                        attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
+    try:
+        entry = sam.search(expression="%s=*" % LAST_PROVISION_USN_ATTRIBUTE,
+                           base="@PROVISION", scope=ldb.SCOPE_BASE,
+                           attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
+    except ldb.LdbError, (ecode, emsg):
+        if ecode == ldb.ERR_NO_SUCH_OBJECT:
+            return None
+        raise
     if len(entry):
         myids = []
         range = {}
@@ -712,13 +719,19 @@ def make_smbconf(smbconf, hostname, domain, realm, serverrole,
     if targetdir is not None:
         privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
         lockdir_line = "lock dir = " + os.path.abspath(targetdir)
+        statedir_line = "state directory = " + os.path.abspath(targetdir)
+        cachedir_line = "cache directory = " + os.path.abspath(targetdir)
 
         lp.set("lock dir", os.path.abspath(targetdir))
+        lp.set("state directory", os.path.abspath(targetdir))
+        lp.set("cache directory", os.path.abspath(targetdir))
     else:
         privatedir_line = ""
         lockdir_line = ""
+        statedir_line = ""
+        cachedir_line = ""
 
-    sysvol = os.path.join(lp.get("lock dir"), "sysvol")
+    sysvol = os.path.join(lp.get("state directory"), "sysvol")
     netlogon = os.path.join(sysvol, realm.lower(), "scripts")
 
     setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
@@ -730,7 +743,9 @@ def make_smbconf(smbconf, hostname, domain, realm, serverrole,
             "NETLOGONPATH": netlogon,
             "SYSVOLPATH": sysvol,
             "PRIVATEDIR_LINE": privatedir_line,
-            "LOCKDIR_LINE": lockdir_line
+            "LOCKDIR_LINE": lockdir_line,
+            "STATEDIR_LINE": statedir_line,
+            "CACHEDIR_LINE": cachedir_line
             })
 
     # reload the smb.conf
@@ -1054,13 +1069,17 @@ def setup_samdb_rootdse(samdb, names):
 def setup_self_join(samdb, names, machinepass, dnspass,
                     domainsid, next_rid, invocationid,
                     policyguid, policyguid_dc, domainControllerFunctionality,
-                    ntdsguid):
+                    ntdsguid, dc_rid=None):
     """Join a host to its own domain."""
     assert isinstance(invocationid, str)
     if ntdsguid is not None:
         ntdsguid_line = "objectGUID: %s\n"%ntdsguid
     else:
         ntdsguid_line = ""
+
+    if dc_rid is None:
+        dc_rid = next_rid
+
     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
               "CONFIGDN": names.configdn,
               "SCHEMADN": names.schemadn,
@@ -1071,7 +1090,7 @@ def setup_self_join(samdb, names, machinepass, dnspass,
               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
               "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
               "DOMAINSID": str(domainsid),
-              "DCRID": str(next_rid),
+              "DCRID": str(dc_rid),
               "SAMBA_VERSION_STRING": version,
               "NTDSGUID": ntdsguid_line,
               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
@@ -1101,9 +1120,9 @@ def setup_self_join(samdb, names, machinepass, dnspass,
               "RIDALLOCATIONEND": str(next_rid + 100 + 499),
               })
 
-    # This is partially Samba4 specific and should be replaced by the correct
+    # This is Samba4 specific and should be replaced by the correct
     # DNS AD-style setup
-    setup_add_ldif(samdb, setup_path("provision_dns_add.ldif"), {
+    setup_add_ldif(samdb, setup_path("provision_dns_add_samba.ldif"), {
               "DNSDOMAIN": names.dnsdomain,
               "DOMAINDN": names.domaindn,
               "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')),
@@ -1160,12 +1179,15 @@ def setup_samdb(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):
+        next_rid=None, dc_rid=None):
     """Setup a complete SAM Database.
 
     :note: This will wipe the main SAM database file!
     """
 
+    if next_rid is None:
+        next_rid = 1000
+
     # 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.
@@ -1254,7 +1276,7 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
 
         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
             "DOMAINDN": names.domaindn,
-            "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
+            "CREATTIME": str(samba.unix2nttime(int(time.time()))),
             "NEXTRID": str(next_rid),
             "DEFAULTSITE": names.sitename,
             "CONFIGDN": names.configdn,
@@ -1341,7 +1363,7 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
                 "DOMAINDN": names.domaindn})
         logger.info("Setting up sam.ldb data")
         setup_add_ldif(samdb, setup_path("provision.ldif"), {
-            "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
+            "CREATTIME": str(samba.unix2nttime(int(time.time()))),
             "DOMAINDN": names.domaindn,
             "NETBIOSNAME": names.netbiosname,
             "DEFAULTSITE": names.sitename,
@@ -1371,14 +1393,15 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
 
             logger.info("Setting up self join")
             setup_self_join(samdb, names=names, invocationid=invocationid,
-                dnspass=dnspass,
-                machinepass=machinepass,
-                domainsid=domainsid,
-                next_rid=next_rid,
-                policyguid=policyguid,
-                policyguid_dc=policyguid_dc,
-                domainControllerFunctionality=domainControllerFunctionality,
-                ntdsguid=ntdsguid)
+                            dnspass=dnspass,
+                            machinepass=machinepass,
+                            domainsid=domainsid,
+                            next_rid=next_rid,
+                            dc_rid=dc_rid,
+                            policyguid=policyguid,
+                            policyguid_dc=policyguid_dc,
+                            domainControllerFunctionality=domainControllerFunctionality,
+                            ntdsguid=ntdsguid)
 
             ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
             names.ntdsguid = samdb.searchone(basedn=ntds_dn,
@@ -1495,7 +1518,7 @@ def provision(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,
-        next_rid=1000, adminpass=None, ldapadminpass=None, krbtgtpass=None,
+        next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None, krbtgtpass=None,
         domainguid=None, policyguid=None, policyguid_dc=None,
         invocationid=None, machinepass=None, ntdsguid=None, dnspass=None,
         root=None, nobody=None, users=None, wheel=None, backup=None, aci=None,
@@ -1697,7 +1720,7 @@ def provision(logger, session_info, credentials, smbconf=None,
             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)
+            next_rid=next_rid, dc_rid=dc_rid)
 
         if serverrole == "domain controller":
             if paths.netlogon is None:
@@ -1730,7 +1753,8 @@ def provision(logger, session_info, credentials, smbconf=None,
                     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"))
+            setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
+                              { 'NTDSGUID' : names.ntdsguid })
 
             secretsdb_self_join(secrets_ldb, domain=names.domain,
                 realm=names.realm, dnsdomain=names.dnsdomain,
@@ -1761,6 +1785,8 @@ def provision(logger, session_info, credentials, smbconf=None,
                     dnsdomain=names.dnsdomain,
                     dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
 
+                setup_ad_dns(samdb, names=names, hostip=hostip, hostip6=hostip6)
+
                 domainguid = samdb.searchone(basedn=domaindn,
                     attribute="objectGUID")
                 assert isinstance(domainguid, str)
@@ -1823,6 +1849,25 @@ def provision(logger, session_info, credentials, smbconf=None,
                 logger.info("Failed to chown %s to bind gid %u",
                             dns_keytab_path, paths.bind_gid)
 
+    if samdb_fill != FILL_DRS:
+        # fix any dangling GUIDs from the provision
+        logger.info("Fixing provision GUIDs")
+        chk = dbcheck(samdb, samdb_schema=samdb,  verbose=False, fix=True, yes=True, quiet=True)
+        samdb.transaction_start()
+        # a small number of GUIDs are missing because of ordering issues in the
+        # provision code
+        for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
+            chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
+                               scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory'])
+        chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
+                           scope=ldb.SCOPE_ONELEVEL,
+                           attrs=['ipsecOwnersReference',
+                                  'ipsecFilterReference',
+                                  'ipsecISAKMPReference',
+                                  'ipsecNegotiationPolicyReference',
+                                  'ipsecNFAReference'])
+        samdb.transaction_commit()
+
 
     logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php",
             paths.phpldapadminconfig)