X-Git-Url: http://git.samba.org/samba.git/?p=nivanova%2Fsamba-autobuild%2F.git;a=blobdiff_plain;f=source4%2Fscripting%2Fpython%2Fsamba%2Fprovision%2F__init__.py;h=db202eff4798f56721c4ba5327940d4267a30db7;hp=1fed220507ef2799f5f3b7e8a9c982d920a4cb1c;hb=051042229db70f6272132c259fb69adc74fb8648;hpb=8caac9462ac09b7ff99a7032329d0e56c2e0aac5 diff --git a/source4/scripting/python/samba/provision/__init__.py b/source4/scripting/python/samba/provision/__init__.py index 1fed220507e..db202eff479 100644 --- a/source4/scripting/python/samba/provision/__init__.py +++ b/source4/scripting/python/samba/provision/__init__.py @@ -2,7 +2,7 @@ # Unix SMB/CIFS implementation. # backend code for provisioning a Samba4 server -# Copyright (C) Jelmer Vernooij 2007-2010 +# Copyright (C) Jelmer Vernooij 2007-2012 # Copyright (C) Andrew Bartlett 2008-2009 # Copyright (C) Oliver Liebel 2008-2009 # @@ -37,160 +37,70 @@ import time 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, + MAX_NETBIOS_NAME_LEN, check_all_substituted, - in_source_tree, - read_and_sub_file, + is_valid_netbios_char, setup_file, substitute_var, valid_netbios_name, version, ) +from samba.dcerpc import security, misc +from samba.dcerpc.misc import ( + SEC_CHAN_BDC, + SEC_CHAN_WKSTA, + ) from samba.dsdb import ( DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008_R2, ENC_ALL_TYPES, ) -from samba.dcerpc import security -from samba.dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA from samba.idmap import IDmapDB from samba.ms_display_specifiers import read_ms_ldif from samba.ntacls import setntacl, dsacl2fsacl -from samba.ndr import ndr_pack,ndr_unpack +from samba.ndr import ndr_pack, ndr_unpack from samba.provision.backend import ( ExistingBackend, FDSBackend, LDBBackend, OpenLDAPBackend, ) +from samba.provision.descriptor import ( + get_config_descriptor, + get_domain_descriptor + ) +from samba.provision.common import ( + setup_path, + setup_add_ldif, + setup_modify_ldif, + ) +from samba.provision.sambadns import ( + setup_ad_dns, + create_dns_update_list + ) + 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" DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9" DEFAULTSITE = "Default-First-Site-Name" LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN" -def find_setup_dir(): - """Find the setup directory used by provision.""" - if in_source_tree(): - # In source tree - dirname = os.path.dirname(__file__) - return os.path.normpath(os.path.join(dirname, "../../../setup")) - else: - 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.normpath(os.path.join(prefix, suffix)) - if os.path.isdir(ret): - return ret - raise Exception("Unable to find setup directory.") - -# Descriptors of naming contexts and other important objects - -# "get_schema_descriptor" is located in "schema.py" - -def get_sites_descriptor(domain_sid): - sddl = "O:EAG:EAD:AI(A;;RPLCLORC;;;AU)" \ - "(A;;RPWPCRCCLCLORCWOWDSW;;;EA)" \ - "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \ - "(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \ - "(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \ - "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)" \ - "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \ - "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \ - "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \ - "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \ - "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \ - "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \ - "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)" - sec = security.descriptor.from_sddl(sddl, domain_sid) - return ndr_pack(sec) - - -def get_domain_descriptor(domain_sid): - sddl= "O:BAG:BAD:AI(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \ - "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;DD)" \ - "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;ED)" \ - "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;ED)" \ - "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;ED)" \ - "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \ - "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;e2a36dc9-ae17-47c3-b58b-be34c55ba633;;IF)" \ - "(OA;;RP;c7407360-20bf-11d0-a768-00aa006e0529;;RU)" \ - "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;RU)" \ - "(OA;CIIO;RPLCLORC;;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RPLCLORC;;bf967a9c-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;CIIO;RPLCLORC;;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;;CR;05c74c5e-4deb-43b4-bd9f-86664c2a7fd5;;AU)" \ - "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \ - "(OA;;CR;ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501;;AU)" \ - "(OA;;CR;280f369c-67c7-438e-ae98-1d46f3c6f541;;AU)" \ - "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;AU)" \ - "(OA;CIIO;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \ - "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \ - "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \ - "(A;;RPRC;;;RU)" \ - "(A;CI;LC;;;RU)" \ - "(A;CI;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \ - "(A;;RP;;;WD)" \ - "(A;;RPLCLORC;;;ED)" \ - "(A;;RPLCLORC;;;AU)" \ - "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \ - "S:AI(OU;CISA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \ - "(OU;CISA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \ - "(AU;SA;CR;;;DU)(AU;SA;CR;;;BA)(AU;SA;WPWOWD;;;WD)" - sec = security.descriptor.from_sddl(sddl, domain_sid) - return ndr_pack(sec) - - class ProvisionPaths(object): def __init__(self): @@ -209,6 +119,7 @@ class ProvisionPaths(object): self.dns = None self.winsdb = None self.private_dir = None + self.phpldapadminconfig = None class ProvisionNames(object): @@ -227,8 +138,113 @@ 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 -def update_provision_usn(samdb, low, high, replace=False): + :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") + names.dnsdomain = names.realm.lower() + basedn = samba.dn_from_dns_name(names.dnsdomain) + 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, id, replace=False): """Update the field provisionUSN in sam.ldb This field is used to track range of USN modified by provision and @@ -239,28 +255,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. @@ -269,9 +292,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, @@ -296,37 +322,87 @@ 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 class ProvisionResult(object): + """Result of a provision. + + :ivar server_role: The server role + :ivar paths: ProvisionPaths instance + :ivar domaindn: The domain dn, as string + """ def __init__(self): + self.server_role = None self.paths = None self.domaindn = None self.lp = None self.samdb = None + self.idmap = None + self.names = None + self.domainsid = None + self.adminpass_generated = None + self.adminpass = None + self.backend_result = None + + def report_logger(self, logger): + """Report this provision result to a logger.""" + logger.info( + "Once the above files are installed, your Samba4 server will " + "be ready to use") + if self.adminpass_generated: + logger.info("Admin password: %s", self.adminpass) + logger.info("Server Role: %s", self.server_role) + logger.info("Hostname: %s", self.names.hostname) + logger.info("NetBIOS Domain: %s", self.names.domain) + logger.info("DNS Domain: %s", self.names.dnsdomain) + logger.info("DOMAIN SID: %s", self.domainsid) + + if self.paths.phpldapadminconfig is not None: + logger.info( + "A phpLDAPadmin configuration file suitable for administering " + "the Samba 4 LDAP server has been created in %s.", + self.paths.phpldapadminconfig) + + if self.backend_result: + self.backend_result.report_logger(logger) def check_install(lp, session_info, credentials): @@ -338,7 +414,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") @@ -363,51 +439,6 @@ findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2] findnss_gid = lambda names: findnss(grp.getgrnam, names)[2] -def setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]): - """Setup a ldb in the private dir. - - :param ldb: LDB file to import data into - :param ldif_path: Path of the LDIF file to load - :param subst_vars: Optional variables to subsitute in LDIF. - :param nocontrols: Optional list of controls, can be None for no controls - """ - assert isinstance(ldif_path, str) - data = read_and_sub_file(ldif_path, subst_vars) - ldb.add_ldif(data, controls) - - -def setup_modify_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]): - """Modify a ldb in the private dir. - - :param ldb: LDB object. - :param ldif_path: LDIF file path. - :param subst_vars: Optional dictionary with substitution variables. - """ - data = read_and_sub_file(ldif_path, subst_vars) - ldb.modify_ldif(data, controls) - - -def setup_ldb(ldb, ldif_path, subst_vars): - """Import a LDIF a file into a LDB handle, optionally substituting - variables. - - :note: Either all LDIF data will be added or none (using transactions). - - :param ldb: LDB file to import into. - :param ldif_path: Path to the LDIF file. - :param subst_vars: Dictionary with substitution variables. - """ - assert ldb is not None - ldb.transaction_start() - try: - setup_add_ldif(ldb, ldif_path, subst_vars) - except: - ldb.transaction_cancel() - raise - else: - ldb.transaction_commit() - - def provision_paths_from_lp(lp, dnsdomain): """Set the default paths for provisioning. @@ -423,12 +454,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") @@ -453,6 +481,13 @@ def provision_paths_from_lp(lp, dnsdomain): return paths +def determine_netbios_name(hostname): + """Determine a netbios name from a hostname.""" + # remove forbidden chars and force the length to be <16 + netbiosname = "".join([x for x in hostname if is_valid_netbios_char(x)]) + return netbiosname[:MAX_NETBIOS_NAME_LEN].upper() + + def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None, rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None, sitename=None): @@ -463,15 +498,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, netbiosname = lp.get("netbios name") if netbiosname is None: - netbiosname = hostname - # remove forbidden chars - newnbname = "" - for x in netbiosname: - if x.isalnum() or x in VALID_NETBIOS_CHARS: - newnbname = "%s%c" % (newnbname, x) - #force the length to be <16 - netbiosname = newnbname[0:15] - assert netbiosname is not None + netbiosname = determine_netbios_name(hostname) netbiosname = netbiosname.upper() if not valid_netbios_name(netbiosname): raise InvalidNetbiosName(netbiosname) @@ -499,7 +526,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, 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 %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)) + 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"), lp.configfile, serverrole)) if serverrole == "domain controller": if domain is None: @@ -511,7 +538,10 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'! Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile)) if domaindn is None: - domaindn = "DC=" + dnsdomain.replace(".", ",DC=") + domaindn = samba.dn_from_dns_name(dnsdomain) + + if domain == netbiosname: + raise ProvisioningError("guess_names: Domain '%s' must not be equal to short host name '%s'!" % (domain, netbiosname)) else: domain = netbiosname if domaindn is None: @@ -522,7 +552,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, if hostname.upper() == realm: raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname)) - if netbiosname == realm: + if netbiosname.upper() == realm: 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)) @@ -536,7 +566,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, schemadn = "CN=Schema," + configdn if sitename is None: - sitename=DEFAULTSITE + sitename = DEFAULTSITE names = ProvisionNames() names.rootdn = rootdn @@ -556,35 +586,21 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, return names -def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, - targetdir, sid_generator="internal", eadb=False, lp=None): +def make_smbconf(smbconf, hostname, domain, realm, targetdir, + serverrole=None, sid_generator=None, eadb=False, use_ntvfs=False, lp=None, + global_param=None): """Create a new smb.conf file based on a couple of basic settings. """ assert smbconf is not None + if hostname is None: hostname = socket.gethostname().split(".")[0] - netbiosname = hostname.upper() - # remove forbidden chars - newnbname = "" - for x in netbiosname: - if x.isalnum() or x in VALID_NETBIOS_CHARS: - newnbname = "%s%c" % (newnbname, x) - #force the length to be <16 - netbiosname = newnbname[0:15] - else: - netbiosname = hostname.upper() + + netbiosname = determine_netbios_name(hostname) if serverrole is None: serverrole = "standalone" - assert serverrole in ("domain controller", "member server", "standalone") - if serverrole == "domain controller": - smbconfsuffix = "dc" - elif serverrole == "member server": - smbconfsuffix = "member" - elif serverrole == "standalone": - smbconfsuffix = "standalone" - if sid_generator is None: sid_generator = "internal" @@ -594,56 +610,68 @@ def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, assert realm is not None realm = realm.upper() + global_settings = { + "passdb backend": "samba4", + "netbios name": netbiosname, + "workgroup": domain, + "realm": realm, + "server role": serverrole, + } + if lp is None: lp = samba.param.LoadParm() #Load non-existant file if os.path.exists(smbconf): lp.load(smbconf) - if eadb and not lp.get("posix:eadb"): - if targetdir is not None: - privdir = os.path.join(targetdir, "private") - else: - privdir = lp.get("private dir") - lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb"))) + if eadb: + if use_ntvfs and not lp.get("posix:eadb"): + if targetdir is not None: + privdir = os.path.join(targetdir, "private") + else: + privdir = lp.get("private dir") + lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb"))) + elif not use_ntvfs and not lp.get("xattr_tdb:file"): + if targetdir is not None: + statedir = os.path.join(targetdir, "state") + else: + statedir = lp.get("state dir") + lp.set("xattr_tdb:file", os.path.abspath(os.path.join(statedir, "xattr.tdb"))) + + if global_param is not None: + for ent in global_param: + if global_param[ent] is not None: + global_settings[ent] = " ".join(global_param[ent]) 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) + global_settings["private dir"] = os.path.abspath(os.path.join(targetdir, "private")) + global_settings["lock dir"] = os.path.abspath(targetdir) + global_settings["state directory"] = os.path.abspath(targetdir) + global_settings["cache directory"] = os.path.abspath(targetdir) lp.set("lock dir", os.path.abspath(targetdir)) - else: - privatedir_line = "" - lockdir_line = "" + lp.set("state directory", os.path.abspath(targetdir)) + lp.set("cache directory", os.path.abspath(targetdir)) - if sid_generator == "internal": - sid_generator_line = "" - else: - sid_generator_line = "sid generator = " + sid_generator - - used_setup_dir = setup_path("") - default_setup_dir = lp.get("setup directory") - setupdir_line = "" - if used_setup_dir != default_setup_dir: - setupdir_line = "setup directory = %s" % used_setup_dir - lp.set("setup directory", used_setup_dir) - - sysvol = os.path.join(lp.get("lock dir"), "sysvol") - netlogon = os.path.join(sysvol, realm.lower(), "scripts") - - setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), - smbconf, { - "NETBIOS_NAME": netbiosname, - "DOMAIN": domain, - "REALM": realm, - "SERVERROLE": serverrole, - "NETLOGONPATH": netlogon, - "SYSVOLPATH": sysvol, - "SETUPDIRECTORY_LINE": setupdir_line, - "SIDGENERATOR_LINE": sid_generator_line, - "PRIVATEDIR_LINE": privatedir_line, - "LOCKDIR_LINE": lockdir_line - }) + shares = {} + if serverrole == "domain controller": + shares["sysvol"] = os.path.join(lp.get("state directory"), "sysvol") + shares["netlogon"] = os.path.join(shares["sysvol"], realm.lower(), + "scripts") + f = open(smbconf, 'w') + try: + f.write("[globals]\n") + for key, val in global_settings.iteritems(): + f.write("\t%s = %s\n" % (key, val)) + f.write("\n") + + for name, path in shares.iteritems(): + f.write("[%s]\n" % name) + f.write("\tpath = %s\n" % path) + f.write("\tread only = no\n") + f.write("\n") + finally: + f.close() # reload the smb.conf lp.load(smbconf) @@ -651,12 +679,13 @@ def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, # this ensures that any smb.conf parameters that were set # on the provision/join command line are set in the resulting smb.conf f = open(smbconf, mode='w') - lp.dump(f, False) - f.close() - + try: + lp.dump(f, False) + finally: + f.close() -def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid, +def setup_name_mappings(idmap, sid, root_uid, nobody_uid, users_gid, wheel_gid): """setup reasonable name mappings for sam names to unix names. @@ -676,7 +705,7 @@ def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid, idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid) -def setup_samdb_partitions(samdb_path, setup_path, logger, lp, session_info, +def setup_samdb_partitions(samdb_path, logger, lp, session_info, provision_backend, names, schema, serverrole, erase=False): """Setup the partitions for the SAM database. @@ -704,17 +733,14 @@ def setup_samdb_partitions(samdb_path, setup_path, logger, lp, session_info, lp=lp, options=["modules:"]) ldap_backend_line = "# No LDAP backend" - if provision_backend.type is not "ldb": + if provision_backend.type != "ldb": ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri samdb.transaction_start() try: 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(), - "DOMAINDN": ldb.Dn(schema.ldb, names.domaindn).get_casefold(), - "LDAP_BACKEND_LINE": ldap_backend_line, + "LDAP_BACKEND_LINE": ldap_backend_line }) @@ -724,7 +750,7 @@ def setup_samdb_partitions(samdb_path, setup_path, logger, lp, session_info, }) logger.info("Setting up sam.ldb rootDSE") - setup_samdb_rootdse(samdb, setup_path, names) + setup_samdb_rootdse(samdb, names) except: samdb.transaction_cancel() raise @@ -751,11 +777,11 @@ def secretsdb_self_join(secretsdb, domain, "privateKeytab"] if realm is not None: - if dnsdomain is None: - dnsdomain = realm.lower() - dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower()) + if dnsdomain is None: + dnsdomain = realm.lower() + dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower()) else: - dnsname = None + dnsname = None shortname = netbiosname.lower() # We don't need to set msg["flatname"] here, because rdn_name will handle @@ -764,11 +790,11 @@ def secretsdb_self_join(secretsdb, domain, msg["secureChannelType"] = [str(secure_channel_type)] msg["objectClass"] = ["top", "primaryDomain"] if dnsname is not None: - msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"] - msg["realm"] = [realm] - msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())] - msg["msDS-KeyVersionNumber"] = [str(key_version_number)] - msg["privateKeytab"] = ["secrets.keytab"] + msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"] + msg["realm"] = [realm] + msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())] + msg["msDS-KeyVersionNumber"] = [str(key_version_number)] + msg["privateKeytab"] = ["secrets.keytab"] msg["secret"] = [machinepass] msg["samAccountName"] = ["%s$" % netbiosname] @@ -780,10 +806,9 @@ def secretsdb_self_join(secretsdb, domain, # than one record for this SID, realm or netbios domain at a time, # but we don't delete the old record that we are about to modify, # because that would delete the keytab and previous password. - res = secretsdb.search(base="cn=Primary Domains", - attrs=attrs, - expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))), - scope=ldb.SCOPE_ONELEVEL) + res = secretsdb.search(base="cn=Primary Domains", attrs=attrs, + expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(distinguishedName=%s)))" % (domain, realm, str(domainsid), str(msg.dn))), + scope=ldb.SCOPE_ONELEVEL) for del_msg in res: secretsdb.delete(del_msg.dn) @@ -820,38 +845,13 @@ def secretsdb_self_join(secretsdb, domain, secretsdb.add(msg) -def secretsdb_setup_dns(secretsdb, setup_path, names, private_dir, realm, - dnsdomain, dns_keytab_path, dnspass): - """Add DNS specific bits to a secrets database. - - :param secretsdb: Ldb Handle to the secrets database - :param setup_path: Setup path function - :param machinepass: Machine password - """ - try: - os.unlink(os.path.join(private_dir, dns_keytab_path)) - except OSError: - pass - - setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { - "REALM": realm, - "DNSDOMAIN": dnsdomain, - "DNS_KEYTAB": dns_keytab_path, - "DNSPASS_B64": b64encode(dnspass), - "HOSTNAME": names.hostname, - "DNSNAME" : '%s.%s' % ( - names.netbiosname.lower(), names.dnsdomain.lower()) - }) - - -def setup_secretsdb(paths, setup_path, session_info, backend_credentials, lp): +def setup_secretsdb(paths, 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. :param credentials: Credentials :param lp: Loadparm context @@ -870,12 +870,10 @@ def setup_secretsdb(paths, setup_path, session_info, backend_credentials, lp): path = paths.secrets - secrets_ldb = Ldb(path, session_info=session_info, - lp=lp) + secrets_ldb = Ldb(path, session_info=session_info, lp=lp) secrets_ldb.erase() secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif")) - secrets_ldb = Ldb(path, session_info=session_info, - lp=lp) + secrets_ldb = Ldb(path, session_info=session_info, lp=lp) secrets_ldb.transaction_start() try: secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif")) @@ -895,18 +893,16 @@ def setup_secretsdb(paths, setup_path, session_info, backend_credentials, lp): "LDAPADMINREALM": backend_credentials.get_realm(), "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password()) }) - - return secrets_ldb except: secrets_ldb.transaction_cancel() raise + return secrets_ldb -def setup_privileges(path, setup_path, session_info, lp): +def setup_privileges(path, session_info, lp): """Setup the privileges database. :param path: Path to the privileges database. - :param setup_path: Get the path to a setup file. :param session_info: Session info. :param credentials: Credentials :param lp: Loadparm context @@ -919,29 +915,26 @@ def setup_privileges(path, setup_path, session_info, lp): privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif")) -def setup_registry(path, setup_path, session_info, lp): +def setup_registry(path, session_info, lp): """Setup the registry. :param path: Path to the registry database - :param setup_path: Function that returns the path to a setup. :param session_info: Session information :param credentials: Credentials :param lp: Loadparm context """ reg = samba.registry.Registry() - hive = samba.registry.open_ldb(path, session_info=session_info, - lp_ctx=lp) + hive = samba.registry.open_ldb(path, session_info=session_info, lp_ctx=lp) 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) -def setup_idmapdb(path, setup_path, session_info, lp): +def setup_idmapdb(path, session_info, lp): """Setup the idmap database. :param path: path to the idmap database - :param setup_path: Function that returns a path to a setup file :param session_info: Session information :param credentials: Credentials :param lp: Loadparm context @@ -955,32 +948,33 @@ def setup_idmapdb(path, setup_path, session_info, lp): return idmap_ldb -def setup_samdb_rootdse(samdb, setup_path, names): +def setup_samdb_rootdse(samdb, names): """Setup the SamDB rootdse. :param samdb: Sam Database handle - :param setup_path: Obtain setup path """ setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), { "SCHEMADN": names.schemadn, "DOMAINDN": names.domaindn, - "ROOTDN": names.rootdn, + "ROOTDN" : names.rootdn, "CONFIGDN": names.configdn, "SERVERDN": names.serverdn, }) -def setup_self_join(samdb, names, - machinepass, dnspass, - domainsid, next_rid, invocationid, setup_path, - policyguid, policyguid_dc, domainControllerFunctionality, - ntdsguid): +def setup_self_join(samdb, admin_session_info, names, fill, machinepass, + dnspass, domainsid, next_rid, invocationid, policyguid, policyguid_dc, + domainControllerFunctionality, ntdsguid=None, 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, @@ -991,11 +985,13 @@ def setup_self_join(samdb, names, "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( - domainControllerFunctionality)}) + domainControllerFunctionality), + "RIDALLOCATIONSTART": str(next_rid + 100), + "RIDALLOCATIONEND": str(next_rid + 100 + 499)}) setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { "POLICYGUID": policyguid, @@ -1003,27 +999,51 @@ def setup_self_join(samdb, names, "DNSDOMAIN": names.dnsdomain, "DOMAINDN": names.domaindn}) - # add the NTDSGUID based SPNs - ntds_dn = "CN=NTDS Settings,%s" % names.serverdn - names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID", - expression="", scope=ldb.SCOPE_BASE) - assert isinstance(names.ntdsguid, str) + # If we are setting up a subdomain, then this has been replicated in, so we + # don't need to add it + if fill == FILL_FULL: + setup_add_ldif(samdb, setup_path("provision_self_join_config.ldif"), { + "CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn, + "DOMAINDN": names.domaindn, + "SERVERDN": names.serverdn, + "INVOCATIONID": invocationid, + "NETBIOSNAME": names.netbiosname, + "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain), + "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')), + "DOMAINSID": str(domainsid), + "DCRID": str(dc_rid), + "SAMBA_VERSION_STRING": version, + "NTDSGUID": ntdsguid_line, + "DOMAIN_CONTROLLER_FUNCTIONALITY": str( + domainControllerFunctionality)}) # Setup fSMORoleOwner entries to point at the newly created DC entry + setup_modify_ldif(samdb, + setup_path("provision_self_join_modify_config.ldif"), { + "CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn, + "DEFAULTSITE": names.sitename, + "NETBIOSNAME": names.netbiosname, + "SERVERDN": names.serverdn, + }) + + system_session_info = system_session() + samdb.set_session_info(system_session_info) + # Setup fSMORoleOwner entries to point at the newly created DC entry to + # modify a serverReference under cn=config when we are a subdomain, we must + # be system due to ACLs setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), { "DOMAINDN": names.domaindn, - "CONFIGDN": names.configdn, - "SCHEMADN": names.schemadn, - "DEFAULTSITE": names.sitename, "SERVERDN": names.serverdn, "NETBIOSNAME": names.netbiosname, - "RIDALLOCATIONSTART": str(next_rid + 100), - "RIDALLOCATIONEND": str(next_rid + 100 + 499), }) - # This is partially Samba4 specific and should be replaced by the correct + samdb.set_session_info(admin_session_info) + + # 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')), @@ -1041,7 +1061,6 @@ def getpolicypath(sysvolpath, dnsdomain, guid): :param guid: The GUID of the policy :return: A string with the complete path to the policy folder """ - if guid[0] != "{": guid = "{%s}" % guid policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid) @@ -1051,8 +1070,11 @@ def getpolicypath(sysvolpath, dnsdomain, guid): def create_gpo_struct(policy_path): if not os.path.exists(policy_path): os.makedirs(policy_path, 0775) - open(os.path.join(policy_path, "GPT.INI"), 'w').write( - "[General]\r\nVersion=0") + f = open(os.path.join(policy_path, "GPT.INI"), 'w') + try: + f.write("[General]\r\nVersion=0") + finally: + f.close() p = os.path.join(policy_path, "MACHINE") if not os.path.exists(p): os.makedirs(p, 0775) @@ -1076,16 +1098,49 @@ def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc): create_gpo_struct(policy_path) -def setup_samdb(path, setup_path, session_info, provision_backend, lp, names, - logger, domainsid, domainguid, policyguid, policyguid_dc, fill, - adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid, - serverrole, am_rodc=False, dom_for_fun_level=None, schema=None, - next_rid=1000): +def setup_samdb(path, session_info, provision_backend, lp, names, + logger, fill, serverrole, schema, am_rodc=False): """Setup a complete SAM Database. :note: This will wipe the main SAM database file! """ + # Also wipes the database + setup_samdb_partitions(path, logger=logger, lp=lp, + provision_backend=provision_backend, session_info=session_info, + names=names, serverrole=serverrole, schema=schema) + + # 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) + + logger.info("Pre-loading the Samba 4 and AD schema") + + # Load the schema from the one we computed earlier + samdb.set_schema(schema) + + # Set the NTDS settings DN manually - in order to have it already around + # before the provisioned tree exists and we connect + samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn) + + # And now we can connect to the DB - the schema won't be loaded from the + # DB + samdb.connect(path) + + return samdb + + +def fill_samdb(samdb, 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=None, dc_rid=None): + + 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. @@ -1108,36 +1163,10 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp, names, domainFunctionality = dom_for_fun_level forestFunctionality = dom_for_fun_level - # Also wipes the database - 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 is None: - schema = Schema(setup_path, domainsid, schemadn=names.schemadn) - - # 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) - - logger.info("Pre-loading the Samba 4 and AD schema") - - # Load the schema from the one we computed earlier - samdb.set_schema(schema) - # Set the NTDS settings DN manually - in order to have it already around # before the provisioned tree exists and we connect samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn) - # And now we can connect to the DB - the schema won't be loaded from the - # DB - samdb.connect(path) - - if fill == FILL_DRS: - return samdb - samdb.transaction_start() try: # Set the domain functionality levels onto the database. @@ -1174,7 +1203,7 @@ def setup_samdb(path, setup_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, @@ -1183,65 +1212,62 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp, names, "SAMBA_VERSION_STRING": version }) - logger.info("Adding configuration container") - descr = b64encode(get_config_descriptor(domainsid)) - setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), { - "CONFIGDN": names.configdn, - "DESCRIPTOR": descr, - }) - - # 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"]) - samdb.modify_ldif(schema.schema_dn_modify) - samdb.write_prefixes_from_schema() - samdb.add_ldif(schema.schema_data, controls=["relax:0"]) - setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), - {"SCHEMADN": names.schemadn}) + # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it + if fill == FILL_FULL: + logger.info("Adding configuration container") + descr = b64encode(get_config_descriptor(domainsid)) + setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), { + "CONFIGDN": names.configdn, + "DESCRIPTOR": descr, + }) + + # 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"]) + samdb.modify_ldif(schema.schema_dn_modify) + samdb.write_prefixes_from_schema() + samdb.add_ldif(schema.schema_data, controls=["relax:0"]) + setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), + {"SCHEMADN": names.schemadn}) + + # 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") - logger.info("Reopening sam.ldb with new schema") except: samdb.transaction_cancel() raise else: samdb.transaction_commit() - samdb = SamDB(session_info=admin_session_info, auto_connect=False, - credentials=provision_backend.credentials, lp=lp, - global_schema=False, am_rodc=am_rodc) - - # Set the NTDS settings DN manually - in order to have it already around - # before the provisioned tree exists and we connect - samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn) - samdb.connect(path) - samdb.transaction_start() try: samdb.invocation_id = invocationid - logger.info("Setting up sam.ldb configuration data") - descr = b64encode(get_sites_descriptor(domainsid)) - setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), { - "CONFIGDN": names.configdn, - "NETBIOSNAME": names.netbiosname, - "DEFAULTSITE": names.sitename, - "DNSDOMAIN": names.dnsdomain, - "DOMAIN": names.domain, - "SCHEMADN": names.schemadn, - "DOMAINDN": names.domaindn, - "SERVERDN": names.serverdn, - "FOREST_FUNCTIONALITY": str(forestFunctionality), - "DOMAIN_FUNCTIONALITY": str(domainFunctionality), - "SITES_DESCRIPTOR": descr - }) - - 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) + # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it + if fill == FILL_FULL: + logger.info("Setting up sam.ldb configuration data") + setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), { + "CONFIGDN": names.configdn, + "NETBIOSNAME": names.netbiosname, + "DEFAULTSITE": names.sitename, + "DNSDOMAIN": names.dnsdomain, + "DOMAIN": names.domain, + "SCHEMADN": names.schemadn, + "DOMAINDN": names.domaindn, + "SERVERDN": names.serverdn, + "FOREST_FUNCTIONALITY": str(forestFunctionality), + "DOMAIN_FUNCTIONALITY": str(domainFunctionality), + }) + + 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) logger.info("Adding users container") setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), { @@ -1258,7 +1284,7 @@ def setup_samdb(path, setup_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, @@ -1268,33 +1294,41 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp, names, "POLICYGUID_DC": policyguid_dc }) - setup_modify_ldif(samdb, - setup_path("provision_basedn_references.ldif"), { - "DOMAINDN": names.domaindn}) + # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it + if fill == FILL_FULL: + setup_modify_ldif(samdb, + setup_path("provision_configuration_references.ldif"), { + "CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn}) - setup_modify_ldif(samdb, - setup_path("provision_configuration_references.ldif"), { + logger.info("Setting up well known security principals") + setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), { "CONFIGDN": names.configdn, - "SCHEMADN": names.schemadn}) - if fill == FILL_FULL: + }) + + if fill == FILL_FULL or fill == FILL_SUBDOMAIN: + setup_modify_ldif(samdb, + setup_path("provision_basedn_references.ldif"), + {"DOMAINDN": names.domaindn}) + logger.info("Setting up sam.ldb users and groups") setup_add_ldif(samdb, setup_path("provision_users.ldif"), { "DOMAINDN": names.domaindn, "DOMAINSID": str(domainsid), - "CONFIGDN": names.configdn, "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')), "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le')) }) logger.info("Setting up self join") - setup_self_join(samdb, names=names, invocationid=invocationid, + setup_self_join(samdb, admin_session_info, names=names, fill=fill, + invocationid=invocationid, dnspass=dnspass, machinepass=machinepass, domainsid=domainsid, next_rid=next_rid, + dc_rid=dc_rid, policyguid=policyguid, policyguid_dc=policyguid_dc, - setup_path=setup_path, domainControllerFunctionality=domainControllerFunctionality, ntdsguid=ntdsguid) @@ -1311,11 +1345,13 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp, names, FILL_FULL = "FULL" +FILL_SUBDOMAIN = "SUBDOMAIN" FILL_NT4SYNC = "NT4SYNC" 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_dir_acl(path, acl, lp, domsid): setntacl(lp, path, acl, domsid) for root, dirs, files in os.walk(path, topdown=False): @@ -1368,7 +1404,7 @@ def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn, try: os.chown(sysvol, -1, gid) - except: + except OSError: canchown = False else: canchown = True @@ -1389,32 +1425,35 @@ def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn, set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp) -def provision(setup_dir, logger, session_info, credentials, smbconf=None, - targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None, - domaindn=None, schemadn=None, configdn=None, serverdn=None, - domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None, - next_rid=1000, 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, - serverrole=None, dom_for_fun_level=None, ldap_backend_extra_port=None, - ldap_backend_forced_uri=None, backend_type=None, sitename=None, - ol_mmr_urls=None, ol_olc=None, setup_ds_path=None, slapd_path=None, - nosync=False, ldap_dryrun_mode=False, useeadb=False, am_rodc=False, - lp=None): - """Provision samba4 - - :note: caution, this wipes all existing data! - """ - - def setup_path(file): - return os.path.join(setup_dir, file) - - if domainsid is None: - domainsid = security.random_sid() - else: - domainsid = security.dom_sid(domainsid) - +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_fill(samdb, secrets_ldb, logger, names, paths, + domainsid, schema=None, + targetdir=None, samdb_fill=FILL_FULL, + hostip=None, hostip6=None, + next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None, + domainguid=None, policyguid=None, policyguid_dc=None, + invocationid=None, machinepass=None, ntdsguid=None, + dns_backend=None, dnspass=None, + serverrole=None, dom_for_fun_level=None, + am_rodc=False, lp=None): # create/adapt the group policy GUIDs # Default GUID for default policy are described at # "How Core Group Policy Works" @@ -1426,21 +1465,164 @@ def provision(setup_dir, logger, session_info, credentials, smbconf=None, policyguid_dc = DEFAULT_DC_POLICY_GUID policyguid_dc = policyguid_dc.upper() - if adminpass is None: - adminpass = samba.generate_random_password(12, 32) + if invocationid is None: + invocationid = str(uuid.uuid4()) + if krbtgtpass is None: krbtgtpass = samba.generate_random_password(128, 255) if machinepass is None: machinepass = samba.generate_random_password(128, 255) if dnspass is None: dnspass = samba.generate_random_password(128, 255) + + samdb = fill_samdb(samdb, lp, names, logger=logger, + 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, am_rodc=am_rodc, + next_rid=next_rid, dc_rid=dc_rid) + + if serverrole == "domain controller": + # Set up group policies (domain policy and domain controller + # policy) + create_default_gpo(paths.sysvol, names.dnsdomain, policyguid, + policyguid_dc) + setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.wheel_gid, + domainsid, names.dnsdomain, names.domaindn, lp) + + secretsdb_self_join(secrets_ldb, domain=names.domain, + realm=names.realm, dnsdomain=names.dnsdomain, + netbiosname=names.netbiosname, domainsid=domainsid, + machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC) + + # Now set up the right msDS-SupportedEncryptionTypes into the DB + # In future, this might be determined from some configuration + kerberos_enctypes = str(ENC_ALL_TYPES) + + try: + msg = ldb.Message(ldb.Dn(samdb, + samdb.searchone("distinguishedName", + expression="samAccountName=%s$" % names.netbiosname, + scope=ldb.SCOPE_SUBTREE))) + msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement( + elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE, + name="msDS-SupportedEncryptionTypes") + samdb.modify(msg) + except ldb.LdbError, (enum, estr): + if enum != ldb.ERR_NO_SUCH_ATTRIBUTE: + # It might be that this attribute does not exist in this schema + raise + + setup_ad_dns(samdb, secrets_ldb, domainsid, names, paths, lp, logger, + hostip=hostip, hostip6=hostip6, dns_backend=dns_backend, + dnspass=dnspass, os_level=dom_for_fun_level, + targetdir=targetdir, site=DEFAULTSITE) + + domainguid = samdb.searchone(basedn=samdb.get_default_basedn(), + attribute="objectGUID") + assert isinstance(domainguid, str) + + 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, invocationid, 1) + else: + set_provision_usn(samdb, 0, maxUSN, invocationid) + + logger.info("Setting up sam.ldb rootDSE marking as synchronized") + setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"), + { 'NTDSGUID' : names.ntdsguid }) + + # 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() + try: + # 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']) + except: + samdb.transaction_cancel() + raise + else: + samdb.transaction_commit() + + +_ROLES_MAP = { + "ROLE_STANDALONE": "standalone", + "ROLE_DOMAIN_MEMBER": "member server", + "ROLE_DOMAIN_BDC": "domain controller", + "ROLE_DOMAIN_PDC": "domain controller", + "dc": "domain controller", + "member": "member server", + "domain controller": "domain controller", + "member server": "member server", + "standalone": "standalone", + } + + +def sanitize_server_role(role): + """Sanitize a server role name. + + :param role: Server role + :raise ValueError: If the role can not be interpreted + :return: Sanitized server role (one of "member server", + "domain controller", "standalone") + """ + try: + return _ROLES_MAP[role] + except KeyError: + raise ValueError(role) + + +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, dc_rid=None, adminpass=None, ldapadminpass=None, krbtgtpass=None, + domainguid=None, policyguid=None, policyguid_dc=None, + dns_backend=None, dnspass=None, + invocationid=None, machinepass=None, ntdsguid=None, + root=None, nobody=None, users=None, wheel=None, backup=None, aci=None, + serverrole=None, dom_for_fun_level=None, + backend_type=None, sitename=None, + ol_mmr_urls=None, ol_olc=None, slapd_path=None, + useeadb=False, am_rodc=False, + lp=None, use_ntvfs=True): + """Provision samba4 + + :note: caution, this wipes all existing data! + """ + + try: + serverrole = sanitize_server_role(serverrole) + except ValueError: + raise ProvisioningError('server role (%s) should be one of "domain controller", "member server", "standalone"' % serverrole) + if ldapadminpass is None: # Make a new, random password between Samba and it's LDAP server - ldapadminpass=samba.generate_random_password(128, 255) + ldapadminpass = samba.generate_random_password(128, 255) if backend_type is None: backend_type = "ldb" + if domainsid is None: + domainsid = security.random_sid() + else: + domainsid = security.dom_sid(domainsid) + sid_generator = "internal" if backend_type == "fedora-ds": sid_generator = "backend" @@ -1464,20 +1646,38 @@ def provision(setup_dir, logger, session_info, credentials, smbconf=None, if not os.path.exists(os.path.dirname(smbconf)): os.makedirs(os.path.dirname(smbconf)) + server_services = [] + global_param = {} + if dns_backend == "SAMBA_INTERNAL": + server_services.append("+dns") + + if not use_ntvfs: + server_services.append("-smb") + server_services.append("+s3fs") + global_param["dcerpc endpoint servers"] = ["-winreg", "-srvsvc"] + + if len(server_services) > 0: + global_param["server services"] = server_services + # only install a new smb.conf if there isn't one there already if os.path.exists(smbconf): # if Samba Team members can't figure out the weird errors # loading an empty smb.conf gives, then we need to be smarter. # Pretend it just didn't exist --abartlet - data = open(smbconf, 'r').read() - data = data.lstrip() + f = open(smbconf, 'r') + try: + data = f.read().lstrip() + finally: + f.close() if data is None or data == "": - make_smbconf(smbconf, setup_path, hostname, domain, realm, - serverrole, targetdir, sid_generator, useeadb, - lp=lp) + make_smbconf(smbconf, hostname, domain, realm, + targetdir, serverrole=serverrole, + sid_generator=sid_generator, eadb=useeadb, use_ntvfs=use_ntvfs, + lp=lp, global_param=global_param) else: - make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, - targetdir, sid_generator, useeadb, lp=lp) + make_smbconf(smbconf, hostname, domain, realm, targetdir, + serverrole=serverrole, sid_generator=sid_generator, + eadb=useeadb, use_ntvfs=use_ntvfs, lp=lp, global_param=global_param) if lp is None: lp = samba.param.LoadParm() @@ -1485,30 +1685,41 @@ def provision(setup_dir, logger, session_info, credentials, smbconf=None, names = guess_names(lp=lp, hostname=hostname, domain=domain, dnsdomain=realm, serverrole=serverrole, domaindn=domaindn, configdn=configdn, schemadn=schemadn, serverdn=serverdn, - sitename=sitename) + sitename=sitename, rootdn=rootdn) paths = provision_paths_from_lp(lp, names.dnsdomain) paths.bind_gid = bind_gid + paths.wheel_gid = wheel_gid 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") + + names.hostip = hostip + names.hostip6 = hostip6 if serverrole is None: serverrole = lp.get("server role") - assert serverrole in ("domain controller", "member server", "standalone") - if invocationid is None: - invocationid = str(uuid.uuid4()) - 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")): @@ -1516,38 +1727,32 @@ def provision(setup_dir, logger, session_info, credentials, smbconf=None, ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="") - schema = Schema(setup_path, domainsid, invocationid=invocationid, + schema = Schema(domainsid, invocationid=invocationid, schemadn=names.schemadn) if backend_type == "ldb": provision_backend = LDBBackend(backend_type, paths=paths, - setup_path=setup_path, lp=lp, credentials=credentials, + lp=lp, credentials=credentials, names=names, logger=logger) elif backend_type == "existing": + # If support for this is ever added back, then the URI will need to be specified again provision_backend = ExistingBackend(backend_type, paths=paths, - setup_path=setup_path, lp=lp, credentials=credentials, + lp=lp, credentials=credentials, names=names, logger=logger, - ldap_backend_forced_uri=ldap_backend_forced_uri) + ldap_backend_forced_uri=None) elif backend_type == "fedora-ds": provision_backend = FDSBackend(backend_type, paths=paths, - setup_path=setup_path, lp=lp, credentials=credentials, + lp=lp, credentials=credentials, names=names, logger=logger, domainsid=domainsid, schema=schema, hostname=hostname, ldapadminpass=ldapadminpass, slapd_path=slapd_path, - ldap_backend_extra_port=ldap_backend_extra_port, - ldap_dryrun_mode=ldap_dryrun_mode, root=root, - setup_ds_path=setup_ds_path, - ldap_backend_forced_uri=ldap_backend_forced_uri) + root=root) elif backend_type == "openldap": provision_backend = OpenLDAPBackend(backend_type, paths=paths, - setup_path=setup_path, lp=lp, credentials=credentials, + lp=lp, credentials=credentials, names=names, logger=logger, domainsid=domainsid, schema=schema, hostname=hostname, ldapadminpass=ldapadminpass, - slapd_path=slapd_path, - ldap_backend_extra_port=ldap_backend_extra_port, - ldap_dryrun_mode=ldap_dryrun_mode, ol_mmr_urls=ol_mmr_urls, - nosync=nosync, - ldap_backend_forced_uri=ldap_backend_forced_uri) + slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls) else: raise ValueError("Unknown LDAP backend type selected") @@ -1557,141 +1762,78 @@ def provision(setup_dir, logger, session_info, credentials, smbconf=None, # only install a new shares config db if there is none if not os.path.exists(paths.shareconf): logger.info("Setting up share.ldb") - share_ldb = Ldb(paths.shareconf, session_info=session_info, - lp=lp) + share_ldb = Ldb(paths.shareconf, session_info=session_info, lp=lp) share_ldb.load_ldif_file_add(setup_path("share.ldif")) logger.info("Setting up secrets.ldb") - secrets_ldb = setup_secretsdb(paths, setup_path, + secrets_ldb = setup_secretsdb(paths, session_info=session_info, backend_credentials=provision_backend.secrets_credentials, lp=lp) try: logger.info("Setting up the registry") - setup_registry(paths.hklm, setup_path, session_info, - lp=lp) + setup_registry(paths.hklm, session_info, lp=lp) logger.info("Setting up the privileges database") - setup_privileges(paths.privilege, setup_path, session_info, lp=lp) + setup_privileges(paths.privilege, session_info, lp=lp) logger.info("Setting up idmap db") - idmap = setup_idmapdb(paths.idmapdb, setup_path, - session_info=session_info, lp=lp) + idmap = setup_idmapdb(paths.idmapdb, session_info=session_info, lp=lp) + + setup_name_mappings(idmap, sid=str(domainsid), + root_uid=root_uid, nobody_uid=nobody_uid, + users_gid=users_gid, wheel_gid=wheel_gid) logger.info("Setting up SAM db") - samdb = setup_samdb(paths.samdb, setup_path, session_info, - provision_backend, lp, names, logger=logger, - 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, am_rodc=am_rodc, - next_rid=next_rid) + samdb = setup_samdb(paths.samdb, session_info, + provision_backend, lp, names, logger=logger, + serverrole=serverrole, + schema=schema, fill=samdb_fill, am_rodc=am_rodc) if serverrole == "domain controller": 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 + raise MissingShareError("netlogon", paths.smbconf, + setup_path("provision.smb.conf.dc")) 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 + raise MissingShareError("sysvol", paths.smbconf, + setup_path("provision.smb.conf.dc")) if not os.path.isdir(paths.netlogon): os.makedirs(paths.netlogon, 0755) - if samdb_fill == FILL_FULL: - setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn, - root_uid=root_uid, nobody_uid=nobody_uid, - users_gid=users_gid, wheel_gid=wheel_gid) - - if serverrole == "domain controller": - # Set up group policies (domain policy and domain controller - # policy) - create_default_gpo(paths.sysvol, names.dnsdomain, policyguid, - policyguid_dc) - setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid, - domainsid, names.dnsdomain, names.domaindn, lp) - - logger.info("Setting up sam.ldb rootDSE marking as synchronized") - setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif")) - - secretsdb_self_join(secrets_ldb, domain=names.domain, - realm=names.realm, dnsdomain=names.dnsdomain, - netbiosname=names.netbiosname, domainsid=domainsid, - machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC) - - # Now set up the right msDS-SupportedEncryptionTypes into the DB - # In future, this might be determined from some configuration - kerberos_enctypes = str(ENC_ALL_TYPES) - - try: - msg = ldb.Message(ldb.Dn(samdb, - samdb.searchone("distinguishedName", - expression="samAccountName=%s$" % names.netbiosname, - scope=ldb.SCOPE_SUBTREE))) - msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement( - elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE, - name="msDS-SupportedEncryptionTypes") - samdb.modify(msg) - except ldb.LdbError, (ldb.ERR_NO_SUCH_ATTRIBUTE, _): - # It might be that this attribute does not exist in this schema - pass - - if serverrole == "domain controller": - secretsdb_setup_dns(secrets_ldb, setup_path, names, - paths.private_dir, realm=names.realm, - dnsdomain=names.dnsdomain, - dns_keytab_path=paths.dns_keytab, dnspass=dnspass) - - domainguid = samdb.searchone(basedn=domaindn, - attribute="objectGUID") - assert isinstance(domainguid, str) - - # Only make a zone file on the first DC, it should be - # replicated with DNS replication - create_zone_file(lp, logger, paths, targetdir, setup_path, - dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6, - hostname=names.hostname, realm=names.realm, - domainguid=domainguid, ntdsguid=names.ntdsguid) - - create_named_conf(paths, setup_path, realm=names.realm, - dnsdomain=names.dnsdomain, private_dir=paths.private_dir) - - create_named_txt(paths.namedtxt, setup_path, - realm=names.realm, dnsdomain=names.dnsdomain, - private_dir=paths.private_dir, - keytab_name=paths.dns_keytab) - logger.info("See %s for an example configuration include file for BIND", paths.namedconf) - logger.info("and %s for further documentation required for secure DNS " - "updates", paths.namedtxt) - - lastProvisionUSNs = get_last_provision_usn(samdb) - maxUSN = get_max_usn(samdb, str(names.rootdn)) - if lastProvisionUSNs is not None: - update_provision_usn(samdb, 0, maxUSN, 1) - else: - set_provision_usn(samdb, 0, maxUSN) + if adminpass is None: + adminpass = samba.generate_random_password(12, 32) + adminpass_generated = True + else: + adminpass_generated = False - create_krb5_conf(paths.krb5conf, setup_path, + if samdb_fill == FILL_FULL: + provision_fill(samdb, secrets_ldb, logger, names, paths, + schema=schema, targetdir=targetdir, samdb_fill=samdb_fill, + hostip=hostip, hostip6=hostip6, domainsid=domainsid, + next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass, + krbtgtpass=krbtgtpass, domainguid=domainguid, + policyguid=policyguid, policyguid_dc=policyguid_dc, + invocationid=invocationid, machinepass=machinepass, + ntdsguid=ntdsguid, dns_backend=dns_backend, + dnspass=dnspass, serverrole=serverrole, + dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc, + lp=lp) + + create_krb5_conf(paths.krb5conf, dnsdomain=names.dnsdomain, hostname=names.hostname, realm=names.realm) logger.info("A Kerberos configuration suitable for Samba 4 has been " "generated at %s", paths.krb5conf) if serverrole == "domain controller": - create_dns_update_list(lp, logger, paths, setup_path) + create_dns_update_list(lp, logger, paths) - provision_backend.post_setup() + backend_result = provision_backend.post_setup() provision_backend.shutdown() - create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, + create_phpldapadmin_config(paths.phpldapadminconfig, ldapi_url) except: secrets_ldb.transaction_cancel() @@ -1711,227 +1853,65 @@ def provision(setup_dir, logger, session_info, credentials, smbconf=None, logger.info("Failed to chown %s to bind gid %u", dns_keytab_path, paths.bind_gid) - - logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php", - paths.phpldapadminconfig) - - 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: - logger.info("Admin password: %s" % adminpass) - if provision_backend.type is not "ldb": - if provision_backend.credentials.get_bind_dn() is not None: - logger.info("LDAP Backend Admin DN: %s" % - provision_backend.credentials.get_bind_dn()) - else: - logger.info("LDAP Admin User: %s" % - provision_backend.credentials.get_username()) - - 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 - 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.server_role = serverrole result.domaindn = domaindn result.paths = paths + result.names = names result.lp = lp result.samdb = samdb + result.idmap = idmap + result.domainsid = str(domainsid) + + if samdb_fill == FILL_FULL: + result.adminpass_generated = adminpass_generated + result.adminpass = adminpass + else: + result.adminpass_generated = False + result.adminpass = None + + result.backend_result = backend_result + return result -def provision_become_dc(setup_dir=None, smbconf=None, targetdir=None, +def provision_become_dc(smbconf=None, targetdir=None, realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=None, serverdn=None, domain=None, hostname=None, domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None, policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None, - root=None, nobody=None, users=None, wheel=None, backup=None, - serverrole=None, ldap_backend=None, ldap_backend_type=None, - sitename=None, debuglevel=1): + dns_backend=None, root=None, nobody=None, users=None, wheel=None, + backup=None, serverrole=None, ldap_backend=None, + ldap_backend_type=None, sitename=None, debuglevel=1): logger = logging.getLogger("provision") samba.set_debug_level(debuglevel) - res = provision(setup_dir, logger, system_session(), None, + res = provision(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, - hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, + hostname=hostname, hostip=None, domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", - sitename=sitename) + sitename=sitename, dns_backend=dns_backend, dnspass=dnspass) res.lp.set("debuglevel", str(debuglevel)) return res -def create_phpldapadmin_config(path, setup_path, ldapi_uri): +def create_phpldapadmin_config(path, ldapi_uri): """Create a PHP LDAP admin configuration file. :param path: Path to write the configuration to. - :param setup_path: Function to generate setup paths. """ setup_file(setup_path("phpldapadmin-config.php"), path, {"S4_LDAPI_URI": ldapi_uri}) -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. - - :param paths: paths object - :param setup_path: Setup path function. - :param dnsdomain: DNS Domain name - :param domaindn: DN of the Domain - :param hostip: Local IPv4 IP - :param hostip6: Local IPv6 IP - :param hostname: Local hostname - :param realm: Realm name - :param domainguid: GUID of the domain. - :param ntdsguid: GUID of the hosts nTDSDSA record. - """ - assert isinstance(domainguid, str) - - 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) - - try: - shutil.rmtree(dns_dir, True) - except OSError: - pass - - os.mkdir(dns_dir, 0775) - - # we need to freeze the zone while we update the contents - if targetdir is None: - rndc = ' '.join(lp.get("rndc command")) - os.system(rndc + " freeze " + lp.get("realm")) - - setup_file(setup_path("provision.zone"), paths.dns, { - "HOSTNAME": hostname, - "DNSDOMAIN": dnsdomain, - "REALM": realm, - "HOSTIP_BASE_LINE": hostip_base_line, - "HOSTIP_HOST_LINE": hostip_host_line, - "DOMAINGUID": domainguid, - "DATESTRING": time.strftime("%Y%m%d%H"), - "DEFAULTSITE": DEFAULTSITE, - "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) - os.chown(paths.dns, -1, paths.bind_gid) - # chmod needed to cope with umask - os.chmod(dns_dir, 0775) - os.chmod(paths.dns, 0664) - except OSError: - if not os.environ.has_key('SAMBA_SELFTEST'): - logger.error("Failed to chown %s to bind gid %u" % ( - dns_dir, paths.bind_gid)) - - if targetdir is None: - os.system(rndc + " unfreeze " + lp.get("realm")) - - -def create_dns_update_list(lp, 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 - named.conf file (including GSS-TSIG configuration). - - :param paths: all paths - :param setup_path: Setup path function. - :param realm: Realm name - :param dnsdomain: DNS Domain name - :param private_dir: Path to private directory - :param keytab_name: File name of DNS keytab file - """ - - setup_file(setup_path("named.conf"), paths.namedconf, { - "DNSDOMAIN": dnsdomain, - "REALM": realm, - "ZONE_FILE": paths.dns, - "REALM_WC": "*." + ".".join(realm.split(".")[1:]), - "NAMED_CONF": paths.namedconf, - "NAMED_CONF_UPDATE": paths.namedconf_update - }) - - 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 - named.conf file (including GSS-TSIG configuration). - - :param path: Path of the new named.conf file. - :param setup_path: Setup path function. - :param realm: Realm name - :param dnsdomain: DNS Domain name - :param private_dir: Path to private directory - :param keytab_name: File name of DNS keytab file - """ - setup_file(setup_path("named.txt"), path, { - "DNSDOMAIN": dnsdomain, - "REALM": realm, - "DNS_KEYTAB": keytab_name, - "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name), - "PRIVATE_DIR": private_dir - }) - - -def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm): +def create_krb5_conf(path, dnsdomain, hostname, realm): """Write out a file containing zone statements suitable for inclusion in a named.conf file (including GSS-TSIG configuration). :param path: Path of the new named.conf file. - :param setup_path: Setup path function. :param dnsdomain: DNS Domain name :param hostname: Local hostname :param realm: Realm name @@ -1955,6 +1935,16 @@ class ProvisioningError(Exception): 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) + + +class MissingShareError(ProvisioningError): + + def __init__(self, name, smbconf, smbconf_template): + super(MissingShareError, self).__init__( + "Existing smb.conf does not have a [%s] share, but you are " + "configuring a DC. Please either remove %s or see the template " + "at %s" % (name, smbconf, smbconf_template))