X-Git-Url: http://git.samba.org/samba.git/?p=nivanova%2Fsamba-autobuild%2F.git;a=blobdiff_plain;f=python%2Fsamba%2Fprovision%2F__init__.py;h=1b7762eb12b17113b8f04152c1c2d01fc2a2f452;hp=7f6d96d760890910c5838fedca150aff004dbf14;hb=67c5ab1752eb9e64fd46eb28589af6c9f5546828;hpb=a90067ec8ef56a8edfbede992848f08e70853cb5 diff --git a/python/samba/provision/__init__.py b/python/samba/provision/__init__.py index 7f6d96d7608..1b7762eb12b 100644 --- a/python/samba/provision/__init__.py +++ b/python/samba/provision/__init__.py @@ -1,5 +1,5 @@ # Unix SMB/CIFS implementation. -# backend code for provisioning a Samba4 server +# backend code for provisioning a Samba AD server # Copyright (C) Jelmer Vernooij 2007-2012 # Copyright (C) Andrew Bartlett 2008-2009 @@ -26,8 +26,13 @@ __docformat__ = "restructuredText" +from samba.compat import urllib_quote +from samba.compat import string_types +from samba.compat import binary_type from base64 import b64encode +import errno import os +import stat import re import pwd import grp @@ -35,14 +40,14 @@ import logging import time import uuid import socket -import urllib -import string import tempfile +import samba.dsdb import ldb from samba.auth import system_session, admin_session import samba +from samba import auth from samba.samba3 import smbd, passdb from samba.samba3 import param as s3param from samba.dsdb import DS_DOMAIN_FUNCTION_2000 @@ -55,27 +60,27 @@ from samba import ( substitute_var, valid_netbios_name, version, - ) + is_heimdal_built, +) 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.idmap import IDmapDB from samba.ms_display_specifiers import read_ms_ldif from samba.ntacls import setntacl, getntacl, dsacl2fsacl from samba.ndr import ndr_pack, ndr_unpack from samba.provision.backend import ( - ExistingBackend, FDSBackend, LDBBackend, OpenLDAPBackend, - ) +) from samba.descriptor import ( get_empty_descriptor, get_config_descriptor, @@ -96,30 +101,38 @@ from samba.descriptor import ( get_dns_partition_descriptor, get_dns_forest_microsoft_dns_descriptor, get_dns_domain_microsoft_dns_descriptor, - ) + get_managed_service_accounts_descriptor, +) from samba.provision.common import ( setup_path, setup_add_ldif, setup_modify_ldif, - ) + FILL_FULL, + FILL_SUBDOMAIN, + FILL_NT4SYNC, + FILL_DRS +) from samba.provision.sambadns import ( get_dnsadmins_sid, 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 - +from samba.provision.kerberos import create_kdc_conf +from samba.samdb import get_default_backend_store DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9" -DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9" +DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04FB984F9" DEFAULTSITE = "Default-First-Site-Name" LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN" +DEFAULT_MIN_PWD_LENGTH = 7 + class ProvisionPaths(object): @@ -139,6 +152,7 @@ class ProvisionPaths(object): self.dns = None self.winsdb = None self.private_dir = None + self.binddns_dir = None self.state_dir = None @@ -160,11 +174,14 @@ class ProvisionNames(object): self.hostname = None self.sitename = None self.smbconf = None + self.domainsid = None + self.forestsid = None + self.domainguid = None self.name_map = {} def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, - lp): + lp): """Get key provision parameters (realm, domain, ...) from a given provision :param samdb: An LDB object connected to the sam.ldb file @@ -179,45 +196,45 @@ def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, names.adminpass = None # NT domain, kerberos realm, root dn, domain dn, domain dns name - names.domain = string.upper(lp.get("workgroup")) + names.domain = lp.get("workgroup").upper() names.realm = lp.get("realm") names.dnsdomain = names.realm.lower() basedn = samba.dn_from_dns_name(names.dnsdomain) - names.realm = string.upper(names.realm) + names.realm = names.realm.upper() # 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.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", - "namingContexts"]) + base="", scope=ldb.SCOPE_BASE, + attrs=["defaultNamingContext", "schemaNamingContext", + "configurationNamingContext", "rootDomainNamingContext", + "namingContexts"]) - names.configdn = current[0]["configurationNamingContext"][0] - names.schemadn = current[0]["schemaNamingContext"][0] + names.configdn = str(current[0]["configurationNamingContext"][0]) + names.schemadn = str(current[0]["schemaNamingContext"][0]) if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb, - current[0]["defaultNamingContext"][0]))): + current[0]["defaultNamingContext"][0].decode('utf8')))): raise ProvisioningError(("basedn in %s (%s) and from %s (%s)" "is not the same ..." % (paths.samdb, - str(current[0]["defaultNamingContext"][0]), - paths.smbconf, basedn))) + str(current[0]["defaultNamingContext"][0].decode('utf8')), + paths.smbconf, basedn))) - names.domaindn=current[0]["defaultNamingContext"][0] - names.rootdn=current[0]["rootDomainNamingContext"][0] - names.ncs=current[0]["namingContexts"] + names.domaindn = str(current[0]["defaultNamingContext"][0]) + names.rootdn = str(current[0]["rootDomainNamingContext"][0]) + names.ncs = current[0]["namingContexts"] names.dnsforestdn = None names.dnsdomaindn = None for i in range(0, len(names.ncs)): - nc = names.ncs[i] + nc = str(names.ncs[i]) dnsforestdn = "DC=ForestDnsZones,%s" % (str(names.rootdn)) if nc == dnsforestdn: @@ -231,52 +248,55 @@ def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, # default site name res3 = samdb.search(expression="(objectClass=site)", - base="CN=Sites," + names.configdn, scope=ldb.SCOPE_ONELEVEL, attrs=["cn"]) + base="CN=Sites," + str(names.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"]) + base="OU=Domain Controllers,%s" % basedn, + scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"]) + if len(res4) == 0: + raise ProvisioningError("Unable to find DC called CN=%s under OU=Domain Controllers,%s" % (names.netbiosname, basedn)) + names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain, "") server_res = samdb.search(expression="serverReference=%s" % res4[0].dn, - attrs=[], base=names.configdn) + attrs=[], base=names.configdn) names.serverdn = str(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"]) + 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" ]) + 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]) + names.domainsid = ndr_unpack(security.dom_sid, res6[0]["objectSid"][0]) + names.forestsid = 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: + 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)", + res7 = samdb.search(expression="(name={%s})" % DEFAULT_POLICY_GUID, base="CN=Policies,CN=System," + basedn, - scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"]) - names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","") + 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"]) + res8 = samdb.search(expression="(name={%s})" % DEFAULT_DC_POLICY_GUID, + 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("}","") + names.policyid_dc = str(res8[0]["cn"]).replace("{", "").replace("}", "") else: names.policyid_dc = None @@ -285,8 +305,8 @@ def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, attrs=["xidNumber", "type"]) if len(res9) != 1: raise ProvisioningError("Unable to find uid/gid for Domain Admins rid (%s-%s" % (str(names.domainsid), security.DOMAIN_RID_ADMINISTRATOR)) - if res9[0]["type"][0] == "ID_TYPE_BOTH": - names.root_gid = res9[0]["xidNumber"][0] + if str(res9[0]["type"][0]) == "ID_TYPE_BOTH": + names.root_gid = int(res9[0]["xidNumber"][0]) else: names.root_gid = pwd.getpwuid(int(res9[0]["xidNumber"][0])).pw_gid @@ -344,15 +364,17 @@ def update_provision_usn(samdb, low, high, id, replace=False): 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) + if not re.search(';', str(e)): + e = "%s;%s" % (str(e), id) tab.append(str(e)) 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) + 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"]) @@ -378,12 +400,14 @@ def set_provision_usn(samdb, low, high, id): delta = ldb.Message() delta.dn = ldb.Dn(samdb, "@PROVISION") - delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab, - ldb.FLAG_MOD_ADD, LAST_PROVISION_USN_ATTRIBUTE) + delta[LAST_PROVISION_USN_ATTRIBUTE] = \ + ldb.MessageElement(tab, + ldb.FLAG_MOD_ADD, + LAST_PROVISION_USN_ATTRIBUTE) samdb.add(delta) -def get_max_usn(samdb,basedn): +def get_max_usn(samdb, basedn): """ This function return the biggest USN present in the provision :param samdb: A LDB object pointing to the sam.ldb @@ -391,11 +415,11 @@ def get_max_usn(samdb,basedn): (ie. DC=foo, DC=bar) :return: The biggest USN in the provision""" - res = samdb.search(expression="objectClass=*",base=basedn, - scope=ldb.SCOPE_SUBTREE,attrs=["uSNChanged"], - controls=["search_options:1:2", - "server_sort:1:1:uSNChanged", - "paged_results:1:1"]) + res = samdb.search(expression="objectClass=*", base=basedn, + scope=ldb.SCOPE_SUBTREE, attrs=["uSNChanged"], + controls=["search_options:1:2", + "server_sort:1:1:uSNChanged", + "paged_results:1:1"]) return res[0]["uSNChanged"] @@ -408,9 +432,10 @@ def get_last_provision_usn(sam): """ 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): + base="@PROVISION", scope=ldb.SCOPE_BASE, + attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"]) + except ldb.LdbError as e1: + (ecode, emsg) = e1.args if ecode == ldb.ERR_NO_SUCH_OBJECT: return None raise @@ -463,7 +488,7 @@ class ProvisionResult(object): def report_logger(self, logger): """Report this provision result to a logger.""" logger.info( - "Once the above files are installed, your Samba4 server will " + "Once the above files are installed, your Samba AD server will " "be ready to use") if self.adminpass_generated: logger.info("Admin password: %s", self.adminpass) @@ -487,7 +512,7 @@ def check_install(lp, session_info, credentials): if lp.get("realm") == "": raise Exception("Realm empty") samdb = Ldb(lp.samdb_url(), session_info=session_info, - credentials=credentials, lp=lp) + credentials=credentials, lp=lp) if len(samdb.search("(cn=Administrator)")) != 1: raise ProvisioningError("No administrator account found") @@ -507,8 +532,12 @@ def findnss(nssfn, names): raise KeyError("Unable to find user/group in %r" % names) -findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2] -findnss_gid = lambda names: findnss(grp.getgrnam, names)[2] +def findnss_uid(names): + return findnss(pwd.getpwnam, names)[2] + + +def findnss_gid(names): + return findnss(grp.getgrnam, names)[2] def provision_paths_from_lp(lp, dnsdomain): @@ -519,6 +548,7 @@ def provision_paths_from_lp(lp, dnsdomain): """ paths = ProvisionPaths() paths.private_dir = lp.get("private dir") + paths.binddns_dir = lp.get("binddns dir") paths.state_dir = lp.get("state directory") # This is stored without path prefix for the "privateKeytab" attribute in @@ -531,15 +561,21 @@ def provision_paths_from_lp(lp, dnsdomain): 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") paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list") - paths.namedconf = os.path.join(paths.private_dir, "named.conf") - paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update") - paths.namedtxt = os.path.join(paths.private_dir, "named.txt") paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf") + paths.kdcconf = os.path.join(paths.private_dir, "kdc.conf") paths.winsdb = os.path.join(paths.private_dir, "wins.ldb") paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi") + paths.encrypted_secrets_key_path = os.path.join( + paths.private_dir, + "encrypted_secrets.key") + + paths.dns = os.path.join(paths.binddns_dir, "dns", dnsdomain + ".zone") + paths.namedconf = os.path.join(paths.binddns_dir, "named.conf") + paths.namedconf_update = os.path.join(paths.binddns_dir, "named.conf.update") + paths.namedtxt = os.path.join(paths.binddns_dir, "named.txt") + paths.hklm = "hklm.ldb" paths.hkcr = "hkcr.ldb" paths.hkcu = "hkcu.ldb" @@ -578,7 +614,9 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, if dnsdomain is None: dnsdomain = lp.get("realm") if dnsdomain is None or dnsdomain == "": - raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile) + raise ProvisioningError( + "guess_names: 'realm' not specified in supplied %s!" % + lp.configfile) dnsdomain = dnsdomain.lower() @@ -595,7 +633,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s. Please remove the smb.conf file and let provision generate it" % lp.configfile) if lp.get("realm").upper() != realm: - 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)) + 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(), lp.configfile, realm)) 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"), lp.configfile, serverrole)) @@ -629,8 +667,24 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, if domain == realm and not domain_names_forced: raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain)) + if serverrole != "active directory domain controller": + # + # This is the code path for a domain member + # where we provision the database as if we where + # on a domain controller, so we should not use + # the same dnsdomain as the domain controllers + # of our primary domain. + # + # This will be important if we start doing + # SID/name filtering and reject the local + # sid and names if they come from a domain + # controller. + # + realm = netbiosname + dnsdomain = netbiosname.lower() + if rootdn is None: - rootdn = domaindn + rootdn = domaindn if configdn is None: configdn = "CN=Configuration," + rootdn @@ -684,11 +738,11 @@ def make_smbconf(smbconf, hostname, domain, realm, targetdir, "workgroup": domain, "realm": realm, "server role": serverrole, - } + } if lp is None: lp = samba.param.LoadParm() - #Load non-existent file + # Load non-existent file if os.path.exists(smbconf): lp.load(smbconf) @@ -702,10 +756,12 @@ def make_smbconf(smbconf, hostname, domain, realm, targetdir, global_settings["lock dir"] = os.path.abspath(targetdir) global_settings["state directory"] = os.path.abspath(os.path.join(targetdir, "state")) global_settings["cache directory"] = os.path.abspath(os.path.join(targetdir, "cache")) + global_settings["binddns dir"] = os.path.abspath(os.path.join(targetdir, "bind-dns")) lp.set("lock dir", os.path.abspath(targetdir)) - lp.set("state directory", global_settings["state directory"]) + lp.set("state directory", global_settings["state directory"]) lp.set("cache directory", global_settings["cache directory"]) + lp.set("binddns dir", global_settings["binddns dir"]) if eadb: if use_ntvfs and not lp.get("posix:eadb"): @@ -725,18 +781,18 @@ def make_smbconf(smbconf, hostname, domain, realm, targetdir, if serverrole == "active directory domain controller": shares["sysvol"] = os.path.join(lp.get("state directory"), "sysvol") shares["netlogon"] = os.path.join(shares["sysvol"], realm.lower(), - "scripts") + "scripts") else: global_settings["passdb backend"] = "samba_dsdb" f = open(smbconf, 'w') try: f.write("[globals]\n") - for key, val in global_settings.iteritems(): + for key, val in global_settings.items(): f.write("\t%s = %s\n" % (key, val)) f.write("\n") - for name, path in shares.iteritems(): + for name, path in shares.items(): f.write("[%s]\n" % name) f.write("\tpath = %s\n" % path) f.write("\tread only = no\n") @@ -749,11 +805,7 @@ def make_smbconf(smbconf, hostname, domain, realm, targetdir, # and dump it without any values that are the default # 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') - try: - lp.dump(f, False) - finally: - f.close() + lp.dump(False, smbconf) def setup_name_mappings(idmap, sid, root_uid, nobody_uid, @@ -776,8 +828,9 @@ def setup_name_mappings(idmap, sid, root_uid, nobody_uid, def setup_samdb_partitions(samdb_path, logger, lp, session_info, - provision_backend, names, schema, serverrole, - erase=False): + provision_backend, names, serverrole, + erase=False, plaintext_secrets=False, + backend_store=None): """Setup the partitions for the SAM database. Alternatively, provision() may call this, and then populate the database. @@ -806,17 +859,36 @@ def setup_samdb_partitions(samdb_path, logger, lp, session_info, if provision_backend.type != "ldb": ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri + required_features = None + if not plaintext_secrets: + required_features = "requiredFeatures: encryptedSecrets" + + if backend_store is None: + backend_store = get_default_backend_store() + backend_store_line = "backendStore: %s" % backend_store + + if backend_store == "mdb": + if required_features is not None: + required_features += "\n" + else: + required_features = "" + required_features += "requiredFeatures: lmdbLevelOne" + + if required_features is None: + required_features = "# No required features" + samdb.transaction_start() try: logger.info("Setting up sam.ldb partitions and settings") setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), { - "LDAP_BACKEND_LINE": ldap_backend_line + "LDAP_BACKEND_LINE": ldap_backend_line, + "BACKEND_STORE": backend_store_line }) - setup_add_ldif(samdb, setup_path("provision_init.ldif"), { "BACKEND_TYPE": provision_backend.type, - "SERVER_ROLE": serverrole + "SERVER_ROLE": serverrole, + "REQUIRED_FEATURES": required_features }) logger.info("Setting up sam.ldb rootDSE") @@ -840,11 +912,11 @@ def secretsdb_self_join(secretsdb, domain, :param machinepass: Machine password """ attrs = ["whenChanged", - "secret", - "priorSecret", - "priorChanged", - "krb5Keytab", - "privateKeytab"] + "secret", + "priorSecret", + "priorChanged", + "krb5Keytab", + "privateKeytab"] if realm is not None: if dnsdomain is None: @@ -866,7 +938,7 @@ def secretsdb_self_join(secretsdb, domain, msg["msDS-KeyVersionNumber"] = [str(key_version_number)] msg["privateKeytab"] = ["secrets.keytab"] - msg["secret"] = [machinepass] + msg["secret"] = [machinepass.encode('utf-8')] msg["samAccountName"] = ["%s$" % netbiosname] msg["secureChannelType"] = [str(secure_channel_type)] if domainsid is not None: @@ -877,8 +949,8 @@ def secretsdb_self_join(secretsdb, domain, # 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)(!(distinguishedName=%s)))" % (domain, realm, str(domainsid), str(msg.dn))), - scope=ldb.SCOPE_ONELEVEL) + 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) @@ -887,7 +959,10 @@ def secretsdb_self_join(secretsdb, domain, if len(res) == 1: msg["priorSecret"] = [res[0]["secret"][0]] - msg["priorWhenChanged"] = [res[0]["whenChanged"][0]] + try: + msg["priorWhenChanged"] = [res[0]["whenChanged"][0]] + except KeyError: + pass try: msg["privateKeytab"] = [res[0]["privateKeytab"][0]] @@ -905,11 +980,11 @@ def secretsdb_self_join(secretsdb, domain, secretsdb.modify(msg) secretsdb.rename(res[0].dn, msg.dn) else: - spn = [ 'HOST/%s' % shortname ] + spn = ['HOST/%s' % shortname] if secure_channel_type == SEC_CHAN_BDC and dnsname is not None: # we are a domain controller then we add servicePrincipalName # entries for the keytab code to update. - spn.extend([ 'HOST/%s' % dnsname ]) + spn.extend(['HOST/%s' % dnsname]) msg["servicePrincipalName"] = spn secretsdb.add(msg) @@ -918,7 +993,7 @@ def secretsdb_self_join(secretsdb, domain, def setup_secretsdb(paths, session_info, backend_credentials, lp): """Setup the secrets database. - :note: This function does not handle exceptions and transaction on purpose, + :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. @@ -934,6 +1009,10 @@ def setup_secretsdb(paths, session_info, backend_credentials, lp): if os.path.exists(keytab_path): os.unlink(keytab_path) + bind_dns_keytab_path = os.path.join(paths.binddns_dir, paths.dns_keytab) + if os.path.exists(bind_dns_keytab_path): + os.unlink(bind_dns_keytab_path) + dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab) if os.path.exists(dns_keytab_path): os.unlink(dns_keytab_path) @@ -952,17 +1031,17 @@ def setup_secretsdb(paths, session_info, backend_credentials, lp): backend_credentials.authentication_requested()): if backend_credentials.get_bind_dn() is not None: setup_add_ldif(secrets_ldb, - setup_path("secrets_simple_ldap.ldif"), { - "LDAPMANAGERDN": backend_credentials.get_bind_dn(), - "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password()) - }) + setup_path("secrets_simple_ldap.ldif"), { + "LDAPMANAGERDN": backend_credentials.get_bind_dn(), + "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password()).decode('utf8') + }) else: setup_add_ldif(secrets_ldb, - setup_path("secrets_sasl_ldap.ldif"), { - "LDAPADMINUSER": backend_credentials.get_username(), - "LDAPADMINREALM": backend_credentials.get_realm(), - "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password()) - }) + setup_path("secrets_sasl_ldap.ldif"), { + "LDAPADMINUSER": backend_credentials.get_username(), + "LDAPADMINREALM": backend_credentials.get_realm(), + "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password()).decode('utf8') + }) except: secrets_ldb.transaction_cancel() raise @@ -985,6 +1064,31 @@ def setup_privileges(path, session_info, lp): privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif")) +def setup_encrypted_secrets_key(path): + """Setup the encrypted secrets key file. + + Any existing key file will be deleted and a new random key generated. + + :param path: Path to the secrets key file. + + """ + if os.path.exists(path): + os.unlink(path) + + flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL + mode = stat.S_IRUSR | stat.S_IWUSR + + umask_original = os.umask(0) + try: + fd = os.open(path, flags, mode) + finally: + os.umask(umask_original) + + with os.fdopen(fd, 'wb') as f: + key = samba.generate_random_bytes(16) + f.write(key) + + def setup_registry(path, session_info, lp): """Setup the registry. @@ -1026,20 +1130,20 @@ def setup_samdb_rootdse(samdb, names): 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, admin_session_info, names, fill, machinepass, - dns_backend, dnspass, domainsid, next_rid, invocationid, - policyguid, policyguid_dc, - domainControllerFunctionality, ntdsguid=None, dc_rid=None): + dns_backend, 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 + ntdsguid_line = "objectGUID: %s\n" % ntdsguid else: ntdsguid_line = "" @@ -1054,7 +1158,7 @@ def setup_self_join(samdb, admin_session_info, names, fill, machinepass, "INVOCATIONID": invocationid, "NETBIOSNAME": names.netbiosname, "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain), - "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')), + "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')).decode('utf8'), "DOMAINSID": str(domainsid), "DCRID": str(dc_rid), "SAMBA_VERSION_STRING": version, @@ -1081,7 +1185,7 @@ def setup_self_join(samdb, admin_session_info, names, fill, machinepass, "INVOCATIONID": invocationid, "NETBIOSNAME": names.netbiosname, "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain), - "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')), + "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')).decode('utf8'), "DOMAINSID": str(domainsid), "DCRID": str(dc_rid), "SAMBA_VERSION_STRING": version, @@ -1091,13 +1195,13 @@ def setup_self_join(samdb, admin_session_info, names, fill, machinepass, # 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, - }) + 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) @@ -1118,9 +1222,9 @@ def setup_self_join(samdb, admin_session_info, names, fill, machinepass, 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')), - "HOSTNAME" : names.hostname, - "DNSNAME" : '%s.%s' % ( + "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')).decode('utf8'), + "HOSTNAME": names.hostname, + "DNSNAME": '%s.%s' % ( names.netbiosname.lower(), names.dnsdomain.lower()) }) @@ -1141,7 +1245,7 @@ def getpolicypath(sysvolpath, dnsdomain, guid): def create_gpo_struct(policy_path): if not os.path.exists(policy_path): - os.makedirs(policy_path, 0775) + os.makedirs(policy_path, 0o775) f = open(os.path.join(policy_path, "GPT.INI"), 'w') try: f.write("[General]\r\nVersion=0") @@ -1149,10 +1253,10 @@ def create_gpo_struct(policy_path): f.close() p = os.path.join(policy_path, "MACHINE") if not os.path.exists(p): - os.makedirs(p, 0775) + os.makedirs(p, 0o775) p = os.path.join(policy_path, "USER") if not os.path.exists(p): - os.makedirs(p, 0775) + os.makedirs(p, 0o775) def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc): @@ -1163,15 +1267,16 @@ def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc): :param policyguid: GUID of the default domain policy :param policyguid_dc: GUID of the default domain controler policy """ - policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid) + policy_path = getpolicypath(sysvolpath, dnsdomain, policyguid) create_gpo_struct(policy_path) - policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc) + policy_path = getpolicypath(sysvolpath, dnsdomain, policyguid_dc) create_gpo_struct(policy_path) def setup_samdb(path, session_info, provision_backend, lp, names, - logger, fill, serverrole, schema, am_rodc=False): + logger, fill, serverrole, schema, am_rodc=False, + plaintext_secrets=False, backend_store=None): """Setup a complete SAM Database. :note: This will wipe the main SAM database file! @@ -1179,8 +1284,9 @@ def setup_samdb(path, session_info, provision_backend, lp, names, # 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) + provision_backend=provision_backend, session_info=session_info, + names=names, serverrole=serverrole, plaintext_secrets=plaintext_secrets, + backend_store=backend_store) # Load the database, but don's load the global schema and don't connect # quite yet @@ -1199,7 +1305,14 @@ def setup_samdb(path, session_info, provision_backend, lp, names, # And now we can connect to the DB - the schema won't be loaded from the # DB - samdb.connect(path) + try: + samdb.connect(path) + except ldb.LdbError as e2: + (num, string_error) = e2.args + if (num == ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS): + raise ProvisioningError("Permission denied connecting to %s, are you running as root?" % path) + else: + raise # But we have to give it one more kick to have it use the schema # during provision - it needs, now that it is connected, to write @@ -1209,10 +1322,11 @@ def setup_samdb(path, session_info, provision_backend, lp, names, return samdb -def fill_samdb(samdb, lp, names, logger, domainsid, domainguid, policyguid, - policyguid_dc, fill, adminpass, krbtgtpass, machinepass, dns_backend, - dnspass, invocationid, ntdsguid, serverrole, am_rodc=False, - dom_for_fun_level=None, schema=None, next_rid=None, dc_rid=None): +def fill_samdb(samdb, lp, names, logger, policyguid, + policyguid_dc, fill, adminpass, krbtgtpass, machinepass, dns_backend, + dnspass, invocationid, ntdsguid, serverrole, am_rodc=False, + dom_for_fun_level=None, schema=None, next_rid=None, dc_rid=None, + backend_store=None): if next_rid is None: next_rid = 1000 @@ -1231,7 +1345,7 @@ def fill_samdb(samdb, lp, names, logger, domainsid, domainguid, policyguid, domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2 if dom_for_fun_level is None: - dom_for_fun_level = DS_DOMAIN_FUNCTION_2003 + dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2 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_R2). This won't work!") @@ -1243,242 +1357,244 @@ def fill_samdb(samdb, lp, names, logger, domainsid, domainguid, policyguid, # before the provisioned tree exists and we connect samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn) - samdb.transaction_start() - try: - # 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)) - samdb.set_invocation_id(invocationid) - - logger.info("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 = "" + # 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. - descr = b64encode(get_domain_descriptor(domainsid)) - setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), { - "DOMAINDN": names.domaindn, - "DOMAINSID": str(domainsid), - "DESCRIPTOR": descr, - "DOMAINGUID": domainguid_line - }) + # 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) - setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), { - "DOMAINDN": names.domaindn, - "CREATTIME": str(samba.unix2nttime(int(time.time()))), - "NEXTRID": str(next_rid), - "DEFAULTSITE": names.sitename, - "CONFIGDN": names.configdn, - "POLICYGUID": policyguid, - "DOMAIN_FUNCTIONALITY": str(domainFunctionality), - "SAMBA_VERSION_STRING": version - }) + samdb.set_domain_sid(str(names.domainsid)) + samdb.set_invocation_id(invocationid) - # 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("Adding DomainDN: %s" % names.domaindn) - except: - samdb.transaction_cancel() - raise + # impersonate domain admin + admin_session_info = admin_session(lp, str(names.domainsid)) + samdb.set_session_info(admin_session_info) + if names.domainguid is not None: + domainguid_line = "objectGUID: %s\n-" % names.domainguid else: - samdb.transaction_commit() + domainguid_line = "" - samdb.transaction_start() - try: - samdb.invocation_id = invocationid - - # 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") - - partitions_descr = b64encode(get_config_partitions_descriptor(domainsid)) - sites_descr = b64encode(get_config_sites_descriptor(domainsid)) - ntdsquotas_descr = b64encode(get_config_ntds_quotas_descriptor(domainsid)) - protected1_descr = b64encode(get_config_delete_protected1_descriptor(domainsid)) - protected1wd_descr = b64encode(get_config_delete_protected1wd_descriptor(domainsid)) - protected2_descr = b64encode(get_config_delete_protected2_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), - "NTDSQUOTAS_DESCRIPTOR": ntdsquotas_descr, - "LOSTANDFOUND_DESCRIPTOR": protected1wd_descr, - "SERVICES_DESCRIPTOR": protected1_descr, - "PHYSICALLOCATIONS_DESCRIPTOR": protected1wd_descr, - "FORESTUPDATES_DESCRIPTOR": protected1wd_descr, - "EXTENDEDRIGHTS_DESCRIPTOR": protected2_descr, - "PARTITIONS_DESCRIPTOR": partitions_descr, - "SITES_DESCRIPTOR": sites_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) - - logger.info("Modifying display specifiers") - setup_modify_ldif(samdb, - setup_path("provision_configuration_modify.ldif"), { + descr = b64encode(get_domain_descriptor(names.domainsid)).decode('utf8') + setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), { + "DOMAINDN": names.domaindn, + "DOMAINSID": str(names.domainsid), + "DESCRIPTOR": descr, + "DOMAINGUID": domainguid_line + }) + + setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), { + "DOMAINDN": names.domaindn, + "CREATTIME": str(samba.unix2nttime(int(time.time()))), + "NEXTRID": str(next_rid), + "DEFAULTSITE": names.sitename, + "CONFIGDN": names.configdn, + "POLICYGUID": policyguid, + "DOMAIN_FUNCTIONALITY": str(domainFunctionality), + "SAMBA_VERSION_STRING": version, + "MIN_PWD_LENGTH": str(DEFAULT_MIN_PWD_LENGTH) + }) + + # 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(names.domainsid)).decode('utf8') + setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), { "CONFIGDN": names.configdn, - "DISPLAYSPECIFIERS_DESCRIPTOR": protected2_descr + "DESCRIPTOR": descr, }) - logger.info("Adding users container") - users_desc = b64encode(get_domain_users_descriptor(domainsid)) - setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), { + # The LDIF here was created when the Schema object was constructed + ignore_checks_oid = "local_oid:%s:0" % samba.dsdb.DSDB_CONTROL_SKIP_DUPLICATES_CHECK_OID + logger.info("Setting up sam.ldb schema") + samdb.add_ldif(schema.schema_dn_add, + controls=["relax:0", ignore_checks_oid]) + samdb.modify_ldif(schema.schema_dn_modify, + controls=[ignore_checks_oid]) + samdb.write_prefixes_from_schema() + samdb.add_ldif(schema.schema_data, controls=["relax:0", ignore_checks_oid]) + setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), + {"SCHEMADN": names.schemadn}, + controls=["relax:0", ignore_checks_oid]) + + # 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") + + samdb.invocation_id = invocationid + + # 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") + + partitions_descr = b64encode(get_config_partitions_descriptor(names.domainsid)).decode('utf8') + sites_descr = b64encode(get_config_sites_descriptor(names.domainsid)).decode('utf8') + ntdsquotas_descr = b64encode(get_config_ntds_quotas_descriptor(names.domainsid)).decode('utf8') + protected1_descr = b64encode(get_config_delete_protected1_descriptor(names.domainsid)).decode('utf8') + protected1wd_descr = b64encode(get_config_delete_protected1wd_descriptor(names.domainsid)).decode('utf8') + protected2_descr = b64encode(get_config_delete_protected2_descriptor(names.domainsid)).decode('utf8') + + if "2008" in schema.base_schema: + # exclude 2012-specific changes if we're using a 2008 schema + incl_2012 = "#" + else: + incl_2012 = "" + + 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, - "USERS_DESCRIPTOR": users_desc + "SERVERDN": names.serverdn, + "FOREST_FUNCTIONALITY": str(forestFunctionality), + "DOMAIN_FUNCTIONALITY": str(domainFunctionality), + "NTDSQUOTAS_DESCRIPTOR": ntdsquotas_descr, + "LOSTANDFOUND_DESCRIPTOR": protected1wd_descr, + "SERVICES_DESCRIPTOR": protected1_descr, + "PHYSICALLOCATIONS_DESCRIPTOR": protected1wd_descr, + "FORESTUPDATES_DESCRIPTOR": protected1wd_descr, + "EXTENDEDRIGHTS_DESCRIPTOR": protected2_descr, + "PARTITIONS_DESCRIPTOR": partitions_descr, + "SITES_DESCRIPTOR": sites_descr, }) - logger.info("Modifying users container") - setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), { - "DOMAINDN": names.domaindn}) - logger.info("Adding computers container") - computers_desc = b64encode(get_domain_computers_descriptor(domainsid)) - setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), { - "DOMAINDN": names.domaindn, - "COMPUTERS_DESCRIPTOR": computers_desc + + setup_add_ldif(samdb, setup_path("extended-rights.ldif"), { + "CONFIGDN": names.configdn, + "INC2012": incl_2012, }) - logger.info("Modifying computers container") + + 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("Modifying display specifiers and extended rights") setup_modify_ldif(samdb, - setup_path("provision_computers_modify.ldif"), { - "DOMAINDN": names.domaindn}) - logger.info("Setting up sam.ldb data") - infrastructure_desc = b64encode(get_domain_infrastructure_descriptor(domainsid)) - lostandfound_desc = b64encode(get_domain_delete_protected2_descriptor(domainsid)) - system_desc = b64encode(get_domain_delete_protected1_descriptor(domainsid)) - builtin_desc = b64encode(get_domain_builtin_descriptor(domainsid)) - controllers_desc = b64encode(get_domain_controllers_descriptor(domainsid)) - setup_add_ldif(samdb, setup_path("provision.ldif"), { - "CREATTIME": str(samba.unix2nttime(int(time.time()))), + setup_path("provision_configuration_modify.ldif"), { + "CONFIGDN": names.configdn, + "DISPLAYSPECIFIERS_DESCRIPTOR": protected2_descr + }) + + logger.info("Adding users container") + users_desc = b64encode(get_domain_users_descriptor(names.domainsid)).decode('utf8') + setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), { "DOMAINDN": names.domaindn, - "NETBIOSNAME": names.netbiosname, - "DEFAULTSITE": names.sitename, - "CONFIGDN": names.configdn, - "SERVERDN": names.serverdn, - "RIDAVAILABLESTART": str(next_rid + 600), - "POLICYGUID_DC": policyguid_dc, - "INFRASTRUCTURE_DESCRIPTOR": infrastructure_desc, - "LOSTANDFOUND_DESCRIPTOR": lostandfound_desc, - "SYSTEM_DESCRIPTOR": system_desc, - "BUILTIN_DESCRIPTOR": builtin_desc, - "DOMAIN_CONTROLLERS_DESCRIPTOR": controllers_desc, + "USERS_DESCRIPTOR": users_desc }) + logger.info("Modifying users container") + setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), { + "DOMAINDN": names.domaindn}) + logger.info("Adding computers container") + computers_desc = b64encode(get_domain_computers_descriptor(names.domainsid)).decode('utf8') + setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), { + "DOMAINDN": names.domaindn, + "COMPUTERS_DESCRIPTOR": computers_desc + }) + logger.info("Modifying computers container") + setup_modify_ldif(samdb, + setup_path("provision_computers_modify.ldif"), { + "DOMAINDN": names.domaindn}) + logger.info("Setting up sam.ldb data") + infrastructure_desc = b64encode(get_domain_infrastructure_descriptor(names.domainsid)).decode('utf8') + lostandfound_desc = b64encode(get_domain_delete_protected2_descriptor(names.domainsid)).decode('utf8') + system_desc = b64encode(get_domain_delete_protected1_descriptor(names.domainsid)).decode('utf8') + builtin_desc = b64encode(get_domain_builtin_descriptor(names.domainsid)).decode('utf8') + controllers_desc = b64encode(get_domain_controllers_descriptor(names.domainsid)).decode('utf8') + setup_add_ldif(samdb, setup_path("provision.ldif"), { + "CREATTIME": str(samba.unix2nttime(int(time.time()))), + "DOMAINDN": names.domaindn, + "NETBIOSNAME": names.netbiosname, + "DEFAULTSITE": names.sitename, + "CONFIGDN": names.configdn, + "SERVERDN": names.serverdn, + "RIDAVAILABLESTART": str(next_rid + 600), + "POLICYGUID_DC": policyguid_dc, + "INFRASTRUCTURE_DESCRIPTOR": infrastructure_desc, + "LOSTANDFOUND_DESCRIPTOR": lostandfound_desc, + "SYSTEM_DESCRIPTOR": system_desc, + "BUILTIN_DESCRIPTOR": builtin_desc, + "DOMAIN_CONTROLLERS_DESCRIPTOR": controllers_desc, + }) + + # 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: + managedservice_descr = b64encode(get_managed_service_accounts_descriptor(names.domainsid)).decode('utf8') + setup_modify_ldif(samdb, + setup_path("provision_configuration_references.ldif"), { + "CONFIGDN": names.configdn, + "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: - setup_modify_ldif(samdb, - setup_path("provision_configuration_references.ldif"), { - "CONFIGDN": names.configdn, - "SCHEMADN": names.schemadn}) - - logger.info("Setting up well known security principals") - protected1wd_descr = b64encode(get_config_delete_protected1wd_descriptor(domainsid)) - setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), { - "CONFIGDN": names.configdn, - "WELLKNOWNPRINCIPALS_DESCRIPTOR": protected1wd_descr, - }) + logger.info("Setting up well known security principals") + protected1wd_descr = b64encode(get_config_delete_protected1wd_descriptor(names.domainsid)).decode('utf8') + setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), { + "CONFIGDN": names.configdn, + "WELLKNOWNPRINCIPALS_DESCRIPTOR": protected1wd_descr, + }, controls=["relax:0", "provision:0"]) - if fill == FILL_FULL or fill == FILL_SUBDOMAIN: - setup_modify_ldif(samdb, - setup_path("provision_basedn_references.ldif"), - {"DOMAINDN": names.domaindn}) + if fill == FILL_FULL or fill == FILL_SUBDOMAIN: + setup_modify_ldif(samdb, + setup_path("provision_basedn_references.ldif"), { + "DOMAINDN": names.domaindn, + "MANAGEDSERVICE_DESCRIPTOR": managedservice_descr + }) - logger.info("Setting up sam.ldb users and groups") - setup_add_ldif(samdb, setup_path("provision_users.ldif"), { - "DOMAINDN": names.domaindn, - "DOMAINSID": str(domainsid), - "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')), - "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le')) - }) + logger.info("Setting up sam.ldb users and groups") + setup_add_ldif(samdb, setup_path("provision_users.ldif"), { + "DOMAINDN": names.domaindn, + "DOMAINSID": str(names.domainsid), + "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')).decode('utf8'), + "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le')).decode('utf8') + }, controls=["relax:0", "provision:0"]) + + logger.info("Setting up self join") + setup_self_join(samdb, admin_session_info, names=names, fill=fill, + invocationid=invocationid, + dns_backend=dns_backend, + dnspass=dnspass, + machinepass=machinepass, + domainsid=names.domainsid, + next_rid=next_rid, + dc_rid=dc_rid, + policyguid=policyguid, + policyguid_dc=policyguid_dc, + domainControllerFunctionality=domainControllerFunctionality, + ntdsguid=ntdsguid) + + ntds_dn = "CN=NTDS Settings,%s" % names.serverdn + names.ntdsguid = samdb.searchone(basedn=ntds_dn, + attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE).decode('utf8') + assert isinstance(names.ntdsguid, string_types) - logger.info("Setting up self join") - setup_self_join(samdb, admin_session_info, names=names, fill=fill, - invocationid=invocationid, - dns_backend=dns_backend, - dnspass=dnspass, - machinepass=machinepass, - domainsid=domainsid, - next_rid=next_rid, - dc_rid=dc_rid, - policyguid=policyguid, - policyguid_dc=policyguid_dc, - domainControllerFunctionality=domainControllerFunctionality, - ntdsguid=ntdsguid) - - ntds_dn = "CN=NTDS Settings,%s" % names.serverdn - names.ntdsguid = samdb.searchone(basedn=ntds_dn, - attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE) - assert isinstance(names.ntdsguid, str) - except: - samdb.transaction_cancel() - raise - else: - samdb.transaction_commit() - return samdb + return samdb -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)" -SYSVOL_SERVICE="sysvol" +SYSVOL_SERVICE = "sysvol" + def set_dir_acl(path, acl, lp, domsid, use_ntvfs, passdb, service=SYSVOL_SERVICE): setntacl(lp, path, acl, domsid, use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service) for root, dirs, files in os.walk(path, topdown=False): for name in files: setntacl(lp, os.path.join(root, name), acl, domsid, - use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service) + use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service) for name in dirs: setntacl(lp, os.path.join(root, name), acl, domsid, - use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service) + use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service) def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, passdb): @@ -1496,15 +1612,15 @@ def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, p # Set ACL for GPO root folder root_policy_path = os.path.join(sysvol, dnsdomain, "Policies") setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid), - use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=SYSVOL_SERVICE) + use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=SYSVOL_SERVICE) - res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn), - attrs=["cn", "nTSecurityDescriptor"], - expression="", scope=ldb.SCOPE_ONELEVEL) + res = samdb.search(base="CN=Policies,CN=System,%s" %(domaindn), + attrs=["cn", "nTSecurityDescriptor"], + expression="", scope=ldb.SCOPE_ONELEVEL) for policy in res: acl = ndr_unpack(security.descriptor, - str(policy["nTSecurityDescriptor"])).as_sddl() + policy["nTSecurityDescriptor"][0]).as_sddl() policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"])) set_dir_acl(policy_path, dsacl2fsacl(acl, domainsid), lp, str(domainsid), use_ntvfs, @@ -1512,7 +1628,7 @@ def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, p def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain, - domaindn, lp, use_ntvfs): + domaindn, lp, use_ntvfs): """Set the ACL for the sysvol share and the subfolders :param samdb: An LDB object on the SAM db @@ -1527,6 +1643,31 @@ def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain, s4_passdb = None if not use_ntvfs: + s3conf = s3param.get_context() + s3conf.load(lp.configfile) + + file = tempfile.NamedTemporaryFile(dir=os.path.abspath(sysvol)) + try: + try: + smbd.set_simple_acl(file.name, 0o755, gid) + except OSError: + if not smbd.have_posix_acls(): + # This clue is only strictly correct for RPM and + # Debian-like Linux systems, but hopefully other users + # will get enough clue from it. + raise ProvisioningError("Samba was compiled without the posix ACL support that s3fs requires. " + "Try installing libacl1-dev or libacl-devel, then re-run configure and make.") + + raise ProvisioningError("Your filesystem or build does not support posix ACLs, which s3fs requires. " + "Try the mounting the filesystem with the 'acl' option.") + try: + smbd.chown(file.name, uid, gid) + except OSError: + raise ProvisioningError("Unable to chown a file on your filesystem. " + "You may not be running provision as root.") + finally: + file.close() + # This will ensure that the smbd code we are running when setting ACLs # is initialised with the smb.conf s3conf = s3param.get_context() @@ -1550,7 +1691,6 @@ def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain, if domain_info["dns_domain"].upper() != dnsdomain.upper(): raise ProvisioningError('Realm as seen by pdb_samba_dsdb [%s] does not match Realm as seen by the provision script [%s]!' % (domain_info["dns_domain"].upper(), dnsdomain.upper())) - try: if use_ntvfs: os.chown(sysvol, -1, gid) @@ -1559,33 +1699,47 @@ def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain, else: canchown = True + # use admin sid dn as user dn, since admin should own most of the files, + # the operation will be much faster + userdn = ''.format(domainsid, security.DOMAIN_RID_ADMINISTRATOR) + + flags = (auth.AUTH_SESSION_INFO_DEFAULT_GROUPS | + auth.AUTH_SESSION_INFO_AUTHENTICATED | + auth.AUTH_SESSION_INFO_SIMPLE_PRIVILEGES) + + session_info = auth.user_session(samdb, lp_ctx=lp, dn=userdn, + session_info_flags=flags) + + def _setntacl(path): + """A helper to reuse args""" + return setntacl( + lp, path, SYSVOL_ACL, str(domainsid), + use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=s4_passdb, + service=SYSVOL_SERVICE, session_info=session_info) + # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level) - setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid), use_ntvfs=use_ntvfs, - skip_invalid_chown=True, passdb=s4_passdb, - service=SYSVOL_SERVICE) + _setntacl(sysvol) for root, dirs, files in os.walk(sysvol, topdown=False): for name in files: if use_ntvfs and canchown: os.chown(os.path.join(root, name), -1, gid) - setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid), - use_ntvfs=use_ntvfs, skip_invalid_chown=True, - passdb=s4_passdb, service=SYSVOL_SERVICE) + _setntacl(os.path.join(root, name)) for name in dirs: if use_ntvfs and canchown: os.chown(os.path.join(root, name), -1, gid) - setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid), - use_ntvfs=use_ntvfs, skip_invalid_chown=True, - passdb=s4_passdb, service=SYSVOL_SERVICE) + _setntacl(os.path.join(root, name)) # Set acls on Policy folder and policies folders set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, passdb=s4_passdb) + def acl_type(direct_db_access): if direct_db_access: return "DB" else: return "VFS" + def check_dir_acl(path, acl, lp, domainsid, direct_db_access): fsacl = getntacl(lp, path, direct_db_access=direct_db_access, service=SYSVOL_SERVICE) fsacl_sddl = fsacl.as_sddl(domainsid) @@ -1597,7 +1751,9 @@ def check_dir_acl(path, acl, lp, domainsid, direct_db_access): fsacl = getntacl(lp, os.path.join(root, name), direct_db_access=direct_db_access, service=SYSVOL_SERVICE) if fsacl is None: - raise ProvisioningError('%s ACL on GPO file %s %s not found!' % (acl_type(direct_db_access), os.path.join(root, name))) + raise ProvisioningError('%s ACL on GPO file %s not found!' % + (acl_type(direct_db_access), + os.path.join(root, name))) fsacl_sddl = fsacl.as_sddl(domainsid) if fsacl_sddl != acl: raise ProvisioningError('%s ACL on GPO file %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), os.path.join(root, name), fsacl_sddl, acl)) @@ -1606,14 +1762,16 @@ def check_dir_acl(path, acl, lp, domainsid, direct_db_access): fsacl = getntacl(lp, os.path.join(root, name), direct_db_access=direct_db_access, service=SYSVOL_SERVICE) if fsacl is None: - raise ProvisioningError('%s ACL on GPO directory %s %s not found!' % (acl_type(direct_db_access), os.path.join(root, name))) + raise ProvisioningError('%s ACL on GPO directory %s not found!' + % (acl_type(direct_db_access), + os.path.join(root, name))) fsacl_sddl = fsacl.as_sddl(domainsid) if fsacl_sddl != acl: raise ProvisioningError('%s ACL on GPO directory %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), os.path.join(root, name), fsacl_sddl, acl)) def check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, - direct_db_access): + direct_db_access): """Set ACL on the sysvol//Policies folder and the policy folders beneath. @@ -1634,20 +1792,20 @@ def check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, fsacl_sddl = fsacl.as_sddl(domainsid) if fsacl_sddl != POLICIES_ACL: raise ProvisioningError('%s ACL on policy root %s %s does not match expected value %s from provision' % (acl_type(direct_db_access), root_policy_path, fsacl_sddl, fsacl)) - res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn), - attrs=["cn", "nTSecurityDescriptor"], - expression="", scope=ldb.SCOPE_ONELEVEL) + res = samdb.search(base="CN=Policies,CN=System,%s" %(domaindn), + attrs=["cn", "nTSecurityDescriptor"], + expression="", scope=ldb.SCOPE_ONELEVEL) for policy in res: acl = ndr_unpack(security.descriptor, - str(policy["nTSecurityDescriptor"])).as_sddl() + policy["nTSecurityDescriptor"][0]).as_sddl() policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"])) check_dir_acl(policy_path, dsacl2fsacl(acl, domainsid), lp, domainsid, direct_db_access) def checksysvolacl(samdb, netlogon, sysvol, domainsid, dnsdomain, domaindn, - lp): + lp): """Set the ACL for the sysvol share and the subfolders :param samdb: An LDB object on the SAM db @@ -1692,7 +1850,7 @@ def checksysvolacl(samdb, netlogon, sysvol, domainsid, dnsdomain, domaindn, # Check acls on Policy folder and policies folders check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, - direct_db_access) + direct_db_access) def interface_ips_v4(lp): @@ -1716,7 +1874,7 @@ def interface_ips_v6(lp): def provision_fill(samdb, secrets_ldb, logger, names, paths, - domainsid, schema=None, + schema=None, targetdir=None, samdb_fill=FILL_FULL, hostip=None, hostip6=None, next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None, @@ -1724,7 +1882,8 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths, invocationid=None, machinepass=None, ntdsguid=None, dns_backend=None, dnspass=None, serverrole=None, dom_for_fun_level=None, - am_rodc=False, lp=None, use_ntvfs=False, skip_sysvolacl=False): + am_rodc=False, lp=None, use_ntvfs=False, + skip_sysvolacl=False, backend_store=None): # create/adapt the group policy GUIDs # Default GUID for default policy are described at # "How Core Group Policy Works" @@ -1740,39 +1899,50 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths, invocationid = str(uuid.uuid4()) if krbtgtpass is None: - krbtgtpass = samba.generate_random_password(128, 255) + krbtgtpass = samba.generate_random_machine_password(128, 255) if machinepass is None: - machinepass = samba.generate_random_password(128, 255) + machinepass = samba.generate_random_machine_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, - dns_backend=dns_backend, 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 == "active directory domain controller": + samdb.transaction_start() + try: + samdb = fill_samdb(samdb, lp, names, logger=logger, + schema=schema, + policyguid=policyguid, policyguid_dc=policyguid_dc, + fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass, + invocationid=invocationid, machinepass=machinepass, + dns_backend=dns_backend, 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, + backend_store=backend_store) # Set up group policies (domain policy and domain controller # policy) - create_default_gpo(paths.sysvol, names.dnsdomain, policyguid, - policyguid_dc) + if serverrole == "active directory domain controller": + create_default_gpo(paths.sysvol, names.dnsdomain, policyguid, + policyguid_dc) + except: + samdb.transaction_cancel() + raise + else: + samdb.transaction_commit() + + if serverrole == "active directory domain controller": + # Continue setting up sysvol for GPO. This appears to require being + # outside a transaction. if not skip_sysvolacl: setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.root_uid, - paths.root_gid, domainsid, names.dnsdomain, + paths.root_gid, names.domainsid, names.dnsdomain, names.domaindn, lp, use_ntvfs) else: logger.info("Setting acl on sysvol skipped") 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) + realm=names.realm, dnsdomain=names.dnsdomain, + netbiosname=names.netbiosname, domainsid=names.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 @@ -1782,24 +1952,26 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths, msg = ldb.Message(ldb.Dn(samdb, samdb.searchone("distinguishedName", expression="samAccountName=%s$" % names.netbiosname, - scope=ldb.SCOPE_SUBTREE))) + scope=ldb.SCOPE_SUBTREE).decode('utf8'))) msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement( elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE, name="msDS-SupportedEncryptionTypes") samdb.modify(msg) - except ldb.LdbError, (enum, estr): + except ldb.LdbError as e: + (enum, estr) = e.args 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, + setup_ad_dns(samdb, secrets_ldb, names, paths, lp, logger, hostip=hostip, hostip6=hostip6, dns_backend=dns_backend, dnspass=dnspass, os_level=dom_for_fun_level, - targetdir=targetdir, site=DEFAULTSITE) + targetdir=targetdir, fill_level=samdb_fill, + backend_store=backend_store) domainguid = samdb.searchone(basedn=samdb.get_default_basedn(), - attribute="objectGUID") - assert isinstance(domainguid, str) + attribute="objectGUID").decode('utf8') + assert isinstance(domainguid, string_types) lastProvisionUSNs = get_last_provision_usn(samdb) maxUSN = get_max_usn(samdb, str(names.rootdn)) @@ -1810,12 +1982,12 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths, logger.info("Setting up sam.ldb rootDSE marking as synchronized") setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"), - { 'NTDSGUID' : names.ntdsguid }) + {'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) + quiet=True) samdb.transaction_start() try: # a small number of GUIDs are missing because of ordering issues in the @@ -1831,6 +2003,9 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths, 'ipsecISAKMPReference', 'ipsecNegotiationPolicyReference', 'ipsecNFAReference']) + if chk.check_database(DN=names.schemadn, scope=ldb.SCOPE_SUBTREE, + attrs=['attributeId', 'governsId']) != 0: + raise ProvisioningError("Duplicate attributeId or governsId in schema. Must be fixed manually!!") except: samdb.transaction_cancel() raise @@ -1850,7 +2025,7 @@ _ROLES_MAP = { "member server": "member server", "standalone": "standalone server", "standalone server": "standalone server", - } +} def sanitize_server_role(role): @@ -1868,7 +2043,7 @@ def sanitize_server_role(role): def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain, - maxuid, maxgid): + maxuid, maxgid): """Create AD entries for the fake ypserver. This is needed for being able to manipulate posix attrs via ADUC. @@ -1877,10 +2052,10 @@ def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain, try: logger.info("Setting up fake yp server settings") setup_add_ldif(samdb, setup_path("ypServ30.ldif"), { - "DOMAINDN": domaindn, - "NETBIOSNAME": netbiosname, - "NISDOMAIN": nisdomain, - }) + "DOMAINDN": domaindn, + "NETBIOSNAME": netbiosname, + "NISDOMAIN": nisdomain, + }) except: samdb.transaction_cancel() raise @@ -1888,20 +2063,64 @@ def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain, samdb.transaction_commit() -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, dns_forwarder=None, dnspass=None, - invocationid=None, machinepass=None, ntdsguid=None, - root=None, nobody=None, users=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=False, - use_rfc2307=False, maxuid=None, maxgid=None, skip_sysvolacl=True, - ldap_backend_forced_uri=None, nosync=False, ldap_dryrun_mode=False, ldap_backend_extra_port=None): +def directory_create_or_exists(path, mode=0o755): + if not os.path.exists(path): + try: + os.mkdir(path, mode) + except OSError as e: + if e.errno in [errno.EEXIST]: + pass + else: + raise ProvisioningError("Failed to create directory %s: %s" % (path, e.strerror)) + + +def determine_host_ip(logger, lp, hostip=None): + if hostip is None: + logger.info("Looking up IPv4 addresses") + 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", + hostip) + if hostip == "127.0.0.1": + hostip = None + if hostip is None: + logger.warning("No IPv4 address will be assigned") + + return hostip + + +def determine_host_ip6(logger, lp, hostip6=None): + if hostip6 is None: + logger.info("Looking up IPv6 addresses") + hostips = interface_ips_v6(lp) + 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") + + return hostip6 + + +def provision(logger, session_info, 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, dns_forwarder=None, dnspass=None, + invocationid=None, machinepass=None, ntdsguid=None, + root=None, nobody=None, users=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=False, + use_rfc2307=False, maxuid=None, maxgid=None, skip_sysvolacl=True, + ldap_backend_forced_uri=None, nosync=False, ldap_dryrun_mode=False, + ldap_backend_extra_port=None, base_schema=None, + plaintext_secrets=False, backend_store=None): """Provision samba4 :note: caution, this wipes all existing data! @@ -1918,11 +2137,11 @@ def provision(logger, session_info, credentials, smbconf=None, if backend_type is None: backend_type = "ldb" + if backend_store is None: + backend_store = get_default_backend_store() if domainsid is None: domainsid = security.random_sid() - else: - domainsid = security.dom_sid(domainsid) root_uid = findnss_uid([root or "root"]) nobody_uid = findnss_uid([nobody or "nobody"]) @@ -1984,112 +2203,61 @@ def provision(logger, session_info, credentials, smbconf=None, lp = samba.param.LoadParm() lp.load(smbconf) names = guess_names(lp=lp, hostname=hostname, domain=domain, - dnsdomain=realm, serverrole=serverrole, domaindn=domaindn, - configdn=configdn, schemadn=schemadn, serverdn=serverdn, - sitename=sitename, rootdn=rootdn, domain_names_forced=(samdb_fill == FILL_DRS)) + dnsdomain=realm, serverrole=serverrole, domaindn=domaindn, + configdn=configdn, schemadn=schemadn, serverdn=serverdn, + sitename=sitename, rootdn=rootdn, domain_names_forced=(samdb_fill == FILL_DRS)) paths = provision_paths_from_lp(lp, names.dnsdomain) paths.bind_gid = bind_gid - paths.root_uid = root_uid; + paths.root_uid = root_uid paths.root_gid = root_gid - if hostip is None: - logger.info("Looking up IPv4 addresses") - 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", - 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) - 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") - + hostip = determine_host_ip(logger, lp, hostip) + hostip6 = determine_host_ip6(logger, lp, hostip6) names.hostip = hostip names.hostip6 = hostip6 + names.domainguid = domainguid + names.domainsid = domainsid + names.forestsid = domainsid if serverrole is None: serverrole = lp.get("server role") - 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")): - os.mkdir(os.path.join(paths.private_dir, "tls")) - if not os.path.exists(paths.state_dir): - os.mkdir(paths.state_dir) + directory_create_or_exists(paths.private_dir, 0o700) + directory_create_or_exists(paths.binddns_dir, 0o770) + directory_create_or_exists(os.path.join(paths.private_dir, "tls")) + directory_create_or_exists(paths.state_dir) + if not plaintext_secrets: + setup_encrypted_secrets_key(paths.encrypted_secrets_key_path) if paths.sysvol and not os.path.exists(paths.sysvol): - os.makedirs(paths.sysvol, 0775) - - if not use_ntvfs and serverrole == "active directory domain controller": - s3conf = s3param.get_context() - s3conf.load(lp.configfile) - - if paths.sysvol is None: - raise MissingShareError("sysvol", paths.smbconf) + os.makedirs(paths.sysvol, 0o775) - file = tempfile.NamedTemporaryFile(dir=os.path.abspath(paths.sysvol)) - try: - try: - smbd.set_simple_acl(file.name, 0755, root_gid) - except Exception: - if not smbd.have_posix_acls(): - # This clue is only strictly correct for RPM and - # Debian-like Linux systems, but hopefully other users - # will get enough clue from it. - raise ProvisioningError("Samba was compiled without the posix ACL support that s3fs requires. Try installing libacl1-dev or libacl-devel, then re-run configure and make.") - - raise ProvisioningError("Your filesystem or build does not support posix ACLs, which s3fs requires. Try the mounting the filesystem with the 'acl' option.") - try: - smbd.chown(file.name, root_uid, root_gid) - except Exception: - raise ProvisioningError("Unable to chown a file on your filesystem. You may not be running provision as root.") - finally: - file.close() - - ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="") + ldapi_url = "ldapi://%s" % urllib_quote(paths.s4_ldapi_path, safe="") schema = Schema(domainsid, invocationid=invocationid, - schemadn=names.schemadn) + schemadn=names.schemadn, base_schema=base_schema) if backend_type == "ldb": provision_backend = LDBBackend(backend_type, paths=paths, - 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, - lp=lp, credentials=credentials, - names=names, logger=logger, - ldap_backend_forced_uri=ldap_backend_forced_uri) + lp=lp, + names=names, logger=logger) elif backend_type == "fedora-ds": provision_backend = FDSBackend(backend_type, paths=paths, - lp=lp, credentials=credentials, - names=names, logger=logger, domainsid=domainsid, - schema=schema, hostname=hostname, ldapadminpass=ldapadminpass, - slapd_path=slapd_path, - root=root) + lp=lp, + names=names, logger=logger, domainsid=domainsid, + schema=schema, hostname=hostname, ldapadminpass=ldapadminpass, + slapd_path=slapd_path, + root=root) elif backend_type == "openldap": provision_backend = OpenLDAPBackend(backend_type, paths=paths, - lp=lp, credentials=credentials, - names=names, logger=logger, domainsid=domainsid, - schema=schema, hostname=hostname, ldapadminpass=ldapadminpass, - slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls, - ldap_backend_extra_port=ldap_backend_extra_port, - ldap_dryrun_mode=ldap_dryrun_mode, nosync=nosync, - ldap_backend_forced_uri=ldap_backend_forced_uri) + lp=lp, + names=names, logger=logger, domainsid=domainsid, + schema=schema, hostname=hostname, ldapadminpass=ldapadminpass, + slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls, + ldap_backend_extra_port=ldap_backend_extra_port, + ldap_dryrun_mode=ldap_dryrun_mode, nosync=nosync, + ldap_backend_forced_uri=ldap_backend_forced_uri) else: raise ValueError("Unknown LDAP backend type selected") @@ -2104,8 +2272,8 @@ def provision(logger, session_info, credentials, smbconf=None, logger.info("Setting up secrets.ldb") secrets_ldb = setup_secretsdb(paths, - session_info=session_info, - backend_credentials=provision_backend.secrets_credentials, lp=lp) + session_info=session_info, + backend_credentials=provision_backend.credentials, lp=lp) try: logger.info("Setting up the registry") @@ -2125,7 +2293,9 @@ def provision(logger, session_info, credentials, smbconf=None, samdb = setup_samdb(paths.samdb, session_info, provision_backend, lp, names, logger=logger, serverrole=serverrole, - schema=schema, fill=samdb_fill, am_rodc=am_rodc) + schema=schema, fill=samdb_fill, am_rodc=am_rodc, + plaintext_secrets=plaintext_secrets, + backend_store=backend_store) if serverrole == "active directory domain controller": if paths.netlogon is None: @@ -2135,34 +2305,44 @@ def provision(logger, session_info, credentials, smbconf=None, raise MissingShareError("sysvol", paths.smbconf) if not os.path.isdir(paths.netlogon): - os.makedirs(paths.netlogon, 0755) + os.makedirs(paths.netlogon, 0o755) if adminpass is None: adminpass = samba.generate_random_password(12, 32) adminpass_generated = True else: - adminpass = unicode(adminpass, 'utf-8') + if isinstance(adminpass, binary_type): + adminpass = adminpass.decode('utf-8') adminpass_generated = False 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, use_ntvfs=use_ntvfs, - skip_sysvolacl=skip_sysvolacl) + schema=schema, targetdir=targetdir, samdb_fill=samdb_fill, + hostip=hostip, hostip6=hostip6, + next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass, + krbtgtpass=krbtgtpass, + 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, use_ntvfs=use_ntvfs, + skip_sysvolacl=skip_sysvolacl, + backend_store=backend_store) + + if not is_heimdal_built(): + create_kdc_conf(paths.kdcconf, realm, domain, os.path.dirname(lp.get("log file"))) + logger.info("The Kerberos KDC configuration for Samba AD is " + "located at %s", paths.kdcconf) 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 " + logger.info("A Kerberos configuration suitable for Samba AD has been " "generated at %s", paths.krb5conf) + logger.info("Merge the contents of this file with your system " + "krb5.conf or replace it with this one. Do not create a " + "symlink!") if serverrole == "active directory domain controller": create_dns_update_list(lp, logger, paths) @@ -2177,16 +2357,42 @@ def provision(logger, session_info, credentials, smbconf=None, # Now commit the secrets.ldb to disk secrets_ldb.transaction_commit() - # the commit creates the dns.keytab, now chown it - dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab) - if os.path.isfile(dns_keytab_path) and paths.bind_gid is not None: + # the commit creates the dns.keytab in the private directory + private_dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab) + bind_dns_keytab_path = os.path.join(paths.binddns_dir, paths.dns_keytab) + + if os.path.isfile(private_dns_keytab_path): + if os.path.isfile(bind_dns_keytab_path): + try: + os.unlink(bind_dns_keytab_path) + except OSError as e: + logger.error("Failed to remove %s: %s" % + (bind_dns_keytab_path, e.strerror)) + + # link the dns.keytab to the bind-dns directory try: - os.chmod(dns_keytab_path, 0640) - os.chown(dns_keytab_path, -1, paths.bind_gid) - except OSError: - if not os.environ.has_key('SAMBA_SELFTEST'): - logger.info("Failed to chown %s to bind gid %u", - dns_keytab_path, paths.bind_gid) + os.link(private_dns_keytab_path, bind_dns_keytab_path) + except OSError as e: + logger.error("Failed to create link %s -> %s: %s" % + (private_dns_keytab_path, bind_dns_keytab_path, e.strerror)) + + # chown the dns.keytab in the bind-dns directory + if paths.bind_gid is not None: + try: + os.chmod(paths.binddns_dir, 0o770) + os.chown(paths.binddns_dir, -1, paths.bind_gid) + except OSError: + if 'SAMBA_SELFTEST' not in os.environ: + logger.info("Failed to chown %s to bind gid %u", + paths.binddns_dir, paths.bind_gid) + + try: + os.chmod(bind_dns_keytab_path, 0o640) + os.chown(bind_dns_keytab_path, -1, paths.bind_gid) + except OSError: + if 'SAMBA_SELFTEST' not in os.environ: + logger.info("Failed to chown %s to bind gid %u", + bind_dns_keytab_path, paths.bind_gid) result = ProvisionResult() result.server_role = serverrole @@ -2209,33 +2415,33 @@ def provision(logger, session_info, credentials, smbconf=None, if use_rfc2307: provision_fake_ypserver(logger=logger, samdb=samdb, - domaindn=names.domaindn, netbiosname=names.netbiosname, - nisdomain=names.domain.lower(), maxuid=maxuid, maxgid=maxgid) + domaindn=names.domaindn, netbiosname=names.netbiosname, + nisdomain=names.domain.lower(), maxuid=maxuid, maxgid=maxgid) return result 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, - dns_backend=None, root=None, nobody=None, users=None, - backup=None, serverrole=None, ldap_backend=None, - ldap_backend_type=None, sitename=None, debuglevel=1, use_ntvfs=False): + 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, + dns_backend=None, root=None, nobody=None, users=None, + backup=None, serverrole=None, ldap_backend=None, + ldap_backend_type=None, sitename=None, debuglevel=1, use_ntvfs=False): logger = logging.getLogger("provision") samba.set_debug_level(debuglevel) - 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=None, domainsid=domainsid, - machinepass=machinepass, - serverrole="active directory domain controller", - sitename=sitename, dns_backend=dns_backend, dnspass=dnspass, - use_ntvfs=use_ntvfs) + res = provision(logger, system_session(), + 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=None, domainsid=domainsid, + machinepass=machinepass, + serverrole="active directory domain controller", + sitename=sitename, dns_backend=dns_backend, dnspass=dnspass, + use_ntvfs=use_ntvfs) res.lp.set("debuglevel", str(debuglevel)) return res @@ -2252,7 +2458,7 @@ def create_krb5_conf(path, dnsdomain, hostname, realm): "DNSDOMAIN": dnsdomain, "HOSTNAME": hostname, "REALM": realm, - }) + }) class ProvisioningError(Exception):