provision: Add idmap database handle to the result of provision
[idra/samba.git] / source4 / scripting / python / samba / provision / __init__.py
index eb6e01f389231f7d69aed96239cf56d4efe8775c..c414bd08f0ead8d366c5d003066c32f415ddf4a5 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"
@@ -94,19 +98,6 @@ def setup_path(file):
 
 # "get_schema_descriptor" is located in "schema.py"
 
-def get_sites_descriptor(domain_sid):
-    sddl = "D:(A;;RPLCLORC;;;AU)" \
-           "(A;;RPWPCRCCLCLORCWOWDSW;;;EA)" \
-           "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
-           "S:AI(AU;CISA;CCDCSDDT;;;WD)" \
-           "(OU;CIIOSA;CR;;f0f8ffab-1191-11d0-a060-00aa006c33ed;WD)" \
-           "(OU;CIIOSA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \
-           "(OU;CIIOSA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \
-           "(OU;CIIOSA;WP;3e10944c-c354-11d0-aff8-0000f80367c1;b7b13124-b82e-11d0-afee-0000f80367c1;WD)"
-    sec = security.descriptor.from_sddl(sddl, domain_sid)
-    return ndr_pack(sec)
-
-
 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)" \
@@ -266,7 +257,7 @@ def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp)
     names.domaindn=current[0]["defaultNamingContext"]
     names.rootdn=current[0]["rootDomainNamingContext"]
     # default site name
