diff options
| author | Jelmer Vernooij <jelmer@samba.org> | 2010-11-28 04:02:28 +0100 |
|---|---|---|
| committer | Jelmer Vernooij <jelmer@samba.org> | 2010-11-28 05:00:06 +0100 |
| commit | 8caac9462ac09b7ff99a7032329d0e56c2e0aac5 (patch) | |
| tree | 10de73138f25a3090dfb3f6b65d6efcec28e33ca /source4/scripting/python/samba/provision | |
| parent | a7675bd5010641051096344bffb9ce569193a8fb (diff) | |
| download | samba-8caac9462ac09b7ff99a7032329d0e56c2e0aac5.tar.gz samba-8caac9462ac09b7ff99a7032329d0e56c2e0aac5.tar.bz2 samba-8caac9462ac09b7ff99a7032329d0e56c2e0aac5.zip | |
samba.provision: Add package with provision and backend modules.
Diffstat (limited to 'source4/scripting/python/samba/provision')
| -rw-r--r-- | source4/scripting/python/samba/provision/__init__.py | 1960 | ||||
| -rw-r--r-- | source4/scripting/python/samba/provision/backend.py | 767 |
2 files changed, 2727 insertions, 0 deletions
diff --git a/source4/scripting/python/samba/provision/__init__.py b/source4/scripting/python/samba/provision/__init__.py new file mode 100644 index 00000000000..1fed220507e --- /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 <jelmer@samba.org> 2007-2010 +# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009 +# Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009 +# +# Based on the original in EJS: +# Copyright (C) Andrew Tridgell <tridge@samba.org> 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 <http://www.gnu.org/licenses/>. +# + +"""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())< |
