From 8caac9462ac09b7ff99a7032329d0e56c2e0aac5 Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Sun, 28 Nov 2010 04:02:28 +0100 Subject: samba.provision: Add package with provision and backend modules. --- source4/scripting/python/samba/provision.py | 1958 ------------------- .../scripting/python/samba/provision/__init__.py | 1960 ++++++++++++++++++++ .../scripting/python/samba/provision/backend.py | 767 ++++++++ source4/scripting/python/samba/provisionbackend.py | 756 -------- source4/scripting/python/samba/tests/samdb.py | 13 +- 5 files changed, 2734 insertions(+), 2720 deletions(-) delete mode 100644 source4/scripting/python/samba/provision.py create mode 100644 source4/scripting/python/samba/provision/__init__.py create mode 100644 source4/scripting/python/samba/provision/backend.py delete mode 100644 source4/scripting/python/samba/provisionbackend.py (limited to 'source4/scripting') diff --git a/source4/scripting/python/samba/provision.py b/source4/scripting/python/samba/provision.py deleted file mode 100644 index 70afc2a1ee..0000000000 --- a/source4/scripting/python/samba/provision.py +++ /dev/null @@ -1,1958 +0,0 @@ - -# Unix SMB/CIFS implementation. -# backend code for provisioning a Samba4 server - -# Copyright (C) Jelmer Vernooij 2007-2010 -# Copyright (C) Andrew Bartlett 2008-2009 -# Copyright (C) Oliver Liebel 2008-2009 -# -# Based on the original in EJS: -# Copyright (C) Andrew Tridgell 2005 -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -"""Functions for setting up a Samba configuration.""" - -from base64 import b64encode -import os -import re -import pwd -import grp -import logging -import time -import uuid -import socket -import urllib -import shutil - -import ldb - -from samba.auth import system_session, admin_session -import samba -from samba import ( - Ldb, - check_all_substituted, - in_source_tree, - read_and_sub_file, - setup_file, - substitute_var, - valid_netbios_name, - version, - ) -from samba.dsdb import ( - DS_DOMAIN_FUNCTION_2003, - DS_DOMAIN_FUNCTION_2008_R2, - ENC_ALL_TYPES, - ) -from samba.dcerpc import security -from samba.dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA -from samba.idmap import IDmapDB -from samba.ms_display_specifiers import read_ms_ldif -from samba.ntacls import setntacl, dsacl2fsacl -from samba.ndr import ndr_pack,ndr_unpack -from samba.provisionbackend import ( - ExistingBackend, - FDSBackend, - LDBBackend, - OpenLDAPBackend, - ) -import samba.param -import samba.registry -from samba.schema import Schema -from samba.samdb import SamDB - -VALID_NETBIOS_CHARS = " !#$%&'()-.@^_{}~" -__docformat__ = "restructuredText" -DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9" -DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9" - -def find_setup_dir(): - """Find the setup directory used by provision.""" - if in_source_tree(): - # In source tree - dirname = os.path.dirname(__file__) - return os.path.normpath(os.path.join(dirname, "../../../setup")) - else: - import sys - for prefix in [sys.prefix, - os.path.join(os.path.dirname(__file__), "../../../..")]: - for suffix in ["share/setup", "share/samba/setup", "setup"]: - ret = os.path.normpath(os.path.join(prefix, suffix)) - if os.path.isdir(ret): - return ret - raise Exception("Unable to find setup directory.") - -# Descriptors of naming contexts and other important objects - -# "get_schema_descriptor" is located in "schema.py" - -def get_sites_descriptor(domain_sid): - sddl = "O:EAG:EAD:AI(A;;RPLCLORC;;;AU)" \ - "(A;;RPWPCRCCLCLORCWOWDSW;;;EA)" \ - "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \ - "(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \ - "(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \ - "S:AI(AU;CISA;CCDCSDDT;;;WD)" \ - "(OU;CIIOSA;CR;;f0f8ffab-1191-11d0-a060-00aa006c33ed;WD)" \ - "(OU;CIIOSA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \ - "(OU;CIIOSA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \ - "(OU;CIIOSA;WP;3e10944c-c354-11d0-aff8-0000f80367c1;b7b13124-b82e-11d0-afee-0000f80367c1;WD)" - sec = security.descriptor.from_sddl(sddl, domain_sid) - return ndr_pack(sec) - -def get_config_descriptor(domain_sid): - sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \ - "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \ - "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \ - "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \ - "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \ - "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \ - "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)" - sec = security.descriptor.from_sddl(sddl, domain_sid) - return ndr_pack(sec) - -def get_domain_descriptor(domain_sid): - sddl= "O:BAG:BAD:AI(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \ - "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;DD)" \ - "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;ED)" \ - "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;ED)" \ - "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;ED)" \ - "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \ - "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ - "(OA;;CR;e2a36dc9-ae17-47c3-b58b-be34c55ba633;;IF)" \ - "(OA;;RP;c7407360-20bf-11d0-a768-00aa006e0529;;RU)" \ - "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;RU)" \ - "(OA;CIIO;RPLCLORC;;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ - "(OA;CIIO;RPLCLORC;;bf967a9c-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;CIIO;RPLCLORC;;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ - "(OA;;CR;05c74c5e-4deb-43b4-bd9f-86664c2a7fd5;;AU)" \ - "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \ - "(OA;;CR;ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501;;AU)" \ - "(OA;;CR;280f369c-67c7-438e-ae98-1d46f3c6f541;;AU)" \ - "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ - "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;AU)" \ - "(OA;CIIO;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \ - "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \ - "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \ - "(A;;RPRC;;;RU)" \ - "(A;CI;LC;;;RU)" \ - "(A;CI;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \ - "(A;;RP;;;WD)" \ - "(A;;RPLCLORC;;;ED)" \ - "(A;;RPLCLORC;;;AU)" \ - "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \ - "S:AI(OU;CISA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \ - "(OU;CISA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \ - "(AU;SA;CR;;;DU)(AU;SA;CR;;;BA)(AU;SA;WPWOWD;;;WD)" - sec = security.descriptor.from_sddl(sddl, domain_sid) - return ndr_pack(sec) - -DEFAULTSITE = "Default-First-Site-Name" -LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN" - -class ProvisionPaths(object): - - def __init__(self): - self.shareconf = None - self.hklm = None - self.hkcu = None - self.hkcr = None - self.hku = None - self.hkpd = None - self.hkpt = None - self.samdb = None - self.idmapdb = None - self.secrets = None - self.keytab = None - self.dns_keytab = None - self.dns = None - self.winsdb = None - self.private_dir = None - - -class ProvisionNames(object): - - def __init__(self): - self.rootdn = None - self.domaindn = None - self.configdn = None - self.schemadn = None - self.ldapmanagerdn = None - self.dnsdomain = None - self.realm = None - self.netbiosname = None - self.domain = None - self.hostname = None - self.sitename = None - self.smbconf = None - - -def update_provision_usn(samdb, low, high, replace=False): - """Update the field provisionUSN in sam.ldb - - This field is used to track range of USN modified by provision and - upgradeprovision. - This value is used afterward by next provision to figure out if - the field have been modified since last provision. - - :param samdb: An LDB object connect to sam.ldb - :param low: The lowest USN modified by this upgrade - :param high: The highest USN modified by this upgrade - :param replace: A boolean indicating if the range should replace any - existing one or appended (default) - """ - - tab = [] - if not replace: - entry = samdb.search(expression="(&(dn=@PROVISION)(%s=*))" % - LAST_PROVISION_USN_ATTRIBUTE, base="", - scope=ldb.SCOPE_SUBTREE, - attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"]) - for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]: - tab.append(str(e)) - - tab.append("%s-%s" % (low, high)) - 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) - samdb.modify(delta) - - -def set_provision_usn(samdb, low, high): - """Set the field provisionUSN in sam.ldb - This field is used to track range of USN modified by provision and - upgradeprovision. - This value is used afterward by next provision to figure out if - the field have been modified since last provision. - - :param samdb: An LDB object connect to sam.ldb - :param low: The lowest USN modified by this upgrade - :param high: The highest USN modified by this upgrade""" - tab = [] - tab.append("%s-%s" % (low, high)) - 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) - samdb.add(delta) - - -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 - :param basedn: A string containing the base DN of the provision - (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"]) - return res[0]["uSNChanged"] - -def get_last_provision_usn(sam): - """Get the lastest USN modified by a provision or an upgradeprovision - - :param sam: An LDB object pointing to the sam.ldb - :return: an integer corresponding to the highest USN modified by - (upgrade)provision, 0 is this value is unknown - """ - entry = sam.search(expression="(&(dn=@PROVISION)(%s=*))" % - LAST_PROVISION_USN_ATTRIBUTE, - base="", scope=ldb.SCOPE_SUBTREE, - attrs=[LAST_PROVISION_USN_ATTRIBUTE]) - if len(entry): - range = [] - idx = 0 - p = re.compile(r'-') - for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]: - tab = p.split(str(r)) - range.append(tab[0]) - range.append(tab[1]) - idx = idx + 1 - return range - else: - return None - -class ProvisionResult(object): - - def __init__(self): - self.paths = None - self.domaindn = None - self.lp = None - self.samdb = None - - -def check_install(lp, session_info, credentials): - """Check whether the current install seems ok. - - :param lp: Loadparm context - :param session_info: Session information - :param credentials: Credentials - """ - if lp.get("realm") == "": - raise Exception("Realm empty") - samdb = Ldb(lp.get("sam database"), session_info=session_info, - credentials=credentials, lp=lp) - if len(samdb.search("(cn=Administrator)")) != 1: - raise ProvisioningError("No administrator account found") - - -def findnss(nssfn, names): - """Find a user or group from a list of possibilities. - - :param nssfn: NSS Function to try (should raise KeyError if not found) - :param names: Names to check. - :return: Value return by first names list. - """ - for name in names: - try: - return nssfn(name) - except KeyError: - pass - 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 setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]): - """Setup a ldb in the private dir. - - :param ldb: LDB file to import data into - :param ldif_path: Path of the LDIF file to load - :param subst_vars: Optional variables to subsitute in LDIF. - :param nocontrols: Optional list of controls, can be None for no controls - """ - assert isinstance(ldif_path, str) - data = read_and_sub_file(ldif_path, subst_vars) - ldb.add_ldif(data, controls) - - -def setup_modify_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]): - """Modify a ldb in the private dir. - - :param ldb: LDB object. - :param ldif_path: LDIF file path. - :param subst_vars: Optional dictionary with substitution variables. - """ - data = read_and_sub_file(ldif_path, subst_vars) - ldb.modify_ldif(data, controls) - - -def setup_ldb(ldb, ldif_path, subst_vars): - """Import a LDIF a file into a LDB handle, optionally substituting variables. - - :note: Either all LDIF data will be added or none (using transactions). - - :param ldb: LDB file to import into. - :param ldif_path: Path to the LDIF file. - :param subst_vars: Dictionary with substitution variables. - """ - assert ldb is not None - ldb.transaction_start() - try: - setup_add_ldif(ldb, ldif_path, subst_vars) - except: - ldb.transaction_cancel() - raise - else: - ldb.transaction_commit() - - -def provision_paths_from_lp(lp, dnsdomain): - """Set the default paths for provisioning. - - :param lp: Loadparm context. - :param dnsdomain: DNS Domain name - """ - paths = ProvisionPaths() - paths.private_dir = lp.get("private dir") - - # This is stored without path prefix for the "privateKeytab" attribute in - # "secrets_dns.ldif". - paths.dns_keytab = "dns.keytab" - paths.keytab = "secrets.keytab" - - paths.shareconf = os.path.join(paths.private_dir, "share.ldb") - paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb") - paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb") - paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb") - paths.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.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" - paths.hku = "hku.ldb" - paths.hkpd = "hkpd.ldb" - paths.hkpt = "hkpt.ldb" - paths.sysvol = lp.get("path", "sysvol") - paths.netlogon = lp.get("path", "netlogon") - paths.smbconf = lp.configfile - return paths - - -def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, - serverrole=None, rootdn=None, domaindn=None, configdn=None, - schemadn=None, serverdn=None, sitename=None): - """Guess configuration settings to use.""" - - if hostname is None: - hostname = socket.gethostname().split(".")[0] - - netbiosname = lp.get("netbios name") - if netbiosname is None: - netbiosname = hostname - # remove forbidden chars - newnbname = "" - for x in netbiosname: - if x.isalnum() or x in VALID_NETBIOS_CHARS: - newnbname = "%s%c" % (newnbname, x) - #force the length to be <16 - netbiosname = newnbname[0:15] - assert netbiosname is not None - netbiosname = netbiosname.upper() - if not valid_netbios_name(netbiosname): - raise InvalidNetbiosName(netbiosname) - - 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) - - dnsdomain = dnsdomain.lower() - - if serverrole is None: - serverrole = lp.get("server role") - if serverrole is None: - raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile) - - serverrole = serverrole.lower() - - realm = dnsdomain.upper() - - if lp.get("realm") == "": - 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)) - - if lp.get("server role").lower() != serverrole: - raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'! Please remove the smb.conf file and let provision generate it" % (lp.get("server role").upper(), serverrole, lp.configfile)) - - if serverrole == "domain controller": - if domain is None: - # This will, for better or worse, default to 'WORKGROUP' - domain = lp.get("workgroup") - domain = domain.upper() - - if lp.get("workgroup").upper() != domain: - raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'! Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile)) - - if domaindn is None: - domaindn = "DC=" + dnsdomain.replace(".", ",DC=") - else: - domain = netbiosname - if domaindn is None: - domaindn = "DC=" + netbiosname - - if not valid_netbios_name(domain): - raise InvalidNetbiosName(domain) - - if hostname.upper() == realm: - raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname)) - if netbiosname == realm: - raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname)) - if domain == realm: - raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain)) - - if rootdn is None: - rootdn = domaindn - - if configdn is None: - configdn = "CN=Configuration," + rootdn - if schemadn is None: - schemadn = "CN=Schema," + configdn - - if sitename is None: - sitename=DEFAULTSITE - - names = ProvisionNames() - names.rootdn = rootdn - names.domaindn = domaindn - names.configdn = configdn - names.schemadn = schemadn - names.ldapmanagerdn = "CN=Manager," + rootdn - names.dnsdomain = dnsdomain - names.domain = domain - names.realm = realm - names.netbiosname = netbiosname - names.hostname = hostname - names.sitename = sitename - names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn) - - return names - - -def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, - targetdir, sid_generator="internal", eadb=False, lp=None): - """Create a new smb.conf file based on a couple of basic settings. - """ - assert smbconf is not None - if hostname is None: - hostname = socket.gethostname().split(".")[0] - netbiosname = hostname.upper() - # remove forbidden chars - newnbname = "" - for x in netbiosname: - if x.isalnum() or x in VALID_NETBIOS_CHARS: - newnbname = "%s%c" % (newnbname, x) - #force the length to be <16 - netbiosname = newnbname[0:15] - else: - netbiosname = hostname.upper() - - if serverrole is None: - serverrole = "standalone" - - assert serverrole in ("domain controller", "member server", "standalone") - if serverrole == "domain controller": - smbconfsuffix = "dc" - elif serverrole == "member server": - smbconfsuffix = "member" - elif serverrole == "standalone": - smbconfsuffix = "standalone" - - if sid_generator is None: - sid_generator = "internal" - - assert domain is not None - domain = domain.upper() - - assert realm is not None - realm = realm.upper() - - if lp is None: - lp = samba.param.LoadParm() - #Load non-existant file - if os.path.exists(smbconf): - lp.load(smbconf) - if eadb and not lp.get("posix:eadb"): - if targetdir is not None: - privdir = os.path.join(targetdir, "private") - else: - privdir = lp.get("private dir") - lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb"))) - - if targetdir is not None: - privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private")) - lockdir_line = "lock dir = " + os.path.abspath(targetdir) - - lp.set("lock dir", os.path.abspath(targetdir)) - else: - privatedir_line = "" - lockdir_line = "" - - if sid_generator == "internal": - sid_generator_line = "" - else: - sid_generator_line = "sid generator = " + sid_generator - - used_setup_dir = setup_path("") - default_setup_dir = lp.get("setup directory") - setupdir_line = "" - if used_setup_dir != default_setup_dir: - setupdir_line = "setup directory = %s" % used_setup_dir - lp.set("setup directory", used_setup_dir) - - sysvol = os.path.join(lp.get("lock dir"), "sysvol") - netlogon = os.path.join(sysvol, realm.lower(), "scripts") - - setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), - smbconf, { - "NETBIOS_NAME": netbiosname, - "DOMAIN": domain, - "REALM": realm, - "SERVERROLE": serverrole, - "NETLOGONPATH": netlogon, - "SYSVOLPATH": sysvol, - "SETUPDIRECTORY_LINE": setupdir_line, - "SIDGENERATOR_LINE": sid_generator_line, - "PRIVATEDIR_LINE": privatedir_line, - "LOCKDIR_LINE": lockdir_line - }) - - # reload the smb.conf - lp.load(smbconf) - - # 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') - lp.dump(f, False) - f.close() - - - -def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid, - users_gid, wheel_gid): - """setup reasonable name mappings for sam names to unix names. - - :param samdb: SamDB object. - :param idmap: IDmap db object. - :param sid: The domain sid. - :param domaindn: The domain DN. - :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.""" - idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid) - idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid) - - idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid) - idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid) - - -def setup_samdb_partitions(samdb_path, setup_path, logger, lp, session_info, - provision_backend, names, schema, serverrole, - erase=False): - """Setup the partitions for the SAM database. - - Alternatively, provision() may call this, and then populate the database. - - :note: This will wipe the Sam Database! - - :note: This function always removes the local SAM LDB file. The erase - parameter controls whether to erase the existing data, which - may not be stored locally but in LDAP. - - """ - assert session_info is not None - - # We use options=["modules:"] to stop the modules loading - we - # just want to wipe and re-initialise the database, not start it up - - try: - os.unlink(samdb_path) - except OSError: - pass - - samdb = Ldb(url=samdb_path, session_info=session_info, - lp=lp, options=["modules:"]) - - ldap_backend_line = "# No LDAP backend" - if provision_backend.type is not "ldb": - ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri - - samdb.transaction_start() - try: - logger.info("Setting up sam.ldb partitions and settings") - setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), { - "SCHEMADN": ldb.Dn(schema.ldb, names.schemadn).get_casefold(), - "CONFIGDN": ldb.Dn(schema.ldb, names.configdn).get_casefold(), - "DOMAINDN": ldb.Dn(schema.ldb, names.domaindn).get_casefold(), - "LDAP_BACKEND_LINE": ldap_backend_line, - }) - - - setup_add_ldif(samdb, setup_path("provision_init.ldif"), { - "BACKEND_TYPE": provision_backend.type, - "SERVER_ROLE": serverrole - }) - - logger.info("Setting up sam.ldb rootDSE") - setup_samdb_rootdse(samdb, setup_path, names) - except: - samdb.transaction_cancel() - raise - else: - samdb.transaction_commit() - - -def secretsdb_self_join(secretsdb, domain, - netbiosname, machinepass, domainsid=None, - realm=None, dnsdomain=None, - keytab_path=None, - key_version_number=1, - secure_channel_type=SEC_CHAN_WKSTA): - """Add domain join-specific bits to a secrets database. - - :param secretsdb: Ldb Handle to the secrets database - :param machinepass: Machine password - """ - attrs=["whenChanged", - "secret", - "priorSecret", - "priorChanged", - "krb5Keytab", - "privateKeytab"] - - if realm is not None: - if dnsdomain is None: - dnsdomain = realm.lower() - dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower()) - else: - dnsname = None - shortname = netbiosname.lower() - - #We don't need to set msg["flatname"] here, because rdn_name will handle it, and it causes problems for modifies anyway - msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain)) - msg["secureChannelType"] = [str(secure_channel_type)] - msg["objectClass"] = ["top", "primaryDomain"] - if dnsname is not None: - msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"] - msg["realm"] = [realm] - msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())] - msg["msDS-KeyVersionNumber"] = [str(key_version_number)] - msg["privateKeytab"] = ["secrets.keytab"] - - msg["secret"] = [machinepass] - msg["samAccountName"] = ["%s$" % netbiosname] - msg["secureChannelType"] = [str(secure_channel_type)] - if domainsid is not None: - msg["objectSid"] = [ndr_pack(domainsid)] - - # This complex expression tries to ensure that we don't have more - # than one record for this SID, realm or netbios domain at a time, - # but we don't delete the old record that we are about to modify, - # because that would delete the keytab and previous password. - res = secretsdb.search(base="cn=Primary Domains", - attrs=attrs, - expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))), - scope=ldb.SCOPE_ONELEVEL) - - for del_msg in res: - secretsdb.delete(del_msg.dn) - - res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE) - - if len(res) == 1: - msg["priorSecret"] = [res[0]["secret"][0]] - msg["priorWhenChanged"] = [res[0]["whenChanged"][0]] - - try: - msg["privateKeytab"] = [res[0]["privateKeytab"][0]] - except KeyError: - pass - - try: - msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]] - except KeyError: - pass - - for el in msg: - if el != 'dn': - msg[el].set_flags(ldb.FLAG_MOD_REPLACE) - secretsdb.modify(msg) - secretsdb.rename(res[0].dn, msg.dn) - else: - 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 ]) - msg["servicePrincipalName"] = spn - - secretsdb.add(msg) - - -def secretsdb_setup_dns(secretsdb, setup_path, names, private_dir, - realm, dnsdomain, - dns_keytab_path, dnspass): - """Add DNS specific bits to a secrets database. - - :param secretsdb: Ldb Handle to the secrets database - :param setup_path: Setup path function - :param machinepass: Machine password - """ - try: - os.unlink(os.path.join(private_dir, dns_keytab_path)) - except OSError: - pass - - setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { - "REALM": realm, - "DNSDOMAIN": dnsdomain, - "DNS_KEYTAB": dns_keytab_path, - "DNSPASS_B64": b64encode(dnspass), - "HOSTNAME": names.hostname, - "DNSNAME" : '%s.%s' % (names.netbiosname.lower(), names.dnsdomain.lower()) - }) - - -def setup_secretsdb(paths, setup_path, session_info, backend_credentials, lp): - """Setup the secrets database. - - :note: This function does not handle exceptions and transaction on purpose, - it's up to the caller to do this job. - - :param path: Path to the secrets database. - :param setup_path: Get the path to a setup file. - :param session_info: Session info. - :param credentials: Credentials - :param lp: Loadparm context - :return: LDB handle for the created secrets database - """ - if os.path.exists(paths.secrets): - os.unlink(paths.secrets) - - keytab_path = os.path.join(paths.private_dir, paths.keytab) - if os.path.exists(keytab_path): - os.unlink(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) - - path = paths.secrets - - secrets_ldb = Ldb(path, session_info=session_info, - lp=lp) - secrets_ldb.erase() - secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif")) - secrets_ldb = Ldb(path, session_info=session_info, - lp=lp) - secrets_ldb.transaction_start() - try: - secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif")) - - if backend_credentials is not None and 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()) - }) - 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()) - }) - - return secrets_ldb - except: - secrets_ldb.transaction_cancel() - raise - -def setup_privileges(path, setup_path, session_info, lp): - """Setup the privileges database. - - :param path: Path to the privileges database. - :param setup_path: Get the path to a setup file. - :param session_info: Session info. - :param credentials: Credentials - :param lp: Loadparm context - :return: LDB handle for the created secrets database - """ - if os.path.exists(path): - os.unlink(path) - privilege_ldb = Ldb(path, session_info=session_info, lp=lp) - privilege_ldb.erase() - privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif")) - - -def setup_registry(path, setup_path, session_info, lp): - """Setup the registry. - - :param path: Path to the registry database - :param setup_path: Function that returns the path to a setup. - :param session_info: Session information - :param credentials: Credentials - :param lp: Loadparm context - """ - reg = samba.registry.Registry() - hive = samba.registry.open_ldb(path, session_info=session_info, - lp_ctx=lp) - reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE) - provision_reg = setup_path("provision.reg") - assert os.path.exists(provision_reg) - reg.diff_apply(provision_reg) - - -def setup_idmapdb(path, setup_path, session_info, lp): - """Setup the idmap database. - - :param path: path to the idmap database - :param setup_path: Function that returns a path to a setup file - :param session_info: Session information - :param credentials: Credentials - :param lp: Loadparm context - """ - if os.path.exists(path): - os.unlink(path) - - idmap_ldb = IDmapDB(path, session_info=session_info, - lp=lp) - - idmap_ldb.erase() - idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif")) - return idmap_ldb - - -def setup_samdb_rootdse(samdb, setup_path, names): - """Setup the SamDB rootdse. - - :param samdb: Sam Database handle - :param setup_path: Obtain setup path - """ - setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), { - "SCHEMADN": names.schemadn, - "DOMAINDN": names.domaindn, - "ROOTDN": names.rootdn, - "CONFIGDN": names.configdn, - "SERVERDN": names.serverdn, - }) - - -def setup_self_join(samdb, names, - machinepass, dnspass, - domainsid, next_rid, invocationid, setup_path, - policyguid, policyguid_dc, domainControllerFunctionality, - ntdsguid): - """Join a host to its own domain.""" - assert isinstance(invocationid, str) - if ntdsguid is not None: - ntdsguid_line = "objectGUID: %s\n"%ntdsguid - else: - ntdsguid_line = "" - setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { - "CONFIGDN": names.configdn, - "SCHEMADN": names.schemadn, - "DOMAINDN": names.domaindn, - "SERVERDN": names.serverdn, - "INVOCATIONID": invocationid, - "NETBIOSNAME": names.netbiosname, - "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain), - "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')), - "DOMAINSID": str(domainsid), - "DCRID": str(next_rid), - "SAMBA_VERSION_STRING": version, - "NTDSGUID": ntdsguid_line, - "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)}) - - setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { - "POLICYGUID": policyguid, - "POLICYGUID_DC": policyguid_dc, - "DNSDOMAIN": names.dnsdomain, - "DOMAINDN": names.domaindn}) - - # add the NTDSGUID based SPNs - ntds_dn = "CN=NTDS Settings,%s" % names.serverdn - names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID", - expression="", scope=ldb.SCOPE_BASE) - assert isinstance(names.ntdsguid, str) - - # Setup fSMORoleOwner entries to point at the newly created DC entry - setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), { - "DOMAINDN": names.domaindn, - "CONFIGDN": names.configdn, - "SCHEMADN": names.schemadn, - "DEFAULTSITE": names.sitename, - "SERVERDN": names.serverdn, - "NETBIOSNAME": names.netbiosname, - "RIDALLOCATIONSTART": str(next_rid + 100), - "RIDALLOCATIONEND": str(next_rid + 100 + 499), - }) - - # This is partially Samba4 specific and should be replaced by the correct - # DNS AD-style setup - setup_add_ldif(samdb, setup_path("provision_dns_add.ldif"), { - "DNSDOMAIN": names.dnsdomain, - "DOMAINDN": names.domaindn, - "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')), - "HOSTNAME" : names.hostname, - "DNSNAME" : '%s.%s' % (names.netbiosname.lower(), names.dnsdomain.lower()) - }) - -def getpolicypath(sysvolpath, dnsdomain, guid): - """Return the physical path of policy given its guid. - - :param sysvolpath: Path to the sysvol folder - :param dnsdomain: DNS name of the AD domain - :param guid: The GUID of the policy - :return: A string with the complete path to the policy folder - """ - - if guid[0] != "{": - guid = "{%s}" % guid - policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid) - return policy_path - -def create_gpo_struct(policy_path): - if not os.path.exists(policy_path): - os.makedirs(policy_path, 0775) - open(os.path.join(policy_path, "GPT.INI"), 'w').write( - "[General]\r\nVersion=0") - p = os.path.join(policy_path, "MACHINE") - if not os.path.exists(p): - os.makedirs(p, 0775) - p = os.path.join(policy_path, "USER") - if not os.path.exists(p): - os.makedirs(p, 0775) - - -def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc): - """Create the default GPO for a domain - - :param sysvolpath: Physical path for the sysvol folder - :param dnsdomain: DNS domain name of the AD domain - :param policyguid: GUID of the default domain policy - :param policyguid_dc: GUID of the default domain controler policy - """ - - policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid) - create_gpo_struct(policy_path) - - policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc) - create_gpo_struct(policy_path) - - -def setup_samdb(path, setup_path, session_info, provision_backend, lp, names, - logger, domainsid, domainguid, policyguid, policyguid_dc, fill, - adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid, - serverrole, am_rodc=False, dom_for_fun_level=None, schema=None, - next_rid=1000): - """Setup a complete SAM Database. - - :note: This will wipe the main SAM database file! - """ - - - # Provision does not make much sense values larger than 1000000000 - # as the upper range of the rIDAvailablePool is 1073741823 and - # we don't want to create a domain that cannot allocate rids. - if next_rid < 1000 or next_rid > 1000000000: - error = "You want to run SAMBA 4 with a next_rid of %u, " % (next_rid) - error += "the valid range is %u-%u. The default is %u." % (1000, 1000000000, 1000) - raise ProvisioningError(error) - - # ATTENTION: Do NOT change these default values without discussion with the - # team and/or release manager. They have a big impact on the whole program! - domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2 - - if dom_for_fun_level is None: - dom_for_fun_level = DS_DOMAIN_FUNCTION_2003 - - 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!") - - domainFunctionality = dom_for_fun_level - forestFunctionality = dom_for_fun_level - - # Also wipes the database - setup_samdb_partitions(path, setup_path, logger=logger, lp=lp, - provision_backend=provision_backend, session_info=session_info, - names=names, serverrole=serverrole, schema=schema) - - if schema is None: - schema = Schema(setup_path, domainsid, schemadn=names.schemadn) - - # Load the database, but don's load the global schema and don't connect quite yet - samdb = SamDB(session_info=session_info, url=None, auto_connect=False, - credentials=provision_backend.credentials, lp=lp, global_schema=False, - am_rodc=am_rodc) - - logger.info("Pre-loading the Samba 4 and AD schema") - - # Load the schema from the one we computed earlier - samdb.set_schema(schema) - - # Set the NTDS settings DN manually - in order to have it already around - # before the provisioned tree exists and we connect - samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn) - - # And now we can connect to the DB - the schema won't be loaded from the DB - samdb.connect(path) - - if fill == FILL_DRS: - return samdb - - samdb.transaction_start() - try: - # Set the domain functionality levels onto the database. - # 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 = "" - - 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 - }) - - setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), { - "DOMAINDN": names.domaindn, - "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks - "NEXTRID": str(next_rid), - "DEFAULTSITE": names.sitename, - "CONFIGDN": names.configdn, - "POLICYGUID": policyguid, - "DOMAIN_FUNCTIONALITY": str(domainFunctionality), - "SAMBA_VERSION_STRING": version - }) - - logger.info("Adding configuration container") - descr = b64encode(get_config_descriptor(domainsid)) - setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), { - "CONFIGDN": names.configdn, - "DESCRIPTOR": descr, - }) - - # The LDIF here was created when the Schema object was constructed - logger.info("Setting up sam.ldb schema") - samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"]) - samdb.modify_ldif(schema.schema_dn_modify) - samdb.write_prefixes_from_schema() - samdb.add_ldif(schema.schema_data, controls=["relax:0"]) - setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), - {"SCHEMADN": names.schemadn}) - - logger.info("Reopening sam.ldb with new schema") - except: - samdb.transaction_cancel() - raise - else: - samdb.transaction_commit() - - samdb = SamDB(session_info=admin_session_info, auto_connect=False, - credentials=provision_backend.credentials, lp=lp, - global_schema=False, am_rodc=am_rodc) - - # Set the NTDS settings DN manually - in order to have it already around - # before the provisioned tree exists and we connect - samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn) - - samdb.connect(path) - - samdb.transaction_start() - try: - samdb.invocation_id = invocationid - - logger.info("Setting up sam.ldb configuration data") - descr = b64encode(get_sites_descriptor(domainsid)) - setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), { - "CONFIGDN": names.configdn, - "NETBIOSNAME": names.netbiosname, - "DEFAULTSITE": names.sitename, - "DNSDOMAIN": names.dnsdomain, - "DOMAIN": names.domain, - "SCHEMADN": names.schemadn, - "DOMAINDN": names.domaindn, - "SERVERDN": names.serverdn, - "FOREST_FUNCTIONALITY": str(forestFunctionality), - "DOMAIN_FUNCTIONALITY": str(domainFunctionality), - "SITES_DESCRIPTOR": descr - }) - - logger.info("Setting up display specifiers") - display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt')) - display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn}) - check_all_substituted(display_specifiers_ldif) - samdb.add_ldif(display_specifiers_ldif) - - logger.info("Adding users container") - setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), { - "DOMAINDN": names.domaindn}) - logger.info("Modifying users container") - setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), { - "DOMAINDN": names.domaindn}) - logger.info("Adding computers container") - setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), { - "DOMAINDN": names.domaindn}) - 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") - setup_add_ldif(samdb, setup_path("provision.ldif"), { - "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks - "DOMAINDN": names.domaindn, - "NETBIOSNAME": names.netbiosname, - "DEFAULTSITE": names.sitename, - "CONFIGDN": names.configdn, - "SERVERDN": names.serverdn, - "RIDAVAILABLESTART": str(next_rid + 600), - "POLICYGUID_DC": policyguid_dc - }) - - setup_modify_ldif(samdb, setup_path("provision_basedn_references.ldif"), { - "DOMAINDN": names.domaindn}) - - setup_modify_ldif(samdb, setup_path("provision_configuration_references.ldif"), { - "CONFIGDN": names.configdn, - "SCHEMADN": names.schemadn}) - if fill == FILL_FULL: - logger.info("Setting up sam.ldb users and groups") - setup_add_ldif(samdb, setup_path("provision_users.ldif"), { - "DOMAINDN": names.domaindn, - "DOMAINSID": str(domainsid), - "CONFIGDN": names.configdn, - "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')), - "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le')) - }) - - logger.info("Setting up self join") - setup_self_join(samdb, names=names, invocationid=invocationid, - dnspass=dnspass, - machinepass=machinepass, - domainsid=domainsid, - next_rid=next_rid, - policyguid=policyguid, - policyguid_dc=policyguid_dc, - setup_path=setup_path, - 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 - - -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_dir_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 set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp): - """Set ACL on the sysvol//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") - setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid)) - - 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"])) - set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp, - str(domainsid)) - -def setsysvolacl(samdb, netlogon, sysvol, gid, 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 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=...) - """ - - try: - os.chown(sysvol,-1,gid) - except: - canchown = False - else: - canchown = True - - # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level) - 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 acls on Policy folder and policies folders - set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp) - - -def provision(setup_dir, logger, session_info, - credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, - realm=None, - rootdn=None, domaindn=None, schemadn=None, configdn=None, - serverdn=None, - domain=None, hostname=None, hostip=None, hostip6=None, - domainsid=None, next_rid=1000, - adminpass=None, ldapadminpass=None, - krbtgtpass=None, domainguid=None, - policyguid=None, policyguid_dc=None, invocationid=None, - machinepass=None, ntdsguid=None, - dnspass=None, root=None, nobody=None, users=None, - wheel=None, backup=None, aci=None, serverrole=None, - dom_for_fun_level=None, - ldap_backend_extra_port=None, ldap_backend_forced_uri=None, backend_type=None, - sitename=None, - ol_mmr_urls=None, ol_olc=None, - setup_ds_path=None, slapd_path=None, nosync=False, - ldap_dryrun_mode=False, useeadb=False, am_rodc=False, - lp=None): - """Provision samba4 - - :note: caution, this wipes all existing data! - """ - - def setup_path(file): - return os.path.join(setup_dir, file) - - if domainsid is None: - domainsid = security.random_sid() - else: - domainsid = security.dom_sid(domainsid) - - # create/adapt the group policy GUIDs - # Default GUID for default policy are described at - # "How Core Group Policy Works" - # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx - if policyguid is None: - policyguid = DEFAULT_POLICY_GUID - policyguid = policyguid.upper() - if policyguid_dc is None: - policyguid_dc = DEFAULT_DC_POLICY_GUID - policyguid_dc = policyguid_dc.upper() - - if adminpass is None: - adminpass = samba.generate_random_password(12, 32) - if krbtgtpass is None: - krbtgtpass = samba.generate_random_password(128, 255) - if machinepass is None: - machinepass = samba.generate_random_password(128, 255) - if dnspass is None: - dnspass = samba.generate_random_password(128, 255) - if ldapadminpass is None: - #Make a new, random password between Samba and it's LDAP server - ldapadminpass=samba.generate_random_password(128, 255) - - if backend_type is None: - backend_type = "ldb" - - 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]) - try: - bind_gid = findnss_gid(["bind", "named"]) - except KeyError: - bind_gid = None - - if targetdir is not None: - smbconf = os.path.join(targetdir, "etc", "smb.conf") - elif smbconf is None: - smbconf = samba.param.default_path() - if not os.path.exists(os.path.dirname(smbconf)): - os.makedirs(os.path.dirname(smbconf)) - - # only install a new smb.conf if there isn't one there already - if os.path.exists(smbconf): - # if Samba Team members can't figure out the weird errors - # loading an empty smb.conf gives, then we need to be smarter. - # Pretend it just didn't exist --abartlet - data = open(smbconf, 'r').read() - data = data.lstrip() - if data is None or data == "": - make_smbconf(smbconf, setup_path, hostname, domain, realm, - serverrole, targetdir, sid_generator, useeadb, - lp=lp) - else: - make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, - targetdir, sid_generator, useeadb, lp=lp) - - if lp is 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) - paths = provision_paths_from_lp(lp, names.dnsdomain) - - paths.bind_gid = bind_gid - - if hostip is None: - logger.info("Looking up IPv4 addresses") - hostips = samba.interface_ips(lp, False) - if len(hostips) == 0: - logger.warning("No external IPv4 address has been found. Using loopback.") - hostip = '127.0.0.1' - else: - hostip = hostips[0] - if len(hostips) > 1: - logger.warning("More than one IPv4 address found. Using %s.", hostip) - - if serverrole is None: - serverrole = lp.get("server role") - - assert serverrole in ("domain controller", "member server", "standalone") - if invocationid is None: - invocationid = str(uuid.uuid4()) - - if not os.path.exists(paths.private_dir): - os.mkdir(paths.private_dir) - if not os.path.exists(os.path.join(paths.private_dir, "tls")): - os.mkdir(os.path.join(paths.private_dir, "tls")) - - ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="") - - schema = Schema(setup_path, domainsid, invocationid=invocationid, schemadn=names.schemadn) - - if backend_type == "ldb": - provision_backend = LDBBackend(backend_type, - paths=paths, setup_path=setup_path, - lp=lp, credentials=credentials, - names=names, - logger=logger) - elif backend_type == "existing": - provision_backend = ExistingBackend(backend_type, - paths=paths, setup_path=setup_path, - lp=lp, credentials=credentials, - names=names, - logger=logger, - ldap_backend_forced_uri=ldap_backend_forced_uri) - elif backend_type == "fedora-ds": - provision_backend = FDSBackend(backend_type, - paths=paths, setup_path=setup_path, - lp=lp, credentials=credentials, - names=names, - logger=logger, - domainsid=domainsid, - schema=schema, - hostname=hostname, - ldapadminpass=ldapadminpass, - slapd_path=slapd_path, - ldap_backend_extra_port=ldap_backend_extra_port, - ldap_dryrun_mode=ldap_dryrun_mode, - root=root, - setup_ds_path=setup_ds_path, - ldap_backend_forced_uri=ldap_backend_forced_uri) - elif backend_type == "openldap": - provision_backend = OpenLDAPBackend(backend_type, - paths=paths, setup_path=setup_path, - lp=lp, credentials=credentials, - names=names, - logger=logger, - domainsid=domainsid, - schema=schema, - hostname=hostname, - ldapadminpass=ldapadminpass, - slapd_path=slapd_path, - ldap_backend_extra_port=ldap_backend_extra_port, - ldap_dryrun_mode=ldap_dryrun_mode, - ol_mmr_urls=ol_mmr_urls, - nosync=nosync, - ldap_backend_forced_uri=ldap_backend_forced_uri) - else: - raise ValueError("Unknown LDAP backend type selected") - - provision_backend.init() - provision_backend.start() - - # only install a new shares config db if there is none - if not os.path.exists(paths.shareconf): - logger.info("Setting up share.ldb") - share_ldb = Ldb(paths.shareconf, session_info=session_info, - lp=lp) - share_ldb.load_ldif_file_add(setup_path("share.ldif")) - - logger.info("Setting up secrets.ldb") - secrets_ldb = setup_secretsdb(paths, setup_path, - session_info=session_info, - backend_credentials=provision_backend.secrets_credentials, lp=lp) - - try: - logger.info("Setting up the registry") - setup_registry(paths.hklm, setup_path, session_info, - lp=lp) - - logger.info("Setting up the privileges database") - setup_privileges(paths.privilege, setup_path, session_info, lp=lp) - - logger.info("Setting up idmap db") - idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info, - lp=lp) - - logger.info("Setting up SAM db") - samdb = setup_samdb(paths.samdb, setup_path, session_info, - provision_backend, lp, names, - logger=logger, - domainsid=domainsid, - schema=schema, domainguid=domainguid, - policyguid=policyguid, policyguid_dc=policyguid_dc, - fill=samdb_fill, - adminpass=adminpass, krbtgtpass=krbtgtpass, - invocationid=invocationid, - machinepass=machinepass, dnspass=dnspass, - ntdsguid=ntdsguid, serverrole=serverrole, - dom_for_fun_level=dom_for_fun_level, - am_rodc=am_rodc, next_rid=next_rid) - - if serverrole == "domain controller": - if paths.netlogon is None: - logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.") - logger.info("Please either remove %s or see the template at %s" % - (paths.smbconf, setup_path("provision.smb.conf.dc"))) - assert paths.netlogon is not None - - if paths.sysvol is None: - logger.info("Existing smb.conf does not have a [sysvol] share, but you" - " are configuring a DC.") - logger.info("Please either remove %s or see the template at %s" % - (paths.smbconf, setup_path("provision.smb.conf.dc"))) - assert paths.sysvol is not None - - if not os.path.isdir(paths.netlogon): - os.makedirs(paths.netlogon, 0755) - - if samdb_fill == FILL_FULL: - setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn, - root_uid=root_uid, nobody_uid=nobody_uid, - users_gid=users_gid, wheel_gid=wheel_gid) - - if serverrole == "domain controller": - # Set up group policies (domain policy and domain controller policy) - create_default_gpo(paths.sysvol, names.dnsdomain, policyguid, policyguid_dc) - setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid, - domainsid, names.dnsdomain, names.domaindn, lp) - - logger.info("Setting up sam.ldb rootDSE marking as synchronized") - setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif")) - - secretsdb_self_join(secrets_ldb, domain=names.domain, - realm=names.realm, - dnsdomain=names.dnsdomain, - netbiosname=names.netbiosname, - domainsid=domainsid, - machinepass=machinepass, - secure_channel_type=SEC_CHAN_BDC) - - # Now set up the right msDS-SupportedEncryptionTypes into the DB - # In future, this might be determined from some configuration - kerberos_enctypes = str(ENC_ALL_TYPES) - - try: - msg = ldb.Message(ldb.Dn(samdb, samdb.searchone("distinguishedName", expression="samAccountName=%s$" % names.netbiosname, scope=ldb.SCOPE_SUBTREE))) - msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(elements=kerberos_enctypes, - flags=ldb.FLAG_MOD_REPLACE, - name="msDS-SupportedEncryptionTypes") - samdb.modify(msg) - except ldb.LdbError, (ldb.ERR_NO_SUCH_ATTRIBUTE, _): - # It might be that this attribute does not exist in this schema - pass - - - if serverrole == "domain controller": - secretsdb_setup_dns(secrets_ldb, setup_path, names, - paths.private_dir, - realm=names.realm, dnsdomain=names.dnsdomain, - dns_keytab_path=paths.dns_keytab, - dnspass=dnspass) - - domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID") - assert isinstance(domainguid, str) - - # Only make a zone file on the first DC, it should be replicated - # with DNS replication - create_zone_file(lp, logger, paths, targetdir, setup_path, - dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6, - hostname=names.hostname, realm=names.realm, - domainguid=domainguid, ntdsguid=names.ntdsguid) - - create_named_conf(paths, setup_path, realm=names.realm, - dnsdomain=names.dnsdomain, private_dir=paths.private_dir) - - create_named_txt(paths.namedtxt, setup_path, realm=names.realm, - dnsdomain=names.dnsdomain, private_dir=paths.private_dir, - keytab_name=paths.dns_keytab) - logger.info("See %s for an example configuration include file for BIND", paths.namedconf) - logger.info("and %s for further documentation required for secure DNS " - "updates", paths.namedtxt) - - lastProvisionUSNs = get_last_provision_usn(samdb) - maxUSN = get_max_usn(samdb, str(names.rootdn)) - if lastProvisionUSNs is not None: - update_provision_usn(samdb, 0, maxUSN, 1) - else: - set_provision_usn(samdb, 0, maxUSN) - - create_krb5_conf(paths.krb5conf, setup_path, - dnsdomain=names.dnsdomain, hostname=names.hostname, - realm=names.realm) - logger.info("A Kerberos configuration suitable for Samba 4 has been " - "generated at %s", paths.krb5conf) - - if serverrole == "domain controller": - create_dns_update_list(lp, logger, paths, setup_path) - - provision_backend.post_setup() - provision_backend.shutdown() - - create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, - ldapi_url) - except: - secrets_ldb.transaction_cancel() - raise - - #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: - if not os.environ.has_key('SAMBA_SELFTEST'): - logger.info("Failed to chown %s to bind gid %u", dns_keytab_path, - paths.bind_gid) - - - logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php", - paths.phpldapadminconfig) - - logger.info("Once the above files are installed, your Samba4 server will be ready to use") - logger.info("Server Role: %s" % serverrole) - logger.info("Hostname: %s" % names.hostname) - logger.info("NetBIOS Domain: %s" % names.domain) - logger.info("DNS Domain: %s" % names.dnsdomain) - logger.info("DOMAIN SID: %s" % str(domainsid)) - if samdb_fill == FILL_FULL: - logger.info("Admin password: %s" % adminpass) - if provision_backend.type is not "ldb": - if provision_backend.credentials.get_bind_dn() is not None: - logger.info("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn()) - else: - logger.info("LDAP Admin User: %s" % provision_backend.credentials.get_username()) - - logger.info("LDAP Admin Password: %s" % provision_backend.credentials.get_password()) - - if provision_backend.slapd_command_escaped is not None: - # now display slapd_command_file.txt to show how slapd must be started next time - logger.info("Use later the following commandline to start slapd, then Samba:") - logger.info(provision_backend.slapd_command_escaped) - logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh", - provision_backend.ldapdir) - - result = ProvisionResult() - result.domaindn = domaindn - result.paths = paths - result.lp = lp - result.samdb = samdb - return result - - -def provision_become_dc(setup_dir=None, - smbconf=None, targetdir=None, realm=None, - rootdn=None, domaindn=None, schemadn=None, - configdn=None, serverdn=None, - domain=None, hostname=None, domainsid=None, - adminpass=None, krbtgtpass=None, domainguid=None, - policyguid=None, policyguid_dc=None, invocationid=None, - machinepass=None, - dnspass=None, root=None, nobody=None, users=None, - wheel=None, backup=None, serverrole=None, - ldap_backend=None, ldap_backend_type=None, - sitename=None, debuglevel=1): - - logger = logging.getLogger("provision") - samba.set_debug_level(debuglevel) - - res = provision(setup_dir, logger, system_session(), None, - smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, - realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, - configdn=configdn, serverdn=serverdn, domain=domain, - hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, - machinepass=machinepass, serverrole="domain controller", - sitename=sitename) - res.lp.set("debuglevel", str(debuglevel)) - return res - - -def create_phpldapadmin_config(path, setup_path, ldapi_uri): - """Create a PHP LDAP admin configuration file. - - :param path: Path to write the configuration to. - :param setup_path: Function to generate setup paths. - """ - setup_file(setup_path("phpldapadmin-config.php"), path, - {"S4_LDAPI_URI": ldapi_uri}) - - -def create_zone_file(lp, logger, paths, targetdir, setup_path, dnsdomain, - hostip, hostip6, hostname, realm, domainguid, - ntdsguid): - """Write out a DNS zone file, from the info in the current database. - - :param paths: paths object - :param setup_path: Setup path function. - :param dnsdomain: DNS Domain name - :param domaindn: DN of the Domain - :param hostip: Local IPv4 IP - :param hostip6: Local IPv6 IP - :param hostname: Local hostname - :param realm: Realm name - :param domainguid: GUID of the domain. - :param ntdsguid: GUID of the hosts nTDSDSA record. - """ - assert isinstance(domainguid, str) - - if hostip6 is not None: - hostip6_base_line = " IN AAAA " + hostip6 - hostip6_host_line = hostname + " IN AAAA " + hostip6 - gc_msdcs_ip6_line = "gc._msdcs IN AAAA " + hostip6 - else: - hostip6_base_line = "" - hostip6_host_line = "" - gc_msdcs_ip6_line = "" - - if hostip is not None: - hostip_base_line = " IN A " + hostip - hostip_host_line = hostname + " IN A " + hostip - gc_msdcs_ip_line = "gc._msdcs IN A " + hostip - else: - hostip_base_line = "" - hostip_host_line = "" - gc_msdcs_ip_line = "" - - dns_dir = os.path.dirname(paths.dns) - - try: - shutil.rmtree(dns_dir, True) - except OSError: - pass - - os.mkdir(dns_dir, 0775) - - # we need to freeze the zone while we update the contents - if targetdir is None: - rndc = ' '.join(lp.get("rndc command")) - os.system(rndc + " freeze " + lp.get("realm")) - - setup_file(setup_path("provision.zone"), paths.dns, { - "HOSTNAME": hostname, - "DNSDOMAIN": dnsdomain, - "REALM": realm, - "HOSTIP_BASE_LINE": hostip_base_line, - "HOSTIP_HOST_LINE": hostip_host_line, - "DOMAINGUID": domainguid, - "DATESTRING": time.strftime("%Y%m%d%H"), - "DEFAULTSITE": DEFAULTSITE, - "NTDSGUID": ntdsguid, - "HOSTIP6_BASE_LINE": hostip6_base_line, - "HOSTIP6_HOST_LINE": hostip6_host_line, - "GC_MSDCS_IP_LINE": gc_msdcs_ip_line, - "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line, - }) - - # note that we use no variable substitution on this file - # the substitution is done at runtime by samba_dnsupdate - setup_file(setup_path("dns_update_list"), paths.dns_update_list, None) - - # and the SPN update list - setup_file(setup_path("spn_update_list"), paths.spn_update_list, None) - - if paths.bind_gid is not None: - try: - os.chown(dns_dir, -1, paths.bind_gid) - os.chown(paths.dns, -1, paths.bind_gid) - # chmod needed to cope with umask - os.chmod(dns_dir, 0775) - os.chmod(paths.dns, 0664) - except OSError: - if not os.environ.has_key('SAMBA_SELFTEST'): - logger.error("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid)) - - if targetdir is None: - os.system(rndc + " unfreeze " + lp.get("realm")) - - -def create_dns_update_list(lp, logger, paths, setup_path): - """Write out a dns_update_list file""" - # note that we use no variable substitution on this file - # the substitution is done at runtime by samba_dnsupdate - setup_file(setup_path("dns_update_list"), paths.dns_update_list, None) - setup_file(setup_path("spn_update_list"), paths.spn_update_list, None) - - -def create_named_conf(paths, setup_path, realm, dnsdomain, - private_dir): - """Write out a file containing zone statements suitable for inclusion in a - named.conf file (including GSS-TSIG configuration). - - :param paths: all paths - :param setup_path: Setup path function. - :param realm: Realm name - :param dnsdomain: DNS Domain name - :param private_dir: Path to private directory - :param keytab_name: File name of DNS keytab file - """ - - setup_file(setup_path("named.conf"), paths.namedconf, { - "DNSDOMAIN": dnsdomain, - "REALM": realm, - "ZONE_FILE": paths.dns, - "REALM_WC": "*." + ".".join(realm.split(".")[1:]), - "NAMED_CONF": paths.namedconf, - "NAMED_CONF_UPDATE": paths.namedconf_update - }) - - setup_file(setup_path("named.conf.update"), paths.namedconf_update) - - -def create_named_txt(path, setup_path, realm, dnsdomain, - private_dir, keytab_name): - """Write out a file containing zone statements suitable for inclusion in a - named.conf file (including GSS-TSIG configuration). - - :param path: Path of the new named.conf file. - :param setup_path: Setup path function. - :param realm: Realm name - :param dnsdomain: DNS Domain name - :param private_dir: Path to private directory - :param keytab_name: File name of DNS keytab file - """ - - setup_file(setup_path("named.txt"), path, { - "DNSDOMAIN": dnsdomain, - "REALM": realm, - "DNS_KEYTAB": keytab_name, - "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name), - "PRIVATE_DIR": private_dir - }) - - -def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm): - """Write out a file containing zone statements suitable for inclusion in a - named.conf file (including GSS-TSIG configuration). - - :param path: Path of the new named.conf file. - :param setup_path: Setup path function. - :param dnsdomain: DNS Domain name - :param hostname: Local hostname - :param realm: Realm name - """ - setup_file(setup_path("krb5.conf"), path, { - "DNSDOMAIN": dnsdomain, - "HOSTNAME": hostname, - "REALM": realm, - }) - - -class ProvisioningError(Exception): - """A generic provision error.""" - - def __init__(self, value): - self.value = value - - def __str__(self): - return "ProvisioningError: " + self.value - - -class InvalidNetbiosName(Exception): - """A specified name was not a valid NetBIOS name.""" - def __init__(self, name): - super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name) diff --git a/source4/scripting/python/samba/provision/__init__.py b/source4/scripting/python/samba/provision/__init__.py new file mode 100644 index 0000000000..1fed220507 --- /dev/null +++ b/source4/scripting/python/samba/provision/__init__.py @@ -0,0 +1,1960 @@ + +# Unix SMB/CIFS implementation. +# backend code for provisioning a Samba4 server + +# Copyright (C) Jelmer Vernooij 2007-2010 +# Copyright (C) Andrew Bartlett 2008-2009 +# Copyright (C) Oliver Liebel 2008-2009 +# +# Based on the original in EJS: +# Copyright (C) Andrew Tridgell 2005 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +"""Functions for setting up a Samba configuration.""" + +__docformat__ = "restructuredText" + +from base64 import b64encode +import os +import re +import pwd +import grp +import logging +import time +import uuid +import socket +import urllib +import shutil + +import ldb + +from samba.auth import system_session, admin_session +import samba +from samba import ( + Ldb, + check_all_substituted, + in_source_tree, + read_and_sub_file, + setup_file, + substitute_var, + valid_netbios_name, + version, + ) +from samba.dsdb import ( + DS_DOMAIN_FUNCTION_2003, + DS_DOMAIN_FUNCTION_2008_R2, + ENC_ALL_TYPES, + ) +from samba.dcerpc import security +from samba.dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA +from samba.idmap import IDmapDB +from samba.ms_display_specifiers import read_ms_ldif +from samba.ntacls import setntacl, dsacl2fsacl +from samba.ndr import ndr_pack,ndr_unpack +from samba.provision.backend import ( + ExistingBackend, + FDSBackend, + LDBBackend, + OpenLDAPBackend, + ) +import samba.param +import samba.registry +from samba.schema import Schema +from samba.samdb import SamDB + +VALID_NETBIOS_CHARS = " !#$%&'()-.@^_{}~" +DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9" +DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9" +DEFAULTSITE = "Default-First-Site-Name" +LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN" + + +def find_setup_dir(): + """Find the setup directory used by provision.""" + if in_source_tree(): + # In source tree + dirname = os.path.dirname(__file__) + return os.path.normpath(os.path.join(dirname, "../../../setup")) + else: + import sys + for prefix in [sys.prefix, + os.path.join(os.path.dirname(__file__), "../../../..")]: + for suffix in ["share/setup", "share/samba/setup", "setup"]: + ret = os.path.normpath(os.path.join(prefix, suffix)) + if os.path.isdir(ret): + return ret + raise Exception("Unable to find setup directory.") + +# Descriptors of naming contexts and other important objects + +# "get_schema_descriptor" is located in "schema.py" + +def get_sites_descriptor(domain_sid): + sddl = "O:EAG:EAD:AI(A;;RPLCLORC;;;AU)" \ + "(A;;RPWPCRCCLCLORCWOWDSW;;;EA)" \ + "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \ + "(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \ + "(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \ + "S:AI(AU;CISA;CCDCSDDT;;;WD)" \ + "(OU;CIIOSA;CR;;f0f8ffab-1191-11d0-a060-00aa006c33ed;WD)" \ + "(OU;CIIOSA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \ + "(OU;CIIOSA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \ + "(OU;CIIOSA;WP;3e10944c-c354-11d0-aff8-0000f80367c1;b7b13124-b82e-11d0-afee-0000f80367c1;WD)" + sec = security.descriptor.from_sddl(sddl, domain_sid) + return ndr_pack(sec) + + +def get_config_descriptor(domain_sid): + sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ + "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ + "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ + "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ + "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ + "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ + "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \ + "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \ + "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ + "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \ + "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ + "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \ + "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \ + "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \ + "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)" + sec = security.descriptor.from_sddl(sddl, domain_sid) + return ndr_pack(sec) + + +def get_domain_descriptor(domain_sid): + sddl= "O:BAG:BAD:AI(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ + "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ + "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ + "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ + "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ + "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ + "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ + "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ + "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ + "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ + "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \ + "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;DD)" \ + "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;ED)" \ + "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;ED)" \ + "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;ED)" \ + "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \ + "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ + "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ + "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ + "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ + "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \ + "(OA;;CR;e2a36dc9-ae17-47c3-b58b-be34c55ba633;;IF)" \ + "(OA;;RP;c7407360-20bf-11d0-a768-00aa006e0529;;RU)" \ + "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;RU)" \ + "(OA;CIIO;RPLCLORC;;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \ + "(OA;CIIO;RPLCLORC;;bf967a9c-0de6-11d0-a285-00aa003049e2;RU)" \ + "(OA;CIIO;RPLCLORC;;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \ + "(OA;;CR;05c74c5e-4deb-43b4-bd9f-86664c2a7fd5;;AU)" \ + "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \ + "(OA;;CR;ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501;;AU)" \ + "(OA;;CR;280f369c-67c7-438e-ae98-1d46f3c6f541;;AU)" \ + "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ + "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ + "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ + "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ + "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;AU)" \ + "(OA;CIIO;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \ + "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \ + "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \ + "(A;;RPRC;;;RU)" \ + "(A;CI;LC;;;RU)" \ + "(A;CI;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \ + "(A;;RP;;;WD)" \ + "(A;;RPLCLORC;;;ED)" \ + "(A;;RPLCLORC;;;AU)" \ + "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \ + "S:AI(OU;CISA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \ + "(OU;CISA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \ + "(AU;SA;CR;;;DU)(AU;SA;CR;;;BA)(AU;SA;WPWOWD;;;WD)" + sec = security.descriptor.from_sddl(sddl, domain_sid) + return ndr_pack(sec) + + +class ProvisionPaths(object): + + def __init__(self): + self.shareconf = None + self.hklm = None + self.hkcu = None + self.hkcr = None + self.hku = None + self.hkpd = None + self.hkpt = None + self.samdb = None + self.idmapdb = None + self.secrets = None + self.keytab = None + self.dns_keytab = None + self.dns = None + self.winsdb = None + self.private_dir = None + + +class ProvisionNames(object): + + def __init__(self): + self.rootdn = None + self.domaindn = None + self.configdn = None + self.schemadn = None + self.ldapmanagerdn = None + self.dnsdomain = None + self.realm = None + self.netbiosname = None + self.domain = None + self.hostname = None + self.sitename = None + self.smbconf = None + + +def update_provision_usn(samdb, low, high, replace=False): + """Update the field provisionUSN in sam.ldb + + This field is used to track range of USN modified by provision and + upgradeprovision. + This value is used afterward by next provision to figure out if + the field have been modified since last provision. + + :param samdb: An LDB object connect to sam.ldb + :param low: The lowest USN modified by this upgrade + :param high: The highest USN modified by this upgrade + :param replace: A boolean indicating if the range should replace any + existing one or appended (default) + """ + + tab = [] + if not replace: + entry = samdb.search(expression="(&(dn=@PROVISION)(%s=*))" % + LAST_PROVISION_USN_ATTRIBUTE, base="", + scope=ldb.SCOPE_SUBTREE, + attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"]) + for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]: + tab.append(str(e)) + + tab.append("%s-%s" % (low, high)) + 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) + samdb.modify(delta) + + +def set_provision_usn(samdb, low, high): + """Set the field provisionUSN in sam.ldb + This field is used to track range of USN modified by provision and + upgradeprovision. + This value is used afterward by next provision to figure out if + the field have been modified since last provision. + + :param samdb: An LDB object connect to sam.ldb + :param low: The lowest USN modified by this upgrade + :param high: The highest USN modified by this upgrade""" + tab = [] + tab.append("%s-%s" % (low, high)) + 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) + samdb.add(delta) + + +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 + :param basedn: A string containing the base DN of the provision + (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"]) + return res[0]["uSNChanged"] + + +def get_last_provision_usn(sam): + """Get the lastest USN modified by a provision or an upgradeprovision + + :param sam: An LDB object pointing to the sam.ldb + :return: an integer corresponding to the highest USN modified by + (upgrade)provision, 0 is this value is unknown + """ + entry = sam.search(expression="(&(dn=@PROVISION)(%s=*))" % + LAST_PROVISION_USN_ATTRIBUTE, + base="", scope=ldb.SCOPE_SUBTREE, + attrs=[LAST_PROVISION_USN_ATTRIBUTE]) + if len(entry): + range = [] + idx = 0 + p = re.compile(r'-') + for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]: + tab = p.split(str(r)) + range.append(tab[0]) + range.append(tab[1]) + idx = idx + 1 + return range + else: + return None + + +class ProvisionResult(object): + + def __init__(self): + self.paths = None + self.domaindn = None + self.lp = None + self.samdb = None + + +def check_install(lp, session_info, credentials): + """Check whether the current install seems ok. + + :param lp: Loadparm context + :param session_info: Session information + :param credentials: Credentials + """ + if lp.get("realm") == "": + raise Exception("Realm empty") + samdb = Ldb(lp.get("sam database"), session_info=session_info, + credentials=credentials, lp=lp) + if len(samdb.search("(cn=Administrator)")) != 1: + raise ProvisioningError("No administrator account found") + + +def findnss(nssfn, names): + """Find a user or group from a list of possibilities. + + :param nssfn: NSS Function to try (should raise KeyError if not found) + :param names: Names to check. + :return: Value return by first names list. + """ + for name in names: + try: + return nssfn(name) + except KeyError: + pass + 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 setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]): + """Setup a ldb in the private dir. + + :param ldb: LDB file to import data into + :param ldif_path: Path of the LDIF file to load + :param subst_vars: Optional variables to subsitute in LDIF. + :param nocontrols: Optional list of controls, can be None for no controls + """ + assert isinstance(ldif_path, str) + data = read_and_sub_file(ldif_path, subst_vars) + ldb.add_ldif(data, controls) + + +def setup_modify_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]): + """Modify a ldb in the private dir. + + :param ldb: LDB object. + :param ldif_path: LDIF file path. + :param subst_vars: Optional dictionary with substitution variables. + """ + data = read_and_sub_file(ldif_path, subst_vars) + ldb.modify_ldif(data, controls) + + +def setup_ldb(ldb, ldif_path, subst_vars): + """Import a LDIF a file into a LDB handle, optionally substituting + variables. + + :note: Either all LDIF data will be added or none (using transactions). + + :param ldb: LDB file to import into. + :param ldif_path: Path to the LDIF file. + :param subst_vars: Dictionary with substitution variables. + """ + assert ldb is not None + ldb.transaction_start() + try: + setup_add_ldif(ldb, ldif_path, subst_vars) + except: + ldb.transaction_cancel() + raise + else: + ldb.transaction_commit() + + +def provision_paths_from_lp(lp, dnsdomain): + """Set the default paths for provisioning. + + :param lp: Loadparm context. + :param dnsdomain: DNS Domain name + """ + paths = ProvisionPaths() + paths.private_dir = lp.get("private dir") + + # This is stored without path prefix for the "privateKeytab" attribute in + # "secrets_dns.ldif". + paths.dns_keytab = "dns.keytab" + paths.keytab = "secrets.keytab" + + paths.shareconf = os.path.join(paths.private_dir, "share.ldb") + paths.samdb = os.path.join(paths.private_dir, + lp.get("sam database") or "samdb.ldb") + paths.idmapdb = os.path.join(paths.private_dir, + lp.get("idmap database") or "idmap.ldb") + paths.secrets = os.path.join(paths.private_dir, + lp.get("secrets database") or "secrets.ldb") + paths.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.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" + paths.hku = "hku.ldb" + paths.hkpd = "hkpd.ldb" + paths.hkpt = "hkpt.ldb" + paths.sysvol = lp.get("path", "sysvol") + paths.netlogon = lp.get("path", "netlogon") + paths.smbconf = lp.configfile + return paths + + +def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, + serverrole=None, rootdn=None, domaindn=None, configdn=None, + schemadn=None, serverdn=None, sitename=None): + """Guess configuration settings to use.""" + + if hostname is None: + hostname = socket.gethostname().split(".")[0] + + netbiosname = lp.get("netbios name") + if netbiosname is None: + netbiosname = hostname + # remove forbidden chars + newnbname = "" + for x in netbiosname: + if x.isalnum() or x in VALID_NETBIOS_CHARS: + newnbname = "%s%c" % (newnbname, x) + #force the length to be <16 + netbiosname = newnbname[0:15] + assert netbiosname is not None + netbiosname = netbiosname.upper() + if not valid_netbios_name(netbiosname): + raise InvalidNetbiosName(netbiosname) + + 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) + + dnsdomain = dnsdomain.lower() + + if serverrole is None: + serverrole = lp.get("server role") + if serverrole is None: + raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile) + + serverrole = serverrole.lower() + + realm = dnsdomain.upper() + + if lp.get("realm") == "": + 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)) + + if lp.get("server role").lower() != serverrole: + raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'! Please remove the smb.conf file and let provision generate it" % (lp.get("server role").upper(), serverrole, lp.configfile)) + + if serverrole == "domain controller": + if domain is None: + # This will, for better or worse, default to 'WORKGROUP' + domain = lp.get("workgroup") + domain = domain.upper() + + if lp.get("workgroup").upper() != domain: + raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'! Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile)) + + if domaindn is None: + domaindn = "DC=" + dnsdomain.replace(".", ",DC=") + else: + domain = netbiosname + if domaindn is None: + domaindn = "DC=" + netbiosname + + if not valid_netbios_name(domain): + raise InvalidNetbiosName(domain) + + if hostname.upper() == realm: + raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname)) + if netbiosname == realm: + raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname)) + if domain == realm: + raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain)) + + if rootdn is None: + rootdn = domaindn + + if configdn is None: + configdn = "CN=Configuration," + rootdn + if schemadn is None: + schemadn = "CN=Schema," + configdn + + if sitename is None: + sitename=DEFAULTSITE + + names = ProvisionNames() + names.rootdn = rootdn + names.domaindn = domaindn + names.configdn = configdn + names.schemadn = schemadn + names.ldapmanagerdn = "CN=Manager," + rootdn + names.dnsdomain = dnsdomain + names.domain = domain + names.realm = realm + names.netbiosname = netbiosname + names.hostname = hostname + names.sitename = sitename + names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % ( + netbiosname, sitename, configdn) + + return names + + +def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, + targetdir, sid_generator="internal", eadb=False, lp=None): + """Create a new smb.conf file based on a couple of basic settings. + """ + assert smbconf is not None + if hostname is None: + hostname = socket.gethostname().split(".")[0] + netbiosname = hostname.upper() + # remove forbidden chars + newnbname = "" + for x in netbiosname: + if x.isalnum() or x in VALID_NETBIOS_CHARS: + newnbname = "%s%c" % (newnbname, x) + #force the length to be <16 + netbiosname = newnbname[0:15] + else: + netbiosname = hostname.upper() + + if serverrole is None: + serverrole = "standalone" + + assert serverrole in ("domain controller", "member server", "standalone") + if serverrole == "domain controller": + smbconfsuffix = "dc" + elif serverrole == "member server": + smbconfsuffix = "member" + elif serverrole == "standalone": + smbconfsuffix = "standalone" + + if sid_generator is None: + sid_generator = "internal" + + assert domain is not None + domain = domain.upper() + + assert realm is not None + realm = realm.upper() + + if lp is None: + lp = samba.param.LoadParm() + #Load non-existant file + if os.path.exists(smbconf): + lp.load(smbconf) + if eadb and not lp.get("posix:eadb"): + if targetdir is not None: + privdir = os.path.join(targetdir, "private") + else: + privdir = lp.get("private dir") + lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb"))) + + if targetdir is not None: + privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private")) + lockdir_line = "lock dir = " + os.path.abspath(targetdir) + + lp.set("lock dir", os.path.abspath(targetdir)) + else: + privatedir_line = "" + lockdir_line = "" + + if sid_generator == "internal": + sid_generator_line = "" + else: + sid_generator_line = "sid generator = " + sid_generator + + used_setup_dir = setup_path("") + default_setup_dir = lp.get("setup directory") + setupdir_line = "" + if used_setup_dir != default_setup_dir: + setupdir_line = "setup directory = %s" % used_setup_dir + lp.set("setup directory", used_setup_dir) + + sysvol = os.path.join(lp.get("lock dir"), "sysvol") + netlogon = os.path.join(sysvol, realm.lower(), "scripts") + + setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), + smbconf, { + "NETBIOS_NAME": netbiosname, + "DOMAIN": domain, + "REALM": realm, + "SERVERROLE": serverrole, + "NETLOGONPATH": netlogon, + "SYSVOLPATH": sysvol, + "SETUPDIRECTORY_LINE": setupdir_line, + "SIDGENERATOR_LINE": sid_generator_line, + "PRIVATEDIR_LINE": privatedir_line, + "LOCKDIR_LINE": lockdir_line + }) + + # reload the smb.conf + lp.load(smbconf) + + # 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') + lp.dump(f, False) + f.close() + + + +def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid, + users_gid, wheel_gid): + """setup reasonable name mappings for sam names to unix names. + + :param samdb: SamDB object. + :param idmap: IDmap db object. + :param sid: The domain sid. + :param domaindn: The domain DN. + :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. + """ + idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid) + idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid) + + idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid) + idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid) + + +def setup_samdb_partitions(samdb_path, setup_path, logger, lp, session_info, + provision_backend, names, schema, serverrole, + erase=False): + """Setup the partitions for the SAM database. + + Alternatively, provision() may call this, and then populate the database. + + :note: This will wipe the Sam Database! + + :note: This function always removes the local SAM LDB file. The erase + parameter controls whether to erase the existing data, which + may not be stored locally but in LDAP. + + """ + assert session_info is not None + + # We use options=["modules:"] to stop the modules loading - we + # just want to wipe and re-initialise the database, not start it up + + try: + os.unlink(samdb_path) + except OSError: + pass + + samdb = Ldb(url=samdb_path, session_info=session_info, + lp=lp, options=["modules:"]) + + ldap_backend_line = "# No LDAP backend" + if provision_backend.type is not "ldb": + ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri + + samdb.transaction_start() + try: + logger.info("Setting up sam.ldb partitions and settings") + setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), { + "SCHEMADN": ldb.Dn(schema.ldb, names.schemadn).get_casefold(), + "CONFIGDN": ldb.Dn(schema.ldb, names.configdn).get_casefold(), + "DOMAINDN": ldb.Dn(schema.ldb, names.domaindn).get_casefold(), + "LDAP_BACKEND_LINE": ldap_backend_line, + }) + + + setup_add_ldif(samdb, setup_path("provision_init.ldif"), { + "BACKEND_TYPE": provision_backend.type, + "SERVER_ROLE": serverrole + }) + + logger.info("Setting up sam.ldb rootDSE") + setup_samdb_rootdse(samdb, setup_path, names) + except: + samdb.transaction_cancel() + raise + else: + samdb.transaction_commit() + + +def secretsdb_self_join(secretsdb, domain, + netbiosname, machinepass, domainsid=None, + realm=None, dnsdomain=None, + keytab_path=None, + key_version_number=1, + secure_channel_type=SEC_CHAN_WKSTA): + """Add domain join-specific bits to a secrets database. + + :param secretsdb: Ldb Handle to the secrets database + :param machinepass: Machine password + """ + attrs = ["whenChanged", + "secret", + "priorSecret", + "priorChanged", + "krb5Keytab", + "privateKeytab"] + + if realm is not None: + if dnsdomain is None: + dnsdomain = realm.lower() + dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower()) + else: + dnsname = None + shortname = netbiosname.lower() + + # We don't need to set msg["flatname"] here, because rdn_name will handle + # it, and it causes problems for modifies anyway + msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain)) + msg["secureChannelType"] = [str(secure_channel_type)] + msg["objectClass"] = ["top", "primaryDomain"] + if dnsname is not None: + msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"] + msg["realm"] = [realm] + msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())] + msg["msDS-KeyVersionNumber"] = [str(key_version_number)] + msg["privateKeytab"] = ["secrets.keytab"] + + msg["secret"] = [machinepass] + msg["samAccountName"] = ["%s$" % netbiosname] + msg["secureChannelType"] = [str(secure_channel_type)] + if domainsid is not None: + msg["objectSid"] = [ndr_pack(domainsid)] + + # This complex expression tries to ensure that we don't have more + # than one record for this SID, realm or netbios domain at a time, + # but we don't delete the old record that we are about to modify, + # because that would delete the keytab and previous password. + res = secretsdb.search(base="cn=Primary Domains", + attrs=attrs, + expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))), + scope=ldb.SCOPE_ONELEVEL) + + for del_msg in res: + secretsdb.delete(del_msg.dn) + + res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE) + + if len(res) == 1: + msg["priorSecret"] = [res[0]["secret"][0]] + msg["priorWhenChanged"] = [res[0]["whenChanged"][0]] + + try: + msg["privateKeytab"] = [res[0]["privateKeytab"][0]] + except KeyError: + pass + + try: + msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]] + except KeyError: + pass + + for el in msg: + if el != 'dn': + msg[el].set_flags(ldb.FLAG_MOD_REPLACE) + secretsdb.modify(msg) + secretsdb.rename(res[0].dn, msg.dn) + else: + 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 ]) + msg["servicePrincipalName"] = spn + + secretsdb.add(msg) + + +def secretsdb_setup_dns(secretsdb, setup_path, names, private_dir, realm, + dnsdomain, dns_keytab_path, dnspass): + """Add DNS specific bits to a secrets database. + + :param secretsdb: Ldb Handle to the secrets database + :param setup_path: Setup path function + :param machinepass: Machine password + """ + try: + os.unlink(os.path.join(private_dir, dns_keytab_path)) + except OSError: + pass + + setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { + "REALM": realm, + "DNSDOMAIN": dnsdomain, + "DNS_KEYTAB": dns_keytab_path, + "DNSPASS_B64": b64encode(dnspass), + "HOSTNAME": names.hostname, + "DNSNAME" : '%s.%s' % ( + names.netbiosname.lower(), names.dnsdomain.lower()) + }) + + +def setup_secretsdb(paths, setup_path, session_info, backend_credentials, lp): + """Setup the secrets database. + + :note: This function does not handle exceptions and transaction on purpose, + it's up to the caller to do this job. + + :param path: Path to the secrets database. + :param setup_path: Get the path to a setup file. + :param session_info: Session info. + :param credentials: Credentials + :param lp: Loadparm context + :return: LDB handle for the created secrets database + """ + if os.path.exists(paths.secrets): + os.unlink(paths.secrets) + + keytab_path = os.path.join(paths.private_dir, paths.keytab) + if os.path.exists(keytab_path): + os.unlink(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) + + path = paths.secrets + + secrets_ldb = Ldb(path, session_info=session_info, + lp=lp) + secrets_ldb.erase() + secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif")) + secrets_ldb = Ldb(path, session_info=session_info, + lp=lp) + secrets_ldb.transaction_start() + try: + secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif")) + + if (backend_credentials is not None and + 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()) + }) + 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()) + }) + + return secrets_ldb + except: + secrets_ldb.transaction_cancel() + raise + + +def setup_privileges(path, setup_path, session_info, lp): + """Setup the privileges database. + + :param path: Path to the privileges database. + :param setup_path: Get the path to a setup file. + :param session_info: Session info. + :param credentials: Credentials + :param lp: Loadparm context + :return: LDB handle for the created secrets database + """ + if os.path.exists(path): + os.unlink(path) + privilege_ldb = Ldb(path, session_info=session_info, lp=lp) + privilege_ldb.erase() + privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif")) + + +def setup_registry(path, setup_path, session_info, lp): + """Setup the registry. + + :param path: Path to the registry database + :param setup_path: Function that returns the path to a setup. + :param session_info: Session information + :param credentials: Credentials + :param lp: Loadparm context + """ + reg = samba.registry.Registry() + hive = samba.registry.open_ldb(path, session_info=session_info, + lp_ctx=lp) + reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE) + provision_reg = setup_path("provision.reg") + assert os.path.exists(provision_reg) + reg.diff_apply(provision_reg) + + +def setup_idmapdb(path, setup_path, session_info, lp): + """Setup the idmap database. + + :param path: path to the idmap database + :param setup_path: Function that returns a path to a setup file + :param session_info: Session information + :param credentials: Credentials + :param lp: Loadparm context + """ + if os.path.exists(path): + os.unlink(path) + + idmap_ldb = IDmapDB(path, session_info=session_info, lp=lp) + idmap_ldb.erase() + idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif")) + return idmap_ldb + + +def setup_samdb_rootdse(samdb, setup_path, names): + """Setup the SamDB rootdse. + + :param samdb: Sam Database handle + :param setup_path: Obtain setup path + """ + setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), { + "SCHEMADN": names.schemadn, + "DOMAINDN": names.domaindn, + "ROOTDN": names.rootdn, + "CONFIGDN": names.configdn, + "SERVERDN": names.serverdn, + }) + + +def setup_self_join(samdb, names, + machinepass, dnspass, + domainsid, next_rid, invocationid, setup_path, + policyguid, policyguid_dc, domainControllerFunctionality, + ntdsguid): + """Join a host to its own domain.""" + assert isinstance(invocationid, str) + if ntdsguid is not None: + ntdsguid_line = "objectGUID: %s\n"%ntdsguid + else: + ntdsguid_line = "" + setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { + "CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn, + "DOMAINDN": names.domaindn, + "SERVERDN": names.serverdn, + "INVOCATIONID": invocationid, + "NETBIOSNAME": names.netbiosname, + "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain), + "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')), + "DOMAINSID": str(domainsid), + "DCRID": str(next_rid), + "SAMBA_VERSION_STRING": version, + "NTDSGUID": ntdsguid_line, + "DOMAIN_CONTROLLER_FUNCTIONALITY": str( + domainControllerFunctionality)}) + + setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { + "POLICYGUID": policyguid, + "POLICYGUID_DC": policyguid_dc, + "DNSDOMAIN": names.dnsdomain, + "DOMAINDN": names.domaindn}) + + # add the NTDSGUID based SPNs + ntds_dn = "CN=NTDS Settings,%s" % names.serverdn + names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID", + expression="", scope=ldb.SCOPE_BASE) + assert isinstance(names.ntdsguid, str) + + # Setup fSMORoleOwner entries to point at the newly created DC entry + setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), { + "DOMAINDN": names.domaindn, + "CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn, + "DEFAULTSITE": names.sitename, + "SERVERDN": names.serverdn, + "NETBIOSNAME": names.netbiosname, + "RIDALLOCATIONSTART": str(next_rid + 100), + "RIDALLOCATIONEND": str(next_rid + 100 + 499), + }) + + # This is partially Samba4 specific and should be replaced by the correct + # DNS AD-style setup + setup_add_ldif(samdb, setup_path("provision_dns_add.ldif"), { + "DNSDOMAIN": names.dnsdomain, + "DOMAINDN": names.domaindn, + "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')), + "HOSTNAME" : names.hostname, + "DNSNAME" : '%s.%s' % ( + names.netbiosname.lower(), names.dnsdomain.lower()) + }) + + +def getpolicypath(sysvolpath, dnsdomain, guid): + """Return the physical path of policy given its guid. + + :param sysvolpath: Path to the sysvol folder + :param dnsdomain: DNS name of the AD domain + :param guid: The GUID of the policy + :return: A string with the complete path to the policy folder + """ + + if guid[0] != "{": + guid = "{%s}" % guid + policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid) + return policy_path + + +def create_gpo_struct(policy_path): + if not os.path.exists(policy_path): + os.makedirs(policy_path, 0775) + open(os.path.join(policy_path, "GPT.INI"), 'w').write( + "[General]\r\nVersion=0") + p = os.path.join(policy_path, "MACHINE") + if not os.path.exists(p): + os.makedirs(p, 0775) + p = os.path.join(policy_path, "USER") + if not os.path.exists(p): + os.makedirs(p, 0775) + + +def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc): + """Create the default GPO for a domain + + :param sysvolpath: Physical path for the sysvol folder + :param dnsdomain: DNS domain name of the AD domain + :param policyguid: GUID of the default domain policy + :param policyguid_dc: GUID of the default domain controler policy + """ + policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid) + create_gpo_struct(policy_path) + + policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc) + create_gpo_struct(policy_path) + + +def setup_samdb(path, setup_path, session_info, provision_backend, lp, names, + logger, domainsid, domainguid, policyguid, policyguid_dc, fill, + adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid, + serverrole, am_rodc=False, dom_for_fun_level=None, schema=None, + next_rid=1000): + """Setup a complete SAM Database. + + :note: This will wipe the main SAM database file! + """ + + # Provision does not make much sense values larger than 1000000000 + # as the upper range of the rIDAvailablePool is 1073741823 and + # we don't want to create a domain that cannot allocate rids. + if next_rid < 1000 or next_rid > 1000000000: + error = "You want to run SAMBA 4 with a next_rid of %u, " % (next_rid) + error += "the valid range is %u-%u. The default is %u." % ( + 1000, 1000000000, 1000) + raise ProvisioningError(error) + + # ATTENTION: Do NOT change these default values without discussion with the + # team and/or release manager. They have a big impact on the whole program! + domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2 + + if dom_for_fun_level is None: + dom_for_fun_level = DS_DOMAIN_FUNCTION_2003 + + 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!") + + domainFunctionality = dom_for_fun_level + forestFunctionality = dom_for_fun_level + + # Also wipes the database + setup_samdb_partitions(path, setup_path, logger=logger, lp=lp, + provision_backend=provision_backend, session_info=session_info, + names=names, serverrole=serverrole, schema=schema) + + if schema is None: + schema = Schema(setup_path, domainsid, schemadn=names.schemadn) + + # Load the database, but don's load the global schema and don't connect + # quite yet + samdb = SamDB(session_info=session_info, url=None, auto_connect=False, + credentials=provision_backend.credentials, lp=lp, + global_schema=False, am_rodc=am_rodc) + + logger.info("Pre-loading the Samba 4 and AD schema") + + # Load the schema from the one we computed earlier + samdb.set_schema(schema) + + # Set the NTDS settings DN manually - in order to have it already around + # before the provisioned tree exists and we connect + samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn) + + # And now we can connect to the DB - the schema won't be loaded from the + # DB + samdb.connect(path) + + if fill == FILL_DRS: + return samdb + + samdb.transaction_start() + try: + # Set the domain functionality levels onto the database. + # 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 = "" + + 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 + }) + + setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), { + "DOMAINDN": names.domaindn, + "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks + "NEXTRID": str(next_rid), + "DEFAULTSITE": names.sitename, + "CONFIGDN": names.configdn, + "POLICYGUID": policyguid, + "DOMAIN_FUNCTIONALITY": str(domainFunctionality), + "SAMBA_VERSION_STRING": version + }) + + logger.info("Adding configuration container") + descr = b64encode(get_config_descriptor(domainsid)) + setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), { + "CONFIGDN": names.configdn, + "DESCRIPTOR": descr, + }) + + # The LDIF here was created when the Schema object was constructed + logger.info("Setting up sam.ldb schema") + samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"]) + samdb.modify_ldif(schema.schema_dn_modify) + samdb.write_prefixes_from_schema() + samdb.add_ldif(schema.schema_data, controls=["relax:0"]) + setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), + {"SCHEMADN": names.schemadn}) + + logger.info("Reopening sam.ldb with new schema") + except: + samdb.transaction_cancel() + raise + else: + samdb.transaction_commit() + + samdb = SamDB(session_info=admin_session_info, auto_connect=False, + credentials=provision_backend.credentials, lp=lp, + global_schema=False, am_rodc=am_rodc) + + # Set the NTDS settings DN manually - in order to have it already around + # before the provisioned tree exists and we connect + samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn) + samdb.connect(path) + + samdb.transaction_start() + try: + samdb.invocation_id = invocationid + + logger.info("Setting up sam.ldb configuration data") + descr = b64encode(get_sites_descriptor(domainsid)) + setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), { + "CONFIGDN": names.configdn, + "NETBIOSNAME": names.netbiosname, + "DEFAULTSITE": names.sitename, + "DNSDOMAIN": names.dnsdomain, + "DOMAIN": names.domain, + "SCHEMADN": names.schemadn, + "DOMAINDN": names.domaindn, + "SERVERDN": names.serverdn, + "FOREST_FUNCTIONALITY": str(forestFunctionality), + "DOMAIN_FUNCTIONALITY": str(domainFunctionality), + "SITES_DESCRIPTOR": descr + }) + + logger.info("Setting up display specifiers") + display_specifiers_ldif = read_ms_ldif( + setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt')) + display_specifiers_ldif = substitute_var(display_specifiers_ldif, + {"CONFIGDN": names.configdn}) + check_all_substituted(display_specifiers_ldif) + samdb.add_ldif(display_specifiers_ldif) + + logger.info("Adding users container") + setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), { + "DOMAINDN": names.domaindn}) + logger.info("Modifying users container") + setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), { + "DOMAINDN": names.domaindn}) + logger.info("Adding computers container") + setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), { + "DOMAINDN": names.domaindn}) + 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") + setup_add_ldif(samdb, setup_path("provision.ldif"), { + "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks + "DOMAINDN": names.domaindn, + "NETBIOSNAME": names.netbiosname, + "DEFAULTSITE": names.sitename, + "CONFIGDN": names.configdn, + "SERVERDN": names.serverdn, + "RIDAVAILABLESTART": str(next_rid + 600), + "POLICYGUID_DC": policyguid_dc + }) + + setup_modify_ldif(samdb, + setup_path("provision_basedn_references.ldif"), { + "DOMAINDN": names.domaindn}) + + setup_modify_ldif(samdb, + setup_path("provision_configuration_references.ldif"), { + "CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn}) + if fill == FILL_FULL: + logger.info("Setting up sam.ldb users and groups") + setup_add_ldif(samdb, setup_path("provision_users.ldif"), { + "DOMAINDN": names.domaindn, + "DOMAINSID": str(domainsid), + "CONFIGDN": names.configdn, + "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')), + "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le')) + }) + + logger.info("Setting up self join") + setup_self_join(samdb, names=names, invocationid=invocationid, + dnspass=dnspass, + machinepass=machinepass, + domainsid=domainsid, + next_rid=next_rid, + policyguid=policyguid, + policyguid_dc=policyguid_dc, + setup_path=setup_path, + 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 + + +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_dir_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 set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp): + """Set ACL on the sysvol//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") + setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid)) + + 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"])) + set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp, + str(domainsid)) + + +def setsysvolacl(samdb, netlogon, sysvol, gid, 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 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=...) + """ + + try: + os.chown(sysvol, -1, gid) + except: + canchown = False + else: + canchown = True + + # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level) + 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 acls on Policy folder and policies folders + set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp) + + +def provision(setup_dir, logger, session_info, credentials, smbconf=None, + targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None, + domaindn=None, schemadn=None, configdn=None, serverdn=None, + domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None, + next_rid=1000, adminpass=None, ldapadminpass=None, krbtgtpass=None, + domainguid=None, policyguid=None, policyguid_dc=None, + invocationid=None, machinepass=None, ntdsguid=None, dnspass=None, + root=None, nobody=None, users=None, wheel=None, backup=None, aci=None, + serverrole=None, dom_for_fun_level=None, ldap_backend_extra_port=None, + ldap_backend_forced_uri=None, backend_type=None, sitename=None, + ol_mmr_urls=None, ol_olc=None, setup_ds_path=None, slapd_path=None, + nosync=False, ldap_dryrun_mode=False, useeadb=False, am_rodc=False, + lp=None): + """Provision samba4 + + :note: caution, this wipes all existing data! + """ + + def setup_path(file): + return os.path.join(setup_dir, file) + + if domainsid is None: + domainsid = security.random_sid() + else: + domainsid = security.dom_sid(domainsid) + + # create/adapt the group policy GUIDs + # Default GUID for default policy are described at + # "How Core Group Policy Works" + # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx + if policyguid is None: + policyguid = DEFAULT_POLICY_GUID + policyguid = policyguid.upper() + if policyguid_dc is None: + policyguid_dc = DEFAULT_DC_POLICY_GUID + policyguid_dc = policyguid_dc.upper() + + if adminpass is None: + adminpass = samba.generate_random_password(12, 32) + if krbtgtpass is None: + krbtgtpass = samba.generate_random_password(128, 255) + if machinepass is None: + machinepass = samba.generate_random_password(128, 255) + if dnspass is None: + dnspass = samba.generate_random_password(128, 255) + if ldapadminpass is None: + # Make a new, random password between Samba and it's LDAP server + ldapadminpass=samba.generate_random_password(128, 255) + + if backend_type is None: + backend_type = "ldb" + + 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]) + try: + bind_gid = findnss_gid(["bind", "named"]) + except KeyError: + bind_gid = None + + if targetdir is not None: + smbconf = os.path.join(targetdir, "etc", "smb.conf") + elif smbconf is None: + smbconf = samba.param.default_path() + if not os.path.exists(os.path.dirname(smbconf)): + os.makedirs(os.path.dirname(smbconf)) + + # only install a new smb.conf if there isn't one there already + if os.path.exists(smbconf): + # if Samba Team members can't figure out the weird errors + # loading an empty smb.conf gives, then we need to be smarter. + # Pretend it just didn't exist --abartlet + data = open(smbconf, 'r').read() + data = data.lstrip() + if data is None or data == "": + make_smbconf(smbconf, setup_path, hostname, domain, realm, + serverrole, targetdir, sid_generator, useeadb, + lp=lp) + else: + make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, + targetdir, sid_generator, useeadb, lp=lp) + + if lp is 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) + paths = provision_paths_from_lp(lp, names.dnsdomain) + + paths.bind_gid = bind_gid + + if hostip is None: + logger.info("Looking up IPv4 addresses") + hostips = samba.interface_ips(lp, False) + if len(hostips) == 0: + logger.warning("No external IPv4 address has been found. Using loopback.") + hostip = '127.0.0.1' + else: + hostip = hostips[0] + if len(hostips) > 1: + logger.warning("More than one IPv4 address found. Using %s.", + hostip) + + if serverrole is None: + serverrole = lp.get("server role") + + assert serverrole in ("domain controller", "member server", "standalone") + if invocationid is None: + invocationid = str(uuid.uuid4()) + + if not os.path.exists(paths.private_dir): + os.mkdir(paths.private_dir) + if not os.path.exists(os.path.join(paths.private_dir, "tls")): + os.mkdir(os.path.join(paths.private_dir, "tls")) + + ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="") + + schema = Schema(setup_path, domainsid, invocationid=invocationid, + schemadn=names.schemadn) + + if backend_type == "ldb": + provision_backend = LDBBackend(backend_type, paths=paths, + setup_path=setup_path, lp=lp, credentials=credentials, + names=names, logger=logger) + elif backend_type == "existing": + provision_backend = ExistingBackend(backend_type, paths=paths, + setup_path=setup_path, lp=lp, credentials=credentials, + names=names, logger=logger, + ldap_backend_forced_uri=ldap_backend_forced_uri) + elif backend_type == "fedora-ds": + provision_backend = FDSBackend(backend_type, paths=paths, + setup_path=setup_path, lp=lp, credentials=credentials, + names=names, logger=logger, domainsid=domainsid, + schema=schema, hostname=hostname, ldapadminpass=ldapadminpass, + slapd_path=slapd_path, + ldap_backend_extra_port=ldap_backend_extra_port, + ldap_dryrun_mode=ldap_dryrun_mode, root=root, + setup_ds_path=setup_ds_path, + ldap_backend_forced_uri=ldap_backend_forced_uri) + elif backend_type == "openldap": + provision_backend = OpenLDAPBackend(backend_type, paths=paths, + setup_path=setup_path, lp=lp, credentials=credentials, + names=names, logger=logger, domainsid=domainsid, + schema=schema, hostname=hostname, ldapadminpass=ldapadminpass, + slapd_path=slapd_path, + ldap_backend_extra_port=ldap_backend_extra_port, + ldap_dryrun_mode=ldap_dryrun_mode, ol_mmr_urls=ol_mmr_urls, + nosync=nosync, + ldap_backend_forced_uri=ldap_backend_forced_uri) + else: + raise ValueError("Unknown LDAP backend type selected") + + provision_backend.init() + provision_backend.start() + + # only install a new shares config db if there is none + if not os.path.exists(paths.shareconf): + logger.info("Setting up share.ldb") + share_ldb = Ldb(paths.shareconf, session_info=session_info, + lp=lp) + share_ldb.load_ldif_file_add(setup_path("share.ldif")) + + logger.info("Setting up secrets.ldb") + secrets_ldb = setup_secretsdb(paths, setup_path, + session_info=session_info, + backend_credentials=provision_backend.secrets_credentials, lp=lp) + + try: + logger.info("Setting up the registry") + setup_registry(paths.hklm, setup_path, session_info, + lp=lp) + + logger.info("Setting up the privileges database") + setup_privileges(paths.privilege, setup_path, session_info, lp=lp) + + logger.info("Setting up idmap db") + idmap = setup_idmapdb(paths.idmapdb, setup_path, + session_info=session_info, lp=lp) + + logger.info("Setting up SAM db") + samdb = setup_samdb(paths.samdb, setup_path, session_info, + provision_backend, lp, names, logger=logger, + domainsid=domainsid, schema=schema, domainguid=domainguid, + policyguid=policyguid, policyguid_dc=policyguid_dc, + fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass, + invocationid=invocationid, machinepass=machinepass, + dnspass=dnspass, ntdsguid=ntdsguid, serverrole=serverrole, + dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc, + next_rid=next_rid) + + if serverrole == "domain controller": + if paths.netlogon is None: + logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.") + logger.info("Please either remove %s or see the template at %s" % + (paths.smbconf, setup_path("provision.smb.conf.dc"))) + assert paths.netlogon is not None + + if paths.sysvol is None: + logger.info("Existing smb.conf does not have a [sysvol] share, but you" + " are configuring a DC.") + logger.info("Please either remove %s or see the template at %s" % + (paths.smbconf, setup_path("provision.smb.conf.dc"))) + assert paths.sysvol is not None + + if not os.path.isdir(paths.netlogon): + os.makedirs(paths.netlogon, 0755) + + if samdb_fill == FILL_FULL: + setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn, + root_uid=root_uid, nobody_uid=nobody_uid, + users_gid=users_gid, wheel_gid=wheel_gid) + + if serverrole == "domain controller": + # Set up group policies (domain policy and domain controller + # policy) + create_default_gpo(paths.sysvol, names.dnsdomain, policyguid, + policyguid_dc) + setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid, + domainsid, names.dnsdomain, names.domaindn, lp) + + logger.info("Setting up sam.ldb rootDSE marking as synchronized") + setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif")) + + secretsdb_self_join(secrets_ldb, domain=names.domain, + realm=names.realm, dnsdomain=names.dnsdomain, + netbiosname=names.netbiosname, domainsid=domainsid, + machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC) + + # Now set up the right msDS-SupportedEncryptionTypes into the DB + # In future, this might be determined from some configuration + kerberos_enctypes = str(ENC_ALL_TYPES) + + try: + msg = ldb.Message(ldb.Dn(samdb, + samdb.searchone("distinguishedName", + expression="samAccountName=%s$" % names.netbiosname, + scope=ldb.SCOPE_SUBTREE))) + msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement( + elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE, + name="msDS-SupportedEncryptionTypes") + samdb.modify(msg) + except ldb.LdbError, (ldb.ERR_NO_SUCH_ATTRIBUTE, _): + # It might be that this attribute does not exist in this schema + pass + + if serverrole == "domain controller": + secretsdb_setup_dns(secrets_ldb, setup_path, names, + paths.private_dir, realm=names.realm, + dnsdomain=names.dnsdomain, + dns_keytab_path=paths.dns_keytab, dnspass=dnspass) + + domainguid = samdb.searchone(basedn=domaindn, + attribute="objectGUID") + assert isinstance(domainguid, str) + + # Only make a zone file on the first DC, it should be + # replicated with DNS replication + create_zone_file(lp, logger, paths, targetdir, setup_path, + dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6, + hostname=names.hostname, realm=names.realm, + domainguid=domainguid, ntdsguid=names.ntdsguid) + + create_named_conf(paths, setup_path, realm=names.realm, + dnsdomain=names.dnsdomain, private_dir=paths.private_dir) + + create_named_txt(paths.namedtxt, setup_path, + realm=names.realm, dnsdomain=names.dnsdomain, + private_dir=paths.private_dir, + keytab_name=paths.dns_keytab) + logger.info("See %s for an example configuration include file for BIND", paths.namedconf) + logger.info("and %s for further documentation required for secure DNS " + "updates", paths.namedtxt) + + lastProvisionUSNs = get_last_provision_usn(samdb) + maxUSN = get_max_usn(samdb, str(names.rootdn)) + if lastProvisionUSNs is not None: + update_provision_usn(samdb, 0, maxUSN, 1) + else: + set_provision_usn(samdb, 0, maxUSN) + + create_krb5_conf(paths.krb5conf, setup_path, + dnsdomain=names.dnsdomain, hostname=names.hostname, + realm=names.realm) + logger.info("A Kerberos configuration suitable for Samba 4 has been " + "generated at %s", paths.krb5conf) + + if serverrole == "domain controller": + create_dns_update_list(lp, logger, paths, setup_path) + + provision_backend.post_setup() + provision_backend.shutdown() + + create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, + ldapi_url) + except: + secrets_ldb.transaction_cancel() + raise + + # 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: + if not os.environ.has_key('SAMBA_SELFTEST'): + logger.info("Failed to chown %s to bind gid %u", + dns_keytab_path, paths.bind_gid) + + + logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php", + paths.phpldapadminconfig) + + logger.info("Once the above files are installed, your Samba4 server will be ready to use") + logger.info("Server Role: %s" % serverrole) + logger.info("Hostname: %s" % names.hostname) + logger.info("NetBIOS Domain: %s" % names.domain) + logger.info("DNS Domain: %s" % names.dnsdomain) + logger.info("DOMAIN SID: %s" % str(domainsid)) + if samdb_fill == FILL_FULL: + logger.info("Admin password: %s" % adminpass) + if provision_backend.type is not "ldb": + if provision_backend.credentials.get_bind_dn() is not None: + logger.info("LDAP Backend Admin DN: %s" % + provision_backend.credentials.get_bind_dn()) + else: + logger.info("LDAP Admin User: %s" % + provision_backend.credentials.get_username()) + + logger.info("LDAP Admin Password: %s" % + provision_backend.credentials.get_password()) + + if provision_backend.slapd_command_escaped is not None: + # now display slapd_command_file.txt to show how slapd must be + # started next time + logger.info("Use later the following commandline to start slapd, then Samba:") + logger.info(provision_backend.slapd_command_escaped) + logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh", + provision_backend.ldapdir) + + result = ProvisionResult() + result.domaindn = domaindn + result.paths = paths + result.lp = lp + result.samdb = samdb + return result + + +def provision_become_dc(setup_dir=None, smbconf=None, targetdir=None, + realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=None, + serverdn=None, domain=None, hostname=None, domainsid=None, + adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None, + policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None, + root=None, nobody=None, users=None, wheel=None, backup=None, + serverrole=None, ldap_backend=None, ldap_backend_type=None, + sitename=None, debuglevel=1): + + logger = logging.getLogger("provision") + samba.set_debug_level(debuglevel) + + res = provision(setup_dir, logger, system_session(), None, + smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, + realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, + configdn=configdn, serverdn=serverdn, domain=domain, + hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, + machinepass=machinepass, serverrole="domain controller", + sitename=sitename) + res.lp.set("debuglevel", str(debuglevel)) + return res + + +def create_phpldapadmin_config(path, setup_path, ldapi_uri): + """Create a PHP LDAP admin configuration file. + + :param path: Path to write the configuration to. + :param setup_path: Function to generate setup paths. + """ + setup_file(setup_path("phpldapadmin-config.php"), path, + {"S4_LDAPI_URI": ldapi_uri}) + + +def create_zone_file(lp, logger, paths, targetdir, setup_path, dnsdomain, + hostip, hostip6, hostname, realm, domainguid, + ntdsguid): + """Write out a DNS zone file, from the info in the current database. + + :param paths: paths object + :param setup_path: Setup path function. + :param dnsdomain: DNS Domain name + :param domaindn: DN of the Domain + :param hostip: Local IPv4 IP + :param hostip6: Local IPv6 IP + :param hostname: Local hostname + :param realm: Realm name + :param domainguid: GUID of the domain. + :param ntdsguid: GUID of the hosts nTDSDSA record. + """ + assert isinstance(domainguid, str) + + if hostip6 is not None: + hostip6_base_line = " IN AAAA " + hostip6 + hostip6_host_line = hostname + " IN AAAA " + hostip6 + gc_msdcs_ip6_line = "gc._msdcs IN AAAA " + hostip6 + else: + hostip6_base_line = "" + hostip6_host_line = "" + gc_msdcs_ip6_line = "" + + if hostip is not None: + hostip_base_line = " IN A " + hostip + hostip_host_line = hostname + " IN A " + hostip + gc_msdcs_ip_line = "gc._msdcs IN A " + hostip + else: + hostip_base_line = "" + hostip_host_line = "" + gc_msdcs_ip_line = "" + + dns_dir = os.path.dirname(paths.dns) + + try: + shutil.rmtree(dns_dir, True) + except OSError: + pass + + os.mkdir(dns_dir, 0775) + + # we need to freeze the zone while we update the contents + if targetdir is None: + rndc = ' '.join(lp.get("rndc command")) + os.system(rndc + " freeze " + lp.get("realm")) + + setup_file(setup_path("provision.zone"), paths.dns, { + "HOSTNAME": hostname, + "DNSDOMAIN": dnsdomain, + "REALM": realm, + "HOSTIP_BASE_LINE": hostip_base_line, + "HOSTIP_HOST_LINE": hostip_host_line, + "DOMAINGUID": domainguid, + "DATESTRING": time.strftime("%Y%m%d%H"), + "DEFAULTSITE": DEFAULTSITE, + "NTDSGUID": ntdsguid, + "HOSTIP6_BASE_LINE": hostip6_base_line, + "HOSTIP6_HOST_LINE": hostip6_host_line, + "GC_MSDCS_IP_LINE": gc_msdcs_ip_line, + "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line, + }) + + # note that we use no variable substitution on this file + # the substitution is done at runtime by samba_dnsupdate + setup_file(setup_path("dns_update_list"), paths.dns_update_list, None) + + # and the SPN update list + setup_file(setup_path("spn_update_list"), paths.spn_update_list, None) + + if paths.bind_gid is not None: + try: + os.chown(dns_dir, -1, paths.bind_gid) + os.chown(paths.dns, -1, paths.bind_gid) + # chmod needed to cope with umask + os.chmod(dns_dir, 0775) + os.chmod(paths.dns, 0664) + except OSError: + if not os.environ.has_key('SAMBA_SELFTEST'): + logger.error("Failed to chown %s to bind gid %u" % ( + dns_dir, paths.bind_gid)) + + if targetdir is None: + os.system(rndc + " unfreeze " + lp.get("realm")) + + +def create_dns_update_list(lp, logger, paths, setup_path): + """Write out a dns_update_list file""" + # note that we use no variable substitution on this file + # the substitution is done at runtime by samba_dnsupdate + setup_file(setup_path("dns_update_list"), paths.dns_update_list, None) + setup_file(setup_path("spn_update_list"), paths.spn_update_list, None) + + +def create_named_conf(paths, setup_path, realm, dnsdomain, + private_dir): + """Write out a file containing zone statements suitable for inclusion in a + named.conf file (including GSS-TSIG configuration). + + :param paths: all paths + :param setup_path: Setup path function. + :param realm: Realm name + :param dnsdomain: DNS Domain name + :param private_dir: Path to private directory + :param keytab_name: File name of DNS keytab file + """ + + setup_file(setup_path("named.conf"), paths.namedconf, { + "DNSDOMAIN": dnsdomain, + "REALM": realm, + "ZONE_FILE": paths.dns, + "REALM_WC": "*." + ".".join(realm.split(".")[1:]), + "NAMED_CONF": paths.namedconf, + "NAMED_CONF_UPDATE": paths.namedconf_update + }) + + setup_file(setup_path("named.conf.update"), paths.namedconf_update) + + +def create_named_txt(path, setup_path, realm, dnsdomain, private_dir, + keytab_name): + """Write out a file containing zone statements suitable for inclusion in a + named.conf file (including GSS-TSIG configuration). + + :param path: Path of the new named.conf file. + :param setup_path: Setup path function. + :param realm: Realm name + :param dnsdomain: DNS Domain name + :param private_dir: Path to private directory + :param keytab_name: File name of DNS keytab file + """ + setup_file(setup_path("named.txt"), path, { + "DNSDOMAIN": dnsdomain, + "REALM": realm, + "DNS_KEYTAB": keytab_name, + "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name), + "PRIVATE_DIR": private_dir + }) + + +def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm): + """Write out a file containing zone statements suitable for inclusion in a + named.conf file (including GSS-TSIG configuration). + + :param path: Path of the new named.conf file. + :param setup_path: Setup path function. + :param dnsdomain: DNS Domain name + :param hostname: Local hostname + :param realm: Realm name + """ + setup_file(setup_path("krb5.conf"), path, { + "DNSDOMAIN": dnsdomain, + "HOSTNAME": hostname, + "REALM": realm, + }) + + +class ProvisioningError(Exception): + """A generic provision error.""" + + def __init__(self, value): + self.value = value + + def __str__(self): + return "ProvisioningError: " + self.value + + +class InvalidNetbiosName(Exception): + """A specified name was not a valid NetBIOS name.""" + def __init__(self, name): + super(InvalidNetbiosName, self).__init__( + "The name '%r' is not a valid NetBIOS name" % name) diff --git a/source4/scripting/python/samba/provision/backend.py b/source4/scripting/python/samba/provision/backend.py new file mode 100644 index 0000000000..32bcfeca95 --- /dev/null +++ b/source4/scripting/python/samba/provision/backend.py @@ -0,0 +1,767 @@ +# +# Unix SMB/CIFS implementation. +# backend code for provisioning a Samba4 server + +# Copyright (C) Jelmer Vernooij 2007-2008 +# Copyright (C) Andrew Bartlett 2008-2009 +# Copyright (C) Oliver Liebel 2008-2009 +# +# Based on the original in EJS: +# Copyright (C) Andrew Tridgell 2005 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +"""Functions for setting up a Samba configuration (LDB and LDAP backends).""" + +from base64 import b64encode +import errno +import ldb +import os +import sys +import uuid +import time +import shutil +import subprocess +import urllib + +from ldb import SCOPE_BASE, SCOPE_ONELEVEL, LdbError, timestring + +from samba import Ldb, read_and_sub_file, setup_file +from samba.credentials import Credentials, DONT_USE_KERBEROS +from samba.schema import Schema + + +class SlapdAlreadyRunning(Exception): + + def __init__(self, uri): + self.ldapi_uri = uri + super(SlapdAlreadyRunning, self).__init__("Another slapd Instance " + "seems already running on this host, listening to %s." % + self.ldapi_uri) + + +class ProvisionBackend(object): + + def __init__(self, backend_type, paths=None, setup_path=None, lp=None, + credentials=None, names=None, logger=None): + """Provision a backend for samba4""" + self.paths = paths + self.setup_path = setup_path + self.lp = lp + self.credentials = credentials + self.names = names + self.logger = logger + + self.type = backend_type + + # Set a default - the code for "existing" below replaces this + self.ldap_backend_type = backend_type + + def init(self): + """Initialize the backend.""" + raise NotImplementedError(self.init) + + def start(self): + """Start the backend.""" + raise NotImplementedError(self.start) + + def shutdown(self): + """Shutdown the backend.""" + raise NotImplementedError(self.shutdown) + + def post_setup(self): + """Post setup.""" + raise NotImplementedError(self.post_setup) + + +class LDBBackend(ProvisionBackend): + + def init(self): + self.credentials = None + self.secrets_credentials = None + + # Wipe the old sam.ldb databases away + shutil.rmtree(self.paths.samdb + ".d", True) + + def start(self): + pass + + def shutdown(self): + pass + + def post_setup(self): + pass + + +class ExistingBackend(ProvisionBackend): + + def __init__(self, backend_type, paths=None, setup_path=None, lp=None, + credentials=None, names=None, logger=None, ldapi_uri=None): + + super(ExistingBackend, self).__init__(backend_type=backend_type, + paths=paths, setup_path=setup_path, lp=lp, + credentials=credentials, names=names, logger=logger, + ldap_backend_forced_uri=ldap_backend_forced_uri) + + def init(self): + # Check to see that this 'existing' LDAP backend in fact exists + ldapi_db = Ldb(self.ldapi_uri, credentials=self.credentials) + ldapi_db.search(base="", scope=SCOPE_BASE, + expression="(objectClass=OpenLDAProotDSE)") + + # If we have got here, then we must have a valid connection to the LDAP + # server, with valid credentials supplied This caused them to be set + # into the long-term database later in the script. + self.secrets_credentials = self.credentials + + # For now, assume existing backends at least emulate OpenLDAP + self.ldap_backend_type = "openldap" + + +class LDAPBackend(ProvisionBackend): + + def __init__(self, backend_type, paths=None, setup_path=None, lp=None, + credentials=None, names=None, logger=None, domainsid=None, + schema=None, hostname=None, ldapadminpass=None, slapd_path=None, + ldap_backend_extra_port=None, + ldap_backend_forced_uri=None, ldap_dryrun_mode=False): + + super(LDAPBackend, self).__init__(backend_type=backend_type, + paths=paths, setup_path=setup_path, lp=lp, + credentials=credentials, names=names, logger=logger) + + self.domainsid = domainsid + self.schema = schema + self.hostname = hostname + + self.ldapdir = os.path.join(paths.private_dir, "ldap") + self.ldapadminpass = ldapadminpass + + self.slapd_path = slapd_path + self.slapd_command = None + self.slapd_command_escaped = None + self.slapd_pid = os.path.join(self.ldapdir, "slapd.pid") + + self.ldap_backend_extra_port = ldap_backend_extra_port + self.ldap_dryrun_mode = ldap_dryrun_mode + + if ldap_backend_forced_uri is not None: + self.ldap_uri = ldap_backend_forced_uri + else: + self.ldap_uri = "ldapi://%s" % urllib.quote( + os.path.join(self.ldapdir, "ldapi"), safe="") + + if not os.path.exists(self.ldapdir): + os.mkdir(self.ldapdir) + + def init(self): + from samba.provision import ProvisioningError + # we will shortly start slapd with ldapi for final provisioning. first + # check with ldapsearch -> rootDSE via self.ldap_uri if another + # instance of slapd is already running + try: + ldapi_db = Ldb(self.ldap_uri) + ldapi_db.search(base="", scope=SCOPE_BASE, + expression="(objectClass=OpenLDAProotDSE)") + try: + f = open(self.slapd_pid, "r") + except IOError, err: + if err != errno.ENOENT: + raise + else: + p = f.read() + f.close() + self.logger.info("Check for slapd Process with PID: %s and terminate it manually." % p) + raise SlapdAlreadyRunning(self.ldap_uri) + except LdbError: + # XXX: We should never be catching all Ldb errors + pass + + # Try to print helpful messages when the user has not specified the + # path to slapd + if self.slapd_path is None: + raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!") + if not os.path.exists(self.slapd_path): + self.logger.warning("Path (%s) to slapd does not exist!", + self.slapd_path) + + if not os.path.isdir(self.ldapdir): + os.makedirs(self.ldapdir, 0700) + + # Put the LDIF of the schema into a database so we can search on + # it to generate schema-dependent configurations in Fedora DS and + # OpenLDAP + schemadb_path = os.path.join(self.ldapdir, "schema-tmp.ldb") + try: + os.unlink(schemadb_path) + except OSError: + pass + + self.schema.write_to_tmp_ldb(schemadb_path) + + self.credentials = Credentials() + self.credentials.guess(self.lp) + # Kerberos to an ldapi:// backend makes no sense + self.credentials.set_kerberos_state(DONT_USE_KERBEROS) + self.credentials.set_password(self.ldapadminpass) + + self.secrets_credentials = Credentials() + self.secrets_credentials.guess(self.lp) + # Kerberos to an ldapi:// backend makes no sense + self.secrets_credentials.set_kerberos_state(DONT_USE_KERBEROS) + self.secrets_credentials.set_username("samba-admin") + self.secrets_credentials.set_password(self.ldapadminpass) + + self.provision() + + def provision(self): + pass + + def start(self): + from samba.provision import ProvisioningError + self.slapd_command_escaped = "\'" + "\' \'".join(self.slapd_command) + "\'" + f = open(os.path.join(self.ldapdir, "ldap_backend_startup.sh"), 'w') + try: + f.write("#!/bin/sh\n" + self.slapd_command_escaped + "\n") + finally: + f.close() + + # Now start the slapd, so we can provision onto it. We keep the + # subprocess context around, to kill this off at the successful + # end of the script + self.slapd = subprocess.Popen(self.slapd_provision_command, + close_fds=True, shell=False) + + count = 0 + while self.slapd.poll() is None: + # Wait until the socket appears + try: + ldapi_db = Ldb(self.ldap_uri, lp=self.lp, credentials=self.credentials) + ldapi_db.search(base="", scope=SCOPE_BASE, + expression="(objectClass=OpenLDAProotDSE)") + # If we have got here, then we must have a valid connection to the LDAP server! + return + except LdbError: + time.sleep(1) + count = count + 1 + + if count > 15: + self.logger.error("Could not connect to slapd started with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'") + raise ProvisioningError("slapd never accepted a connection within 15 seconds of starting") + + self.logger.error("Could not start slapd with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'") + raise ProvisioningError("slapd died before we could make a connection to it") + + def shutdown(self): + # if an LDAP backend is in use, terminate slapd after final provision + # and check its proper termination + if self.slapd.poll() is None: + # Kill the slapd + if hasattr(self.slapd, "terminate"): + self.slapd.terminate() + else: + # Older python versions don't have .terminate() + import signal + os.kill(self.slapd.pid, signal.SIGTERM) + + # and now wait for it to die + self.slapd.communicate() + + def post_setup(self): + pass + + +class OpenLDAPBackend(LDAPBackend): + + def __init__(self, backend_type, paths=None, setup_path=None, lp=None, + credentials=None, names=None, logger=None, domainsid=None, + schema=None, hostname=None, ldapadminpass=None, slapd_path=None, + ldap_backend_extra_port=None, ldap_dryrun_mode=False, + ol_mmr_urls=None, nosync=False, ldap_backend_forced_uri=None): + super(OpenLDAPBackend, self).__init__( backend_type=backend_type, + paths=paths, setup_path=setup_path, lp=lp, + credentials=credentials, names=names, logger=logger, + domainsid=domainsid, schema=schema, hostname=hostname, + ldapadminpass=ldapadminpass, slapd_path=slapd_path, + ldap_backend_extra_port=ldap_backend_extra_port, + ldap_backend_forced_uri=ldap_backend_forced_uri, + ldap_dryrun_mode=ldap_dryrun_mode) + + self.ol_mmr_urls = ol_mmr_urls + self.nosync = nosync + + self.slapdconf = os.path.join(self.ldapdir, "slapd.conf") + self.modulesconf = os.path.join(self.ldapdir, "modules.conf") + self.memberofconf = os.path.join(self.ldapdir, "memberof.conf") + self.olmmrserveridsconf = os.path.join(self.ldapdir, "mmr_serverids.conf") + self.olmmrsyncreplconf = os.path.join(self.ldapdir, "mmr_syncrepl.conf") + self.olcdir = os.path.join(self.ldapdir, "slapd.d") + self.olcseedldif = os.path.join(self.ldapdir, "olc_seed.ldif") + + self.schema = Schema(self.setup_path, self.domainsid, + schemadn=self.names.schemadn, files=[ + setup_path("schema_samba4.ldif")]) + + def setup_db_config(self, dbdir): + """Setup a Berkeley database. + + :param setup_path: Setup path function. + :param dbdir: Database directory.""" + if not os.path.isdir(os.path.join(dbdir, "bdb-logs")): + os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700) + if not os.path.isdir(os.path.join(dbdir, "tmp")): + os.makedirs(os.path.join(dbdir, "tmp"), 0700) + + setup_file(self.setup_path("DB_CONFIG"), + os.path.join(dbdir, "DB_CONFIG"), {"LDAPDBDIR": dbdir}) + + def provision(self): + from samba.provision import ProvisioningError + # Wipe the directories so we can start + shutil.rmtree(os.path.join(self.ldapdir, "db"), True) + + #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB + nosync_config = "" + if self.nosync: + nosync_config = "dbnosync" + + lnkattr = self.schema.linked_attributes() + refint_attributes = "" + memberof_config = "# Generated from Samba4 schema\n" + for att in lnkattr.keys(): + if lnkattr[att] is not None: + refint_attributes = refint_attributes + " " + att + + memberof_config += read_and_sub_file( + self.setup_path("memberof.conf"), { + "MEMBER_ATTR": att, + "MEMBEROF_ATTR" : lnkattr[att] }) + + refint_config = read_and_sub_file(self.setup_path("refint.conf"), + { "LINK_ATTRS" : refint_attributes}) + + attrs = ["linkID", "lDAPDisplayName"] + res = self.schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs) + index_config = "" + for i in range (0, len(res)): + index_attr = res[i]["lDAPDisplayName"][0] + if index_attr == "objectGUID": + index_attr = "entryUUID" + + index_config += "index " + index_attr + " eq\n" + + # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts + mmr_on_config = "" + mmr_replicator_acl = "" + mmr_serverids_config = "" + mmr_syncrepl_schema_config = "" + mmr_syncrepl_config_config = "" + mmr_syncrepl_user_config = "" + + if self.ol_mmr_urls is not None: + # For now, make these equal + mmr_pass = self.ldapadminpass + + url_list = filter(None,self.ol_mmr_urls.split(',')) + for url in url_list: + self.logger.info("Using LDAP-URL: "+url) + if len(url_list) == 1: + raise ProvisioningError("At least 2 LDAP-URLs needed for MMR!") + + mmr_on_config = "MirrorMode On" + mmr_replicator_acl = " by dn=cn=replicator,cn=samba read" + serverid = 0 + for url in url_list: + serverid = serverid + 1 + mmr_serverids_config += read_and_sub_file( + self.setup_path("mmr_serverids.conf"), { + "SERVERID": str(serverid), + "LDAPSERVER": url }) + rid = serverid * 10 + rid = rid + 1 + mmr_syncrepl_schema_config += read_and_sub_file( + self.setup_path("mmr_syncrepl.conf"), { + "RID" : str(rid), + "MMRDN": self.names.schemadn, + "LDAPSERVER" : url, + "MMR_PASSWORD": mmr_pass}) + + rid = rid + 1 + mmr_syncrepl_config_config += read_and_sub_file( + self.setup_path("mmr_syncrepl.conf"), { + "RID" : str(rid), + "MMRDN": self.names.configdn, + "LDAPSERVER" : url, + "MMR_PASSWORD": mmr_pass}) + + rid = rid + 1 + mmr_syncrepl_user_config += read_and_sub_file( + self.setup_path("mmr_syncrepl.conf"), { + "RID" : str(rid), + "MMRDN": self.names.domaindn, + "LDAPSERVER" : url, + "MMR_PASSWORD": mmr_pass }) + # OpenLDAP cn=config initialisation + olc_syncrepl_config = "" + olc_mmr_config = "" + # if mmr = yes, generate cn=config-replication directives + # and olc_seed.lif for the other mmr-servers + if self.ol_mmr_urls is not None: + serverid = 0 + olc_serverids_config = "" + olc_syncrepl_seed_config = "" + olc_mmr_config += read_and_sub_file( + self.setup_path("olc_mmr.conf"), {}) + rid = 500 + for url in url_list: + serverid = serverid + 1 + olc_serverids_config += read_and_sub_file( + self.setup_path("olc_serverid.conf"), { + "SERVERID" : str(serverid), "LDAPSERVER" : url }) + + rid = rid + 1 + olc_syncrepl_config += read_and_sub_file( + self.setup_path("olc_syncrepl.conf"), { + "RID" : str(rid), "LDAPSERVER" : url, + "MMR_PASSWORD": mmr_pass}) + + olc_syncrepl_seed_config += read_and_sub_file( + self.setup_path("olc_syncrepl_seed.conf"), { + "RID" : str(rid), "LDAPSERVER" : url}) + + setup_file(self.setup_path("olc_seed.ldif"), self.olcseedldif, + {"OLC_SERVER_ID_CONF": olc_serverids_config, + "OLC_PW": self.ldapadminpass, + "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config}) + # end olc + + setup_file(self.setup_path("slapd.conf"), self.slapdconf, + {"DNSDOMAIN": self.names.dnsdomain, + "LDAPDIR": self.ldapdir, + "DOMAINDN": self.names.domaindn, + "CONFIGDN": self.names.configdn, + "SCHEMADN": self.names.schemadn, + "MEMBEROF_CONFIG": memberof_config, + "MIRRORMODE": mmr_on_config, + "REPLICATOR_ACL": mmr_replicator_acl, + "MMR_SERVERIDS_CONFIG": mmr_serverids_config, + "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config, + "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config, + "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config, + "OLC_SYNCREPL_CONFIG": olc_syncrepl_config, + "OLC_MMR_CONFIG": olc_mmr_config, + "REFINT_CONFIG": refint_config, + "INDEX_CONFIG": index_config, + "NOSYNC": nosync_config}) + + self.setup_db_config(os.path.join(self.ldapdir, "db", "user")) + self.setup_db_config(os.path.join(self.ldapdir, "db", "config")) + self.setup_db_config(os.path.join(self.ldapdir, "db", "schema")) + + if not os.path.exists(os.path.join(self.ldapdir, "db", "samba", "cn=samba")): + os.makedirs(os.path.join(self.ldapdir, "db", "samba", "cn=samba"), 0700) + + setup_file(self.setup_path("cn=samba.ldif"), + os.path.join(self.ldapdir, "db", "samba", "cn=samba.ldif"), + { "UUID": str(uuid.uuid4()), + "LDAPTIME": timestring(int(time.time()))} ) + setup_file(self.setup_path("cn=samba-admin.ldif"), + os.path.join(self.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"), + {"LDAPADMINPASS_B64": b64encode(self.ldapadminpass), + "UUID": str(uuid.uuid4()), + "LDAPTIME": timestring(int(time.time()))} ) + + if self.ol_mmr_urls is not None: + setup_file(self.setup_path("cn=replicator.ldif"), + os.path.join(self.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"), + {"MMR_PASSWORD_B64": b64encode(mmr_pass), + "UUID": str(uuid.uuid4()), + "LDAPTIME": timestring(int(time.time()))} ) + + mapping = "schema-map-openldap-2.3" + backend_schema = "backend-schema.schema" + + f = open(self.setup_path(mapping), 'r') + backend_schema_data = self.schema.convert_to_openldap( + "openldap", f.read()) + assert backend_schema_data is not None + f = open(os.path.join(self.ldapdir, backend_schema), 'w') + try: + f.write(backend_schema_data) + finally: + f.close() + + # now we generate the needed strings to start slapd automatically, + if self.ldap_backend_extra_port is not None: + # When we use MMR, we can't use 0.0.0.0 as it uses the name + # specified there as part of it's clue as to it's own name, + # and not to replicate to itself + if self.ol_mmr_urls is None: + server_port_string = "ldap://0.0.0.0:%d" % self.ldap_backend_extra_port + else: + server_port_string = "ldap://%s.%s:%d" (self.names.hostname, + self.names.dnsdomain, self.ldap_backend_extra_port) + else: + server_port_string = "" + + # Prepare the 'result' information - the commands to return in + # particular + self.slapd_provision_command = [self.slapd_path, "-F" + self.olcdir, + "-h"] + + # copy this command so we have two version, one with -d0 and only + # ldapi (or the forced ldap_uri), and one with all the listen commands + self.slapd_command = list(self.slapd_provision_command) + + self.slapd_provision_command.extend([self.ldap_uri, "-d0"]) + + uris = self.ldap_uri + if server_port_string is not "": + uris = uris + " " + server_port_string + + self.slapd_command.append(uris) + + # Set the username - done here because Fedora DS still uses the admin + # DN and simple bind + self.credentials.set_username("samba-admin") + + # If we were just looking for crashes up to this point, it's a + # good time to exit before we realise we don't have OpenLDAP on + # this system + if self.ldap_dryrun_mode: + sys.exit(0) + + # Finally, convert the configuration into cn=config style! + if not os.path.isdir(self.olcdir): + os.makedirs(self.olcdir, 0770) + + slapd_cmd = [self.slapd_path, "-Ttest", "-n", "0", "-f", + self.slapdconf, "-F", self.olcdir] + retcode = subprocess.call(slapd_cmd, close_fds=True, shell=False) + + if retcode != 0: + self.logger.error("conversion from slapd.conf to cn=config failed slapd started with: %s" % "\'" + "\' \'".join(slapd_cmd) + "\'") + raise ProvisioningError("conversion from slapd.conf to cn=config failed") + + if not os.path.exists(os.path.join(self.olcdir, "cn=config.ldif")): + raise ProvisioningError("conversion from slapd.conf to cn=config failed") + + # Don't confuse the admin by leaving the slapd.conf around + os.remove(self.slapdconf) + + +class FDSBackend(LDAPBackend): + + def __init__(self, backend_type, paths=None, setup_path=None, lp=None, + credentials=None, names=None, logger=None, domainsid=None, + schema=None, hostname=None, ldapadminpass=None, slapd_path=None, + ldap_backend_extra_port=None, ldap_dryrun_mode=False, root=None, + setup_ds_path=None): + + super(FDSBackend, self).__init__(backend_type=backend_type, + paths=paths, setup_path=setup_path, lp=lp, + credentials=credentials, names=names, logger=logger, + domainsid=domainsid, schema=schema, hostname=hostname, + ldapadminpass=ldapadminpass, slapd_path=slapd_path, + ldap_backend_extra_port=ldap_backend_extra_port, + ldap_backend_forced_uri=ldap_backend_forced_uri, + ldap_dryrun_mode=ldap_dryrun_mode) + + self.root = root + self.setup_ds_path = setup_ds_path + self.ldap_instance = self.names.netbiosname.lower() + + self.sambadn = "CN=Samba" + + self.fedoradsinf = os.path.join(self.ldapdir, "fedorads.inf") + self.partitions_ldif = os.path.join(self.ldapdir, "fedorads-partitions.ldif") + self.sasl_ldif = os.path.join(self.ldapdir, "fedorads-sasl.ldif") + self.dna_ldif = os.path.join(self.ldapdir, "fedorads-dna.ldif") + self.pam_ldif = os.path.join(self.ldapdir, "fedorads-pam.ldif") + self.refint_ldif = os.path.join(self.ldapdir, "fedorads-refint.ldif") + self.linked_attrs_ldif = os.path.join(self.ldapdir, "fedorads-linked-attributes.ldif") + self.index_ldif = os.path.join(self.ldapdir, "fedorads-index.ldif") + self.samba_ldif = os.path.join(self.ldapdir, "fedorads-samba.ldif") + + self.samba3_schema = self.setup_path("../../examples/LDAP/samba.schema") + self.samba3_ldif = os.path.join(self.ldapdir, "samba3.ldif") + + self.retcode = subprocess.call(["bin/oLschema2ldif", + "-I", self.samba3_schema, + "-O", self.samba3_ldif, + "-b", self.names.domaindn], + close_fds=True, shell=False) + + if self.retcode != 0: + raise Exception("Unable to convert Samba 3 schema.") + + self.schema = Schema( + self.setup_path, + self.domainsid, + schemadn=self.names.schemadn, + files=[setup_path("schema_samba4.ldif"), self.samba3_ldif], + additional_prefixmap=["1000:1.3.6.1.4.1.7165.2.1", + "1001:1.3.6.1.4.1.7165.2.2"]) + + def provision(self): + from samba.provision import ProvisioningError + if self.ldap_backend_extra_port is not None: + serverport = "ServerPort=%d" % self.ldap_backend_extra_port + else: + serverport = "" + + setup_file(self.setup_path("fedorads.inf"), self.fedoradsinf, + {"ROOT": self.root, + "HOSTNAME": self.hostname, + "DNSDOMAIN": self.names.dnsdomain, + "LDAPDIR": self.ldapdir, + "DOMAINDN": self.names.domaindn, + "LDAP_INSTANCE": self.ldap_instance, + "LDAPMANAGERDN": self.names.ldapmanagerdn, + "LDAPMANAGERPASS": self.ldapadminpass, + "SERVERPORT": serverport}) + + setup_file(self.setup_path("fedorads-partitions.ldif"), + self.partitions_ldif, + {"CONFIGDN": self.names.configdn, + "SCHEMADN": self.names.schemadn, + "SAMBADN": self.sambadn, + }) + + setup_file(self.setup_path("fedorads-sasl.ldif"), self.sasl_ldif, + {"SAMBADN": self.sambadn, + }) + + setup_file(self.setup_path("fedorads-dna.ldif"), self.dna_ldif, + {"DOMAINDN": self.names.domaindn, + "SAMBADN": self.sambadn, + "DOMAINSID": str(self.domainsid), + }) + + setup_file(self.setup_path("fedorads-pam.ldif"), self.pam_ldif) + + lnkattr = self.schema.linked_attributes() + + refint_config = open(self.setup_path("fedorads-refint-delete.ldif"), 'r').read() + memberof_config = "" + index_config = "" + argnum = 3 + + for attr in lnkattr.keys(): + if lnkattr[attr] is not None: + refint_config += read_and_sub_file( + self.setup_path("fedorads-refint-add.ldif"), + { "ARG_NUMBER" : str(argnum), + "LINK_ATTR" : attr }) + memberof_config += read_and_sub_file( + self.setup_path("fedorads-linked-attributes.ldif"), + { "MEMBER_ATTR" : attr, + "MEMBEROF_ATTR" : lnkattr[attr] }) + index_config += read_and_sub_file( + self.setup_path("fedorads-index.ldif"), { "ATTR" : attr }) + argnum += 1 + + open(self.refint_ldif, 'w').write(refint_config) + open(self.linked_attrs_ldif, 'w').write(memberof_config) + + attrs = ["lDAPDisplayName"] + res = self.schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs) + + for i in range (0, len(res)): + attr = res[i]["lDAPDisplayName"][0] + + if attr == "objectGUID": + attr = "nsUniqueId" + + index_config += read_and_sub_file( + self.setup_path("fedorads-index.ldif"), { "ATTR" : attr }) + + open(self.index_ldif, 'w').write(index_config) + + setup_file(self.setup_path("fedorads-samba.ldif"), self.samba_ldif, { + "SAMBADN": self.sambadn, + "LDAPADMINPASS": self.ldapadminpass + }) + + mapping = "schema-map-fedora-ds-1.0" + backend_schema = "99_ad.ldif" + + # Build a schema file in Fedora DS format + backend_schema_data = self.schema.convert_to_openldap("fedora-ds", + open(self.setup_path(mapping), 'r').read()) + assert backend_schema_data is not None + f = open(os.path.join(self.ldapdir, backend_schema), 'w') + try: + f.write(backend_schema_data) + finally: + f.close() + + self.credentials.set_bind_dn(self.names.ldapmanagerdn) + + # Destory the target directory, or else setup-ds.pl will complain + fedora_ds_dir = os.path.join(self.ldapdir, + "slapd-" + self.ldap_instance) + shutil.rmtree(fedora_ds_dir, True) + + self.slapd_provision_command = [self.slapd_path, "-D", fedora_ds_dir, + "-i", self.slapd_pid] + # In the 'provision' command line, stay in the foreground so we can + # easily kill it + self.slapd_provision_command.append("-d0") + + #the command for the final run is the normal script + self.slapd_command = [os.path.join(self.ldapdir, + "slapd-" + self.ldap_instance, "start-slapd")] + + # If we were just looking for crashes up to this point, it's a + # good time to exit before we realise we don't have Fedora DS on + if self.ldap_dryrun_mode: + sys.exit(0) + + # Try to print helpful messages when the user has not specified the + # path to the setup-ds tool + if self.setup_ds_path is None: + raise ProvisioningError("Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!") + if not os.path.exists(self.setup_ds_path): + self.logger.warning("Path (%s) to slapd does not exist!", + self.setup_ds_path) + + # Run the Fedora DS setup utility + retcode = subprocess.call([self.setup_ds_path, "--silent", "--file", + self.fedoradsinf], close_fds=True, shell=False) + if retcode != 0: + raise ProvisioningError("setup-ds failed") + + # Load samba-admin + retcode = subprocess.call([ + os.path.join(self.ldapdir, "slapd-" + self.ldap_instance, "ldif2db"), "-s", self.sambadn, "-i", self.samba_ldif], + close_fds=True, shell=False) + if retcode != 0: + raise ProvisioningError("ldif2db failed") + + def post_setup(self): + ldapi_db = Ldb(self.ldap_uri, credentials=self.credentials) + + # configure in-directory access control on Fedora DS via the aci + # attribute (over a direct ldapi:// socket) + aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % self.sambadn + + m = ldb.Message() + m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci") + + for dnstring in (self.names.domaindn, self.names.configdn, + self.names.schemadn): + m.dn = ldb.Dn(ldapi_db, dnstring) + ldapi_db.modify(m) diff --git a/source4/scripting/python/samba/provisionbackend.py b/source4/scripting/python/samba/provisionbackend.py deleted file mode 100644 index 25563517c6..0000000000 --- a/source4/scripting/python/samba/provisionbackend.py +++ /dev/null @@ -1,756 +0,0 @@ -# -# Unix SMB/CIFS implementation. -# backend code for provisioning a Samba4 server - -# Copyright (C) Jelmer Vernooij 2007-2008 -# Copyright (C) Andrew Bartlett 2008-2009 -# Copyright (C) Oliver Liebel 2008-2009 -# -# Based on the original in EJS: -# Copyright (C) Andrew Tridgell 2005 -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -"""Functions for setting up a Samba configuration (LDB and LDAP backends).""" - -from base64 import b64encode -import errno -import ldb -import os -import sys -import uuid -import time -import shutil -import subprocess -import urllib - -from ldb import SCOPE_BASE, SCOPE_ONELEVEL, LdbError, timestring - -from samba import Ldb, read_and_sub_file, setup_file -from samba.credentials import Credentials, DONT_USE_KERBEROS -from samba.schema import Schema - - -class SlapdAlreadyRunning(Exception): - - def __init__(self, uri): - self.ldapi_uri = uri - super(SlapdAlreadyRunning, self).__init__("Another slapd Instance " - "seems already running on this host, listening to %s." % - self.ldapi_uri) - - -class ProvisionBackend(object): - - def __init__(self, backend_type, paths=None, setup_path=None, lp=None, - credentials=None, names=None, logger=None): - """Provision a backend for samba4""" - self.paths = paths - self.setup_path = setup_path - self.lp = lp - self.credentials = credentials - self.names = names - self.logger = logger - - self.type = backend_type - - # Set a default - the code for "existing" below replaces this - self.ldap_backend_type = backend_type - - def init(self): - """Initialize the backend.""" - raise NotImplementedError(self.init) - - def start(self): - """Start the backend.""" - raise NotImplementedError(self.start) - - def shutdown(self): - """Shutdown the backend.""" - raise NotImplementedError(self.shutdown) - - def post_setup(self): - """Post setup.""" - raise NotImplementedError(self.post_setup) - - -class LDBBackend(ProvisionBackend): - - def init(self): - self.credentials = None - self.secrets_credentials = None - - # Wipe the old sam.ldb databases away - shutil.rmtree(self.paths.samdb + ".d", True) - - def start(self): - pass - - def shutdown(self): - pass - - def post_setup(self): - pass - - -class ExistingBackend(ProvisionBackend): - - def __init__(self, backend_type, paths=None, setup_path=None, lp=None, - credentials=None, names=None, logger=None, ldapi_uri=None): - - super(ExistingBackend, self).__init__(backend_type=backend_type, - paths=paths, setup_path=setup_path, lp=lp, - credentials=credentials, names=names, logger=logger, - ldap_backend_forced_uri=ldap_backend_forced_uri) - - def init(self): - # Check to see that this 'existing' LDAP backend in fact exists - ldapi_db = Ldb(self.ldapi_uri, credentials=self.credentials) - ldapi_db.search(base="", scope=SCOPE_BASE, - expression="(objectClass=OpenLDAProotDSE)") - - # If we have got here, then we must have a valid connection to the LDAP - # server, with valid credentials supplied This caused them to be set - # into the long-term database later in the script. - self.secrets_credentials = self.credentials - - # For now, assume existing backends at least emulate OpenLDAP - self.ldap_backend_type = "openldap" - - -class LDAPBackend(ProvisionBackend): - - def __init__(self, backend_type, paths=None, setup_path=None, lp=None, - credentials=None, names=None, logger=None, domainsid=None, - schema=None, hostname=None, ldapadminpass=None, slapd_path=None, - ldap_backend_extra_port=None, - ldap_backend_forced_uri=None, ldap_dryrun_mode=False): - - super(LDAPBackend, self).__init__(backend_type=backend_type, - paths=paths, setup_path=setup_path, lp=lp, - credentials=credentials, names=names, logger=logger) - - self.domainsid = domainsid - self.schema = schema - self.hostname = hostname - - self.ldapdir = os.path.join(paths.private_dir, "ldap") - self.ldapadminpass = ldapadminpass - - self.slapd_path = slapd_path - self.slapd_command = None - self.slapd_command_escaped = None - self.slapd_pid = os.path.join(self.ldapdir, "slapd.pid") - - self.ldap_backend_extra_port = ldap_backend_extra_port - self.ldap_dryrun_mode = ldap_dryrun_mode - - if ldap_backend_forced_uri is not None: - self.ldap_uri = ldap_backend_forced_uri - else: - self.ldap_uri = "ldapi://%s" % urllib.quote(os.path.join(self.ldapdir, "ldapi"), safe="") - - if not os.path.exists(self.ldapdir): - os.mkdir(self.ldapdir) - - def init(self): - from samba.provision import ProvisioningError - # we will shortly start slapd with ldapi for final provisioning. first - # check with ldapsearch -> rootDSE via self.ldap_uri if another - # instance of slapd is already running - try: - ldapi_db = Ldb(self.ldap_uri) - ldapi_db.search(base="", scope=SCOPE_BASE, - expression="(objectClass=OpenLDAProotDSE)") - try: - f = open(self.slapd_pid, "r") - except IOError, err: - if err != errno.ENOENT: - raise - else: - p = f.read() - f.close() - self.logger.info("Check for slapd Process with PID: " + str(p) + " and terminate it manually.") - raise SlapdAlreadyRunning(self.ldap_uri) - except LdbError: - # XXX: We should never be catching all Ldb errors - pass - - # Try to print helpful messages when the user has not specified the - # path to slapd - if self.slapd_path is None: - raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!") - if not os.path.exists(self.slapd_path): - self.logger.warning("Path (%s) to slapd does not exist!", self.slapd_path) - - if not os.path.isdir(self.ldapdir): - os.makedirs(self.ldapdir, 0700) - - # Put the LDIF of the schema into a database so we can search on - # it to generate schema-dependent configurations in Fedora DS and - # OpenLDAP - schemadb_path = os.path.join(self.ldapdir, "schema-tmp.ldb") - try: - os.unlink(schemadb_path) - except OSError: - pass - - self.schema.write_to_tmp_ldb(schemadb_path) - - self.credentials = Credentials() - self.credentials.guess(self.lp) - # Kerberos to an ldapi:// backend makes no sense - self.credentials.set_kerberos_state(DONT_USE_KERBEROS) - self.credentials.set_password(self.ldapadminpass) - - self.secrets_credentials = Credentials() - self.secrets_credentials.guess(self.lp) - # Kerberos to an ldapi:// backend makes no sense - self.secrets_credentials.set_kerberos_state(DONT_USE_KERBEROS) - self.secrets_credentials.set_username("samba-admin") - self.secrets_credentials.set_password(self.ldapadminpass) - - self.provision() - - def provision(self): - pass - - def start(self): - from samba.provision import ProvisioningError - self.slapd_command_escaped = "\'" + "\' \'".join(self.slapd_command) + "\'" - f = open(os.path.join(self.ldapdir, "ldap_backend_startup.sh"), 'w') - try: - f.write("#!/bin/sh\n" + self.slapd_command_escaped + "\n") - finally: - f.close() - - # Now start the slapd, so we can provision onto it. We keep the - # subprocess context around, to kill this off at the successful - # end of the script - self.slapd = subprocess.Popen(self.slapd_provision_command, - close_fds=True, shell=False) - - count = 0 - while self.slapd.poll() is None: - # Wait until the socket appears - try: - ldapi_db = Ldb(self.ldap_uri, lp=self.lp, credentials=self.credentials) - ldapi_db.search(base="", scope=SCOPE_BASE, - expression="(objectClass=OpenLDAProotDSE)") - # If we have got here, then we must have a valid connection to the LDAP server! - return - except LdbError: - time.sleep(1) - count = count + 1 - - if count > 15: - self.logger.error("Could not connect to slapd started with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'") - raise ProvisioningError("slapd never accepted a connection within 15 seconds of starting") - - self.logger.error("Could not start slapd with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'") - raise ProvisioningError("slapd died before we could make a connection to it") - - def shutdown(self): - # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination - if self.slapd.poll() is None: - # Kill the slapd - if hasattr(self.slapd, "terminate"): - self.slapd.terminate() - else: - # Older python versions don't have .terminate() - import signal - os.kill(self.slapd.pid, signal.SIGTERM) - - # and now wait for it to die - self.slapd.communicate() - - def post_setup(self): - pass - -class OpenLDAPBackend(LDAPBackend): - - def __init__(self, backend_type, paths=None, setup_path=None, lp=None, - credentials=None, names=None, logger=None, domainsid=None, - schema=None, hostname=None, ldapadminpass=None, slapd_path=None, - ldap_backend_extra_port=None, ldap_dryrun_mode=False, - ol_mmr_urls=None, nosync=False, ldap_backend_forced_uri=None): - super(OpenLDAPBackend, self).__init__( backend_type=backend_type, - paths=paths, setup_path=setup_path, lp=lp, - credentials=credentials, names=names, logger=logger, - domainsid=domainsid, schema=schema, hostname=hostname, - ldapadminpass=ldapadminpass, slapd_path=slapd_path, - ldap_backend_extra_port=ldap_backend_extra_port, - ldap_backend_forced_uri=ldap_backend_forced_uri, - ldap_dryrun_mode=ldap_dryrun_mode) - - self.ol_mmr_urls = ol_mmr_urls - self.nosync = nosync - - self.slapdconf = os.path.join(self.ldapdir, "slapd.conf") - self.modulesconf = os.path.join(self.ldapdir, "modules.conf") - self.memberofconf = os.path.join(self.ldapdir, "memberof.conf") - self.olmmrserveridsconf = os.path.join(self.ldapdir, "mmr_serverids.conf") - self.olmmrsyncreplconf = os.path.join(self.ldapdir, "mmr_syncrepl.conf") - self.olcdir = os.path.join(self.ldapdir, "slapd.d") - self.olcseedldif = os.path.join(self.ldapdir, "olc_seed.ldif") - - self.schema = Schema(self.setup_path, self.domainsid, - schemadn=self.names.schemadn, - files=[setup_path("schema_samba4.ldif")]) - - def setup_db_config(self, dbdir): - """Setup a Berkeley database. - - :param setup_path: Setup path function. - :param dbdir: Database directory.""" - if not os.path.isdir(os.path.join(dbdir, "bdb-logs")): - os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700) - if not os.path.isdir(os.path.join(dbdir, "tmp")): - os.makedirs(os.path.join(dbdir, "tmp"), 0700) - - setup_file(self.setup_path("DB_CONFIG"), - os.path.join(dbdir, "DB_CONFIG"), {"LDAPDBDIR": dbdir}) - - def provision(self): - from samba.provision import ProvisioningError - # Wipe the directories so we can start - shutil.rmtree(os.path.join(self.ldapdir, "db"), True) - - #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB - nosync_config = "" - if self.nosync: - nosync_config = "dbnosync" - - lnkattr = self.schema.linked_attributes() - refint_attributes = "" - memberof_config = "# Generated from Samba4 schema\n" - for att in lnkattr.keys(): - if lnkattr[att] is not None: - refint_attributes = refint_attributes + " " + att - - memberof_config += read_and_sub_file(self.setup_path("memberof.conf"), - { "MEMBER_ATTR" : att , - "MEMBEROF_ATTR" : lnkattr[att] }) - - refint_config = read_and_sub_file(self.setup_path("refint.conf"), - { "LINK_ATTRS" : refint_attributes}) - - attrs = ["linkID", "lDAPDisplayName"] - res = self.schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs) - index_config = "" - for i in range (0, len(res)): - index_attr = res[i]["lDAPDisplayName"][0] - if index_attr == "objectGUID": - index_attr = "entryUUID" - - index_config += "index " + index_attr + " eq\n" - - # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts - mmr_on_config = "" - mmr_replicator_acl = "" - mmr_serverids_config = "" - mmr_syncrepl_schema_config = "" - mmr_syncrepl_config_config = "" - mmr_syncrepl_user_config = "" - - if self.ol_mmr_urls is not None: - # For now, make these equal - mmr_pass = self.ldapadminpass - - url_list=filter(None,self.ol_mmr_urls.split(',')) - for url in url_list: - self.logger.info("Using LDAP-URL: "+url) - if (len(url_list) == 1): - raise ProvisioningError("At least 2 LDAP-URLs needed for MMR!") - - - mmr_on_config = "MirrorMode On" - mmr_replicator_acl = " by dn=cn=replicator,cn=samba read" - serverid=0 - for url in url_list: - serverid=serverid+1 - mmr_serverids_config += read_and_sub_file(self.setup_path("mmr_serverids.conf"), - { "SERVERID" : str(serverid), - "LDAPSERVER" : url }) - rid=serverid*10 - rid=rid+1 - mmr_syncrepl_schema_config += read_and_sub_file( - self.setup_path("mmr_syncrepl.conf"), - { "RID" : str(rid), - "MMRDN": self.names.schemadn, - "LDAPSERVER" : url, - "MMR_PASSWORD": mmr_pass}) - - rid = rid+1 - mmr_syncrepl_config_config += read_and_sub_file( - self.setup_path("mmr_syncrepl.conf"), { - "RID" : str(rid), - "MMRDN": self.names.configdn, - "LDAPSERVER" : url, - "MMR_PASSWORD": mmr_pass}) - - rid = rid+1 - mmr_syncrepl_user_config += read_and_sub_file( - self.setup_path("mmr_syncrepl.conf"), { - "RID" : str(rid), - "MMRDN": self.names.domaindn, - "LDAPSERVER" : url, - "MMR_PASSWORD": mmr_pass }) - # OpenLDAP cn=config initialisation - olc_syncrepl_config = "" - olc_mmr_config = "" - # if mmr = yes, generate cn=config-replication directives - # and olc_seed.lif for the other mmr-servers - if self.ol_mmr_urls is not None: - serverid=0 - olc_serverids_config = "" - olc_syncrepl_seed_config = "" - olc_mmr_config += read_and_sub_file( - self.setup_path("olc_mmr.conf"), {}) - rid = 500 - for url in url_list: - serverid = serverid + 1 - olc_serverids_config += read_and_sub_file( - self.setup_path("olc_serverid.conf"), { - "SERVERID" : str(serverid), "LDAPSERVER" : url }) - - rid = rid + 1 - olc_syncrepl_config += read_and_sub_file( - self.setup_path("olc_syncrepl.conf"), { - "RID" : str(rid), "LDAPSERVER" : url, - "MMR_PASSWORD": mmr_pass}) - - olc_syncrepl_seed_config += read_and_sub_file( - self.setup_path("olc_syncrepl_seed.conf"), { - "RID" : str(rid), "LDAPSERVER" : url}) - - setup_file(self.setup_path("olc_seed.ldif"), self.olcseedldif, - {"OLC_SERVER_ID_CONF": olc_serverids_config, - "OLC_PW": self.ldapadminpass, - "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config}) - # end olc - - setup_file(self.setup_path("slapd.conf"), self.slapdconf, - {"DNSDOMAIN": self.names.dnsdomain, - "LDAPDIR": self.ldapdir, - "DOMAINDN": self.names.domaindn, - "CONFIGDN": self.names.configdn, - "SCHEMADN": self.names.schemadn, - "MEMBEROF_CONFIG": memberof_config, - "MIRRORMODE": mmr_on_config, - "REPLICATOR_ACL": mmr_replicator_acl, - "MMR_SERVERIDS_CONFIG": mmr_serverids_config, - "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config, - "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config, - "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config, - "OLC_SYNCREPL_CONFIG": olc_syncrepl_config, - "OLC_MMR_CONFIG": olc_mmr_config, - "REFINT_CONFIG": refint_config, - "INDEX_CONFIG": index_config, - "NOSYNC": nosync_config}) - - self.setup_db_config(os.path.join(self.ldapdir, "db", "user")) - self.setup_db_config(os.path.join(self.ldapdir, "db", "config")) - self.setup_db_config(os.path.join(self.ldapdir, "db", "schema")) - - if not os.path.exists(os.path.join(self.ldapdir, "db", "samba", "cn=samba")): - os.makedirs(os.path.join(self.ldapdir, "db", "samba", "cn=samba"), 0700) - - setup_file(self.setup_path("cn=samba.ldif"), - os.path.join(self.ldapdir, "db", "samba", "cn=samba.ldif"), - { "UUID": str(uuid.uuid4()), - "LDAPTIME": timestring(int(time.time()))} ) - setup_file(self.setup_path("cn=samba-admin.ldif"), - os.path.join(self.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"), - {"LDAPADMINPASS_B64": b64encode(self.ldapadminpass), - "UUID": str(uuid.uuid4()), - "LDAPTIME": timestring(int(time.time()))} ) - - if self.ol_mmr_urls is not None: - setup_file(self.setup_path("cn=replicator.ldif"), - os.path.join(self.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"), - {"MMR_PASSWORD_B64": b64encode(mmr_pass), - "UUID": str(uuid.uuid4()), - "LDAPTIME": timestring(int(time.time()))} ) - - - mapping = "schema-map-openldap-2.3" - backend_schema = "backend-schema.schema" - - f = open(self.setup_path(mapping), 'r') - backend_schema_data = self.schema.convert_to_openldap( - "openldap", f.read()) - assert backend_schema_data is not None - f = open(os.path.join(self.ldapdir, backend_schema), 'w') - try: - f.write(backend_schema_data) - finally: - f.close() - - # now we generate the needed strings to start slapd automatically, - if self.ldap_backend_extra_port is not None: - # When we use MMR, we can't use 0.0.0.0 as it uses the name - # specified there as part of it's clue as to it's own name, - # and not to replicate to itself - if self.ol_mmr_urls is None: - server_port_string = "ldap://0.0.0.0:%d" % self.ldap_backend_extra_port - else: - server_port_string = "ldap://%s.%s:%d" (self.names.hostname, - self.names.dnsdomain, self.ldap_backend_extra_port) - else: - server_port_string = "" - - # Prepare the 'result' information - the commands to return in - # particular - self.slapd_provision_command = [self.slapd_path, "-F" + self.olcdir, - "-h"] - - # copy this command so we have two version, one with -d0 and only - # ldapi (or the forced ldap_uri), and one with all the listen commands - self.slapd_command = list(self.slapd_provision_command) - - self.slapd_provision_command.extend([self.ldap_uri, "-d0"]) - - uris = self.ldap_uri - if server_port_string is not "": - uris = uris + " " + server_port_string - - self.slapd_command.append(uris) - - # Set the username - done here because Fedora DS still uses the admin - # DN and simple bind - self.credentials.set_username("samba-admin") - - # If we were just looking for crashes up to this point, it's a - # good time to exit before we realise we don't have OpenLDAP on - # this system - if self.ldap_dryrun_mode: - sys.exit(0) - - # Finally, convert the configuration into cn=config style! - if not os.path.isdir(self.olcdir): - os.makedirs(self.olcdir, 0770) - - slapd_cmd = [self.slapd_path, "-Ttest", "-n", "0", "-f", self.slapdconf, "-F", self.olcdir] - retcode = subprocess.call(slapd_cmd, close_fds=True, - shell=False) - - if retcode != 0: - self.logger.error("conversion from slapd.conf to cn=config failed slapd started with: %s" % "\'" + "\' \'".join(slapd_cmd) + "\'") - raise ProvisioningError("conversion from slapd.conf to cn=config failed") - - if not os.path.exists(os.path.join(self.olcdir, "cn=config.ldif")): - raise ProvisioningError("conversion from slapd.conf to cn=config failed") - - # Don't confuse the admin by leaving the slapd.conf around - os.remove(self.slapdconf) - - -class FDSBackend(LDAPBackend): - - def __init__(self, backend_type, paths=None, setup_path=None, lp=None, - credentials=None, names=None, logger=None, domainsid=None, - schema=None, hostname=None, ldapadminpass=None, slapd_path=None, - ldap_backend_extra_port=None, ldap_dryrun_mode=False, root=None, - setup_ds_path=None): - - super(FDSBackend, self).__init__(backend_type=backend_type, - paths=paths, setup_path=setup_path, lp=lp, - credentials=credentials, names=names, logger=logger, - domainsid=domainsid, schema=schema, hostname=hostname, - ldapadminpass=ldapadminpass, slapd_path=slapd_path, - ldap_backend_extra_port=ldap_backend_extra_port, - ldap_backend_forced_uri=ldap_backend_forced_uri, - ldap_dryrun_mode=ldap_dryrun_mode) - - self.root = root - self.setup_ds_path = setup_ds_path - self.ldap_instance = self.names.netbiosname.lower() - - self.sambadn = "CN=Samba" - - self.fedoradsinf = os.path.join(self.ldapdir, "fedorads.inf") - self.partitions_ldif = os.path.join(self.ldapdir, "fedorads-partitions.ldif") - self.sasl_ldif = os.path.join(self.ldapdir, "fedorads-sasl.ldif") - self.dna_ldif = os.path.join(self.ldapdir, "fedorads-dna.ldif") - self.pam_ldif = os.path.join(self.ldapdir, "fedorads-pam.ldif") - self.refint_ldif = os.path.join(self.ldapdir, "fedorads-refint.ldif") - self.linked_attrs_ldif = os.path.join(self.ldapdir, "fedorads-linked-attributes.ldif") - self.index_ldif = os.path.join(self.ldapdir, "fedorads-index.ldif") - self.samba_ldif = os.path.join(self.ldapdir, "fedorads-samba.ldif") - - self.samba3_schema = self.setup_path("../../examples/LDAP/samba.schema") - self.samba3_ldif = os.path.join(self.ldapdir, "samba3.ldif") - - self.retcode = subprocess.call(["bin/oLschema2ldif", - "-I", self.samba3_schema, - "-O", self.samba3_ldif, - "-b", self.names.domaindn], - close_fds=True, shell=False) - - if self.retcode != 0: - raise Exception("Unable to convert Samba 3 schema.") - - self.schema = Schema( - self.setup_path, - self.domainsid, - schemadn=self.names.schemadn, - files=[setup_path("schema_samba4.ldif"), self.samba3_ldif], - additional_prefixmap=["1000:1.3.6.1.4.1.7165.2.1", "1001:1.3.6.1.4.1.7165.2.2"]) - - def provision(self): - from samba.provision import ProvisioningError - if self.ldap_backend_extra_port is not None: - serverport = "ServerPort=%d" % self.ldap_backend_extra_port - else: - serverport = "" - - setup_file(self.setup_path("fedorads.inf"), self.fedoradsinf, - {"ROOT": self.root, - "HOSTNAME": self.hostname, - "DNSDOMAIN": self.names.dnsdomain, - "LDAPDIR": self.ldapdir, - "DOMAINDN": self.names.domaindn, - "LDAP_INSTANCE": self.ldap_instance, - "LDAPMANAGERDN": self.names.ldapmanagerdn, - "LDAPMANAGERPASS": self.ldapadminpass, - "SERVERPORT": serverport}) - - setup_file(self.setup_path("fedorads-partitions.ldif"), - self.partitions_ldif, - {"CONFIGDN": self.names.configdn, - "SCHEMADN": self.names.schemadn, - "SAMBADN": self.sambadn, - }) - - setup_file(self.setup_path("fedorads-sasl.ldif"), self.sasl_ldif, - {"SAMBADN": self.sambadn, - }) - - setup_file(self.setup_path("fedorads-dna.ldif"), self.dna_ldif, - {"DOMAINDN": self.names.domaindn, - "SAMBADN": self.sambadn, - "DOMAINSID": str(self.domainsid), - }) - - setup_file(self.setup_path("fedorads-pam.ldif"), self.pam_ldif) - - lnkattr = self.schema.linked_attributes() - - refint_config = open(self.setup_path("fedorads-refint-delete.ldif"), 'r').read() - memberof_config = "" - index_config = "" - argnum = 3 - - for attr in lnkattr.keys(): - if lnkattr[attr] is not None: - refint_config += read_and_sub_file(self.setup_path("fedorads-refint-add.ldif"), - { "ARG_NUMBER" : str(argnum), - "LINK_ATTR" : attr }) - memberof_config += read_and_sub_file(self.setup_path("fedorads-linked-attributes.ldif"), - { "MEMBER_ATTR" : attr, - "MEMBEROF_ATTR" : lnkattr[attr] }) - index_config += read_and_sub_file( - self.setup_path("fedorads-index.ldif"), { "ATTR" : attr }) - argnum += 1 - - open(self.refint_ldif, 'w').write(refint_config) - open(self.linked_attrs_ldif, 'w').write(memberof_config) - - attrs = ["lDAPDisplayName"] - res = self.schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs) - - for i in range (0, len(res)): - attr = res[i]["lDAPDisplayName"][0] - - if attr == "objectGUID": - attr = "nsUniqueId" - - index_config += read_and_sub_file( - self.setup_path("fedorads-index.ldif"), { "ATTR" : attr }) - - open(self.index_ldif, 'w').write(index_config) - - setup_file(self.setup_path("fedorads-samba.ldif"), self.samba_ldif, - {"SAMBADN": self.sambadn, - "LDAPADMINPASS": self.ldapadminpass - }) - - mapping = "schema-map-fedora-ds-1.0" - backend_schema = "99_ad.ldif" - - # Build a schema file in Fedora DS format - backend_schema_data = self.schema.convert_to_openldap("fedora-ds", open(self.setup_path(mapping), 'r').read()) - assert backend_schema_data is not None - f = open(os.path.join(self.ldapdir, backend_schema), 'w') - try: - f.write(backend_schema_data) - finally: - f.close() - - self.credentials.set_bind_dn(self.names.ldapmanagerdn) - - # Destory the target directory, or else setup-ds.pl will complain - fedora_ds_dir = os.path.join(self.ldapdir, - "slapd-" + self.ldap_instance) - shutil.rmtree(fedora_ds_dir, True) - - self.slapd_provision_command = [self.slapd_path, "-D", fedora_ds_dir, - "-i", self.slapd_pid] - # In the 'provision' command line, stay in the foreground so we can - # easily kill it - self.slapd_provision_command.append("-d0") - - #the command for the final run is the normal script - self.slapd_command = [os.path.join(self.ldapdir, - "slapd-" + self.ldap_instance, "start-slapd")] - - # If we were just looking for crashes up to this point, it's a - # good time to exit before we realise we don't have Fedora DS on - if self.ldap_dryrun_mode: - sys.exit(0) - - # Try to print helpful messages when the user has not specified the path to the setup-ds tool - if self.setup_ds_path is None: - raise ProvisioningError("Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!") - if not os.path.exists(self.setup_ds_path): - self.logger.warning("Path (%s) to slapd does not exist!", self.setup_ds_path) - - # Run the Fedora DS setup utility - retcode = subprocess.call([self.setup_ds_path, "--silent", "--file", self.fedoradsinf], close_fds=True, shell=False) - if retcode != 0: - raise ProvisioningError("setup-ds failed") - - # Load samba-admin - retcode = subprocess.call([ - os.path.join(self.ldapdir, "slapd-" + self.ldap_instance, "ldif2db"), "-s", self.sambadn, "-i", self.samba_ldif], - close_fds=True, shell=False) - if retcode != 0: - raise ProvisioningError("ldif2db failed") - - def post_setup(self): - ldapi_db = Ldb(self.ldap_uri, credentials=self.credentials) - - # configure in-directory access control on Fedora DS via the aci - # attribute (over a direct ldapi:// socket) - aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % self.sambadn - - m = ldb.Message() - m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci") - - for dnstring in (self.names.domaindn, self.names.configdn, - self.names.schemadn): - m.dn = ldb.Dn(ldapi_db, dnstring) - ldapi_db.modify(m) diff --git a/source4/scripting/python/samba/tests/samdb.py b/source4/scripting/python/samba/tests/samdb.py index f0a594dcf0..1536f163d1 100644 --- a/source4/scripting/python/samba/tests/samdb.py +++ b/source4/scripting/python/samba/tests/samdb.py @@ -2,17 +2,17 @@ # Unix SMB/CIFS implementation. Tests for SamDB # Copyright (C) Jelmer Vernooij 2008 -# +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program. If not, see . # @@ -21,9 +21,10 @@ import os import uuid from samba.auth import system_session -from samba.provision import setup_samdb, guess_names, make_smbconf, find_setup_dir, provision_paths_from_lp +from samba.provision import (setup_samdb, guess_names, make_smbconf, + find_setup_dir, provision_paths_from_lp) from samba.provision import DEFAULT_POLICY_GUID, DEFAULT_DC_POLICY_GUID -from samba.provisionbackend import ProvisionBackend +from samba.provision.backend import ProvisionBackend from samba.tests import TestCaseInTempDir from samba.dcerpc import security from samba.schema import Schema @@ -32,7 +33,7 @@ from samba import param class SamDBTestCase(TestCaseInTempDir): """Base-class for tests with a Sam Database. - + This is used by the Samba SamDB-tests, but e.g. also by the OpenChange provisioning tests (which need a Sam). """ -- cgit