-    res3 = samdb.search(expression="(objectClass=*)",
+    res3 = samdb.search(expression="(objectClass=site)",
         base="CN=Sites," + configdn, scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
     names.sitename = str(res3[0]["cn"])
 
@@ -322,7 +313,7 @@ def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp)
         raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
     return names
 
-def update_provision_usn(samdb, low, high, replace=False):
+def update_provision_usn(samdb, low, high, id, replace=False):
     """Update the field provisionUSN in sam.ldb
 
     This field is used to track range of USN modified by provision and
@@ -333,28 +324,35 @@ def update_provision_usn(samdb, low, high, replace=False):
     :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 id: The invocation id of the samba's dc
     :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"])
+        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)
             tab.append(str(e))
 
-    tab.append("%s-%s" % (low, high))
+    tab.append("%s-%s;%s" % (low, high, id))
     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)
+    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)
 
 
-def set_provision_usn(samdb, low, high):
+def set_provision_usn(samdb, low, high, id):
     """Set the field provisionUSN in sam.ldb
     This field is used to track range of USN modified by provision and
     upgradeprovision.
@@ -363,9 +361,12 @@ def set_provision_usn(samdb, low, high):
 
     :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 high: The highest USN modified by this upgrade
+    :param id: The invocationId of the provision"""
+
     tab = []
-    tab.append("%s-%s" % (low, high))
+    tab.append("%s-%s;%s" % (low, high, id))
+
     delta = ldb.Message()
     delta.dn = ldb.Dn(samdb, "@PROVISION")
     delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
@@ -390,25 +391,40 @@ def get_max_usn(samdb,basedn):
 
 
 def get_last_provision_usn(sam):
-    """Get the lastest USN modified by a provision or an upgradeprovision
+    """Get USNs ranges 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
+    :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])
+    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):
-        range = []
-        idx = 0
+        myids = []
+        range = {}
         p = re.compile(r'-')
+        if entry[0].get("provisionnerID"):
+            for e in entry[0]["provisionnerID"]:
+                myids.append(str(e))
         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
+            tab1 = str(r).split(';')
+            if len(tab1) == 2:
+                id = tab1[1]
+            else:
+                id = "default"
+            if (len(myids) > 0 and id not in myids):
+                continue
+            tab2 = p.split(tab1[0])
+            if range.get(id) == None:
+                range[id] = []
+            range[id].append(tab2[0])
+            range[id].append(tab2[1])
         return range
     else:
         return None
@@ -421,6 +437,7 @@ class ProvisionResult(object):
         self.domaindn = None
         self.lp = None
         self.samdb = None
+        self.idmap = None
 
 
 def check_install(lp, session_info, credentials):
@@ -703,13 +720,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),
@@ -721,7 +744,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
@@ -1045,13 +1070,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,
@@ -1062,7 +1091,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(
@@ -1092,9 +1121,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')),
@@ -1151,12 +1180,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.
@@ -1245,7 +1277,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,
@@ -1296,7 +1328,6 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
         samdb.invocation_id = invocationid
 
         logger.info("Setting up sam.ldb configuration data")
-        descr = b64encode(get_sites_descriptor(domainsid))
         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
             "CONFIGDN": names.configdn,
             "NETBIOSNAME": names.netbiosname,
@@ -1308,7 +1339,6 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
             "SERVERDN": names.serverdn,
             "FOREST_FUNCTIONALITY": str(forestFunctionality),
             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
-            "SITES_DESCRIPTOR": descr
             })
 
         logger.info("Setting up display specifiers")
@@ -1334,7 +1364,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,
@@ -1364,14 +1394,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,
@@ -1465,11 +1496,30 @@ def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
     set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
 
 
+def interface_ips_v4(lp):
+    '''return only IPv4 IPs'''
+    ips = samba.interface_ips(lp, False)
+    ret = []
+    for i in ips:
+        if i.find(':') == -1:
+            ret.append(i)
+    return ret
+
+def interface_ips_v6(lp, linklocal=False):
+    '''return only IPv6 IPs'''
+    ips = samba.interface_ips(lp, False)
+    ret = []
+    for i in ips:
+        if i.find(':') != -1 and (linklocal or i.find('%') == -1):
+            ret.append(i)
+    return ret
+
+
 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,
@@ -1565,15 +1615,26 @@ def provision(logger, session_info, credentials, smbconf=None,
 
     if hostip is None:
         logger.info("Looking up IPv4 addresses")
-        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:
+        hostips = interface_ips_v4(lp)
+        if len(hostips) > 0:
             hostip = hostips[0]
             if len(hostips) > 1:
-                logger.warning("More than one IPv4 address found. Using %s.",
+                logger.warning("More than one IPv4 address found. Using %s",
                     hostip)
+    if hostip == "127.0.0.1":
+        hostip = None
+    if hostip is None:
+        logger.warning("No IPv4 address will be assigned")
+
+    if hostip6 is None:
+        logger.info("Looking up IPv6 addresses")
+        hostips = interface_ips_v6(lp, linklocal=False)
+        if hostips:
+            hostip6 = hostips[0]
+        if len(hostips) > 1:
+            logger.warning("More than one IPv6 address found. Using %s", hostip6)
+    if hostip6 is None:
+        logger.warning("No IPv6 address will be assigned")
 
     if serverrole is None:
         serverrole = lp.get("server role")
@@ -1660,7 +1721,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:
@@ -1693,7 +1754,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,
@@ -1724,6 +1786,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)
@@ -1750,9 +1814,9 @@ def provision(logger, session_info, credentials, smbconf=None,
             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)
+                update_provision_usn(samdb, 0, maxUSN, invocationid, 1)
             else:
-                set_provision_usn(samdb, 0, maxUSN)
+                set_provision_usn(samdb, 0, maxUSN, invocationid)
 
         create_krb5_conf(paths.krb5conf,
                          dnsdomain=names.dnsdomain, hostname=names.hostname,
@@ -1786,6 +1850,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)
@@ -1822,6 +1905,7 @@ def provision(logger, session_info, credentials, smbconf=None,
     result.paths = paths
     result.lp = lp
     result.samdb = samdb
+    result.idmap = idmap
     return result
 
 
@@ -1841,7 +1925,7 @@ def provision_become_dc(smbconf=None, targetdir=None,
         smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
         realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
         configdn=configdn, serverdn=serverdn, domain=domain,
-        hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
+        hostname=hostname, hostip=None, domainsid=domainsid,
         machinepass=machinepass, serverrole="domain controller",
         sitename=sitename)
     res.lp.set("debuglevel", str(debuglevel))