diff options
Diffstat (limited to 'source4/scripting/python/samba/provision/__init__.py')
-rw-r--r-- | source4/scripting/python/samba/provision/__init__.py | 251 |
1 files changed, 195 insertions, 56 deletions
diff --git a/source4/scripting/python/samba/provision/__init__.py b/source4/scripting/python/samba/provision/__init__.py index ff9b00122d..5aabd36c1a 100644 --- a/source4/scripting/python/samba/provision/__init__.py +++ b/source4/scripting/python/samba/provision/__init__.py @@ -38,23 +38,23 @@ import uuid import socket import urllib import shutil +import string import ldb from samba.auth import system_session, admin_session import samba +from samba.dsdb import DS_DOMAIN_FUNCTION_2000 from samba import ( Ldb, check_all_substituted, - in_source_tree, - source_tree_topdir, read_and_sub_file, setup_file, substitute_var, valid_netbios_name, version, ) -from samba.dcerpc import security +from samba.dcerpc import security, misc from samba.dcerpc.misc import ( SEC_CHAN_BDC, SEC_CHAN_WKSTA, @@ -94,19 +94,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)" \ @@ -217,8 +204,112 @@ class ProvisionNames(object): self.sitename = None self.smbconf = None +def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp): + """Get key provision parameters (realm, domain, ...) from a given provision + + :param samdb: An LDB object connected to the sam.ldb file + :param secretsdb: An LDB object connected to the secrets.ldb file + :param idmapdb: An LDB object connected to the idmap.ldb file + :param paths: A list of path to provision object + :param smbconf: Path to the smb.conf file + :param lp: A LoadParm object + :return: A list of key provision parameters + """ + names = ProvisionNames() + names.adminpass = None + + # NT domain, kerberos realm, root dn, domain dn, domain dns name + names.domain = string.upper(lp.get("workgroup")) + names.realm = lp.get("realm") + basedn = "DC=" + names.realm.replace(".",",DC=") + names.dnsdomain = names.realm.lower() + names.realm = string.upper(names.realm) + # netbiosname + # Get the netbiosname first (could be obtained from smb.conf in theory) + res = secretsdb.search(expression="(flatname=%s)" % + names.domain,base="CN=Primary Domains", + scope=ldb.SCOPE_SUBTREE, attrs=["sAMAccountName"]) + names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","") + + names.smbconf = smbconf + + # That's a bit simplistic but it's ok as long as we have only 3 + # partitions + current = samdb.search(expression="(objectClass=*)", + base="", scope=ldb.SCOPE_BASE, + attrs=["defaultNamingContext", "schemaNamingContext", + "configurationNamingContext","rootDomainNamingContext"]) + + names.configdn = current[0]["configurationNamingContext"] + configdn = str(names.configdn) + names.schemadn = current[0]["schemaNamingContext"] + if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb, + current[0]["defaultNamingContext"][0]))): + raise ProvisioningError(("basedn in %s (%s) and from %s (%s)" + "is not the same ..." % (paths.samdb, + str(current[0]["defaultNamingContext"][0]), + paths.smbconf, basedn))) + + names.domaindn=current[0]["defaultNamingContext"] + names.rootdn=current[0]["rootDomainNamingContext"] + # default site name + res3 = samdb.search(expression="(objectClass=site)", + base="CN=Sites," + configdn, scope=ldb.SCOPE_ONELEVEL, attrs=["cn"]) + names.sitename = str(res3[0]["cn"]) + + # dns hostname and server dn + res4 = samdb.search(expression="(CN=%s)" % names.netbiosname, + base="OU=Domain Controllers,%s" % basedn, + scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"]) + names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"") + + server_res = samdb.search(expression="serverReference=%s" % res4[0].dn, + attrs=[], base=configdn) + names.serverdn = server_res[0].dn + + # invocation id/objectguid + res5 = samdb.search(expression="(objectClass=*)", + base="CN=NTDS Settings,%s" % str(names.serverdn), scope=ldb.SCOPE_BASE, + attrs=["invocationID", "objectGUID"]) + names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0])) + names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0])) + + # domain guid/sid + res6 = samdb.search(expression="(objectClass=*)", base=basedn, + scope=ldb.SCOPE_BASE, attrs=["objectGUID", + "objectSid","msDS-Behavior-Version" ]) + names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0])) + names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0]) + if res6[0].get("msDS-Behavior-Version") is None or \ + int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000: + names.domainlevel = DS_DOMAIN_FUNCTION_2000 + else: + names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0]) + + # policy guid + res7 = samdb.search(expression="(displayName=Default Domain Policy)", + base="CN=Policies,CN=System," + basedn, + scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"]) + names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","") + # dc policy guid + res8 = samdb.search(expression="(displayName=Default Domain Controllers" + " Policy)", + base="CN=Policies,CN=System," + basedn, + scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"]) + if len(res8) == 1: + names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","") + else: + names.policyid_dc = None + res9 = idmapdb.search(expression="(cn=%s)" % + (security.SID_BUILTIN_ADMINISTRATORS), + attrs=["xidNumber"]) + if len(res9) == 1: + names.wheel_gid = res9[0]["xidNumber"] + else: + 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 @@ -229,6 +320,7 @@ 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) """ @@ -240,17 +332,24 @@ def update_provision_usn(samdb, low, high, replace=False): scope=ldb.SCOPE_SUBTREE, 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="(&(dn=@PROVISION)(provisionnerID=*))", + base="", scope=ldb.SCOPE_SUBTREE, + 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. @@ -259,9 +358,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, @@ -286,25 +388,36 @@ 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]) + attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"]) 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 @@ -328,7 +441,7 @@ def check_install(lp, session_info, credentials): """ if lp.get("realm") == "": raise Exception("Realm empty") - samdb = Ldb(lp.get("sam database"), session_info=session_info, + samdb = Ldb(lp.samdb_url(), session_info=session_info, credentials=credentials, lp=lp) if len(samdb.search("(cn=Administrator)")) != 1: raise ProvisioningError("No administrator account found") @@ -413,12 +526,9 @@ def provision_paths_from_lp(lp, dnsdomain): paths.keytab = "secrets.keytab" paths.shareconf = os.path.join(paths.private_dir, "share.ldb") - paths.samdb = os.path.join(paths.private_dir, - lp.get("sam database") or "samdb.ldb") - paths.idmapdb = os.path.join(paths.private_dir, - lp.get("idmap database") or "idmap.ldb") - paths.secrets = os.path.join(paths.private_dir, - lp.get("secrets database") or "secrets.ldb") + paths.samdb = os.path.join(paths.private_dir, "sam.ldb") + paths.idmapdb = os.path.join(paths.private_dir, "idmap.ldb") + paths.secrets = os.path.join(paths.private_dir, "secrets.ldb") paths.privilege = os.path.join(paths.private_dir, "privilege.ldb") paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone") paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list") @@ -608,11 +718,6 @@ def make_smbconf(smbconf, hostname, domain, realm, serverrole, privatedir_line = "" lockdir_line = "" - if sid_generator == "internal": - sid_generator_line = "" - else: - sid_generator_line = "sid generator = " + sid_generator - sysvol = os.path.join(lp.get("lock dir"), "sysvol") netlogon = os.path.join(sysvol, realm.lower(), "scripts") @@ -624,7 +729,6 @@ def make_smbconf(smbconf, hostname, domain, realm, serverrole, "SERVERROLE": serverrole, "NETLOGONPATH": netlogon, "SYSVOLPATH": sysvol, - "SIDGENERATOR_LINE": sid_generator_line, "PRIVATEDIR_LINE": privatedir_line, "LOCKDIR_LINE": lockdir_line }) @@ -1166,6 +1270,11 @@ def setup_samdb(path, session_info, provision_backend, lp, names, "DESCRIPTOR": descr, }) + # Now register this container in the root of the forest + msg = ldb.Message(ldb.Dn(samdb, names.domaindn)) + msg["subRefs"] = ldb.MessageElement(names.configdn , ldb.FLAG_MOD_ADD, + "subRefs") + # The LDIF here was created when the Schema object was constructed logger.info("Setting up sam.ldb schema") samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"]) @@ -1196,7 +1305,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, @@ -1208,7 +1316,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") @@ -1365,6 +1472,25 @@ 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, @@ -1465,15 +1591,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") @@ -1640,6 +1777,7 @@ def provision(logger, session_info, credentials, smbconf=None, create_named_txt(paths.namedtxt, realm=names.realm, dnsdomain=names.dnsdomain, + dnsname = "%s.%s" % (names.hostname, 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) @@ -1649,9 +1787,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, @@ -1740,7 +1878,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)) @@ -1876,7 +2014,7 @@ def create_named_conf(paths, realm, dnsdomain, setup_file(setup_path("named.conf.update"), paths.namedconf_update) -def create_named_txt(path, realm, dnsdomain, private_dir, +def create_named_txt(path, realm, dnsdomain, dnsname, private_dir, keytab_name): """Write out a file containing zone statements suitable for inclusion in a named.conf file (including GSS-TSIG configuration). @@ -1889,6 +2027,7 @@ def create_named_txt(path, realm, dnsdomain, private_dir, """ setup_file(setup_path("named.txt"), path, { "DNSDOMAIN": dnsdomain, + "DNSNAME" : dnsname, "REALM": realm, "DNS_KEYTAB": keytab_name, "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name), |