X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source4%2Fscripting%2Fpython%2Fsamba%2Fprovision.py;h=fdf1fe9e61d0e316134c03ad2c773d7fb5aa3256;hb=aaca10b3e13d5a6119d7f47bc21bbf0cac3efb96;hp=d089cb25136bd96b04219215e57fb18650f2eb68;hpb=6ac77d19b5a25a53459a58e4828fa9eac0bf11f4;p=samba.git diff --git a/source4/scripting/python/samba/provision.py b/source4/scripting/python/samba/provision.py index d089cb25136..fdf1fe9e61d 100644 --- a/source4/scripting/python/samba/provision.py +++ b/source4/scripting/python/samba/provision.py @@ -3,7 +3,7 @@ # backend code for provisioning a Samba4 server # Copyright (C) Jelmer Vernooij 2007-2008 -# Copyright (C) Andrew Bartlett 2008 +# Copyright (C) Andrew Bartlett 2008-2009 # Copyright (C) Oliver Liebel 2008-2009 # # Based on the original in EJS: @@ -36,19 +36,28 @@ import socket import param import registry import samba -from auth import system_session -from samba import Ldb, substitute_var, valid_netbios_name, check_all_substituted +import subprocess +import ldb + +import shutil +from credentials import Credentials, DONT_USE_KERBEROS +from auth import system_session, admin_session +from samba import version, Ldb, substitute_var, valid_netbios_name +from samba import check_all_substituted +from samba import DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008, DS_DC_FUNCTION_2008 from samba.samdb import SamDB from samba.idmap import IDmapDB from samba.dcerpc import security +from samba.ndr import ndr_pack import urllib -from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, \ - timestring, CHANGETYPE_MODIFY, CHANGETYPE_NONE +from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring from ms_schema import read_ms_schema +from ms_display_specifiers import read_ms_ldif +from signal import SIGTERM +from dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA __docformat__ = "restructuredText" - def find_setup_dir(): """Find the setup directory used by provision.""" dirname = os.path.dirname(__file__) @@ -64,9 +73,47 @@ def find_setup_dir(): return ret raise Exception("Unable to find setup directory.") +def get_schema_descriptor(domain_sid): + sddl = "O:SAG:SAD:(A;CI;RPLCLORC;;;AU)(A;CI;RPWPCRCCLCLORCWOWDSW;;;SA)" \ + "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \ + "(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)" \ + "S:(AU;SA;WPCCDCWOWDSDDTSW;;;WD)" \ + "(AU;CISA;WP;;;WD)(AU;SA;CR;;;BA)" \ + "(AU;SA;CR;;;DU)(OU;SA;CR;e12b56b6-0a95-11d1-adbb-00c04fd8d5cd;;WD)" \ + "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)" + sec = security.descriptor.from_sddl(sddl, domain_sid) + return b64encode(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;;S-1-5-21-3191434175-1265308384-3577286990-498)" \ + "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 b64encode(ndr_pack(sec)) + DEFAULTSITE = "Default-First-Site-Name" +# Exception classes + +class ProvisioningError(Exception): + """A generic provision error.""" + class InvalidNetbiosName(Exception): """A specified name was not a valid NetBIOS name.""" def __init__(self, name): @@ -96,11 +143,17 @@ class ProvisionPaths(object): self.memberofconf = None self.fedoradsinf = None self.fedoradspartitions = None + self.fedoradssasl = None + self.fedoradspam = None + self.fedoradsrefint = None + self.fedoradslinkedattributes = None + self.fedoradsindex = None + self.fedoradssamba = None self.olmmron = None self.olmmrserveridsconf = None self.olmmrsyncreplconf = None self.olcdir = None - self.olslaptest = None + self.olslapd = None self.olcseedldif = None @@ -110,6 +163,7 @@ class ProvisionNames(object): self.domaindn = None self.configdn = None self.schemadn = None + self.sambadn = None self.ldapmanagerdn = None self.dnsdomain = None self.realm = None @@ -126,7 +180,74 @@ class ProvisionResult(object): self.domaindn = None self.lp = None self.samdb = None + +class Schema(object): + def __init__(self, setup_path, domain_sid, schemadn=None, + serverdn=None, sambadn=None, ldap_backend_type=None): + """Load schema for the SamDB from the AD schema files and samba4_schema.ldif + + :param samdb: Load a schema into a SamDB. + :param setup_path: Setup path function. + :param schemadn: DN of the schema + :param serverdn: DN of the server + + Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db + """ + + self.ldb = Ldb() + self.schema_data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'), + setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt')) + self.schema_data += open(setup_path("schema_samba4.ldif"), 'r').read() + self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn}) + check_all_substituted(self.schema_data) + + self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"), + {"SCHEMADN": schemadn, + "SERVERDN": serverdn, + }) + + descr = get_schema_descriptor(domain_sid) + self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"), + {"SCHEMADN": schemadn, + "DESCRIPTOR": descr + }) + prefixmap = open(setup_path("prefixMap.txt"), 'r').read() + prefixmap = b64encode(prefixmap) + + + + # We don't actually add this ldif, just parse it + prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap + self.ldb.set_schema_from_ldif(prefixmap_ldif, self.schema_data) + + +# Return a hash with the forward attribute as a key and the back as the value +def get_linked_attributes(schemadn,schemaldb): + attrs = ["linkID", "lDAPDisplayName"] + res = schemaldb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs) + attributes = {} + for i in range (0, len(res)): + expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1) + target = schemaldb.searchone(basedn=schemadn, + expression=expression, + attribute="lDAPDisplayName", + scope=SCOPE_SUBTREE) + if target is not None: + attributes[str(res[i]["lDAPDisplayName"])]=str(target) + + return attributes + +def get_dnsyntax_attributes(schemadn,schemaldb): + attrs = ["linkID", "lDAPDisplayName"] + res = schemaldb.search(expression="(&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs) + attributes = [] + for i in range (0, len(res)): + attributes.append(str(res[i]["lDAPDisplayName"])) + + return attributes + + def check_install(lp, session_info, credentials): """Check whether the current install seems ok. @@ -139,7 +260,7 @@ def check_install(lp, session_info, credentials): ldb = Ldb(lp.get("sam database"), session_info=session_info, credentials=credentials, lp=lp) if len(ldb.search("(cn=Administrator)")) != 1: - raise "No administrator account found" + raise ProvisioningError("No administrator account found") def findnss(nssfn, names): @@ -174,17 +295,17 @@ def read_and_sub_file(file, subst_vars): return data -def setup_add_ldif(ldb, ldif_path, subst_vars=None): +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) + ldb.add_ldif(data,controls) def setup_modify_ldif(ldb, ldif_path, subst_vars=None): @@ -218,7 +339,7 @@ def setup_ldb(ldb, ldif_path, subst_vars): ldb.transaction_commit() -def setup_file(template, fname, subst_vars): +def setup_file(template, fname, subst_vars=None): """Setup a file in the private dir. :param template: Path of the template file. @@ -242,14 +363,12 @@ def provision_paths_from_lp(lp, dnsdomain): """ paths = ProvisionPaths() paths.private_dir = lp.get("private dir") - paths.keytab = "secrets.keytab" paths.dns_keytab = "dns.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.templates = os.path.join(paths.private_dir, "templates.ldb") paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone") paths.namedconf = os.path.join(paths.private_dir, "named.conf") paths.namedtxt = os.path.join(paths.private_dir, "named.txt") @@ -262,6 +381,8 @@ def provision_paths_from_lp(lp, dnsdomain): "ldap") paths.slapdconf = os.path.join(paths.ldapdir, "slapd.conf") + paths.slapdpid = os.path.join(paths.ldapdir, + "slapd.pid") paths.modulesconf = os.path.join(paths.ldapdir, "modules.conf") paths.memberofconf = os.path.join(paths.ldapdir, @@ -270,6 +391,18 @@ def provision_paths_from_lp(lp, dnsdomain): "fedorads.inf") paths.fedoradspartitions = os.path.join(paths.ldapdir, "fedorads-partitions.ldif") + paths.fedoradssasl = os.path.join(paths.ldapdir, + "fedorads-sasl.ldif") + paths.fedoradspam = os.path.join(paths.ldapdir, + "fedorads-pam.ldif") + paths.fedoradsrefint = os.path.join(paths.ldapdir, + "fedorads-refint.ldif") + paths.fedoradslinkedattributes = os.path.join(paths.ldapdir, + "fedorads-linked-attributes.ldif") + paths.fedoradsindex = os.path.join(paths.ldapdir, + "fedorads-index.ldif") + paths.fedoradssamba = os.path.join(paths.ldapdir, + "fedorads-samba.ldif") paths.olmmrserveridsconf = os.path.join(paths.ldapdir, "mmr_serverids.conf") paths.olmmrsyncreplconf = os.path.join(paths.ldapdir, @@ -294,9 +427,9 @@ def provision_paths_from_lp(lp, dnsdomain): return paths -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): +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, sambadn=None): """Guess configuration settings to use.""" if hostname is None: @@ -309,7 +442,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole= hostname = hostname.lower() if dnsdomain is None: - dnsdomain = lp.get("realm") + dnsdomain = lp.get("realm").lower() if serverrole is None: serverrole = lp.get("server role") @@ -321,8 +454,6 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole= raise Exception("realm '%s' in %s must match chosen realm '%s'" % (lp.get("realm"), lp.configfile, realm)) - dnsdomain = dnsdomain.lower() - if serverrole == "domain controller": if domain is None: domain = lp.get("workgroup") @@ -334,13 +465,23 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole= else: domain = netbiosname if domaindn is None: - domaindn = "CN=" + netbiosname + domaindn = "DC=" + netbiosname assert domain is not None domain = domain.upper() + if not valid_netbios_name(domain): raise InvalidNetbiosName(domain) + if netbiosname.upper() == realm: + raise Exception("realm %s must not be equal to netbios domain name %s", realm, netbiosname) + + if hostname.upper() == realm: + raise Exception("realm %s must not be equal to hostname %s", realm, hostname) + + if domain.upper() == realm: + raise Exception("realm %s must not be equal to domain name %s", realm, domain) + if rootdn is None: rootdn = domaindn @@ -348,6 +489,8 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole= configdn = "CN=Configuration," + rootdn if schemadn is None: schemadn = "CN=Schema," + configdn + if sambadn is None: + sambadn = "CN=Samba" if sitename is None: sitename=DEFAULTSITE @@ -357,6 +500,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole= names.domaindn = domaindn names.configdn = configdn names.schemadn = schemadn + names.sambadn = sambadn names.ldapmanagerdn = "CN=Manager," + rootdn names.dnsdomain = dnsdomain names.domain = domain @@ -433,20 +577,17 @@ def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid, :param nobody_uid: uid of the UNIX nobody user. :param users_gid: gid of the UNIX users group. :param wheel_gid: gid of the UNIX wheel group.""" - # add some foreign sids if they are not present already - samdb.add_stock_foreign_sids() idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid) idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid) - + idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid) idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid) - def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, credentials, names, serverrole, ldap_backend=None, - ldap_backend_type=None, erase=False): + erase=False): """Setup the partitions for the SAM database. Alternatively, provision() may call this, and then populate the database. @@ -459,17 +600,20 @@ def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, """ assert session_info is not None + # We use options=["modules:"] to stop the modules loading - we + # just want to wipe and re-initialise the database, not start it up + try: - samdb = SamDB(samdb_path, session_info=session_info, - credentials=credentials, lp=lp) + samdb = Ldb(url=samdb_path, session_info=session_info, + credentials=credentials, lp=lp, options=["modules:"]) # Wipes the database - samdb.erase() + samdb.erase_except_schema_controlled() except LdbError: os.unlink(samdb_path) - samdb = SamDB(samdb_path, session_info=session_info, - credentials=credentials, lp=lp) + samdb = Ldb(url=samdb_path, session_info=session_info, + credentials=credentials, lp=lp, options=["modules:"]) # Wipes the database - samdb.erase() + samdb.erase_except_schema_controlled() #Add modules to the list to activate them by default @@ -482,7 +626,10 @@ def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, # module when expanding the objectclass list) # - partition must be last # - each partition has its own module list then - modules_list = ["rootdse", + modules_list = ["resolve_oids", + "rootdse", + "lazy_commit", + "acl", "paged_results", "ranged_results", "anr", @@ -492,10 +639,12 @@ def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, "extended_dn_in", "rdn_name", "objectclass", + "descriptor", "samldb", - "kludge_acl", "password_hash", - "operational"] + "operational", + "kludge_acl", + "instancetype"] tdb_modules_list = [ "subtree_rename", "subtree_delete", @@ -505,28 +654,25 @@ def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, "partition"] domaindn_ldb = "users.ldb" - if ldap_backend is not None: - domaindn_ldb = ldap_backend configdn_ldb = "configuration.ldb" - if ldap_backend is not None: - configdn_ldb = ldap_backend schemadn_ldb = "schema.ldb" if ldap_backend is not None: - schema_ldb = ldap_backend - schemadn_ldb = ldap_backend + domaindn_ldb = ldap_backend.ldapi_uri + configdn_ldb = ldap_backend.ldapi_uri + schemadn_ldb = ldap_backend.ldapi_uri - if ldap_backend_type == "fedora-ds": - backend_modules = ["nsuniqueid", "paged_searches"] - # We can handle linked attributes here, as we don't have directory-side subtree operations - tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"] - elif ldap_backend_type == "openldap": - backend_modules = ["entryuuid", "paged_searches"] - # OpenLDAP handles subtree renames, so we don't want to do any of these things - tdb_modules_list = ["extended_dn_out_dereference"] - elif ldap_backend is not None: - raise "LDAP Backend specified, but LDAP Backend Type not specified" + if ldap_backend.ldap_backend_type == "fedora-ds": + backend_modules = ["nsuniqueid", "paged_searches"] + # We can handle linked attributes here, as we don't have directory-side subtree operations + tdb_modules_list = ["extended_dn_out_dereference"] + elif ldap_backend.ldap_backend_type == "openldap": + backend_modules = ["entryuuid", "paged_searches"] + # OpenLDAP handles subtree renames, so we don't want to do any of these things + tdb_modules_list = ["extended_dn_out_dereference"] + elif serverrole == "domain controller": - backend_modules = ["repl_meta_data"] + tdb_modules_list.insert(0, "repl_meta_data") + backend_modules = [] else: backend_modules = ["objectguid"] @@ -537,6 +683,7 @@ def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, samdb.transaction_start() try: + message("Setting up sam.ldb partitions and settings") setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), { "SCHEMADN": names.schemadn, "SCHEMADN_LDB": schemadn_ldb, @@ -545,63 +692,103 @@ def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, "CONFIGDN_LDB": configdn_ldb, "DOMAINDN": names.domaindn, "DOMAINDN_LDB": domaindn_ldb, - "SCHEMADN_MOD": "schema_fsmo,instancetype", - "CONFIGDN_MOD": "naming_fsmo,instancetype", - "DOMAINDN_MOD": "pdc_fsmo,instancetype", + "SCHEMADN_MOD": "schema_fsmo", + "CONFIGDN_MOD": "naming_fsmo", + "DOMAINDN_MOD": "pdc_fsmo", "MODULES_LIST": ",".join(modules_list), "TDB_MODULES_LIST": tdb_modules_list_as_string, "MODULES_LIST2": ",".join(modules_list2), "BACKEND_MOD": ",".join(backend_modules), }) - except: - samdb.transaction_cancel() - raise - - samdb.transaction_commit() - - samdb = SamDB(samdb_path, session_info=session_info, - credentials=credentials, lp=lp) - - samdb.transaction_start() - try: - message("Setting up sam.ldb attributes") samdb.load_ldif_file_add(setup_path("provision_init.ldif")) message("Setting up sam.ldb rootDSE") setup_samdb_rootdse(samdb, setup_path, names) - if erase: - message("Erasing data from partitions") - samdb.erase_partitions() - except: samdb.transaction_cancel() raise samdb.transaction_commit() - return samdb +def secretsdb_self_join(secretsdb, domain, + netbiosname, domainsid, machinepass, + realm=None, dnsdomain=None, + keytab_path=None, + key_version_number=1, + secure_channel_type=SEC_CHAN_WKSTA): + """Add domain join-specific bits to a secrets database. + + :param secretsdb: Ldb Handle to the secrets database + :param machinepass: Machine password + """ + attrs=["whenChanged", + "secret", + "priorSecret", + "priorChanged", + "krb5Keytab", + "privateKeytab"] + + + msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain)); + msg["secureChannelType"] = str(secure_channel_type) + msg["flatname"] = [domain] + msg["objectClass"] = ["top", "primaryDomain"] + if realm is not None: + if dnsdomain is None: + dnsdomain = realm.lower() + msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"] + msg["realm"] = realm + msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper()) + msg["msDS-KeyVersionNumber"] = [str(key_version_number)] + msg["privateKeytab"] = ["secrets.keytab"]; + + + msg["secret"] = [machinepass] + msg["samAccountName"] = ["%s$" % netbiosname] + msg["secureChannelType"] = [str(secure_channel_type)] + msg["objectSid"] = [ndr_pack(domainsid)] + + res = secretsdb.search(base="cn=Primary Domains", + attrs=attrs, + expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))), + scope=SCOPE_ONELEVEL) + + for del_msg in res: + if del_msg.dn is not msg.dn: + secretsdb.delete(del_msg.dn) + + res = secretsdb.search(base=msg.dn, attrs=attrs, scope=SCOPE_BASE) + + if len(res) == 1: + msg["priorSecret"] = res[0]["secret"] + msg["priorWhenChanged"] = res[0]["whenChanged"] + + if res["privateKeytab"] is not None: + msg["privateKeytab"] = res[0]["privateKeytab"] + if res["krb5Keytab"] is not None: + msg["krb5Keytab"] = res[0]["krb5Keytab"] -def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain, - netbiosname, domainsid, keytab_path, samdb_url, - dns_keytab_path, dnspass, machinepass): - """Add DC-specific bits to a secrets database. + for el in msg: + el.set_flags(ldb.FLAG_MOD_REPLACE) + secretsdb.modify(msg) + else: + secretsdb.add(msg) + + +def secretsdb_setup_dns(secretsdb, setup_path, 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 """ - setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), { - "MACHINEPASS_B64": b64encode(machinepass), - "DOMAIN": domain, + setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { "REALM": realm, "DNSDOMAIN": dnsdomain, - "DOMAINSID": str(domainsid), - "SECRETS_KEYTAB": keytab_path, - "NETBIOSNAME": netbiosname, - "SAM_LDB": samdb_url, "DNS_KEYTAB": dns_keytab_path, "DNSPASS_B64": b64encode(dnspass), }) @@ -625,6 +812,7 @@ def setup_secretsdb(path, setup_path, session_info, credentials, lp): secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif")) secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials, lp=lp) + secrets_ldb.transaction_start() secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif")) if credentials is not None and credentials.authentication_requested(): @@ -642,34 +830,7 @@ def setup_secretsdb(path, setup_path, session_info, credentials, lp): return secrets_ldb - -def setup_templatesdb(path, setup_path, session_info, credentials, lp): - """Setup the templates database. - - :param path: Path to the database. - :param setup_path: Function for obtaining the path to setup files. - :param session_info: Session info - :param credentials: Credentials - :param lp: Loadparm context - """ - templates_ldb = SamDB(path, session_info=session_info, - credentials=credentials, lp=lp) - # Wipes the database - try: - templates_ldb.erase() - # This should be 'except LdbError', but on a re-provision the assert in ldb.erase fires, and we need to catch that too - except: - os.unlink(path) - - templates_ldb.load_ldif_file_add(setup_path("provision_templates_init.ldif")) - - templates_ldb = SamDB(path, session_info=session_info, - credentials=credentials, lp=lp) - - templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif")) - - -def setup_registry(path, setup_path, session_info, credentials, lp): +def setup_registry(path, setup_path, session_info, lp): """Setup the registry. :param path: Path to the registry database @@ -680,14 +841,14 @@ def setup_registry(path, setup_path, session_info, credentials, lp): """ reg = registry.Registry() hive = registry.open_ldb(path, session_info=session_info, - credentials=credentials, lp_ctx=lp) + lp_ctx=lp) reg.mount_hive(hive, 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, credentials, lp): +def setup_idmapdb(path, setup_path, session_info, lp): """Setup the idmap database. :param path: path to the idmap database @@ -700,7 +861,7 @@ def setup_idmapdb(path, setup_path, session_info, credentials, lp): os.unlink(path) idmap_ldb = IDmapDB(path, session_info=session_info, - credentials=credentials, lp=lp) + lp=lp) idmap_ldb.erase() idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif")) @@ -729,9 +890,14 @@ def setup_samdb_rootdse(samdb, setup_path, names): def setup_self_join(samdb, names, machinepass, dnspass, domainsid, invocationid, setup_path, - policyguid): + policyguid, policyguid_dc, domainControllerFunctionality, + ntdsguid): """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 = "" setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { "CONFIGDN": names.configdn, "SCHEMADN": names.schemadn, @@ -745,71 +911,133 @@ def setup_self_join(samdb, names, "DNSPASS_B64": b64encode(dnspass), "REALM": names.realm, "DOMAIN": names.domain, - "DNSDOMAIN": names.dnsdomain}) + "DNSDOMAIN": names.dnsdomain, + "SAMBA_VERSION_STRING": version, + "NTDSGUID": ntdsguid_line, + "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)}) + setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { "POLICYGUID": policyguid, + "POLICYGUID_DC": policyguid_dc, "DNSDOMAIN": names.dnsdomain, "DOMAINSID": str(domainsid), "DOMAINDN": names.domaindn}) + + # add the NTDSGUID based SPNs + ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn) + names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID", + expression="", scope=SCOPE_BASE) + assert isinstance(names.ntdsguid, str) + + # Setup fSMORoleOwner entries to point at the newly created DC entry + setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), { + "DOMAIN": names.domain, + "DNSDOMAIN": names.dnsdomain, + "DOMAINDN": names.domaindn, + "CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn, + "DEFAULTSITE": names.sitename, + "SERVERDN": names.serverdn, + "NETBIOSNAME": names.netbiosname, + "NTDSGUID": names.ntdsguid + }) def setup_samdb(path, setup_path, session_info, credentials, lp, names, message, - domainsid, aci, domainguid, policyguid, + domainsid, domainguid, policyguid, policyguid_dc, fill, adminpass, krbtgtpass, - machinepass, invocationid, dnspass, - serverrole, ldap_backend=None, - ldap_backend_type=None): + machinepass, invocationid, dnspass, ntdsguid, + serverrole, dom_for_fun_level=None, + schema=None, ldap_backend=None): """Setup a complete SAM Database. :note: This will wipe the main SAM database file! """ - erase = (fill != FILL_DRS) + # ATTENTION: Do NOT change these default values without discussion with the + # team and/or release manager. They have a big impact on the whole program! + domainControllerFunctionality = DS_DC_FUNCTION_2008 + + if dom_for_fun_level is None: + dom_for_fun_level = DS_DOMAIN_FUNCTION_2003 + if dom_for_fun_level < DS_DOMAIN_FUNCTION_2003: + raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level lower than Windows 2003 (Native). This isn't supported!") + + if dom_for_fun_level > domainControllerFunctionality: + raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level which itself is higher than its actual DC function level (2008). This won't work!") + + domainFunctionality = dom_for_fun_level + forestFunctionality = dom_for_fun_level # Also wipes the database setup_samdb_partitions(path, setup_path, message=message, lp=lp, credentials=credentials, session_info=session_info, - names=names, - ldap_backend=ldap_backend, serverrole=serverrole, - ldap_backend_type=ldap_backend_type, erase=erase) + names=names, ldap_backend=ldap_backend, + serverrole=serverrole) - samdb = SamDB(path, session_info=session_info, - credentials=credentials, lp=lp) - if fill == FILL_DRS: - return samdb + if (schema == None): + schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn, + sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type) + + # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema + samdb = Ldb(session_info=session_info, + credentials=credentials, lp=lp) message("Pre-loading the Samba 4 and AD schema") - samdb.set_domain_sid(str(domainsid)) - if serverrole == "domain controller": - samdb.set_invocation_id(invocationid) - schema_data = load_schema(setup_path, samdb, names.schemadn, names.netbiosname, - names.configdn, names.sitename, names.serverdn, - names.hostname) + # Load the schema from the one we computed earlier + samdb.set_schema_from_ldb(schema.ldb) + + # And now we can connect to the DB - the schema won't be loaded from the DB + samdb.connect(path) + + # Load @OPTIONS + samdb.load_ldif_file_add(setup_path("provision_options.ldif")) + + if fill == FILL_DRS: + return samdb + samdb.transaction_start() - try: - message("Adding DomainDN: %s (permitted to fail)" % names.domaindn) + message("Erasing data from partitions") + # Load the schema (again). This time it will force a reindex, + # and will therefore make the erase_partitions() below + # computationally sane + samdb.set_schema_from_ldb(schema.ldb) + samdb.erase_partitions() + + # Set the domain functionality levels onto the database. + # Various module (the password_hash module in particular) need + # to know what level of AD we are emulating. + + # These will be fixed into the database via the database + # modifictions below, but we need them set from the start. + samdb.set_opaque_integer("domainFunctionality", domainFunctionality) + samdb.set_opaque_integer("forestFunctionality", forestFunctionality) + samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality) + + samdb.set_domain_sid(str(domainsid)) if serverrole == "domain controller": - domain_oc = "domainDNS" - else: - domain_oc = "samba4LocalDomain" + samdb.set_invocation_id(invocationid) + message("Adding DomainDN: %s" % names.domaindn) + +#impersonate domain admin + admin_session_info = admin_session(lp, str(domainsid)) + samdb.set_session_info(admin_session_info) + if domainguid is not None: + domainguid_line = "objectGUID: %s\n-" % domainguid + else: + domainguid_line = "" setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), { "DOMAINDN": names.domaindn, - "ACI": aci, - "DOMAIN_OC": domain_oc + "DOMAINGUID": domainguid_line }) - message("Modifying DomainDN: " + names.domaindn + "") - if domainguid is not None: - domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid - else: - domainguid_mod = "" setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), { - "LDAPTIME": timestring(int(time.time())), + "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks "DOMAINSID": str(domainsid), "SCHEMADN": names.schemadn, "NETBIOSNAME": names.netbiosname, @@ -818,13 +1046,15 @@ def setup_samdb(path, setup_path, session_info, credentials, lp, "SERVERDN": names.serverdn, "POLICYGUID": policyguid, "DOMAINDN": names.domaindn, - "DOMAINGUID_MOD": domainguid_mod, + "DOMAIN_FUNCTIONALITY": str(domainFunctionality), + "SAMBA_VERSION_STRING": version }) - message("Adding configuration container (permitted to fail)") + message("Adding configuration container") + descr = get_config_descriptor(domainsid); setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), { "CONFIGDN": names.configdn, - "ACI": aci, + "DESCRIPTOR": descr, }) message("Modifying configuration container") setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), { @@ -832,27 +1062,12 @@ def setup_samdb(path, setup_path, session_info, credentials, lp, "SCHEMADN": names.schemadn, }) - message("Adding schema container (permitted to fail)") - setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), { - "SCHEMADN": names.schemadn, - "ACI": aci, - }) - message("Modifying schema container") - - prefixmap = open(setup_path("prefixMap.txt"), 'r').read() - - setup_modify_ldif(samdb, - setup_path("provision_schema_basedn_modify.ldif"), { - "SCHEMADN": names.schemadn, - "NETBIOSNAME": names.netbiosname, - "DEFAULTSITE": names.sitename, - "CONFIGDN": names.configdn, - "SERVERDN": names.serverdn, - "PREFIXMAP_B64": b64encode(prefixmap) - }) - + # The LDIF here was created when the Schema object was constructed message("Setting up sam.ldb schema") - samdb.add_ldif(schema_data) + 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}) @@ -865,20 +1080,23 @@ def setup_samdb(path, setup_path, session_info, credentials, lp, "DOMAIN": names.domain, "SCHEMADN": names.schemadn, "DOMAINDN": names.domaindn, - "SERVERDN": names.serverdn + "SERVERDN": names.serverdn, + "FOREST_FUNCTIONALALITY": str(forestFunctionality) }) message("Setting up display specifiers") - setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), - {"CONFIGDN": names.configdn}) + display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt')) + display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn}) + check_all_substituted(display_specifiers_ldif) + samdb.add_ldif(display_specifiers_ldif) - message("Adding users container (permitted to fail)") + message("Adding users container") setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), { "DOMAINDN": names.domaindn}) message("Modifying users container") setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), { "DOMAINDN": names.domaindn}) - message("Adding computers container (permitted to fail)") + message("Adding computers container") setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), { "DOMAINDN": names.domaindn}) message("Modifying computers container") @@ -886,11 +1104,13 @@ def setup_samdb(path, setup_path, session_info, credentials, lp, "DOMAINDN": names.domaindn}) message("Setting up sam.ldb data") setup_add_ldif(samdb, setup_path("provision.ldif"), { + "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks "DOMAINDN": names.domaindn, "NETBIOSNAME": names.netbiosname, "DEFAULTSITE": names.sitename, "CONFIGDN": names.configdn, - "SERVERDN": names.serverdn + "SERVERDN": names.serverdn, + "POLICYGUID_DC": policyguid_dc }) if fill == FILL_FULL: @@ -909,7 +1129,15 @@ def setup_samdb(path, setup_path, session_info, credentials, lp, dnspass=dnspass, machinepass=machinepass, domainsid=domainsid, policyguid=policyguid, - setup_path=setup_path) + policyguid_dc=policyguid_dc, + setup_path=setup_path, + domainControllerFunctionality=domainControllerFunctionality, + ntdsguid=ntdsguid) + + ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn) + names.ntdsguid = samdb.searchone(basedn=ntds_dn, + attribute="objectGUID", expression="", scope=SCOPE_BASE) + assert isinstance(names.ntdsguid, str) except: samdb.transaction_cancel() @@ -923,29 +1151,46 @@ FILL_FULL = "FULL" FILL_NT4SYNC = "NT4SYNC" FILL_DRS = "DRS" + def provision(setup_dir, message, session_info, - credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None, + 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, adminpass=None, krbtgtpass=None, domainguid=None, - policyguid=None, invocationid=None, machinepass=None, - dnspass=None, root=None, nobody=None, nogroup=None, users=None, - wheel=None, backup=None, aci=None, serverrole=None, - ldap_backend=None, ldap_backend_type=None, sitename=None): + domainsid=None, adminpass=None, ldapadminpass=None, + krbtgtpass=None, domainguid=None, + policyguid=None, policyguid_dc=None, invocationid=None, + machinepass=None, ntdsguid=None, + dnspass=None, root=None, nobody=None, users=None, + wheel=None, backup=None, aci=None, serverrole=None, + dom_for_fun_level=None, + ldap_backend_extra_port=None, ldap_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): """Provision samba4 :note: caution, this wipes all existing data! """ def setup_path(file): - return os.path.join(setup_dir, file) + return os.path.join(setup_dir, file) if domainsid is None: - domainsid = security.random_sid() + domainsid = security.random_sid() + else: + domainsid = security.dom_sid(domainsid) + # create/adapt the group policy GUIDs if policyguid is None: policyguid = str(uuid.uuid4()) + policyguid = policyguid.upper() + if policyguid_dc is None: + policyguid_dc = str(uuid.uuid4()) + policyguid_dc = policyguid_dc.upper() + if adminpass is None: adminpass = glue.generate_random_str(12) if krbtgtpass is None: @@ -954,6 +1199,11 @@ def provision(setup_dir, message, session_info, machinepass = glue.generate_random_str(12) if dnspass is None: dnspass = glue.generate_random_str(12) + if ldapadminpass is None: + #Make a new, random password between Samba and it's LDAP server + ldapadminpass=glue.generate_random_str(12) + + root_uid = findnss_uid([root or "root"]) nobody_uid = findnss_uid([nobody or "nobody"]) users_gid = findnss_gid([users or "users"]) @@ -961,8 +1211,6 @@ def provision(setup_dir, message, session_info, wheel_gid = findnss_gid(["wheel", "adm"]) else: wheel_gid = findnss_gid([wheel]) - if aci is None: - aci = "# no aci for local ldb" if targetdir is not None: if (not os.path.exists(os.path.join(targetdir, "etc"))): @@ -1010,11 +1258,32 @@ def provision(setup_dir, message, session_info, ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="") - if ldap_backend is not None: - if ldap_backend == "ldapi": - # provision-backend will set this path suggested slapd command line / fedorads.inf - ldap_backend = "ldapi://%s" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="") - + schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn, + sambadn=names.sambadn, ldap_backend_type=ldap_backend_type) + + secrets_credentials = credentials + provision_backend = None + if ldap_backend_type: + # We only support an LDAP backend over ldapi:// + + provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path, + lp=lp, credentials=credentials, + names=names, + message=message, hostname=hostname, + root=root, schema=schema, + ldap_backend_type=ldap_backend_type, + ldapadminpass=ldapadminpass, + ldap_backend_extra_port=ldap_backend_extra_port, + ol_mmr_urls=ol_mmr_urls, + slapd_path=slapd_path, + setup_ds_path=setup_ds_path, + ldap_dryrun_mode=ldap_dryrun_mode) + + # Now use the backend credentials to access the databases + credentials = provision_backend.credentials + secrets_credentials = provision_backend.adminCredentials + ldapi_url = provision_backend.ldapi_uri + # only install a new shares config db if there is none if not os.path.exists(paths.shareconf): message("Setting up share.ldb") @@ -1026,33 +1295,32 @@ def provision(setup_dir, message, session_info, message("Setting up secrets.ldb") secrets_ldb = setup_secretsdb(paths.secrets, setup_path, session_info=session_info, - credentials=credentials, lp=lp) + credentials=secrets_credentials, lp=lp) message("Setting up the registry") setup_registry(paths.hklm, setup_path, session_info, - credentials=credentials, lp=lp) - - message("Setting up templates db") - setup_templatesdb(paths.templates, setup_path, session_info=session_info, - credentials=credentials, lp=lp) + lp=lp) message("Setting up idmap db") idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info, - credentials=credentials, lp=lp) + lp=lp) + message("Setting up SAM db") samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, credentials=credentials, lp=lp, names=names, message=message, domainsid=domainsid, - aci=aci, domainguid=domainguid, policyguid=policyguid, + schema=schema, domainguid=domainguid, + policyguid=policyguid, policyguid_dc=policyguid_dc, fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass, invocationid=invocationid, - machinepass=machinepass, dnspass=dnspass, - serverrole=serverrole, ldap_backend=ldap_backend, - ldap_backend_type=ldap_backend_type) + machinepass=machinepass, dnspass=dnspass, + ntdsguid=ntdsguid, serverrole=serverrole, + dom_for_fun_level=dom_for_fun_level, + ldap_backend=provision_backend) - if lp.get("server role") == "domain controller": + if serverrole == "domain controller": if paths.netlogon is None: message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.") message("Please either remove %s or see the template at %s" % @@ -1065,12 +1333,24 @@ def provision(setup_dir, message, session_info, (paths.smbconf, setup_path("provision.smb.conf.dc"))) assert(paths.sysvol is not None) - policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", + # Set up group policies (domain policy and domain controller policy) + + policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", "{" + policyguid + "}") os.makedirs(policy_path, 0755) - open(os.path.join(policy_path, "GPT.INI"), 'w').write("") - os.makedirs(os.path.join(policy_path, "Machine"), 0755) - os.makedirs(os.path.join(policy_path, "User"), 0755) + open(os.path.join(policy_path, "GPT.INI"), 'w').write( + "[General]\r\nVersion=65543") + os.makedirs(os.path.join(policy_path, "MACHINE"), 0755) + os.makedirs(os.path.join(policy_path, "USER"), 0755) + + policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies", + "{" + policyguid_dc + "}") + os.makedirs(policy_path_dc, 0755) + open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write( + "[General]\r\nVersion=2") + os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755) + os.makedirs(os.path.join(policy_path_dc, "USER"), 0755) + if not os.path.isdir(paths.netlogon): os.makedirs(paths.netlogon, 0755) @@ -1084,29 +1364,27 @@ def provision(setup_dir, message, session_info, # Only make a zone file on the first DC, it should be replicated with DNS replication if serverrole == "domain controller": - secrets_ldb = Ldb(paths.secrets, session_info=session_info, - credentials=credentials, lp=lp) - secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm, - netbiosname=names.netbiosname, domainsid=domainsid, - keytab_path=paths.keytab, samdb_url=paths.samdb, - dns_keytab_path=paths.dns_keytab, dnspass=dnspass, - machinepass=machinepass, dnsdomain=names.dnsdomain) - - samdb = SamDB(paths.samdb, session_info=session_info, - credentials=credentials, lp=lp) + secretsdb_self_join(secrets_ldb, domain=domain, + realm=names.realm, + dnsdomain=names.dnsdomain, + netbiosname=names.netbiosname, + domainsid=domainsid, + machinepass=machinepass, + secure_channel_type=SEC_CHAN_BDC) + + secretsdb_setup_dns(secrets_ldb, setup_path, + 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) - hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID", - expression="(&(objectClass=computer)(cn=%s))" % names.hostname, - scope=SCOPE_SUBTREE) - assert isinstance(hostguid, str) create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain, - domaindn=names.domaindn, hostip=hostip, + hostip=hostip, hostip6=hostip6, hostname=names.hostname, - dnspass=dnspass, realm=names.realm, - domainguid=domainguid, hostguid=hostguid) + realm=names.realm, + domainguid=domainguid, ntdsguid=names.ntdsguid) create_named_conf(paths.namedconf, setup_path, realm=names.realm, dnsdomain=names.dnsdomain, private_dir=paths.private_dir) @@ -1117,24 +1395,84 @@ def provision(setup_dir, message, session_info, message("See %s for an example configuration include file for BIND" % paths.namedconf) message("and %s for further documentation required for secure DNS updates" % paths.namedtxt) - create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain, - hostname=names.hostname, realm=names.realm) + create_krb5_conf(paths.krb5conf, setup_path, + dnsdomain=names.dnsdomain, hostname=names.hostname, + realm=names.realm) message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf) + #Now commit the secrets.ldb to disk + secrets_ldb.transaction_commit() + + if provision_backend is not None: + if ldap_backend_type == "fedora-ds": + ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials) + + # delete default SASL mappings + res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"]) + + # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket) + for i in range (0, len(res)): + dn = str(res[i]["dn"]) + ldapi_db.delete(dn) + + aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn + + m = ldb.Message() + m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci") + + m.dn = ldb.Dn(1, names.domaindn) + ldapi_db.modify(m) + + m.dn = ldb.Dn(1, names.configdn) + ldapi_db.modify(m) + + m.dn = ldb.Dn(1, names.schemadn) + ldapi_db.modify(m) + + # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination + if provision_backend.slapd.poll() is None: + #Kill the slapd + if hasattr(provision_backend.slapd, "terminate"): + provision_backend.slapd.terminate() + else: + # Older python versions don't have .terminate() + import signal + os.kill(provision_backend.slapd.pid, signal.SIGTERM) + + #and now wait for it to die + provision_backend.slapd.communicate() + + # now display slapd_command_file.txt to show how slapd must be started next time + message("Use later the following commandline to start slapd, then Samba:") + slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'" + message(slapd_command) + message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh") + + setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", { + "SLAPD_COMMAND" : slapd_command}) + + create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, ldapi_url) message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig) message("Once the above files are installed, your Samba4 server will be ready to use") - message("Server Role: %s" % serverrole) - message("Hostname: %s" % names.hostname) - message("NetBIOS Domain: %s" % names.domain) - message("DNS Domain: %s" % names.dnsdomain) - message("DOMAIN SID: %s" % str(domainsid)) + message("Server Role: %s" % serverrole) + message("Hostname: %s" % names.hostname) + message("NetBIOS Domain: %s" % names.domain) + message("DNS Domain: %s" % names.dnsdomain) + message("DOMAIN SID: %s" % str(domainsid)) if samdb_fill == FILL_FULL: - message("Admin password: %s" % adminpass) + message("Admin password: %s" % adminpass) + if provision_backend: + if provision_backend.credentials.get_bind_dn() is not None: + message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn()) + else: + message("LDAP Admin User: %s" % provision_backend.credentials.get_username()) + message("LDAP Admin Password: %s" % provision_backend.credentials.get_password()) + result = ProvisionResult() result.domaindn = domaindn result.paths = paths @@ -1143,26 +1481,34 @@ def provision(setup_dir, message, session_info, return result + def provision_become_dc(setup_dir=None, smbconf=None, targetdir=None, realm=None, - rootdn=None, domaindn=None, schemadn=None, configdn=None, - serverdn=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, invocationid=None, machinepass=None, - dnspass=None, root=None, nobody=None, nogroup=None, users=None, - wheel=None, backup=None, aci=None, serverrole=None, - ldap_backend=None, ldap_backend_type=None, sitename=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): def message(text): """print a message if quiet is not set.""" print text + glue.set_debug_level(debuglevel) + return provision(setup_dir, message, 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, machinepass=machinepass, serverrole="domain controller", sitename=sitename) - + 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, + machinepass=machinepass, serverrole="domain controller", + sitename=sitename) + def setup_db_config(setup_path, dbdir): """Setup a Berkeley database. @@ -1171,366 +1517,502 @@ def setup_db_config(setup_path, dbdir): :param dbdir: Database directory.""" if not os.path.isdir(os.path.join(dbdir, "bdb-logs")): os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700) - if not os.path.isdir(os.path.join(dbdir, "tmp")): - os.makedirs(os.path.join(dbdir, "tmp"), 0700) - + if not os.path.isdir(os.path.join(dbdir, "tmp")): + os.makedirs(os.path.join(dbdir, "tmp"), 0700) + setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"), {"LDAPDBDIR": dbdir}) +class ProvisionBackend(object): + def __init__(self, paths=None, setup_path=None, lp=None, credentials=None, + names=None, message=None, + hostname=None, root=None, + schema=None, ldapadminpass=None, + ldap_backend_type=None, ldap_backend_extra_port=None, + ol_mmr_urls=None, + setup_ds_path=None, slapd_path=None, + nosync=False, ldap_dryrun_mode=False): + """Provision an LDAP backend for samba4 + + This works for OpenLDAP and Fedora DS + """ + self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="") + + if not os.path.isdir(paths.ldapdir): + os.makedirs(paths.ldapdir, 0700) + + if ldap_backend_type == "existing": + #Check to see that this 'existing' LDAP backend in fact exists + ldapi_db = Ldb(self.ldapi_uri, credentials=credentials) + search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE, + expression="(objectClass=OpenLDAProotDSE)") + + # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied + # This caused them to be set into the long-term database later in the script. + self.credentials = credentials + self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP + return + + # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri + # if another instance of slapd is already running + try: + ldapi_db = Ldb(self.ldapi_uri) + search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE, + expression="(objectClass=OpenLDAProotDSE)"); + try: + f = open(paths.slapdpid, "r") + p = f.read() + f.close() + message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.") + except: + pass + + raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ") + + except LdbError, e: + pass -def provision_backend(setup_dir=None, message=None, - smbconf=None, targetdir=None, realm=None, - rootdn=None, domaindn=None, schemadn=None, configdn=None, - domain=None, hostname=None, adminpass=None, root=None, serverrole=None, - ldap_backend_type=None, ldap_backend_port=None, - ol_mmr_urls=None,ol_olc=None,ol_slaptest=None): + # Try to print helpful messages when the user has not specified the path to slapd + if slapd_path is None: + raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!") + if not os.path.exists(slapd_path): + message (slapd_path) + raise ProvisioningError("Warning: Given Path to slapd does not exist!") - def setup_path(file): - return os.path.join(setup_dir, file) + schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb") + try: + os.unlink(schemadb_path) + except OSError: + pass - if hostname is None: - hostname = socket.gethostname().split(".")[0].lower() - if root is None: - root = findnss(pwd.getpwnam, ["root"])[0] + # Put the LDIF of the schema into a database so we can search on + # it to generate schema-dependent configurations in Fedora DS and + # OpenLDAP + os.path.join(paths.ldapdir, "schema-tmp.ldb") + schema.ldb.connect(schemadb_path) + schema.ldb.transaction_start() + + # These bits of LDIF are supplied when the Schema object is created + schema.ldb.add_ldif(schema.schema_dn_add) + schema.ldb.modify_ldif(schema.schema_dn_modify) + schema.ldb.add_ldif(schema.schema_data) + schema.ldb.transaction_commit() + + self.credentials = Credentials() + self.credentials.guess(lp) + #Kerberos to an ldapi:// backend makes no sense + self.credentials.set_kerberos_state(DONT_USE_KERBEROS) + + self.adminCredentials = Credentials() + self.adminCredentials.guess(lp) + #Kerberos to an ldapi:// backend makes no sense + self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS) + + self.ldap_backend_type = ldap_backend_type + + if ldap_backend_type == "fedora-ds": + provision_fds_backend(self, paths=paths, setup_path=setup_path, + names=names, message=message, + hostname=hostname, + ldapadminpass=ldapadminpass, root=root, + schema=schema, + ldap_backend_extra_port=ldap_backend_extra_port, + setup_ds_path=setup_ds_path, + slapd_path=slapd_path, + nosync=nosync, + ldap_dryrun_mode=ldap_dryrun_mode) + + elif ldap_backend_type == "openldap": + provision_openldap_backend(self, paths=paths, setup_path=setup_path, + names=names, message=message, + hostname=hostname, + ldapadminpass=ldapadminpass, root=root, + schema=schema, + ldap_backend_extra_port=ldap_backend_extra_port, + ol_mmr_urls=ol_mmr_urls, + slapd_path=slapd_path, + nosync=nosync, + ldap_dryrun_mode=ldap_dryrun_mode) + else: + raise ProvisioningError("Unknown LDAP backend type selected") - if adminpass is None: - adminpass = glue.generate_random_str(12) + self.credentials.set_password(ldapadminpass) + self.adminCredentials.set_username("samba-admin") + self.adminCredentials.set_password(ldapadminpass) - if targetdir is not None: - if (not os.path.exists(os.path.join(targetdir, "etc"))): - os.makedirs(os.path.join(targetdir, "etc")) - smbconf = os.path.join(targetdir, "etc", "smb.conf") - elif smbconf is None: - smbconf = param.default_path() - assert smbconf is not None + # Now start the slapd, so we can provision onto it. We keep the + # subprocess context around, to kill this off at the successful + # end of the script + self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False) + + while self.slapd.poll() is None: + # Wait until the socket appears + try: + ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials) + search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE, + expression="(objectClass=OpenLDAProotDSE)") + # If we have got here, then we must have a valid connection to the LDAP server! + return + except LdbError, e: + time.sleep(1) + pass + + raise ProvisioningError("slapd died before we could make a connection to it") + + +def provision_openldap_backend(result, paths=None, setup_path=None, names=None, + message=None, + hostname=None, ldapadminpass=None, root=None, + schema=None, + ldap_backend_extra_port=None, + ol_mmr_urls=None, + slapd_path=None, nosync=False, + ldap_dryrun_mode=False): + + #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB + nosync_config = "" + if nosync: + nosync_config = "dbnosync" + + lnkattr = get_linked_attributes(names.schemadn,schema.ldb) + refint_attributes = "" + memberof_config = "# Generated from Samba4 schema\n" + for att in lnkattr.keys(): + if lnkattr[att] is not None: + refint_attributes = refint_attributes + " " + att + + memberof_config += read_and_sub_file(setup_path("memberof.conf"), + { "MEMBER_ATTR" : att , + "MEMBEROF_ATTR" : lnkattr[att] }) + + refint_config = read_and_sub_file(setup_path("refint.conf"), + { "LINK_ATTRS" : refint_attributes}) + + attrs = ["linkID", "lDAPDisplayName"] + res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs) + index_config = "" + for i in range (0, len(res)): + index_attr = res[i]["lDAPDisplayName"][0] + if index_attr == "objectGUID": + index_attr = "entryUUID" + + index_config += "index " + index_attr + " eq\n" - # only install a new smb.conf if there isn't one there already - if not os.path.exists(smbconf): - make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, - targetdir) +# generate serverids, ldap-urls and syncrepl-blocks for mmr hosts + mmr_on_config = "" + mmr_replicator_acl = "" + mmr_serverids_config = "" + mmr_syncrepl_schema_config = "" + mmr_syncrepl_config_config = "" + mmr_syncrepl_user_config = "" + + + if ol_mmr_urls is not None: + # For now, make these equal + mmr_pass = ldapadminpass + + url_list=filter(None,ol_mmr_urls.split(' ')) + if (len(url_list) == 1): + url_list=filter(None,ol_mmr_urls.split(',')) + + + mmr_on_config = "MirrorMode On" + mmr_replicator_acl = " by dn=cn=replicator,cn=samba read" + serverid=0 + for url in url_list: + serverid=serverid+1 + mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"), + { "SERVERID" : str(serverid), + "LDAPSERVER" : url }) + rid=serverid*10 + rid=rid+1 + mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"), + { "RID" : str(rid), + "MMRDN": names.schemadn, + "LDAPSERVER" : url, + "MMR_PASSWORD": mmr_pass}) + + rid=rid+1 + mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"), + { "RID" : str(rid), + "MMRDN": names.configdn, + "LDAPSERVER" : url, + "MMR_PASSWORD": mmr_pass}) + + rid=rid+1 + mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"), + { "RID" : str(rid), + "MMRDN": names.domaindn, + "LDAPSERVER" : url, + "MMR_PASSWORD": mmr_pass }) + # OpenLDAP cn=config initialisation + olc_syncrepl_config = "" + olc_mmr_config = "" + # if mmr = yes, generate cn=config-replication directives + # and olc_seed.lif for the other mmr-servers + if ol_mmr_urls is not None: + serverid=0 + olc_serverids_config = "" + olc_syncrepl_seed_config = "" + olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{}) + rid=1000 + for url in url_list: + serverid=serverid+1 + olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"), + { "SERVERID" : str(serverid), + "LDAPSERVER" : url }) + + rid=rid+1 + olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"), + { "RID" : str(rid), + "LDAPSERVER" : url, + "MMR_PASSWORD": mmr_pass}) + + olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"), + { "RID" : str(rid), + "LDAPSERVER" : url}) + + setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif, + {"OLC_SERVER_ID_CONF": olc_serverids_config, + "OLC_PW": ldapadminpass, + "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config}) + # end olc + + setup_file(setup_path("slapd.conf"), paths.slapdconf, + {"DNSDOMAIN": names.dnsdomain, + "LDAPDIR": paths.ldapdir, + "DOMAINDN": names.domaindn, + "CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn, + "MEMBEROF_CONFIG": memberof_config, + "MIRRORMODE": mmr_on_config, + "REPLICATOR_ACL": mmr_replicator_acl, + "MMR_SERVERIDS_CONFIG": mmr_serverids_config, + "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config, + "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config, + "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config, + "OLC_SYNCREPL_CONFIG": olc_syncrepl_config, + "OLC_MMR_CONFIG": olc_mmr_config, + "REFINT_CONFIG": refint_config, + "INDEX_CONFIG": index_config, + "NOSYNC": nosync_config}) + + setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user")) + setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config")) + setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema")) + + if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")): + os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700) + + setup_file(setup_path("cn=samba.ldif"), + os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"), + { "UUID": str(uuid.uuid4()), + "LDAPTIME": timestring(int(time.time()))} ) + setup_file(setup_path("cn=samba-admin.ldif"), + os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"), + {"LDAPADMINPASS_B64": b64encode(ldapadminpass), + "UUID": str(uuid.uuid4()), + "LDAPTIME": timestring(int(time.time()))} ) + + if ol_mmr_urls is not None: + setup_file(setup_path("cn=replicator.ldif"), + os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"), + {"MMR_PASSWORD_B64": b64encode(mmr_pass), + "UUID": str(uuid.uuid4()), + "LDAPTIME": timestring(int(time.time()))} ) + - # openldap-online-configuration: validation of olc and slaptest - if ol_olc == "yes" and ol_slaptest is None: - sys.exit("Warning: OpenLDAP-Online-Configuration cant be setup without path to slaptest-Binary!") + mapping = "schema-map-openldap-2.3" + backend_schema = "backend-schema.schema" - if ol_olc == "yes" and ol_slaptest is not None: - ol_slaptest = ol_slaptest + "/slaptest" - if not os.path.exists(ol_slaptest): - message (ol_slaptest) - sys.exit("Warning: Given Path to slaptest-Binary does not exist!") - ### + backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read()) + assert backend_schema_data is not None + open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data) + # now we generate the needed strings to start slapd automatically, + # first ldapi_uri... + if ldap_backend_extra_port is not None: + # When we use MMR, we can't use 0.0.0.0 as it uses the name + # specified there as part of it's clue as to it's own name, + # and not to replicate to itself + if ol_mmr_urls is None: + server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port + else: + server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port + else: + server_port_string = "" + # Prepare the 'result' information - the commands to return in particular + result.slapd_provision_command = [slapd_path] - lp = param.LoadParm() - lp.load(smbconf) + result.slapd_provision_command.append("-F" + paths.olcdir) - if serverrole is None: - serverrole = lp.get("server role") + result.slapd_provision_command.append("-h") - names = guess_names(lp=lp, hostname=hostname, domain=domain, - dnsdomain=realm, serverrole=serverrole, - rootdn=rootdn, domaindn=domaindn, configdn=configdn, - schemadn=schemadn) + # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands + result.slapd_command = list(result.slapd_provision_command) + + result.slapd_provision_command.append(result.ldapi_uri) + result.slapd_provision_command.append("-d0") - paths = provision_paths_from_lp(lp, names.dnsdomain) + uris = result.ldapi_uri + if server_port_string is not "": + uris = uris + " " + server_port_string - if not os.path.isdir(paths.ldapdir): - os.makedirs(paths.ldapdir, 0700) - schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb") - try: - os.unlink(schemadb_path) - except OSError: - pass + result.slapd_command.append(uris) - schemadb = SamDB(schemadb_path, lp=lp) - schemadb.transaction_start() - try: - - prefixmap = open(setup_path("prefixMap.txt"), 'r').read() + # Set the username - done here because Fedora DS still uses the admin DN and simple bind + result.credentials.set_username("samba-admin") + + # If we were just looking for crashes up to this point, it's a + # good time to exit before we realise we don't have OpenLDAP on + # this system + if ldap_dryrun_mode: + sys.exit(0) - setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"), - {"SCHEMADN": names.schemadn, - "ACI": "#", - }) - setup_modify_ldif(schemadb, - setup_path("provision_schema_basedn_modify.ldif"), \ - {"SCHEMADN": names.schemadn, - "NETBIOSNAME": names.netbiosname, - "DEFAULTSITE": DEFAULTSITE, - "CONFIGDN": names.configdn, - "SERVERDN": names.serverdn, - "PREFIXMAP_B64": b64encode(prefixmap) - }) - - data = load_schema(setup_path, schemadb, names.schemadn, names.netbiosname, - names.configdn, DEFAULTSITE, names.serverdn) - schemadb.add_ldif(data) - except: - schemadb.transaction_cancel() - raise - schemadb.transaction_commit() + # Finally, convert the configuration into cn=config style! + if not os.path.isdir(paths.olcdir): + os.makedirs(paths.olcdir, 0770) - if ldap_backend_type == "fedora-ds": - if ldap_backend_port is not None: - serverport = "ServerPort=%d" % ldap_backend_port - else: - serverport = "" - - setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, - {"ROOT": root, - "HOSTNAME": hostname, - "DNSDOMAIN": names.dnsdomain, - "LDAPDIR": paths.ldapdir, - "DOMAINDN": names.domaindn, - "LDAPMANAGERDN": names.ldapmanagerdn, - "LDAPMANAGERPASS": adminpass, - "SERVERPORT": serverport}) - - setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, - {"CONFIGDN": names.configdn, - "SCHEMADN": names.schemadn, - }) - - mapping = "schema-map-fedora-ds-1.0" - backend_schema = "99_ad.ldif" + retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False) + +# We can't do this, as OpenLDAP is strange. It gives an error +# output to the above, but does the conversion sucessfully... +# +# if retcode != 0: +# raise ProvisioningError("conversion from slapd.conf to cn=config failed") + + if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")): + raise ProvisioningError("conversion from slapd.conf to cn=config failed") + + # Don't confuse the admin by leaving the slapd.conf around + os.remove(paths.slapdconf) + + +def provision_fds_backend(result, paths=None, setup_path=None, names=None, + message=None, + hostname=None, ldapadminpass=None, root=None, + schema=None, + ldap_backend_extra_port=None, + setup_ds_path=None, + slapd_path=None, + nosync=False, + ldap_dryrun_mode=False): + + if ldap_backend_extra_port is not None: + serverport = "ServerPort=%d" % ldap_backend_extra_port + else: + serverport = "" - slapdcommand="Initialise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf - - ldapuser = "--simple-bind-dn=" + names.ldapmanagerdn + setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, + {"ROOT": root, + "HOSTNAME": hostname, + "DNSDOMAIN": names.dnsdomain, + "LDAPDIR": paths.ldapdir, + "DOMAINDN": names.domaindn, + "LDAPMANAGERDN": names.ldapmanagerdn, + "LDAPMANAGERPASS": ldapadminpass, + "SERVERPORT": serverport}) + + setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, + {"CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn, + "SAMBADN": names.sambadn, + }) - elif ldap_backend_type == "openldap": - attrs = ["linkID", "lDAPDisplayName"] - res = schemadb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs) + setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl, + {"SAMBADN": names.sambadn, + }) - memberof_config = "# Generated from schema in %s\n" % schemadb_path - refint_attributes = "" - for i in range (0, len(res)): - expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1) - target = schemadb.searchone(basedn=names.schemadn, - expression=expression, - attribute="lDAPDisplayName", - scope=SCOPE_SUBTREE) - if target is not None: - refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0] - - memberof_config += read_and_sub_file(setup_path("memberof.conf"), - { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]), - "MEMBEROF_ATTR" : str(target) }) + setup_file(setup_path("fedorads-pam.ldif"), paths.fedoradspam) - refint_config = read_and_sub_file(setup_path("refint.conf"), - { "LINK_ATTRS" : refint_attributes}) + lnkattr = get_linked_attributes(names.schemadn,schema.ldb) -# generate serverids, ldap-urls and syncrepl-blocks for mmr hosts - mmr_on_config = "" - mmr_replicator_acl = "" - mmr_serverids_config = "" - mmr_syncrepl_schema_config = "" - mmr_syncrepl_config_config = "" - mmr_syncrepl_user_config = "" - - - if ol_mmr_urls is not None: - # For now, make these equal - mmr_pass = adminpass + refint_config = data = open(setup_path("fedorads-refint-delete.ldif"), 'r').read() + memberof_config = "" + index_config = "" + argnum = 3 - url_list=filter(None,ol_mmr_urls.split(' ')) - if (len(url_list) == 1): - url_list=filter(None,ol_mmr_urls.split(',')) - + for attr in lnkattr.keys(): + if lnkattr[attr] is not None: + refint_config += read_and_sub_file(setup_path("fedorads-refint-add.ldif"), + { "ARG_NUMBER" : str(argnum) , + "LINK_ATTR" : attr }) + memberof_config += read_and_sub_file(setup_path("fedorads-linked-attributes.ldif"), + { "MEMBER_ATTR" : attr , + "MEMBEROF_ATTR" : lnkattr[attr] }) + index_config += read_and_sub_file(setup_path("fedorads-index.ldif"), + { "ATTR" : attr }) + argnum += 1 - mmr_on_config = "MirrorMode On" - mmr_replicator_acl = " by dn=cn=replicator,cn=samba read" - serverid=0 - for url in url_list: - serverid=serverid+1 - mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"), - { "SERVERID" : str(serverid), - "LDAPSERVER" : url }) - rid=serverid*10 - rid=rid+1 - mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"), - { "RID" : str(rid), - "MMRDN": names.schemadn, - "LDAPSERVER" : url, - "MMR_PASSWORD": mmr_pass}) - - rid=rid+1 - mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"), - { "RID" : str(rid), - "MMRDN": names.configdn, - "LDAPSERVER" : url, - "MMR_PASSWORD": mmr_pass}) - - rid=rid+1 - mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"), - { "RID" : str(rid), - "MMRDN": names.domaindn, - "LDAPSERVER" : url, - "MMR_PASSWORD": mmr_pass }) - # olc = yes? - olc_config_pass = "" - olc_config_acl = "" - olc_syncrepl_config = "" - olc_mmr_config = "" - if ol_olc == "yes": - olc_config_pass += read_and_sub_file(setup_path("olc_pass.conf"), - { "OLC_PW": adminpass }) - olc_config_acl += read_and_sub_file(setup_path("olc_acl.conf"),{}) - - # if olc = yes + mmr = yes, generate cn=config-replication directives - # and olc_seed.lif for the other mmr-servers - if ol_olc == "yes" and ol_mmr_urls is not None: - serverid=0 - olc_serverids_config = "" - olc_syncrepl_config = "" - olc_syncrepl_seed_config = "" - olc_mmr_config = "" - olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{}) - rid=1000 - for url in url_list: - serverid=serverid+1 - olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"), - { "SERVERID" : str(serverid), - "LDAPSERVER" : url }) - - rid=rid+1 - olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"), - { "RID" : str(rid), - "LDAPSERVER" : url, - "MMR_PASSWORD": adminpass}) - - olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"), - { "RID" : str(rid), - "LDAPSERVER" : url}) - - setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif, - {"OLC_SERVER_ID_CONF": olc_serverids_config, - "OLC_PW": adminpass, - "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config}) - + open(paths.fedoradsrefint, 'w').write(refint_config) + open(paths.fedoradslinkedattributes, 'w').write(memberof_config) - # end olc - - setup_file(setup_path("slapd.conf"), paths.slapdconf, - {"DNSDOMAIN": names.dnsdomain, - "LDAPDIR": paths.ldapdir, - "DOMAINDN": names.domaindn, - "CONFIGDN": names.configdn, - "SCHEMADN": names.schemadn, - "MEMBEROF_CONFIG": memberof_config, - "MIRRORMODE": mmr_on_config, - "REPLICATOR_ACL": mmr_replicator_acl, - "MMR_SERVERIDS_CONFIG": mmr_serverids_config, - "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config, - "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config, - "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config, - "OLC_CONFIG_PASS": olc_config_pass, - "OLC_SYNCREPL_CONFIG": olc_syncrepl_config, - "OLC_CONFIG_ACL": olc_config_acl, - "OLC_MMR_CONFIG": olc_mmr_config, - "REFINT_CONFIG": refint_config}) - setup_file(setup_path("modules.conf"), paths.modulesconf, - {"REALM": names.realm}) - - setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user")) - setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config")) - setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema")) - - if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")): - os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700) - - setup_file(setup_path("cn=samba.ldif"), - os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"), - { "UUID": str(uuid.uuid4()), - "LDAPTIME": timestring(int(time.time()))} ) - setup_file(setup_path("cn=samba-admin.ldif"), - os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"), - {"LDAPADMINPASS_B64": b64encode(adminpass), - "UUID": str(uuid.uuid4()), - "LDAPTIME": timestring(int(time.time()))} ) - - if ol_mmr_urls is not None: - setup_file(setup_path("cn=replicator.ldif"), - os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"), - {"MMR_PASSWORD_B64": b64encode(mmr_pass), - "UUID": str(uuid.uuid4()), - "LDAPTIME": timestring(int(time.time()))} ) + attrs = ["lDAPDisplayName"] + res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs) + for i in range (0, len(res)): + attr = res[i]["lDAPDisplayName"][0] - mapping = "schema-map-openldap-2.3" - backend_schema = "backend-schema.schema" + if attr == "objectGUID": + attr = "nsUniqueId" - ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="") - if ldap_backend_port is not None: - server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port - else: - server_port_string = "" + index_config += read_and_sub_file(setup_path("fedorads-index.ldif"), + { "ATTR" : attr }) - if ol_olc != "yes" and ol_mmr_urls is None: - slapdcommand="Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string + open(paths.fedoradsindex, 'w').write(index_config) - if ol_olc == "yes" and ol_mmr_urls is None: - slapdcommand="Start slapd with: slapd -F " + paths.olcdir + " -h \"" + ldapi_uri + " ldap://:\"" + setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba, + {"SAMBADN": names.sambadn, + "LDAPADMINPASS": ldapadminpass + }) - if ol_olc != "yes" and ol_mmr_urls is not None: - slapdcommand="Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h \"" + ldapi_uri + " ldap://:\"" + mapping = "schema-map-fedora-ds-1.0" + backend_schema = "99_ad.ldif" + + # Build a schema file in Fedora DS format + backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read()) + assert backend_schema_data is not None + open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data) - if ol_olc == "yes" and ol_mmr_urls is not None: - slapdcommand="Start slapd with: slapd -F " + paths.olcdir + " -h \"" + ldapi_uri + " ldap://:\"" + result.credentials.set_bind_dn(names.ldapmanagerdn) + # Destory the target directory, or else setup-ds.pl will complain + fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4") + shutil.rmtree(fedora_ds_dir, True) - ldapuser = "--username=samba-admin" + result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid]; + #In the 'provision' command line, stay in the foreground so we can easily kill it + result.slapd_provision_command.append("-d0") + #the command for the final run is the normal script + result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")] - backend_schema_data = schemadb.convert_schema_to_openldap(ldap_backend_type, open(setup_path(mapping), 'r').read()) - assert backend_schema_data is not None - open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data) + # If we were just looking for crashes up to this point, it's a + # good time to exit before we realise we don't have Fedora DS on + if ldap_dryrun_mode: + sys.exit(0) - message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ldap_backend_type) - message("Server Role: %s" % serverrole) - message("Hostname: %s" % names.hostname) - message("DNS Domain: %s" % names.dnsdomain) - message("Base DN: %s" % names.domaindn) + # Try to print helpful messages when the user has not specified the path to the setup-ds tool + if setup_ds_path is None: + raise ProvisioningError("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!") + if not os.path.exists(setup_ds_path): + message (setup_ds_path) + raise ProvisioningError("Warning: Given Path to slapd does not exist!") - if ldap_backend_type == "openldap": - message("LDAP admin user: samba-admin") - else: - message("LDAP admin DN: %s" % names.ldapmanagerdn) - - message("LDAP admin password: %s" % adminpass) - message(slapdcommand) - if ol_olc == "yes" or ol_mmr_urls is not None: - message("Attention to slapd-Port: must be different than 389!") - assert isinstance(ldap_backend_type, str) - assert isinstance(ldapuser, str) - assert isinstance(adminpass, str) - assert isinstance(names.dnsdomain, str) - assert isinstance(names.domain, str) - assert isinstance(serverrole, str) - args = ["--ldap-backend=ldapi", - "--ldap-backend-type=" + ldap_backend_type, - "--password=" + adminpass, - ldapuser, - "--realm=" + names.dnsdomain, - "--domain=" + names.domain, - "--server-role='" + serverrole + "'"] - message("Run provision with: " + " ".join(args)) - - - # if --ol-olc=yes, generate online-configuration in ../private/ldap/slapd.d - if ol_olc == "yes": - if not os.path.isdir(paths.olcdir): - os.makedirs(paths.olcdir, 0770) - paths.olslaptest = str(ol_slaptest) - olc_command = paths.olslaptest + " -f" + paths.slapdconf + " -F" + paths.olcdir + " >/dev/null 2>&1" - os.system(olc_command) - os.remove(paths.slapdconf) - # use line below for debugging during olc-conversion with slaptest, instead of olc_command above - #olc_command = paths.olslaptest + " -f" + paths.slapdconf + " -F" + paths.olcdir" + # Run the Fedora DS setup utility + retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False) + if retcode != 0: + raise ProvisioningError("setup-ds failed") + # Load samba-admin + retcode = subprocess.call([ + os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba], + close_fds=True, shell=False) + if retcode != 0: + raise("ldib2db failed") def create_phpldapadmin_config(path, setup_path, ldapi_uri): """Create a PHP LDAP admin configuration file. @@ -1542,8 +2024,9 @@ def create_phpldapadmin_config(path, setup_path, ldapi_uri): {"S4_LDAPI_URI": ldapi_uri}) -def create_zone_file(path, setup_path, dnsdomain, domaindn, - hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid): +def create_zone_file(path, setup_path, dnsdomain, + hostip, hostip6, hostname, realm, domainguid, + ntdsguid): """Write out a DNS zone file, from the info in the current database. :param path: Path of the new zone file. @@ -1553,10 +2036,9 @@ def create_zone_file(path, setup_path, dnsdomain, domaindn, :param hostip: Local IPv4 IP :param hostip6: Local IPv6 IP :param hostname: Local hostname - :param dnspass: Password for DNS :param realm: Realm name :param domainguid: GUID of the domain. - :param hostguid: GUID of the host. + :param ntdsguid: GUID of the hosts nTDSDSA record. """ assert isinstance(domainguid, str) @@ -1575,7 +2057,6 @@ def create_zone_file(path, setup_path, dnsdomain, domaindn, hostip_host_line = "" setup_file(setup_path("provision.zone"), path, { - "DNSPASS_B64": b64encode(dnspass), "HOSTNAME": hostname, "DNSDOMAIN": dnsdomain, "REALM": realm, @@ -1584,7 +2065,7 @@ def create_zone_file(path, setup_path, dnsdomain, domaindn, "DOMAINGUID": domainguid, "DATESTRING": time.strftime("%Y%m%d%H"), "DEFAULTSITE": DEFAULTSITE, - "HOSTGUID": hostguid, + "NTDSGUID": ntdsguid, "HOSTIP6_BASE_LINE": hostip6_base_line, "HOSTIP6_HOST_LINE": hostip6_host_line, }) @@ -1649,54 +2130,3 @@ def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm): }) -def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename, - serverdn): - """Load schema for the SamDB. - - :param samdb: Load a schema into a SamDB. - :param setup_path: Setup path function. - :param schemadn: DN of the schema - :param netbiosname: NetBIOS name of the host. - :param configdn: DN of the configuration - :param serverdn: DN of the server - - Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db - """ - schema_data = get_schema_data(setup_path, {"SCHEMADN": schemadn}) - schema_data += open(setup_path("schema_samba4.ldif"), 'r').read() - schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn}) - check_all_substituted(schema_data) - prefixmap = open(setup_path("prefixMap.txt"), 'r').read() - prefixmap = b64encode(prefixmap) - - head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read() - head_data = substitute_var(head_data, { - "SCHEMADN": schemadn, - "NETBIOSNAME": netbiosname, - "CONFIGDN": configdn, - "DEFAULTSITE": sitename, - "PREFIXMAP_B64": prefixmap, - "SERVERDN": serverdn, - }) - check_all_substituted(head_data) - samdb.attach_schema_from_ldif(head_data, schema_data) - return schema_data; - -def get_schema_data(setup_path, subst_vars = None): - """Get schema data from the AD schema files instead of schema.ldif. - - :param setup_path: Setup path function. - :param subst_vars: Optional variables to substitute in the file. - - Returns the schema data after substitution - """ - - # this data used to be read from schema.ldif - - data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8Attributes.txt'), - setup_path('ad-schema/MS-AD_Schema_2K8Classes.txt')) - - if subst_vars is not None: - data = substitute_var(data, subst_vars) - check_all_substituted(data) - return data