from base64 import b64encode
import os
-import sys
import pwd
import grp
import time
-import uuid, glue
+import uuid
import socket
import param
import registry
-import samba
-import subprocess
-import ldb
+import urllib
+import shutil
+import ldb
-from auth import system_session, admin_session
-from samba import version, Ldb, substitute_var, valid_netbios_name, setup_file
-from samba import check_all_substituted, read_and_sub_file
-from samba import DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008, DS_DC_FUNCTION_2008
-from samba.samdb import SamDB
-from samba.idmap import IDmapDB
+from samba.auth import system_session, admin_session
+from samba import glue, version, Ldb, substitute_var, valid_netbios_name
+from samba import check_all_substituted, read_and_sub_file, setup_file
+from samba import DS_DOMAIN_FUNCTION_2003, DS_DC_FUNCTION_2008, DS_DC_FUNCTION_2008_R2
from samba.dcerpc import security
-from samba.ndr import ndr_pack
-import urllib
-from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError
+from samba.dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
+from samba.idmap import IDmapDB
+from samba.ntacls import setntacl, dsacl2fsacl
+from samba.ndr import ndr_pack,ndr_unpack
+from samba.schema import Schema
from ms_display_specifiers import read_ms_ldif
-from schema import Schema
-from provisionbackend import LDBBackend, ExistingBackend, FDSBackend, OpenLDAPBackend
+from samba.provisionbackend import LDBBackend, ExistingBackend, FDSBackend, OpenLDAPBackend
from provisionexceptions import ProvisioningError, InvalidNetbiosName
-from signal import SIGTERM
-from dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
__docformat__ = "restructuredText"
paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
- paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
+ paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
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.winsdb = os.path.join(paths.private_dir, "wins.ldb")
if dnsdomain is None:
dnsdomain = lp.get("realm")
- assert dnsdomain is not None
+ if dnsdomain is None or dnsdomain == "":
+ raise ProvisioningError("guess_names: 'realm' not specified in supplied smb.conf!")
+
dnsdomain = dnsdomain.lower()
if serverrole is None:
serverrole = lp.get("server role")
- assert serverrole is not None
+ if serverrole is None:
+ raise ProvisioningError("guess_names: 'server role' not specified in supplied smb.conf!")
+
serverrole = serverrole.lower()
realm = dnsdomain.upper()
if lp.get("realm").upper() != realm:
raise ProvisioningError("guess_names: Realm '%s' in smb.conf must match chosen realm '%s'!", lp.get("realm").upper(), realm)
+ if lp.get("server role").lower() != serverrole:
+ raise ProvisioningError("guess_names: server role '%s' in smb.conf must match chosen server role '%s'!", lp.get("server role").upper(), serverrole)
+
if serverrole == "domain controller":
if domain is None:
domain = lp.get("workgroup")
- assert domain is not None
+ if domain is None:
+ raise ProvisioningError("guess_names: 'workgroup' not specified in supplied smb.conf!")
domain = domain.upper()
if lp.get("workgroup").upper() != domain:
def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
- targetdir, sid_generator):
+ targetdir, sid_generator,eadb):
"""Create a new smb.conf file based on a couple of basic settings.
"""
assert smbconf is not None
#Load non-existant file
if os.path.exists(smbconf):
default_lp.load(smbconf)
-
+ if eadb:
+ if targetdir is not None:
+ privdir = os.path.join(targetdir, "private")
+ else:
+ privdir = default_lp.get("private dir")
+ posixeadb_line = "posix:eadb = " + os.path.abspath(os.path.join(privdir,"eadb.tdb"))
+ else:
+ posixeadb_line = ""
+
if targetdir is not None:
privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
lockdir_line = "lock dir = " + os.path.abspath(targetdir)
"SYSVOLPATH": sysvol,
"SIDGENERATOR_LINE": sid_generator_line,
"PRIVATEDIR_LINE": privatedir_line,
- "LOCKDIR_LINE": lockdir_line
+ "LOCKDIR_LINE": lockdir_line,
+ "POSIXEADB_LINE": posixeadb_line
})
res = secretsdb.search(base="cn=Primary Domains",
attrs=attrs,
expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))),
- scope=SCOPE_ONELEVEL)
+ scope=ldb.SCOPE_ONELEVEL)
for del_msg in res:
if del_msg.dn is not msg.dn:
secretsdb.delete(del_msg.dn)
- res = secretsdb.search(base=msg.dn, attrs=attrs, scope=SCOPE_BASE)
+ res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
if len(res) == 1:
msg["priorSecret"] = res[0]["secret"]
secretsdb.add(msg)
-def secretsdb_setup_dns(secretsdb, setup_path, realm, dnsdomain,
+def secretsdb_setup_dns(secretsdb, setup_path, private_dir,
+ realm, dnsdomain,
dns_keytab_path, dnspass):
"""Add DNS specific bits to a secrets database.
:param setup_path: Setup path function
:param machinepass: Machine password
"""
+ try:
+ os.unlink(os.path.join(private_dir, dns_keytab_path))
+ except OSError:
+ pass
+
setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
"REALM": realm,
"DNSDOMAIN": dnsdomain,
"DEFAULTSITE": names.sitename,
"DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
"MACHINEPASS_B64": b64encode(machinepass),
- "DNSPASS_B64": b64encode(dnspass),
"REALM": names.realm,
"DOMAIN": names.domain,
+ "DOMAINSID": str(domainsid),
"DNSDOMAIN": names.dnsdomain,
"SAMBA_VERSION_STRING": version,
"NTDSGUID": ntdsguid_line,
# add the NTDSGUID based SPNs
ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
- expression="", scope=SCOPE_BASE)
+ expression="", scope=ldb.SCOPE_BASE)
assert isinstance(names.ntdsguid, str)
# Setup fSMORoleOwner entries to point at the newly created DC entry
"DEFAULTSITE": names.sitename,
"SERVERDN": names.serverdn,
"NETBIOSNAME": names.netbiosname,
- "NTDSGUID": names.ntdsguid
+ "NTDSGUID": names.ntdsguid,
+ "DNSPASS_B64": b64encode(dnspass),
})
+def setup_gpo(paths,names,samdb,policyguid,policyguid_dc,domainsid):
+ policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
+ "{" + policyguid + "}")
+ os.makedirs(policy_path, 0755)
+ open(os.path.join(policy_path, "GPT.INI"), 'w').write(
+ "[General]\r\nVersion=65543")
+ os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
+ os.makedirs(os.path.join(policy_path, "USER"), 0755)
+
+ policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
+ "{" + policyguid_dc + "}")
+ os.makedirs(policy_path_dc, 0755)
+ open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
+ "[General]\r\nVersion=2")
+ os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
+ os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
+
+
def setup_samdb(path, setup_path, session_info, provision_backend, lp,
names, message,
domainsid, domainguid, policyguid, policyguid_dc,
if dom_for_fun_level is None:
dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
if dom_for_fun_level < DS_DOMAIN_FUNCTION_2003:
- raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level lower than Windows 2003 (Native). This isn't supported!")
+ message("You want to run SAMBA 4 on a domain and forest function level lower than Windows 2003 (Native). This is not recommended")
if dom_for_fun_level > domainControllerFunctionality:
raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level which itself is higher than its actual DC function level (2008). This won't work!")
samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
samdb.set_domain_sid(str(domainsid))
- if serverrole == "domain controller":
- samdb.set_invocation_id(invocationid)
+ samdb.set_invocation_id(invocationid)
message("Adding DomainDN: %s" % names.domaindn)
setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
{"SCHEMADN": names.schemadn})
+ message("Reopening sam.ldb with new schema");
+ samdb.transaction_commit()
+ samdb = Ldb(session_info=admin_session_info,
+ credentials=provision_backend.credentials, lp=lp)
+ samdb.connect(path)
+ samdb.transaction_start()
+ samdb.set_invocation_id(invocationid)
+
message("Setting up sam.ldb configuration data")
setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
"CONFIGDN": names.configdn,
"KRBTGTPASS_B64": b64encode(krbtgtpass),
})
- if serverrole == "domain controller":
- message("Setting up self join")
- setup_self_join(samdb, names=names, invocationid=invocationid,
- dnspass=dnspass,
- machinepass=machinepass,
- domainsid=domainsid, policyguid=policyguid,
- policyguid_dc=policyguid_dc,
- setup_path=setup_path,
- domainControllerFunctionality=domainControllerFunctionality,
- ntdsguid=ntdsguid)
-
- ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
- names.ntdsguid = samdb.searchone(basedn=ntds_dn,
- attribute="objectGUID", expression="", scope=SCOPE_BASE)
- assert isinstance(names.ntdsguid, str)
+ message("Setting up self join")
+ setup_self_join(samdb, names=names, invocationid=invocationid,
+ dnspass=dnspass,
+ machinepass=machinepass,
+ domainsid=domainsid, policyguid=policyguid,
+ policyguid_dc=policyguid_dc,
+ setup_path=setup_path,
+ domainControllerFunctionality=domainControllerFunctionality,
+ ntdsguid=ntdsguid)
+
+ ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
+ names.ntdsguid = samdb.searchone(basedn=ntds_dn,
+ attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
+ assert isinstance(names.ntdsguid, str)
except:
samdb.transaction_cancel()
FILL_FULL = "FULL"
FILL_NT4SYNC = "NT4SYNC"
FILL_DRS = "DRS"
+SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
+POLICIES_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)(A;OICI;0x001301bf;;;PA)"
+
+def set_gpo_acl(path,acl,lp,domsid):
+ setntacl(lp,path,acl,domsid)
+ for root, dirs, files in os.walk(path, topdown=False):
+ for name in files:
+ setntacl(lp,os.path.join(root, name),acl,domsid)
+ for name in dirs:
+ setntacl(lp,os.path.join(root, name),acl,domsid)
+
+def setsysvolacl(samdb,names,netlogon,sysvol,gid,domainsid,lp):
+ canchown = 1
+ try:
+ os.chown(sysvol,-1,gid)
+ except:
+ canchown = 0
+
+ setntacl(lp,sysvol,SYSVOL_ACL,str(domainsid))
+ for root, dirs, files in os.walk(sysvol, topdown=False):
+ for name in files:
+ if canchown:
+ os.chown(os.path.join(root, name),-1,gid)
+ setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
+ for name in dirs:
+ if canchown:
+ os.chown(os.path.join(root, name),-1,gid)
+ setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
+
+ # Set ACL for GPO
+ policy_path = os.path.join(sysvol, names.dnsdomain, "Policies")
+ set_gpo_acl(policy_path,dsacl2fsacl(POLICIES_ACL,str(domainsid)),lp,str(domainsid))
+ res = samdb.search(base="CN=Policies,CN=System,%s"%(names.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 = os.path.join(sysvol, names.dnsdomain, "Policies",
+ str(policy["cn"]))
+ set_gpo_acl(policy_path,dsacl2fsacl(acl,str(domainsid)),lp,str(domainsid))
+
def provision(setup_dir, message, session_info,
sitename=None,
ol_mmr_urls=None, ol_olc=None,
setup_ds_path=None, slapd_path=None, nosync=False,
- ldap_dryrun_mode=False):
+ ldap_dryrun_mode=False,useeadb=False):
"""Provision samba4
:note: caution, this wipes all existing data!
wheel_gid = findnss_gid(["wheel", "adm"])
else:
wheel_gid = findnss_gid([wheel])
+ try:
+ bind_gid = findnss_gid(["bind", "named"])
+ except KeyError:
+ bind_gid = None
if targetdir is not None:
if (not os.path.exists(os.path.join(targetdir, "etc"))):
smbconf = param.default_path()
# only install a new smb.conf if there isn't one there already
- if not os.path.exists(smbconf):
+ if os.path.exists(smbconf):
+ # if Samba Team members can't figure out the weird errors
+ # loading an empty smb.conf gives, then we need to be smarter.
+ # Pretend it just didn't exist --abartlet
+ data = open(smbconf, 'r').read()
+ data = data.lstrip()
+ if data is None or data == "":
+ make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
+ targetdir, sid_generator, useeadb)
+ else:
make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
- targetdir, sid_generator)
+ targetdir, sid_generator, useeadb)
lp = param.LoadParm()
lp.load(smbconf)
paths = provision_paths_from_lp(lp, names.dnsdomain)
+ paths.bind_gid = bind_gid
+
if hostip is None:
try:
hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
serverrole = lp.get("server role")
assert serverrole in ("domain controller", "member server", "standalone")
- if invocationid is None and serverrole == "domain controller":
+ if invocationid is None:
invocationid = str(uuid.uuid4())
if not os.path.exists(paths.private_dir):
(paths.smbconf, setup_path("provision.smb.conf.dc")))
assert(paths.sysvol is not None)
- # Set up group policies (domain policy and domain controller policy)
-
- policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
- "{" + policyguid + "}")
- os.makedirs(policy_path, 0755)
- open(os.path.join(policy_path, "GPT.INI"), 'w').write(
- "[General]\r\nVersion=65543")
- os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
- os.makedirs(os.path.join(policy_path, "USER"), 0755)
-
- policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
- "{" + policyguid_dc + "}")
- os.makedirs(policy_path_dc, 0755)
- open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
- "[General]\r\nVersion=2")
- os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
- os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
if not os.path.isdir(paths.netlogon):
os.makedirs(paths.netlogon, 0755)
root_uid=root_uid, nobody_uid=nobody_uid,
users_gid=users_gid, wheel_gid=wheel_gid)
+ if serverrole == "domain controller":
+ # Set up group policies (domain policy and domain controller policy)
+ setup_gpo(paths,names,samdb,policyguid,policyguid_dc,domainsid)
+ setsysvolacl(samdb,names,paths.netlogon,paths.sysvol,wheel_gid,domainsid,lp)
+
message("Setting up sam.ldb rootDSE marking as synchronized")
setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
secure_channel_type=SEC_CHAN_BDC)
if serverrole == "domain controller":
- secretsdb_setup_dns(secrets_ldb, setup_path,
+ secretsdb_setup_dns(secrets_ldb, setup_path,
+ paths.private_dir,
realm=names.realm, dnsdomain=names.dnsdomain,
dns_keytab_path=paths.dns_keytab,
dnspass=dnspass)
# Only make a zone file on the first DC, it should be replicated
# with DNS replication
- create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
+ create_zone_file(lp, message, paths, targetdir, setup_path, dnsdomain=names.dnsdomain,
hostip=hostip,
hostip6=hostip6, hostname=names.hostname,
realm=names.realm,
domainguid=domainguid, ntdsguid=names.ntdsguid)
- create_named_conf(paths.namedconf, setup_path, realm=names.realm,
+ create_named_conf(paths, setup_path, realm=names.realm,
dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
#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):
+ try:
+ os.chmod(dns_keytab_path, 0640)
+ os.chown(dns_keytab_path, -1, paths.bind_gid)
+ except OSError:
+ message("Failed to chown %s to bind gid %u" % (dns_keytab_path, paths.bind_gid))
+
+
message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
message("Once the above files are installed, your Samba4 server will be ready to use")
{"S4_LDAPI_URI": ldapi_uri})
-def create_zone_file(path, setup_path, dnsdomain,
+def create_zone_file(lp, message, paths, targetdir, setup_path, dnsdomain,
hostip, hostip6, hostname, realm, domainguid,
ntdsguid):
"""Write out a DNS zone file, from the info in the current database.
- :param path: Path of the new zone file.
+ :param paths: paths object
:param setup_path: Setup path function.
:param dnsdomain: DNS Domain name
:param domaindn: DN of the Domain
hostip_base_line = ""
hostip_host_line = ""
- setup_file(setup_path("provision.zone"), path, {
+ dns_dir = os.path.dirname(paths.dns)
+
+ try:
+ shutil.rmtree(dns_dir, True)
+ except OSError:
+ pass
+
+ os.mkdir(dns_dir, 0775)
+
+ # we need to freeze the zone while we update the contents
+ if targetdir is None:
+ rndc = lp.get("rndc command")
+ os.system(rndc + " freeze " + lp.get("realm"))
+
+ setup_file(setup_path("provision.zone"), paths.dns, {
"HOSTNAME": hostname,
"DNSDOMAIN": dnsdomain,
"REALM": realm,
"HOSTIP6_HOST_LINE": hostip6_host_line,
})
+ if paths.bind_gid is not None:
+ try:
+ os.chown(dns_dir, -1, paths.bind_gid)
+ os.chown(paths.dns, -1, paths.bind_gid)
+ # chmod needed to cope with umask
+ os.chmod(dns_dir, 0775)
+ os.chmod(paths.dns, 0664)
+ except OSError:
+ message("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
+
+ if targetdir is None:
+ os.system(rndc + " unfreeze " + lp.get("realm"))
+
-def create_named_conf(path, setup_path, realm, dnsdomain,
+def create_named_conf(paths, setup_path, realm, dnsdomain,
private_dir):
"""Write out a file containing zone statements suitable for inclusion in a
named.conf file (including GSS-TSIG configuration).
- :param path: Path of the new named.conf file.
+ :param paths: all paths
:param setup_path: Setup path function.
:param realm: Realm name
:param dnsdomain: DNS Domain name
:param keytab_name: File name of DNS keytab file
"""
- setup_file(setup_path("named.conf"), path, {
+ setup_file(setup_path("named.conf"), paths.namedconf, {
"DNSDOMAIN": dnsdomain,
"REALM": realm,
+ "ZONE_FILE": paths.dns,
"REALM_WC": "*." + ".".join(realm.split(".")[1:]),
- "PRIVATE_DIR": private_dir
+ "NAMED_CONF": paths.namedconf,
+ "NAMED_CONF_UPDATE": paths.namedconf_update
})
+ setup_file(setup_path("named.conf.update"), paths.namedconf_update)
+
def create_named_txt(path, setup_path, realm, dnsdomain,
private_dir, keytab_name):
"""Write out a file containing zone statements suitable for inclusion in a