-
# Unix SMB/CIFS implementation.
# backend code for provisioning a Samba4 server
import socket
import urllib
import string
+import tempfile
import ldb
from samba.auth import system_session, admin_session
import samba
+from samba.samba3 import smbd, passdb
+from samba.samba3 import param as s3param
from samba.dsdb import DS_DOMAIN_FUNCTION_2000
from samba import (
Ldb,
)
from samba.idmap import IDmapDB
from samba.ms_display_specifiers import read_ms_ldif
-from samba.ntacls import setntacl, dsacl2fsacl
+from samba.ntacls import setntacl, getntacl, dsacl2fsacl
from samba.ndr import ndr_pack, ndr_unpack
from samba.provision.backend import (
ExistingBackend,
OpenLDAPBackend,
)
from samba.provision.descriptor import (
+ get_empty_descriptor,
get_config_descriptor,
- get_domain_descriptor
+ get_config_partitions_descriptor,
+ get_config_sites_descriptor,
+ get_config_delete_protected1_descriptor,
+ get_config_delete_protected1wd_descriptor,
+ get_config_delete_protected2_descriptor,
+ get_domain_descriptor,
+ get_domain_infrastructure_descriptor,
+ get_domain_builtin_descriptor,
+ get_domain_computers_descriptor,
+ get_domain_users_descriptor,
+ get_domain_controllers_descriptor,
+ get_domain_delete_protected1_descriptor,
+ get_domain_delete_protected2_descriptor,
+ get_dns_partition_descriptor,
)
from samba.provision.common import (
setup_path,
self.dns = None
self.winsdb = None
self.private_dir = None
- self.phpldapadminconfig = None
+ self.state_dir = None
class ProvisionNames(object):
def __init__(self):
+ self.ncs = None
self.rootdn = None
self.domaindn = None
self.configdn = None
self.schemadn = None
+ self.dnsforestdn = None
+ self.dnsdomaindn = None
self.ldapmanagerdn = None
self.dnsdomain = None
self.realm = None
self.sitename = None
self.smbconf = None
-def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
+
+def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf,
+ lp):
"""Get key provision parameters (realm, domain, ...) from a given provision
:param samdb: An LDB object connected to the sam.ldb file
current = samdb.search(expression="(objectClass=*)",
base="", scope=ldb.SCOPE_BASE,
attrs=["defaultNamingContext", "schemaNamingContext",
- "configurationNamingContext","rootDomainNamingContext"])
+ "configurationNamingContext","rootDomainNamingContext",
+ "namingContexts"])
names.configdn = current[0]["configurationNamingContext"]
configdn = str(names.configdn)
names.domaindn=current[0]["defaultNamingContext"]
names.rootdn=current[0]["rootDomainNamingContext"]
+ names.ncs=current[0]["namingContexts"]
+ names.dnsforestdn = None
+ names.dnsdomaindn = None
+
+ for i in range(0, len(names.ncs)):
+ nc = names.ncs[i]
+
+ dnsforestdn = "DC=ForestDnsZones,%s" % (str(names.rootdn))
+ if nc == dnsforestdn:
+ names.dnsforestdn = dnsforestdn
+ continue
+
+ dnsdomaindn = "DC=DomainDnsZones,%s" % (str(names.domaindn))
+ if nc == dnsdomaindn:
+ names.dnsdomaindn = dnsdomaindn
+ continue
+
# default site name
res3 = samdb.search(expression="(objectClass=site)",
base="CN=Sites," + configdn, scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
base="OU=Domain Controllers,%s" % basedn,
scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"])
- names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
+ names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain, "")
server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
attrs=[], base=configdn)
# invocation id/objectguid
res5 = samdb.search(expression="(objectClass=*)",
- base="CN=NTDS Settings,%s" % str(names.serverdn), scope=ldb.SCOPE_BASE,
+ 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]))
res8 = samdb.search(expression="(displayName=Default Domain Controllers"
" Policy)",
base="CN=Policies,CN=System," + basedn,
- scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
+ scope=ldb.SCOPE_ONELEVEL,
+ attrs=["cn","displayName"])
if len(res8) == 1:
names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
else:
names.policyid_dc = None
- res9 = idmapdb.search(expression="(cn=%s)" %
- (security.SID_BUILTIN_ADMINISTRATORS),
- attrs=["xidNumber"])
- if len(res9) == 1:
- names.wheel_gid = res9[0]["xidNumber"]
+
+ res9 = idmapdb.search(expression="(cn=%s-%s)" %
+ (str(names.domainsid), security.DOMAIN_RID_ADMINISTRATOR),
+ 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]
else:
- raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
+ names.root_gid = pwd.getpwuid(int(res9[0]["xidNumber"][0])).pw_gid
return names
"""Get USNs ranges modified by a provision or an upgradeprovision
:param sam: An LDB object pointing to the sam.ldb
- :return: a dictionnary which keys are invocation id and values are an array
+ :return: a dictionary which keys are invocation id and values are an array
of integer representing the different ranges
"""
try:
entry = sam.search(expression="%s=*" % LAST_PROVISION_USN_ATTRIBUTE,
- base="@PROVISION", scope=ldb.SCOPE_BASE,
- attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
+ base="@PROVISION", scope=ldb.SCOPE_BASE,
+ attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
except ldb.LdbError, (ecode, emsg):
if ecode == ldb.ERR_NO_SUCH_OBJECT:
return None
raise
- if len(entry):
+ if len(entry) > 0:
myids = []
range = {}
p = re.compile(r'-')
if (len(myids) > 0 and id not in myids):
continue
tab2 = p.split(tab1[0])
- if range.get(id) == None:
+ if range.get(id) is None:
range[id] = []
range[id].append(tab2[0])
range[id].append(tab2[1])
logger.info("DNS Domain: %s", self.names.dnsdomain)
logger.info("DOMAIN SID: %s", self.domainsid)
- if self.paths.phpldapadminconfig is not None:
- logger.info(
- "A phpLDAPadmin configuration file suitable for administering "
- "the Samba 4 LDAP server has been created in %s.",
- self.paths.phpldapadminconfig)
-
if self.backend_result:
self.backend_result.report_logger(logger)
"""
paths = ProvisionPaths()
paths.private_dir = lp.get("private dir")
+ paths.state_dir = lp.get("state directory")
# This is stored without path prefix for the "privateKeytab" attribute in
# "secrets_dns.ldif".
paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
- paths.phpldapadminconfig = os.path.join(paths.private_dir,
- "phpldapadmin-config.php")
paths.hklm = "hklm.ldb"
paths.hkcr = "hkcr.ldb"
paths.hkcu = "hkcu.ldb"
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))
- if serverrole == "domain controller":
+ if serverrole == "active directory domain controller":
if domain is None:
# This will, for better or worse, default to 'WORKGROUP'
domain = lp.get("workgroup")
def make_smbconf(smbconf, hostname, domain, realm, targetdir,
- serverrole=None, sid_generator=None, eadb=False, lp=None,
+ serverrole=None, eadb=False, use_ntvfs=False, lp=None,
global_param=None):
"""Create a new smb.conf file based on a couple of basic settings.
"""
netbiosname = determine_netbios_name(hostname)
if serverrole is None:
- serverrole = "standalone"
-
- if sid_generator is None:
- sid_generator = "internal"
+ serverrole = "standalone server"
assert domain is not None
domain = domain.upper()
realm = realm.upper()
global_settings = {
- "passdb backend": "samba4",
"netbios name": netbiosname,
"workgroup": domain,
"realm": realm,
if lp is None:
lp = samba.param.LoadParm()
- #Load non-existant file
+ #Load non-existent file
if os.path.exists(smbconf):
lp.load(smbconf)
- if eadb and not lp.get("posix:eadb"):
- if targetdir is not None:
- privdir = os.path.join(targetdir, "private")
- else:
- privdir = lp.get("private dir")
- lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb")))
if global_param is not None:
for ent in global_param:
if targetdir is not None:
global_settings["private dir"] = os.path.abspath(os.path.join(targetdir, "private"))
global_settings["lock dir"] = os.path.abspath(targetdir)
- global_settings["state directory"] = os.path.abspath(targetdir)
- global_settings["cache directory"] = os.path.abspath(targetdir)
+ global_settings["state directory"] = os.path.abspath(os.path.join(targetdir, "state"))
+ global_settings["cache directory"] = os.path.abspath(os.path.join(targetdir, "cache"))
lp.set("lock dir", os.path.abspath(targetdir))
- lp.set("state directory", os.path.abspath(targetdir))
- lp.set("cache directory", os.path.abspath(targetdir))
+ lp.set("state directory", global_settings["state directory"])
+ lp.set("cache directory", global_settings["cache directory"])
+
+ if eadb:
+ if use_ntvfs and not lp.get("posix:eadb"):
+ if targetdir is not None:
+ privdir = os.path.join(targetdir, "private")
+ else:
+ privdir = lp.get("private dir")
+ lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb")))
+ elif not use_ntvfs and not lp.get("xattr_tdb:file"):
+ if targetdir is not None:
+ statedir = os.path.join(targetdir, "state")
+ else:
+ statedir = lp.get("state directory")
+ lp.set("xattr_tdb:file", os.path.abspath(os.path.join(statedir, "xattr.tdb")))
shares = {}
- if serverrole == "domain controller":
+ 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")
+ else:
+ global_settings["passdb backend"] = "samba_dsdb"
f = open(smbconf, 'w')
try:
def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
- users_gid, wheel_gid):
+ users_gid, root_gid):
"""setup reasonable name mappings for sam names to unix names.
:param samdb: SamDB object.
:param root_uid: uid of the UNIX root user.
: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.
+ :param root_gid: gid of the UNIX root group.
"""
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_self_join(samdb, admin_session_info, names, fill, machinepass,
- dnspass, domainsid, next_rid, invocationid, policyguid, policyguid_dc,
+ 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)
samdb.set_session_info(admin_session_info)
- # This is Samba4 specific and should be replaced by the correct
- # DNS AD-style setup
- setup_add_ldif(samdb, setup_path("provision_dns_add_samba.ldif"), {
+ if dns_backend != "SAMBA_INTERNAL":
+ # This is Samba4 specific and should be replaced by the correct
+ # DNS AD-style setup
+ 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')),
logger.info("Pre-loading the Samba 4 and AD schema")
# Load the schema from the one we computed earlier
- samdb.set_schema(schema)
+ samdb.set_schema(schema, write_indices_and_attributes=False)
# Set the NTDS settings DN manually - in order to have it already around
# before the provisioned tree exists and we connect
# DB
samdb.connect(path)
+ # 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
+ # the schema @ATTRIBUTES and @INDEXLIST records to the database.
+ samdb.set_schema(schema, write_indices_and_attributes=True)
+
return samdb
-def fill_samdb(samdb, lp, names,
- logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
- adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
- serverrole, am_rodc=False, dom_for_fun_level=None, schema=None,
- next_rid=None, dc_rid=None):
+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):
if next_rid is None:
next_rid = 1000
# 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))
setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
"CONFIGDN": names.configdn,
"NETBIOSNAME": names.netbiosname,
"SERVERDN": names.serverdn,
"FOREST_FUNCTIONALITY": str(forestFunctionality),
"DOMAIN_FUNCTIONALITY": str(domainFunctionality),
+ "PARTITIONS_DESCRIPTOR": partitions_descr,
+ "SITES_DESCRIPTOR": sites_descr,
})
logger.info("Setting up display specifiers")
samdb.add_ldif(display_specifiers_ldif)
logger.info("Adding users container")
+ users_desc = b64encode(get_domain_users_descriptor(domainsid))
setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
- "DOMAINDN": names.domaindn})
+ "DOMAINDN": names.domaindn,
+ "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(domainsid))
setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
- "DOMAINDN": names.domaindn})
+ "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(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()))),
"DOMAINDN": names.domaindn,
"CONFIGDN": names.configdn,
"SERVERDN": names.serverdn,
"RIDAVAILABLESTART": str(next_rid + 600),
- "POLICYGUID_DC": policyguid_dc
+ "POLICYGUID_DC": policyguid_dc,
+ "INFRASTRUCTURE_DESCRIPTOR": infrastructure_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
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,
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"
-
-def set_dir_acl(path, acl, lp, domsid):
- setntacl(lp, path, acl, domsid)
+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)
+ setntacl(lp, os.path.join(root, name), acl, domsid,
+ 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)
+ setntacl(lp, os.path.join(root, name), acl, domsid,
+ use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service)
-def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
+def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, passdb):
"""Set ACL on the sysvol/<dnsname>/Policies folder and the policy
folders beneath.
# Set ACL for GPO root folder
root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
- setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid))
+ setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid),
+ 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"],
acl = ndr_unpack(security.descriptor,
str(policy["nTSecurityDescriptor"])).as_sddl()
policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
- set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
- str(domainsid))
+ set_dir_acl(policy_path, dsacl2fsacl(acl, domainsid), lp,
+ str(domainsid), use_ntvfs,
+ passdb=passdb)
-def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
- lp):
+def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain,
+ domaindn, lp, use_ntvfs):
"""Set the ACL for the sysvol share and the subfolders
:param samdb: An LDB object on the SAM db
:param netlogon: Physical path for the netlogon folder
:param sysvol: Physical path for the sysvol folder
+ :param uid: The UID of the "Administrator" user
:param gid: The GID of the "Domain adminstrators" group
:param domainsid: The SID of the domain
:param dnsdomain: The DNS name of the domain
:param domaindn: The DN of the domain (ie. DC=...)
"""
+ s4_passdb = None
+
+ if not use_ntvfs:
+ # This will ensure that the smbd code we are running when setting ACLs
+ # is initialised with the smb.conf
+ s3conf = s3param.get_context()
+ s3conf.load(lp.configfile)
+ # ensure we are using the right samba_dsdb passdb backend, no matter what
+ s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
+ passdb.reload_static_pdb()
+
+ # ensure that we init the samba_dsdb backend, so the domain sid is
+ # marked in secrets.tdb
+ s4_passdb = passdb.PDB(s3conf.get("passdb backend"))
+
+ # now ensure everything matches correctly, to avoid wierd issues
+ if passdb.get_global_sam_sid() != domainsid:
+ raise ProvisioningError('SID as seen by smbd [%s] does not match SID as seen by the provision script [%s]!' % (passdb.get_global_sam_sid(), domainsid))
+
+ domain_info = s4_passdb.domain_info()
+ if domain_info["dom_sid"] != domainsid:
+ raise ProvisioningError('SID as seen by pdb_samba_dsdb [%s] does not match SID as seen by the provision script [%s]!' % (domain_info["dom_sid"], domainsid))
+
+ 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:
- os.chown(sysvol, -1, gid)
+ if use_ntvfs:
+ os.chown(sysvol, -1, gid)
except OSError:
canchown = False
else:
canchown = True
# Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
- setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid))
+ setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid), use_ntvfs=use_ntvfs,
+ skip_invalid_chown=True, passdb=s4_passdb,
+ service=SYSVOL_SERVICE)
for root, dirs, files in os.walk(sysvol, topdown=False):
for name in files:
- if canchown:
+ 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))
+ 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)
for name in dirs:
- if canchown:
+ 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))
+ 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)
# Set acls on Policy folder and policies folders
- set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
+ 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)
+ 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), path, fsacl_sddl, acl))
+
+ for root, dirs, files in os.walk(path, topdown=False):
+ for name in files:
+ 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)))
+ 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))
+
+ for name in dirs:
+ 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)))
+ 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):
+ """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
+ folders beneath.
+
+ :param sysvol: Physical path for the sysvol folder
+ :param dnsdomain: The DNS name of the domain
+ :param domainsid: The SID of the domain
+ :param domaindn: The DN of the domain (ie. DC=...)
+ :param samdb: An LDB object on the SAM db
+ :param lp: an LP object
+ """
+
+ # Set ACL for GPO root folder
+ root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
+ fsacl = getntacl(lp, root_policy_path,
+ direct_db_access=direct_db_access, service=SYSVOL_SERVICE)
+ if fsacl is None:
+ raise ProvisioningError('DB ACL on policy root %s %s not found!' % (acl_type(direct_db_access), root_policy_path))
+ 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)
+
+ for policy in res:
+ acl = ndr_unpack(security.descriptor,
+ str(policy["nTSecurityDescriptor"])).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):
+ """Set the ACL for the sysvol share and the subfolders
+
+ :param samdb: An LDB object on the SAM db
+ :param netlogon: Physical path for the netlogon folder
+ :param sysvol: Physical path for the sysvol folder
+ :param uid: The UID of the "Administrator" user
+ :param gid: The GID of the "Domain adminstrators" group
+ :param domainsid: The SID of the domain
+ :param dnsdomain: The DNS name of the domain
+ :param domaindn: The DN of the domain (ie. DC=...)
+ """
+
+ # This will ensure that the smbd code we are running when setting ACLs is initialised with the smb.conf
+ s3conf = s3param.get_context()
+ s3conf.load(lp.configfile)
+ # ensure we are using the right samba_dsdb passdb backend, no matter what
+ s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
+ # ensure that we init the samba_dsdb backend, so the domain sid is marked in secrets.tdb
+ s4_passdb = passdb.PDB(s3conf.get("passdb backend"))
+
+ # now ensure everything matches correctly, to avoid wierd issues
+ if passdb.get_global_sam_sid() != domainsid:
+ raise ProvisioningError('SID as seen by smbd [%s] does not match SID as seen by the provision script [%s]!' % (passdb.get_global_sam_sid(), domainsid))
+
+ domain_info = s4_passdb.domain_info()
+ if domain_info["dom_sid"] != domainsid:
+ raise ProvisioningError('SID as seen by pdb_samba_dsdb [%s] does not match SID as seen by the provision script [%s]!' % (domain_info["dom_sid"], domainsid))
+
+ 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()))
+
+ # Ensure we can read this directly, and via the smbd VFS
+ for direct_db_access in [True, False]:
+ # Check the SYSVOL_ACL on the sysvol folder and subfolder (first level)
+ for dir_path in [os.path.join(sysvol, dnsdomain), netlogon]:
+ fsacl = getntacl(lp, dir_path, direct_db_access=direct_db_access, service=SYSVOL_SERVICE)
+ if fsacl is None:
+ raise ProvisioningError('%s ACL on sysvol directory %s not found!' % (acl_type(direct_db_access), dir_path))
+ fsacl_sddl = fsacl.as_sddl(domainsid)
+ if fsacl_sddl != SYSVOL_ACL:
+ raise ProvisioningError('%s ACL on sysvol directory %s %s does not match expected value %s from provision' % (acl_type(direct_db_access), dir_path, fsacl_sddl, SYSVOL_ACL))
+
+ # Check acls on Policy folder and policies folders
+ check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp,
+ direct_db_access)
def interface_ips_v4(lp):
- '''return only IPv4 IPs'''
+ """return only IPv4 IPs"""
ips = samba.interface_ips(lp, False)
ret = []
for i in ips:
ret.append(i)
return ret
+
def interface_ips_v6(lp, linklocal=False):
- '''return only IPv6 IPs'''
+ """return only IPv6 IPs"""
ips = samba.interface_ips(lp, False)
ret = []
for i in ips:
invocationid=None, machinepass=None, ntdsguid=None,
dns_backend=None, dnspass=None,
serverrole=None, dom_for_fun_level=None,
- am_rodc=False, lp=None):
+ am_rodc=False, lp=None, use_ntvfs=False, skip_sysvolacl=False):
# create/adapt the group policy GUIDs
# Default GUID for default policy are described at
# "How Core Group Policy Works"
dnspass = samba.generate_random_password(128, 255)
samdb = fill_samdb(samdb, lp, names, logger=logger,
- domainsid=domainsid, schema=schema, domainguid=domainguid,
- policyguid=policyguid, policyguid_dc=policyguid_dc,
- fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
- invocationid=invocationid, machinepass=machinepass,
- dnspass=dnspass, ntdsguid=ntdsguid, serverrole=serverrole,
- dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
- next_rid=next_rid, dc_rid=dc_rid)
-
- if serverrole == "domain controller":
+ 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":
+
# Set up group policies (domain policy and domain controller
# policy)
create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
policyguid_dc)
- setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.wheel_gid,
- domainsid, names.dnsdomain, names.domaindn, lp)
+ if not skip_sysvolacl:
+ setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.root_uid,
+ paths.root_gid, 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=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
# provision code
for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
- scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory'])
+ scope=ldb.SCOPE_BASE,
+ attrs=['defaultObjectCategory'])
chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
scope=ldb.SCOPE_ONELEVEL,
attrs=['ipsecOwnersReference',
_ROLES_MAP = {
- "ROLE_STANDALONE": "standalone",
+ "ROLE_STANDALONE": "standalone server",
"ROLE_DOMAIN_MEMBER": "member server",
- "ROLE_DOMAIN_BDC": "domain controller",
- "ROLE_DOMAIN_PDC": "domain controller",
- "dc": "domain controller",
+ "ROLE_DOMAIN_BDC": "active directory domain controller",
+ "ROLE_DOMAIN_PDC": "active directory domain controller",
+ "dc": "active directory domain controller",
"member": "member server",
- "domain controller": "domain controller",
+ "domain controller": "active directory domain controller",
+ "active directory domain controller": "active directory domain controller",
"member server": "member server",
- "standalone": "standalone",
+ "standalone": "standalone server",
+ "standalone server": "standalone server",
}
:param role: Server role
:raise ValueError: If the role can not be interpreted
:return: Sanitized server role (one of "member server",
- "domain controller", "standalone")
+ "active directory domain controller", "standalone server")
"""
try:
- return _ROLES_MAP[role]
+ return _ROLES_MAP[role]
except KeyError:
raise ValueError(role)
+def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain,
+ maxuid, maxgid):
+ """Create AD entries for the fake ypserver.
+
+ This is needed for being able to manipulate posix attrs via ADUC.
+ """
+ samdb.transaction_start()
+ try:
+ logger.info("Setting up fake yp server settings")
+ setup_add_ldif(samdb, setup_path("ypServ30.ldif"), {
+ "DOMAINDN": domaindn,
+ "NETBIOSNAME": netbiosname,
+ "NISDOMAIN": nisdomain,
+ })
+ except:
+ samdb.transaction_cancel()
+ raise
+ else:
+ 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, dnspass=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, wheel=None, backup=None, aci=None,
- serverrole=None, dom_for_fun_level=None,
- backend_type=None, sitename=None,
- ol_mmr_urls=None, ol_olc=None, slapd_path=None,
- useeadb=False, am_rodc=False,
- lp=None, use_ntvfs=True):
+ 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="/bin/false",
+ useeadb=False, am_rodc=False, lp=None, use_ntvfs=False,
+ use_rfc2307=False, maxuid=None, maxgid=None, skip_sysvolacl=True):
"""Provision samba4
:note: caution, this wipes all existing data!
try:
serverrole = sanitize_server_role(serverrole)
except ValueError:
- raise ProvisioningError('server role (%s) should be one of "domain controller", "member server", "standalone"' % serverrole)
+ raise ProvisioningError('server role (%s) should be one of "active directory domain controller", "member server", "standalone server"' % serverrole)
if ldapadminpass is None:
# Make a new, random password between Samba and it's LDAP server
else:
domainsid = security.dom_sid(domainsid)
- sid_generator = "internal"
- if backend_type == "fedora-ds":
- sid_generator = "backend"
-
root_uid = findnss_uid([root or "root"])
nobody_uid = findnss_uid([nobody or "nobody"])
users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
- if wheel is None:
- wheel_gid = findnss_gid(["wheel", "adm"])
- else:
- wheel_gid = findnss_gid([wheel])
+ root_gid = pwd.getpwuid(root_uid).pw_gid
+
try:
bind_gid = findnss_gid(["bind", "named"])
except KeyError:
server_services = []
global_param = {}
- if dns_backend == "SAMBA_INTERNAL":
- server_services.append("+dns")
+ if use_rfc2307:
+ global_param["idmap_ldb:use rfc2307"] = ["yes"]
- if not use_ntvfs:
- server_services.append("-smb")
- server_services.append("+s3fs")
- global_param["vfs objects"] = ["acl_xattr"]
+ if dns_backend != "SAMBA_INTERNAL":
+ server_services.append("-dns")
+ else:
+ if dns_forwarder is not None:
+ global_param["dns forwarder"] = [dns_forwarder]
+
+ if use_ntvfs:
+ server_services.append("+smb")
+ server_services.append("-s3fs")
+ global_param["dcerpc endpoint servers"] = ["+winreg", "+srvsvc"]
if len(server_services) > 0:
global_param["server services"] = server_services
if data is None or data == "":
make_smbconf(smbconf, hostname, domain, realm,
targetdir, serverrole=serverrole,
- sid_generator=sid_generator, eadb=useeadb,
+ eadb=useeadb, use_ntvfs=use_ntvfs,
lp=lp, global_param=global_param)
else:
make_smbconf(smbconf, hostname, domain, realm, targetdir,
- serverrole=serverrole, sid_generator=sid_generator,
- eadb=useeadb, lp=lp, global_param=global_param)
+ serverrole=serverrole,
+ eadb=useeadb, use_ntvfs=use_ntvfs, lp=lp, global_param=global_param)
if lp is None:
lp = samba.param.LoadParm()
paths = provision_paths_from_lp(lp, names.dnsdomain)
paths.bind_gid = bind_gid
- paths.wheel_gid = wheel_gid
+ paths.root_uid = root_uid;
+ paths.root_gid = root_gid
if hostip is None:
logger.info("Looking up IPv4 addresses")
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)
+
+ 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)
+
+ 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="")
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
+ # 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,
setup_name_mappings(idmap, sid=str(domainsid),
root_uid=root_uid, nobody_uid=nobody_uid,
- users_gid=users_gid, wheel_gid=wheel_gid)
+ users_gid=users_gid, root_gid=root_gid)
logger.info("Setting up SAM db")
samdb = setup_samdb(paths.samdb, session_info,
serverrole=serverrole,
schema=schema, fill=samdb_fill, am_rodc=am_rodc)
- if serverrole == "domain controller":
+ if serverrole == "active directory domain controller":
if paths.netlogon is None:
- raise MissingShareError("netlogon", paths.smbconf,
- setup_path("provision.smb.conf.dc"))
+ raise MissingShareError("netlogon", paths.smbconf)
if paths.sysvol is None:
- raise MissingShareError("sysvol", paths.smbconf,
- setup_path("provision.smb.conf.dc"))
+ raise MissingShareError("sysvol", paths.smbconf)
if not os.path.isdir(paths.netlogon):
os.makedirs(paths.netlogon, 0755)
ntdsguid=ntdsguid, dns_backend=dns_backend,
dnspass=dnspass, serverrole=serverrole,
dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
- lp=lp)
+ lp=lp, use_ntvfs=use_ntvfs,
+ skip_sysvolacl=skip_sysvolacl)
create_krb5_conf(paths.krb5conf,
dnsdomain=names.dnsdomain, hostname=names.hostname,
logger.info("A Kerberos configuration suitable for Samba 4 has been "
"generated at %s", paths.krb5conf)
- if serverrole == "domain controller":
+ if serverrole == "active directory domain controller":
create_dns_update_list(lp, logger, paths)
backend_result = provision_backend.post_setup()
provision_backend.shutdown()
- create_phpldapadmin_config(paths.phpldapadminconfig,
- ldapi_url)
except:
secrets_ldb.transaction_cancel()
raise
result.backend_result = backend_result
+ if use_rfc2307:
+ provision_fake_ypserver(logger=logger, samdb=samdb,
+ domaindn=names.domaindn, netbiosname=names.netbiosname,
+ nisdomain=names.domain.lower(), maxuid=maxuid, maxgid=maxgid)
+
return result
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, wheel=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):
+ ldap_backend_type=None, sitename=None, debuglevel=1, use_ntvfs=False):
logger = logging.getLogger("provision")
samba.set_debug_level(debuglevel)
realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
configdn=configdn, serverdn=serverdn, domain=domain,
hostname=hostname, hostip=None, domainsid=domainsid,
- machinepass=machinepass, serverrole="domain controller",
- sitename=sitename, dns_backend=dns_backend, dnspass=dnspass)
+ 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
-def create_phpldapadmin_config(path, ldapi_uri):
- """Create a PHP LDAP admin configuration file.
-
- :param path: Path to write the configuration to.
- """
- setup_file(setup_path("phpldapadmin-config.php"), path,
- {"S4_LDAPI_URI": ldapi_uri})
-
-
def create_krb5_conf(path, dnsdomain, hostname, realm):
"""Write out a file containing zone statements suitable for inclusion in a
named.conf file (including GSS-TSIG configuration).
class MissingShareError(ProvisioningError):
- def __init__(self, name, smbconf, smbconf_template):
+ def __init__(self, name, smbconf):
super(MissingShareError, self).__init__(
"Existing smb.conf does not have a [%s] share, but you are "
- "configuring a DC. Please either remove %s or see the template "
- "at %s" % (name, smbconf, smbconf_template))
+ "configuring a DC. Please remove %s or add the share manually." %
+ (name, smbconf))