summaryrefslogtreecommitdiffstats
path: root/python/samba/provision
diff options
context:
space:
mode:
Diffstat (limited to 'python/samba/provision')
-rw-r--r--python/samba/provision/__init__.py2524
-rw-r--r--python/samba/provision/backend.py87
-rw-r--r--python/samba/provision/common.py91
-rw-r--r--python/samba/provision/kerberos.py104
-rw-r--r--python/samba/provision/sambadns.py1329
5 files changed, 4135 insertions, 0 deletions
diff --git a/python/samba/provision/__init__.py b/python/samba/provision/__init__.py
new file mode 100644
index 0000000..56ca749
--- /dev/null
+++ b/python/samba/provision/__init__.py
@@ -0,0 +1,2524 @@
+# Unix SMB/CIFS implementation.
+# backend code for provisioning a Samba AD server
+
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2012
+# 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 errno
+import os
+import stat
+import re
+import pwd
+import grp
+import logging
+import time
+import uuid
+import socket
+import tempfile
+import samba.dsdb
+
+import ldb
+
+from samba.auth import system_session, admin_session
+from samba.auth_util import system_session_unix
+import samba
+from samba import auth
+from samba.samba3 import smbd, passdb
+from samba.samba3 import param as s3param
+from samba import (
+ Ldb,
+ MAX_NETBIOS_NAME_LEN,
+ check_all_substituted,
+ is_valid_netbios_char,
+ setup_file,
+ substitute_var,
+ valid_netbios_name,
+ version,
+ is_heimdal_built,
+)
+from samba.dcerpc import security, misc
+from samba.dcerpc.misc import (
+ SEC_CHAN_BDC,
+ SEC_CHAN_WKSTA,
+)
+from samba.dsdb import (
+ DS_DOMAIN_FUNCTION_2000,
+ DS_DOMAIN_FUNCTION_2008,
+ DS_DOMAIN_FUNCTION_2008_R2,
+ DS_DOMAIN_FUNCTION_2012,
+ DS_DOMAIN_FUNCTION_2012_R2,
+ DS_DOMAIN_FUNCTION_2016,
+ ENC_ALL_TYPES,
+)
+from samba.idmap import IDmapDB
+from samba.ms_display_specifiers import read_ms_ldif
+from samba.ntacls import setntacl, getntacl, dsacl2fsacl
+from samba.ndr import ndr_pack, ndr_unpack
+from samba.provision.backend import (
+ LDBBackend,
+)
+from samba.descriptor import (
+ get_deletedobjects_descriptor,
+ get_config_descriptor,
+ get_config_partitions_descriptor,
+ get_config_sites_descriptor,
+ get_config_ntds_quotas_descriptor,
+ get_config_delete_protected1_descriptor,
+ get_config_delete_protected1wd_descriptor,
+ get_config_delete_protected2_descriptor,
+ get_domain_descriptor,
+ get_domain_infrastructure_descriptor,
+ get_domain_builtin_descriptor,
+ get_domain_computers_descriptor,
+ get_domain_users_descriptor,
+ get_domain_controllers_descriptor,
+ get_domain_delete_protected1_descriptor,
+ get_domain_delete_protected2_descriptor,
+ get_managed_service_accounts_descriptor,
+)
+from samba.provision.common import (
+ setup_path,
+ setup_add_ldif,
+ setup_modify_ldif,
+ FILL_FULL,
+ FILL_SUBDOMAIN,
+ FILL_DRS
+)
+from samba.provision.sambadns import (
+ get_dnsadmins_sid,
+ setup_ad_dns,
+ create_dns_dir_keytab_link,
+ create_dns_update_list
+)
+
+import samba.param
+import samba.registry
+from samba.schema import Schema
+from samba.samdb import SamDB
+from samba.dbchecker import dbcheck
+from samba.provision.kerberos import create_kdc_conf
+from samba.samdb import get_default_backend_store
+from samba import functional_level
+
+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"
+
+DEFAULT_MIN_PWD_LENGTH = 7
+
+
+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
+ self.binddns_dir = None
+ self.state_dir = None
+
+
+class ProvisionNames(object):
+
+ def __init__(self):
+ self.ncs = None
+ self.rootdn = None
+ self.domaindn = None
+ self.configdn = None
+ self.schemadn = None
+ self.dnsforestdn = None
+ self.dnsdomaindn = None
+ self.ldapmanagerdn = None
+ self.dnsdomain = None
+ self.realm = None
+ self.netbiosname = None
+ self.domain = None
+ self.hostname = None
+ self.sitename = None
+ self.smbconf = None
+ self.domainsid = None
+ self.forestsid = None
+ self.domainguid = None
+ self.name_map = {}
+
+
+def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf,
+ lp):
+ """Get key provision parameters (realm, domain, ...) from a given provision
+
+ :param samdb: An LDB object connected to the sam.ldb file
+ :param secretsdb: An LDB object connected to the secrets.ldb file
+ :param idmapdb: An LDB object connected to the idmap.ldb file
+ :param paths: A list of path to provision object
+ :param smbconf: Path to the smb.conf file
+ :param lp: A LoadParm object
+ :return: A list of key provision parameters
+ """
+ names = ProvisionNames()
+ names.adminpass = None
+
+ # NT domain, kerberos realm, root dn, domain dn, domain dns name
+ names.domain = lp.get("workgroup").upper()
+ names.realm = lp.get("realm")
+ names.dnsdomain = names.realm.lower()
+ basedn = samba.dn_from_dns_name(names.dnsdomain)
+ names.realm = names.realm.upper()
+ # netbiosname
+ # Get the netbiosname first (could be obtained from smb.conf in theory)
+ res = secretsdb.search(expression="(flatname=%s)" %
+ names.domain, base="CN=Primary Domains",
+ scope=ldb.SCOPE_SUBTREE, attrs=["sAMAccountName"])
+ names.netbiosname = str(res[0]["sAMAccountName"]).replace("$", "")
+
+ names.smbconf = smbconf
+
+ # That's a bit simplistic but it's ok as long as we have only 3
+ # partitions
+ current = samdb.search(expression="(objectClass=*)",
+ base="", scope=ldb.SCOPE_BASE,
+ attrs=["defaultNamingContext", "schemaNamingContext",
+ "configurationNamingContext", "rootDomainNamingContext",
+ "namingContexts"])
+
+ names.configdn = str(current[0]["configurationNamingContext"][0])
+ names.schemadn = str(current[0]["schemaNamingContext"][0])
+ if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
+ current[0]["defaultNamingContext"][0].decode('utf8')))):
+ raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
+ "is not the same ..." % (paths.samdb,
+ str(current[0]["defaultNamingContext"][0].decode('utf8')),
+ paths.smbconf, basedn)))
+
+ names.domaindn = str(current[0]["defaultNamingContext"][0])
+ names.rootdn = str(current[0]["rootDomainNamingContext"][0])
+ names.ncs = current[0]["namingContexts"]
+ names.dnsforestdn = None
+ names.dnsdomaindn = None
+
+ for i in range(0, len(names.ncs)):
+ nc = str(names.ncs[i])
+
+ dnsforestdn = "DC=ForestDnsZones,%s" % (str(names.rootdn))
+ if nc == dnsforestdn:
+ names.dnsforestdn = dnsforestdn
+ continue
+
+ dnsdomaindn = "DC=DomainDnsZones,%s" % (str(names.domaindn))
+ if nc == dnsdomaindn:
+ names.dnsdomaindn = dnsdomaindn
+ continue
+
+ # default site name
+ res3 = samdb.search(expression="(objectClass=site)",
+ base="CN=Sites," + str(names.configdn), scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
+ names.sitename = str(res3[0]["cn"])
+
+ # dns hostname and server dn
+ res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
+ base="OU=Domain Controllers,%s" % basedn,
+ scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"])
+ if len(res4) == 0:
+ raise ProvisioningError("Unable to find DC called CN=%s under OU=Domain Controllers,%s" % (names.netbiosname, basedn))
+
+ names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain, "")
+
+ server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
+ attrs=[], base=names.configdn)
+ names.serverdn = str(server_res[0].dn)
+
+ # invocation id/objectguid
+ res5 = samdb.search(expression="(objectClass=*)",
+ base="CN=NTDS Settings,%s" % str(names.serverdn),
+ scope=ldb.SCOPE_BASE,
+ attrs=["invocationID", "objectGUID"])
+ names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
+ names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
+
+ # domain guid/sid
+ res6 = samdb.search(expression="(objectClass=*)", base=basedn,
+ scope=ldb.SCOPE_BASE, attrs=["objectGUID",
+ "objectSid", "msDS-Behavior-Version"])
+ names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
+ names.domainsid = ndr_unpack(security.dom_sid, res6[0]["objectSid"][0])
+ names.forestsid = ndr_unpack(security.dom_sid, res6[0]["objectSid"][0])
+ if res6[0].get("msDS-Behavior-Version") is None or \
+ int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
+ names.domainlevel = DS_DOMAIN_FUNCTION_2000
+ else:
+ names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
+
+ # policy guid
+ res7 = samdb.search(expression="(name={%s})" % DEFAULT_POLICY_GUID,
+ base="CN=Policies,CN=System," + basedn,
+ scope=ldb.SCOPE_ONELEVEL, attrs=["cn", "displayName"])
+ names.policyid = str(res7[0]["cn"]).replace("{", "").replace("}", "")
+ # dc policy guid
+ res8 = samdb.search(expression="(name={%s})" % DEFAULT_DC_POLICY_GUID,
+ base="CN=Policies,CN=System," + basedn,
+ scope=ldb.SCOPE_ONELEVEL,
+ attrs=["cn", "displayName"])
+ if len(res8) == 1:
+ names.policyid_dc = str(res8[0]["cn"]).replace("{", "").replace("}", "")
+ else:
+ names.policyid_dc = None
+
+ res9 = idmapdb.search(expression="(cn=%s-%s)" %
+ (str(names.domainsid), security.DOMAIN_RID_ADMINISTRATOR),
+ attrs=["xidNumber", "type"])
+ if len(res9) != 1:
+ raise ProvisioningError("Unable to find uid/gid for Domain Admins rid (%s-%s" % (str(names.domainsid), security.DOMAIN_RID_ADMINISTRATOR))
+ if str(res9[0]["type"][0]) == "ID_TYPE_BOTH":
+ names.root_gid = int(res9[0]["xidNumber"][0])
+ else:
+ names.root_gid = pwd.getpwuid(int(res9[0]["xidNumber"][0])).pw_gid
+
+ res10 = samdb.search(expression="(samaccountname=dns)",
+ scope=ldb.SCOPE_SUBTREE, attrs=["dn"],
+ controls=["search_options:1:2"])
+ if (len(res10) > 0):
+ has_legacy_dns_account = True
+ else:
+ has_legacy_dns_account = False
+
+ res11 = samdb.search(expression="(samaccountname=dns-%s)" % names.netbiosname,
+ scope=ldb.SCOPE_SUBTREE, attrs=["dn"],
+ controls=["search_options:1:2"])
+ if (len(res11) > 0):
+ has_dns_account = True
+ else:
+ has_dns_account = False
+
+ if names.dnsdomaindn is not None:
+ if has_dns_account:
+ names.dns_backend = 'BIND9_DLZ'
+ else:
+ names.dns_backend = 'SAMBA_INTERNAL'
+ elif has_dns_account or has_legacy_dns_account:
+ names.dns_backend = 'BIND9_FLATFILE'
+ else:
+ names.dns_backend = 'NONE'
+
+ dns_admins_sid = get_dnsadmins_sid(samdb, names.domaindn)
+ names.name_map['DnsAdmins'] = str(dns_admins_sid)
+
+ return names
+
+
+def update_provision_usn(samdb, low, high, id, 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 id: The invocation id of the samba's dc
+ :param replace: A boolean indicating if the range should replace any
+ existing one or appended (default)
+ """
+
+ tab = []
+ if not replace:
+ entry = samdb.search(base="@PROVISION",
+ scope=ldb.SCOPE_BASE,
+ attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
+ for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
+ if not re.search(';', str(e)):
+ e = "%s;%s" % (str(e), id)
+ tab.append(str(e))
+
+ tab.append("%s-%s;%s" % (low, high, id))
+ delta = ldb.Message()
+ delta.dn = ldb.Dn(samdb, "@PROVISION")
+ delta[LAST_PROVISION_USN_ATTRIBUTE] = \
+ ldb.MessageElement(tab,
+ ldb.FLAG_MOD_REPLACE,
+ LAST_PROVISION_USN_ATTRIBUTE)
+ entry = samdb.search(expression='provisionnerID=*',
+ base="@PROVISION", scope=ldb.SCOPE_BASE,
+ attrs=["provisionnerID"])
+ if len(entry) == 0 or len(entry[0]) == 0:
+ delta["provisionnerID"] = ldb.MessageElement(id, ldb.FLAG_MOD_ADD, "provisionnerID")
+ samdb.modify(delta)
+
+
+def set_provision_usn(samdb, low, high, id):
+ """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
+ :param id: The invocationId of the provision"""
+
+ tab = []
+ tab.append("%s-%s;%s" % (low, high, id))
+
+ delta = ldb.Message()
+ delta.dn = ldb.Dn(samdb, "@PROVISION")
+ delta[LAST_PROVISION_USN_ATTRIBUTE] = \
+ ldb.MessageElement(tab,
+ ldb.FLAG_MOD_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 USNs ranges modified by a provision or an upgradeprovision
+
+ :param sam: An LDB object pointing to the sam.ldb
+ :return: a dictionary which keys are invocation id and values are an array
+ of integer representing the different ranges
+ """
+ try:
+ entry = sam.search(expression="%s=*" % LAST_PROVISION_USN_ATTRIBUTE,
+ base="@PROVISION", scope=ldb.SCOPE_BASE,
+ attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
+ except ldb.LdbError as e1:
+ (ecode, emsg) = e1.args
+ if ecode == ldb.ERR_NO_SUCH_OBJECT:
+ return None
+ raise
+ if len(entry) > 0:
+ myids = []
+ range = {}
+ p = re.compile(r'-')
+ if entry[0].get("provisionnerID"):
+ for e in entry[0]["provisionnerID"]:
+ myids.append(str(e))
+ for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
+ tab1 = str(r).split(';')
+ if len(tab1) == 2:
+ id = tab1[1]
+ else:
+ id = "default"
+ if (len(myids) > 0 and id not in myids):
+ continue
+ tab2 = p.split(tab1[0])
+ if range.get(id) is None:
+ range[id] = []
+ range[id].append(tab2[0])
+ range[id].append(tab2[1])
+ return range
+ else:
+ return None
+
+
+class ProvisionResult(object):
+ """Result of a provision.
+
+ :ivar server_role: The server role
+ :ivar paths: ProvisionPaths instance
+ :ivar domaindn: The domain dn, as string
+ """
+
+ def __init__(self):
+ self.server_role = None
+ self.paths = None
+ self.domaindn = None
+ self.lp = None
+ self.samdb = None
+ self.idmap = None
+ self.names = None
+ self.domainsid = None
+ self.adminpass_generated = None
+ self.adminpass = None
+ self.backend_result = None
+
+ def report_logger(self, logger):
+ """Report this provision result to a logger."""
+ logger.info(
+ "Once the above files are installed, your Samba AD server will "
+ "be ready to use")
+ if self.adminpass_generated:
+ logger.info("Admin password: %s", self.adminpass)
+ logger.info("Server Role: %s", self.server_role)
+ logger.info("Hostname: %s", self.names.hostname)
+ logger.info("NetBIOS Domain: %s", self.names.domain)
+ logger.info("DNS Domain: %s", self.names.dnsdomain)
+ logger.info("DOMAIN SID: %s", self.domainsid)
+
+ if self.backend_result:
+ self.backend_result.report_logger(logger)
+
+
+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)
+
+
+def findnss_uid(names):
+ return findnss(pwd.getpwnam, names)[2]
+
+
+def findnss_gid(names):
+ return findnss(grp.getgrnam, names)[2]
+
+
+def get_root_uid(root, logger):
+ try:
+ root_uid = findnss_uid(root)
+ except KeyError as e:
+ logger.info(e)
+ logger.info("Assuming root user has UID zero")
+ root_uid = 0
+ return root_uid
+
+
+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")
+ paths.binddns_dir = lp.get("binddns dir")
+ paths.state_dir = lp.get("state directory")
+
+ # This is stored without path prefix for the "privateKeytab" attribute in
+ # "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, "sam.ldb")
+ paths.idmapdb = os.path.join(paths.private_dir, "idmap.ldb")
+ paths.secrets = os.path.join(paths.private_dir, "secrets.ldb")
+ paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
+ paths.dns_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.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
+ paths.kdcconf = os.path.join(paths.private_dir, "kdc.conf")
+ paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
+ paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
+ paths.encrypted_secrets_key_path = os.path.join(
+ paths.private_dir,
+ "encrypted_secrets.key")
+
+ paths.dns = os.path.join(paths.binddns_dir, "dns", dnsdomain + ".zone")
+ paths.namedconf = os.path.join(paths.binddns_dir, "named.conf")
+ paths.namedconf_update = os.path.join(paths.binddns_dir, "named.conf.update")
+ paths.namedtxt = os.path.join(paths.binddns_dir, "named.txt")
+
+ paths.hklm = "hklm.ldb"
+ paths.hkcr = "hkcr.ldb"
+ paths.hkcu = "hkcu.ldb"
+ 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 determine_netbios_name(hostname):
+ """Determine a netbios name from a hostname."""
+ # remove forbidden chars and force the length to be <16
+ netbiosname = "".join([x for x in hostname if is_valid_netbios_char(x)])
+ return netbiosname[:MAX_NETBIOS_NAME_LEN].upper()
+
+
+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,
+ domain_names_forced=False):
+ """Guess configuration settings to use."""
+
+ if hostname is None:
+ hostname = socket.gethostname().split(".")[0]
+
+ netbiosname = lp.get("netbios name")
+ if netbiosname is None:
+ netbiosname = determine_netbios_name(hostname)
+ 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(), lp.configfile, realm))
+
+ if lp.get("server role").lower() != serverrole:
+ raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'! Please remove the smb.conf file and let provision generate it" % (lp.get("server role"), lp.configfile, serverrole))
+
+ if serverrole == "active directory 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 = samba.dn_from_dns_name(dnsdomain)
+
+ if domain == netbiosname:
+ raise ProvisioningError("guess_names: Domain '%s' must not be equal to short host name '%s'!" % (domain, netbiosname))
+ 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.upper() == realm:
+ raise ProvisioningError("guess_names: Realm '%s' must not be equal to NetBIOS hostname '%s'!" % (realm, netbiosname))
+ if domain == realm and not domain_names_forced:
+ raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
+
+ if serverrole != "active directory domain controller":
+ #
+ # This is the code path for a domain member
+ # where we provision the database as if we were
+ # on a domain controller, so we should not use
+ # the same dnsdomain as the domain controllers
+ # of our primary domain.
+ #
+ # This will be important if we start doing
+ # SID/name filtering and reject the local
+ # sid and names if they come from a domain
+ # controller.
+ #
+ realm = netbiosname
+ dnsdomain = netbiosname.lower()
+
+ if rootdn is None:
+ rootdn = domaindn
+
+ if configdn is None:
+ configdn = "CN=Configuration," + rootdn
+ if schemadn is None:
+ schemadn = "CN=Schema," + configdn
+
+ if sitename is None:
+ sitename = DEFAULTSITE
+
+ if serverdn is None:
+ serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (
+ netbiosname, sitename, configdn)
+
+ 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 = serverdn
+
+ return names
+
+
+def make_smbconf(smbconf, hostname, domain, realm, targetdir,
+ serverrole=None, eadb=False, use_ntvfs=False, lp=None,
+ global_param=None):
+ """Create a new smb.conf file based on a couple of basic settings.
+ """
+ assert smbconf is not None
+
+ if hostname is None:
+ hostname = socket.gethostname().split(".")[0]
+
+ netbiosname = determine_netbios_name(hostname)
+
+ if serverrole is None:
+ serverrole = "standalone server"
+
+ assert domain is not None
+ domain = domain.upper()
+
+ assert realm is not None
+ realm = realm.upper()
+
+ global_settings = {
+ "netbios name": netbiosname,
+ "workgroup": domain,
+ "realm": realm,
+ "server role": serverrole,
+ }
+
+ if lp is None:
+ lp = samba.param.LoadParm()
+ # Load non-existent file
+ if os.path.exists(smbconf):
+ lp.load(smbconf)
+
+ if global_param is not None:
+ for ent in global_param:
+ if global_param[ent] is not None:
+ global_settings[ent] = " ".join(global_param[ent])
+
+ if targetdir is not None:
+ global_settings["private dir"] = os.path.abspath(os.path.join(targetdir, "private"))
+ global_settings["lock dir"] = os.path.abspath(targetdir)
+ global_settings["state directory"] = os.path.abspath(os.path.join(targetdir, "state"))
+ global_settings["cache directory"] = os.path.abspath(os.path.join(targetdir, "cache"))
+ global_settings["binddns dir"] = os.path.abspath(os.path.join(targetdir, "bind-dns"))
+
+ lp.set("lock dir", os.path.abspath(targetdir))
+ lp.set("state directory", global_settings["state directory"])
+ lp.set("cache directory", global_settings["cache directory"])
+ lp.set("binddns dir", global_settings["binddns dir"])
+
+ if eadb:
+ if use_ntvfs:
+ if targetdir is not None:
+ privdir = os.path.join(targetdir, "private")
+ lp.set("posix:eadb",
+ os.path.abspath(os.path.join(privdir, "eadb.tdb")))
+ elif not lp.get("posix:eadb"):
+ privdir = lp.get("private dir")
+ lp.set("posix:eadb",
+ os.path.abspath(os.path.join(privdir, "eadb.tdb")))
+ else:
+ if targetdir is not None:
+ statedir = os.path.join(targetdir, "state")
+ lp.set("xattr_tdb:file",
+ os.path.abspath(os.path.join(statedir, "xattr.tdb")))
+ elif not lp.get("xattr_tdb:file"):
+ statedir = lp.get("state directory")
+ lp.set("xattr_tdb:file",
+ os.path.abspath(os.path.join(statedir, "xattr.tdb")))
+
+ shares = {}
+ if serverrole == "active directory domain controller":
+ shares["sysvol"] = os.path.join(lp.get("state directory"), "sysvol")
+ shares["netlogon"] = os.path.join(shares["sysvol"], realm.lower(),
+ "scripts")
+ else:
+ global_settings["passdb backend"] = "samba_dsdb"
+
+ f = open(smbconf, 'w')
+ try:
+ f.write("[globals]\n")
+ for key, val in global_settings.items():
+ f.write("\t%s = %s\n" % (key, val))
+ f.write("\n")
+
+ for name, path in shares.items():
+ f.write("[%s]\n" % name)
+ f.write("\tpath = %s\n" % path)
+ f.write("\tread only = no\n")
+ f.write("\n")
+ finally:
+ f.close()
+ # 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
+ lp.dump(False, smbconf)
+
+
+def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
+ users_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.
+ """
+ idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
+
+ 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, logger, lp, session_info,
+ provision_backend, names, serverrole,
+ plaintext_secrets=False,
+ backend_store=None):
+ """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 != "ldb":
+ ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri
+
+ required_features = None
+ if not plaintext_secrets:
+ required_features = "requiredFeatures: encryptedSecrets"
+
+ if backend_store is None:
+ backend_store = get_default_backend_store()
+ backend_store_line = "backendStore: %s" % backend_store
+
+ if backend_store == "mdb":
+ if required_features is not None:
+ required_features += "\n"
+ else:
+ required_features = ""
+ required_features += "requiredFeatures: lmdbLevelOne"
+
+ if required_features is None:
+ required_features = "# No required features"
+
+ samdb.transaction_start()
+ try:
+ logger.info("Setting up sam.ldb partitions and settings")
+ setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
+ "LDAP_BACKEND_LINE": ldap_backend_line,
+ "BACKEND_STORE": backend_store_line
+ })
+
+ setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
+ "BACKEND_TYPE": provision_backend.type,
+ "SERVER_ROLE": serverrole,
+ "REQUIRED_FEATURES": required_features
+ })
+
+ logger.info("Setting up sam.ldb rootDSE")
+ setup_samdb_rootdse(samdb, names)
+ except:
+ samdb.transaction_cancel()
+ raise
+ else:
+ samdb.transaction_commit()
+
+
+def secretsdb_self_join(secretsdb, domain,
+ netbiosname, machinepass, domainsid=None,
+ realm=None, dnsdomain=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.encode('utf-8')]
+ 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)(!(distinguishedName=%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]]
+ try:
+ msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
+ except KeyError:
+ pass
+
+ 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:
+ # if 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 setup_secretsdb(paths, session_info, 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 session_info: Session info.
+ :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)
+
+ bind_dns_keytab_path = os.path.join(paths.binddns_dir, paths.dns_keytab)
+ if os.path.exists(bind_dns_keytab_path):
+ os.unlink(bind_dns_keytab_path)
+
+ dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
+ if os.path.exists(dns_keytab_path):
+ os.unlink(dns_keytab_path)
+
+ 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"))
+ except:
+ secrets_ldb.transaction_cancel()
+ raise
+ return secrets_ldb
+
+
+def setup_privileges(path, session_info, lp):
+ """Setup the privileges database.
+
+ :param path: Path to the privileges database.
+ :param session_info: Session info.
+ :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_encrypted_secrets_key(path):
+ """Setup the encrypted secrets key file.
+
+ Any existing key file will be deleted and a new random key generated.
+
+ :param path: Path to the secrets key file.
+
+ """
+ if os.path.exists(path):
+ os.unlink(path)
+
+ flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
+ mode = stat.S_IRUSR | stat.S_IWUSR
+
+ umask_original = os.umask(0)
+ try:
+ fd = os.open(path, flags, mode)
+ finally:
+ os.umask(umask_original)
+
+ with os.fdopen(fd, 'wb') as f:
+ key = samba.generate_random_bytes(16)
+ f.write(key)
+
+
+def setup_registry(path, session_info, lp):
+ """Setup the registry.
+
+ :param path: Path to the registry database
+ :param session_info: Session information
+ :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, session_info, lp):
+ """Setup the idmap database.
+
+ :param path: path to the idmap database
+ :param session_info: Session information
+ :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, names):
+ """Setup the SamDB rootdse.
+
+ :param samdb: Sam Database handle
+ """
+ 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, admin_session_info, names, fill, machinepass,
+ dns_backend, dnspass, domainsid, next_rid, invocationid,
+ policyguid, policyguid_dc,
+ domainControllerFunctionality, ntdsguid=None, dc_rid=None):
+ """Join a host to its own domain."""
+ assert isinstance(invocationid, str)
+ if ntdsguid is not None:
+ ntdsguid_line = "objectGUID: %s\n" % ntdsguid
+ else:
+ ntdsguid_line = ""
+
+ if dc_rid is None:
+ dc_rid = next_rid
+
+ # Some clients/applications (like exchange) make use of
+ # the operatingSystemVersion attribute in order to
+ # find if a DC is good enough.
+ #
+ # So we better use a value matching a Windows DC
+ # with the same domainControllerFunctionality level
+ operatingSystemVersion = samba.dsdb.dc_operatingSystemVersion(domainControllerFunctionality)
+
+ 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')).decode('utf8'),
+ "DOMAINSID": str(domainsid),
+ "DCRID": str(dc_rid),
+ "OPERATING_SYSTEM": "Samba-%s" % version,
+ "OPERATING_SYSTEM_VERSION": operatingSystemVersion,
+ "NTDSGUID": ntdsguid_line,
+ "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
+ domainControllerFunctionality),
+ "RIDALLOCATIONSTART": str(next_rid + 100),
+ "RIDALLOCATIONEND": str(next_rid + 100 + 499)})
+
+ setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
+ "POLICYGUID": policyguid,
+ "POLICYGUID_DC": policyguid_dc,
+ "DNSDOMAIN": names.dnsdomain,
+ "DOMAINDN": names.domaindn})
+
+ # If we are setting up a subdomain, then this has been replicated in, so we
+ # don't need to add it
+ if fill == FILL_FULL:
+ setup_add_ldif(samdb, setup_path("provision_self_join_config.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')).decode('utf8'),
+ "DOMAINSID": str(domainsid),
+ "DCRID": str(dc_rid),
+ "SAMBA_VERSION_STRING": version,
+ "NTDSGUID": ntdsguid_line,
+ "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
+ domainControllerFunctionality)})
+
+ # Setup fSMORoleOwner entries to point at the newly created DC entry
+ setup_modify_ldif(samdb,
+ setup_path("provision_self_join_modify_schema.ldif"), {
+ "SCHEMADN": names.schemadn,
+ "SERVERDN": names.serverdn,
+ },
+ controls=["provision:0", "relax:0"])
+ setup_modify_ldif(samdb,
+ setup_path("provision_self_join_modify_config.ldif"), {
+ "CONFIGDN": names.configdn,
+ "DEFAULTSITE": names.sitename,
+ "NETBIOSNAME": names.netbiosname,
+ "SERVERDN": names.serverdn,
+ })
+
+ system_session_info = system_session()
+ samdb.set_session_info(system_session_info)
+ # Setup fSMORoleOwner entries to point at the newly created DC entry to
+ # modify a serverReference under cn=config when we are a subdomain, we must
+ # be system due to ACLs
+ setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
+ "DOMAINDN": names.domaindn,
+ "SERVERDN": names.serverdn,
+ "NETBIOSNAME": names.netbiosname,
+ })
+
+ samdb.set_session_info(admin_session_info)
+
+ if dns_backend != "SAMBA_INTERNAL":
+ # This is Samba4 specific and should be replaced by the correct
+ # DNS AD-style setup
+ setup_add_ldif(samdb, setup_path("provision_dns_add_samba.ldif"), {
+ "DNSDOMAIN": names.dnsdomain,
+ "DOMAINDN": names.domaindn,
+ "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')).decode('utf8'),
+ "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, 0o775)
+ f = open(os.path.join(policy_path, "GPT.INI"), 'w')
+ try:
+ f.write("[General]\r\nVersion=0")
+ finally:
+ f.close()
+ p = os.path.join(policy_path, "MACHINE")
+ if not os.path.exists(p):
+ os.makedirs(p, 0o775)
+ p = os.path.join(policy_path, "USER")
+ if not os.path.exists(p):
+ os.makedirs(p, 0o775)
+
+
+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 controller policy
+ """
+ policy_path = getpolicypath(sysvolpath, dnsdomain, policyguid)
+ create_gpo_struct(policy_path)
+
+ policy_path = getpolicypath(sysvolpath, dnsdomain, policyguid_dc)
+ create_gpo_struct(policy_path)
+
+
+# Default the database size to 8Gb
+DEFAULT_BACKEND_SIZE = 8 * 1024 * 1024 *1024
+
+def setup_samdb(path, session_info, provision_backend, lp, names,
+ logger, serverrole, schema, am_rodc=False,
+ plaintext_secrets=False, backend_store=None,
+ backend_store_size=None, batch_mode=False):
+ """Setup a complete SAM Database.
+
+ :note: This will wipe the main SAM database file!
+ """
+
+ # Also wipes the database
+ setup_samdb_partitions(path, logger=logger, lp=lp,
+ provision_backend=provision_backend, session_info=session_info,
+ names=names, serverrole=serverrole, plaintext_secrets=plaintext_secrets,
+ backend_store=backend_store)
+
+ store_size = DEFAULT_BACKEND_SIZE
+ if backend_store_size:
+ store_size = backend_store_size
+
+ options = []
+ if backend_store == "mdb":
+ options.append("lmdb_env_size:" + str(store_size))
+ if batch_mode:
+ options.append("batch_mode:1")
+ if batch_mode:
+ # Estimate the number of index records in the transaction_index_cache
+ # Numbers chosen give the prime 202481 for the default backend size,
+ # which works well for a 100,000 user database
+ cache_size = int(store_size / 42423) + 1
+ options.append("transaction_index_cache_size:" + str(cache_size))
+
+ # 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,
+ lp=lp,
+ global_schema=False, am_rodc=am_rodc, options=options)
+
+ logger.info("Pre-loading the Samba 4 and AD schema")
+
+ # Load the schema from the one we computed earlier
+ samdb.set_schema(schema, write_indices_and_attributes=False)
+
+ # Set the NTDS settings DN manually - in order to have it already around
+ # before the provisioned tree exists and we connect
+ 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
+ try:
+ samdb.connect(path, options=options)
+ except ldb.LdbError as e2:
+ (num, string_error) = e2.args
+ if (num == ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS):
+ raise ProvisioningError("Permission denied connecting to %s, are you running as root?" % path)
+ else:
+ raise
+
+ # But we have to give it one more kick to have it use the schema
+ # during provision - it needs, now that it is connected, to write
+ # the schema @ATTRIBUTES and @INDEXLIST records to the database.
+ samdb.set_schema(schema, write_indices_and_attributes=True)
+
+ return samdb
+
+
+def fill_samdb(samdb, lp, names, logger, policyguid,
+ policyguid_dc, fill, adminpass, krbtgtpass, machinepass, dns_backend,
+ dnspass, invocationid, ntdsguid,
+ dom_for_fun_level=None, schema=None, next_rid=None, dc_rid=None):
+
+ if next_rid is None:
+ next_rid = 1000
+
+ # 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)
+
+ domainControllerFunctionality = functional_level.dc_level_from_lp(lp)
+
+ # 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!
+ if dom_for_fun_level is None:
+ dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
+
+ if dom_for_fun_level > domainControllerFunctionality:
+ level = functional_level.level_to_string(domainControllerFunctionality)
+ raise ProvisioningError(f"You want to run SAMBA 4 on a domain and forest function level which itself is higher than its actual DC function level ({level}). This won't work!")
+
+ domainFunctionality = dom_for_fun_level
+ forestFunctionality = dom_for_fun_level
+
+ # 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)
+
+ # 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(names.domainsid))
+ samdb.set_invocation_id(invocationid)
+
+ logger.info("Adding DomainDN: %s" % names.domaindn)
+
+ # impersonate domain admin
+ admin_session_info = admin_session(lp, str(names.domainsid))
+ samdb.set_session_info(admin_session_info)
+ if names.domainguid is not None:
+ domainguid_line = "objectGUID: %s\n-" % names.domainguid
+ else:
+ domainguid_line = ""
+
+ descr = b64encode(get_domain_descriptor(names.domainsid)).decode('utf8')
+ setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
+ "DOMAINDN": names.domaindn,
+ "DOMAINSID": str(names.domainsid),
+ "DESCRIPTOR": descr,
+ "DOMAINGUID": domainguid_line
+ })
+
+ setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
+ "DOMAINDN": names.domaindn,
+ "CREATTIME": str(samba.unix2nttime(int(time.time()))),
+ "NEXTRID": str(next_rid),
+ "DEFAULTSITE": names.sitename,
+ "CONFIGDN": names.configdn,
+ "POLICYGUID": policyguid,
+ "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
+ "SAMBA_VERSION_STRING": version,
+ "MIN_PWD_LENGTH": str(DEFAULT_MIN_PWD_LENGTH)
+ })
+
+ # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
+ if fill == FILL_FULL:
+ logger.info("Adding configuration container")
+ descr = b64encode(get_config_descriptor(names.domainsid)).decode('utf8')
+ setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
+ "CONFIGDN": names.configdn,
+ "DESCRIPTOR": descr,
+ })
+
+ # The LDIF here was created when the Schema object was constructed
+ ignore_checks_oid = "local_oid:%s:0" % samba.dsdb.DSDB_CONTROL_SKIP_DUPLICATES_CHECK_OID
+ schema_controls = [
+ "provision:0",
+ "relax:0",
+ ignore_checks_oid
+ ]
+
+ logger.info("Setting up sam.ldb schema")
+ samdb.add_ldif(schema.schema_dn_add, controls=schema_controls)
+ samdb.modify_ldif(schema.schema_dn_modify, controls=schema_controls)
+ samdb.write_prefixes_from_schema()
+ samdb.add_ldif(schema.schema_data, controls=schema_controls)
+ setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
+ {"SCHEMADN": names.schemadn},
+ controls=schema_controls)
+
+ # Now register this container in the root of the forest
+ msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
+ msg["subRefs"] = ldb.MessageElement(names.configdn, ldb.FLAG_MOD_ADD,
+ "subRefs")
+
+ deletedobjects_descr = b64encode(get_deletedobjects_descriptor(names.domainsid)).decode('utf8')
+
+ samdb.invocation_id = invocationid
+
+ # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
+ if fill == FILL_FULL:
+ logger.info("Setting up sam.ldb configuration data")
+
+ partitions_descr = b64encode(get_config_partitions_descriptor(names.domainsid)).decode('utf8')
+ sites_descr = b64encode(get_config_sites_descriptor(names.domainsid)).decode('utf8')
+ ntdsquotas_descr = b64encode(get_config_ntds_quotas_descriptor(names.domainsid)).decode('utf8')
+ protected1_descr = b64encode(get_config_delete_protected1_descriptor(names.domainsid)).decode('utf8')
+ protected1wd_descr = b64encode(get_config_delete_protected1wd_descriptor(names.domainsid)).decode('utf8')
+ protected2_descr = b64encode(get_config_delete_protected2_descriptor(names.domainsid)).decode('utf8')
+
+ if "2008" in schema.base_schema:
+ # exclude 2012-specific changes if we're using a 2008 schema
+ incl_2012 = "#"
+ else:
+ incl_2012 = ""
+
+ setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
+ "CONFIGDN": names.configdn,
+ "NETBIOSNAME": names.netbiosname,
+ "DEFAULTSITE": names.sitename,
+ "DNSDOMAIN": names.dnsdomain,
+ "DOMAIN": names.domain,
+ "SCHEMADN": names.schemadn,
+ "DOMAINDN": names.domaindn,
+ "SERVERDN": names.serverdn,
+ "FOREST_FUNCTIONALITY": str(forestFunctionality),
+ "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
+ "NTDSQUOTAS_DESCRIPTOR": ntdsquotas_descr,
+ "DELETEDOBJECTS_DESCRIPTOR": deletedobjects_descr,
+ "LOSTANDFOUND_DESCRIPTOR": protected1wd_descr,
+ "SERVICES_DESCRIPTOR": protected1_descr,
+ "PHYSICALLOCATIONS_DESCRIPTOR": protected1wd_descr,
+ "FORESTUPDATES_DESCRIPTOR": protected1wd_descr,
+ "EXTENDEDRIGHTS_DESCRIPTOR": protected2_descr,
+ "PARTITIONS_DESCRIPTOR": partitions_descr,
+ "SITES_DESCRIPTOR": sites_descr,
+ })
+
+ setup_add_ldif(samdb, setup_path("extended-rights.ldif"), {
+ "CONFIGDN": names.configdn,
+ "INC2012": incl_2012,
+ })
+
+ logger.info("Setting up display specifiers")
+ display_specifiers_ldif = read_ms_ldif(
+ setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
+ display_specifiers_ldif = substitute_var(display_specifiers_ldif,
+ {"CONFIGDN": names.configdn})
+ check_all_substituted(display_specifiers_ldif)
+ samdb.add_ldif(display_specifiers_ldif)
+
+ logger.info("Modifying display specifiers and extended rights")
+ setup_modify_ldif(samdb,
+ setup_path("provision_configuration_modify.ldif"), {
+ "CONFIGDN": names.configdn,
+ "DISPLAYSPECIFIERS_DESCRIPTOR": protected2_descr
+ })
+
+ logger.info("Adding users container")
+ users_desc = b64encode(get_domain_users_descriptor(names.domainsid)).decode('utf8')
+ setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
+ "DOMAINDN": names.domaindn,
+ "USERS_DESCRIPTOR": users_desc
+ })
+ logger.info("Modifying users container")
+ setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
+ "DOMAINDN": names.domaindn})
+ logger.info("Adding computers container")
+ computers_desc = b64encode(get_domain_computers_descriptor(names.domainsid)).decode('utf8')
+ setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
+ "DOMAINDN": names.domaindn,
+ "COMPUTERS_DESCRIPTOR": computers_desc
+ })
+ logger.info("Modifying computers container")
+ setup_modify_ldif(samdb,
+ setup_path("provision_computers_modify.ldif"), {
+ "DOMAINDN": names.domaindn})
+ logger.info("Setting up sam.ldb data")
+ infrastructure_desc = b64encode(get_domain_infrastructure_descriptor(names.domainsid)).decode('utf8')
+ lostandfound_desc = b64encode(get_domain_delete_protected2_descriptor(names.domainsid)).decode('utf8')
+ system_desc = b64encode(get_domain_delete_protected1_descriptor(names.domainsid)).decode('utf8')
+ builtin_desc = b64encode(get_domain_builtin_descriptor(names.domainsid)).decode('utf8')
+ controllers_desc = b64encode(get_domain_controllers_descriptor(names.domainsid)).decode('utf8')
+ setup_add_ldif(samdb, setup_path("provision.ldif"), {
+ "CREATTIME": str(samba.unix2nttime(int(time.time()))),
+ "DOMAINDN": names.domaindn,
+ "NETBIOSNAME": names.netbiosname,
+ "DEFAULTSITE": names.sitename,
+ "CONFIGDN": names.configdn,
+ "SERVERDN": names.serverdn,
+ "RIDAVAILABLESTART": str(next_rid + 600),
+ "POLICYGUID_DC": policyguid_dc,
+ "INFRASTRUCTURE_DESCRIPTOR": infrastructure_desc,
+ "DELETEDOBJECTS_DESCRIPTOR": deletedobjects_descr,
+ "LOSTANDFOUND_DESCRIPTOR": lostandfound_desc,
+ "SYSTEM_DESCRIPTOR": system_desc,
+ "BUILTIN_DESCRIPTOR": builtin_desc,
+ "DOMAIN_CONTROLLERS_DESCRIPTOR": controllers_desc,
+ })
+
+ # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
+ if fill == FILL_FULL:
+ managedservice_descr = b64encode(get_managed_service_accounts_descriptor(names.domainsid)).decode('utf8')
+ setup_modify_ldif(samdb,
+ setup_path("provision_configuration_references.ldif"), {
+ "CONFIGDN": names.configdn,
+ "SCHEMADN": names.schemadn})
+
+ logger.info("Setting up well known security principals")
+ protected1wd_descr = b64encode(get_config_delete_protected1wd_descriptor(names.domainsid)).decode('utf8')
+ setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), {
+ "CONFIGDN": names.configdn,
+ "WELLKNOWNPRINCIPALS_DESCRIPTOR": protected1wd_descr,
+ }, controls=["relax:0", "provision:0"])
+
+ if fill == FILL_FULL or fill == FILL_SUBDOMAIN:
+ setup_modify_ldif(samdb,
+ setup_path("provision_basedn_references.ldif"), {
+ "DOMAINDN": names.domaindn,
+ "MANAGEDSERVICE_DESCRIPTOR": managedservice_descr
+ })
+
+ logger.info("Setting up sam.ldb users and groups")
+ setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
+ "DOMAINDN": names.domaindn,
+ "DOMAINSID": str(names.domainsid),
+ "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')).decode('utf8'),
+ "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le')).decode('utf8')
+ }, controls=["relax:0", "provision:0"])
+
+ logger.info("Setting up self join")
+ setup_self_join(samdb, admin_session_info, names=names, fill=fill,
+ invocationid=invocationid,
+ dns_backend=dns_backend,
+ dnspass=dnspass,
+ machinepass=machinepass,
+ domainsid=names.domainsid,
+ next_rid=next_rid,
+ dc_rid=dc_rid,
+ policyguid=policyguid,
+ policyguid_dc=policyguid_dc,
+ domainControllerFunctionality=domainControllerFunctionality,
+ ntdsguid=ntdsguid)
+
+ ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
+ names.ntdsguid = samdb.searchone(basedn=ntds_dn,
+ attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE).decode('utf8')
+ assert isinstance(names.ntdsguid, str)
+
+ return samdb
+
+
+SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;FA;;;BA)(A;OICI;0x1200a9;;;SO)(A;OICI;FA;;;SY)(A;OICI;0x1200a9;;;AU)"
+POLICIES_ACL = "O:LAG:BAD:P(A;OICI;FA;;;BA)(A;OICI;0x1200a9;;;SO)(A;OICI;FA;;;SY)(A;OICI;0x1200a9;;;AU)(A;OICI;0x1301bf;;;PA)"
+SYSVOL_SERVICE = "sysvol"
+
+
+def set_dir_acl(path, acl, lp, domsid, use_ntvfs, passdb, service=SYSVOL_SERVICE):
+ session_info = system_session_unix()
+ setntacl(lp, path, acl, domsid, session_info, use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service)
+ for root, dirs, files in os.walk(path, topdown=False):
+ for name in files:
+ setntacl(lp, os.path.join(root, name), acl, domsid, session_info,
+ use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service)
+ for name in dirs:
+ setntacl(lp, os.path.join(root, name), acl, domsid, session_info,
+ use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=service)
+
+
+def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, passdb):
+ """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
+ folders beneath.
+
+ :param sysvol: Physical path for the sysvol folder
+ :param dnsdomain: The DNS name of the domain
+ :param domainsid: The SID of the domain
+ :param domaindn: The DN of the domain (ie. DC=...)
+ :param samdb: An LDB object on the SAM db
+ :param lp: an LP object
+ """
+
+ # Set ACL for GPO root folder
+ root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
+ session_info = system_session_unix()
+
+ setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid), session_info,
+ use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb, service=SYSVOL_SERVICE)
+
+ res = samdb.search(base="CN=Policies,CN=System,%s" %(domaindn),
+ attrs=["cn", "nTSecurityDescriptor"],
+ expression="", scope=ldb.SCOPE_ONELEVEL)
+
+ for policy in res:
+ acl = ndr_unpack(security.descriptor,
+ policy["nTSecurityDescriptor"][0]).as_sddl()
+ policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
+ set_dir_acl(policy_path, dsacl2fsacl(acl, domainsid), lp,
+ str(domainsid), use_ntvfs,
+ passdb=passdb)
+
+
+def setsysvolacl(samdb, sysvol, uid, gid, domainsid, dnsdomain,
+ domaindn, lp, use_ntvfs):
+ """Set the ACL for the sysvol share and the subfolders
+
+ :param samdb: An LDB object on the SAM db
+ :param sysvol: Physical path for the sysvol folder
+ :param uid: The UID of the "Administrator" user
+ :param gid: The GID of the "Domain administrators" group
+ :param domainsid: The SID of the domain
+ :param dnsdomain: The DNS name of the domain
+ :param domaindn: The DN of the domain (ie. DC=...)
+ """
+ s4_passdb = None
+
+ if not use_ntvfs:
+ s3conf = s3param.get_context()
+ s3conf.load(lp.configfile)
+
+ file = tempfile.NamedTemporaryFile(dir=os.path.abspath(sysvol))
+ try:
+ try:
+ smbd.set_simple_acl(file.name, 0o755, system_session_unix(), gid)
+ except OSError:
+ if not smbd.have_posix_acls():
+ # This clue is only strictly correct for RPM and
+ # Debian-like Linux systems, but hopefully other users
+ # will get enough clue from it.
+ raise ProvisioningError("Samba was compiled without the posix ACL support that s3fs requires. "
+ "Try installing libacl1-dev or libacl-devel, then re-run configure and make.")
+
+ raise ProvisioningError("Your filesystem or build does not support posix ACLs, which s3fs requires. "
+ "Try the mounting the filesystem with the 'acl' option.")
+ try:
+ smbd.chown(file.name, uid, gid, system_session_unix())
+ except OSError:
+ raise ProvisioningError("Unable to chown a file on your filesystem. "
+ "You may not be running provision as root.")
+ finally:
+ file.close()
+
+ # This will ensure that the smbd code we are running when setting ACLs
+ # is initialised with the smb.conf
+ s3conf = s3param.get_context()
+ s3conf.load(lp.configfile)
+ # ensure we are using the right samba_dsdb passdb backend, no matter what
+ s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
+ passdb.reload_static_pdb()
+
+ # ensure that we init the samba_dsdb backend, so the domain sid is
+ # marked in secrets.tdb
+ s4_passdb = passdb.PDB(s3conf.get("passdb backend"))
+
+ # now ensure everything matches correctly, to avoid weird issues
+ if passdb.get_global_sam_sid() != domainsid:
+ raise ProvisioningError('SID as seen by smbd [%s] does not match SID as seen by the provision script [%s]!' % (passdb.get_global_sam_sid(), domainsid))
+
+ domain_info = s4_passdb.domain_info()
+ if domain_info["dom_sid"] != domainsid:
+ raise ProvisioningError('SID as seen by pdb_samba_dsdb [%s] does not match SID as seen by the provision script [%s]!' % (domain_info["dom_sid"], domainsid))
+
+ if domain_info["dns_domain"].upper() != dnsdomain.upper():
+ raise ProvisioningError('Realm as seen by pdb_samba_dsdb [%s] does not match Realm as seen by the provision script [%s]!' % (domain_info["dns_domain"].upper(), dnsdomain.upper()))
+
+ try:
+ if use_ntvfs:
+ os.chown(sysvol, -1, gid)
+ except OSError:
+ canchown = False
+ else:
+ canchown = True
+
+ # use admin sid dn as user dn, since admin should own most of the files,
+ # the operation will be much faster
+ userdn = '<SID={}-{}>'.format(domainsid, security.DOMAIN_RID_ADMINISTRATOR)
+
+ flags = (auth.AUTH_SESSION_INFO_DEFAULT_GROUPS |
+ auth.AUTH_SESSION_INFO_AUTHENTICATED |
+ auth.AUTH_SESSION_INFO_SIMPLE_PRIVILEGES)
+
+ session_info = auth.user_session(samdb, lp_ctx=lp, dn=userdn,
+ session_info_flags=flags)
+ auth.session_info_set_unix(session_info,
+ lp_ctx=lp,
+ user_name="Administrator",
+ uid=uid,
+ gid=gid)
+
+ def _setntacl(path):
+ """A helper to reuse args"""
+ return setntacl(
+ lp, path, SYSVOL_ACL, str(domainsid), session_info,
+ use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=s4_passdb,
+ service=SYSVOL_SERVICE)
+
+ # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
+ _setntacl(sysvol)
+ for root, dirs, files in os.walk(sysvol, topdown=False):
+ for name in files:
+ if use_ntvfs and canchown:
+ os.chown(os.path.join(root, name), -1, gid)
+ _setntacl(os.path.join(root, name))
+ for name in dirs:
+ if use_ntvfs and canchown:
+ os.chown(os.path.join(root, name), -1, gid)
+ _setntacl(os.path.join(root, name))
+
+ # Set acls on Policy folder and policies folders
+ set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, passdb=s4_passdb)
+
+
+def acl_type(direct_db_access):
+ if direct_db_access:
+ return "DB"
+ else:
+ return "VFS"
+
+
+def check_dir_acl(path, acl, lp, domainsid, direct_db_access):
+ session_info = system_session_unix()
+ fsacl = getntacl(lp, path, session_info, direct_db_access=direct_db_access, service=SYSVOL_SERVICE)
+ fsacl_sddl = fsacl.as_sddl(domainsid)
+ if fsacl_sddl != acl:
+ raise ProvisioningError('%s ACL on GPO directory %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), path, fsacl_sddl, acl))
+
+ for root, dirs, files in os.walk(path, topdown=False):
+ for name in files:
+ fsacl = getntacl(lp, os.path.join(root, name), session_info,
+ direct_db_access=direct_db_access, service=SYSVOL_SERVICE)
+ if fsacl is None:
+ raise ProvisioningError('%s ACL on GPO file %s not found!' %
+ (acl_type(direct_db_access),
+ os.path.join(root, name)))
+ fsacl_sddl = fsacl.as_sddl(domainsid)
+ if fsacl_sddl != acl:
+ raise ProvisioningError('%s ACL on GPO file %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), os.path.join(root, name), fsacl_sddl, acl))
+
+ for name in dirs:
+ fsacl = getntacl(lp, os.path.join(root, name), session_info,
+ direct_db_access=direct_db_access, service=SYSVOL_SERVICE)
+ if fsacl is None:
+ raise ProvisioningError('%s ACL on GPO directory %s not found!'
+ % (acl_type(direct_db_access),
+ os.path.join(root, name)))
+ fsacl_sddl = fsacl.as_sddl(domainsid)
+ if fsacl_sddl != acl:
+ raise ProvisioningError('%s ACL on GPO directory %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), os.path.join(root, name), fsacl_sddl, acl))
+
+
+def check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp,
+ direct_db_access):
+ """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
+ folders beneath.
+
+ :param sysvol: Physical path for the sysvol folder
+ :param dnsdomain: The DNS name of the domain
+ :param domainsid: The SID of the domain
+ :param domaindn: The DN of the domain (ie. DC=...)
+ :param samdb: An LDB object on the SAM db
+ :param lp: an LP object
+ """
+
+ # Set ACL for GPO root folder
+ root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
+ session_info = system_session_unix()
+ fsacl = getntacl(lp, root_policy_path, session_info,
+ direct_db_access=direct_db_access, service=SYSVOL_SERVICE)
+ if fsacl is None:
+ raise ProvisioningError('DB ACL on policy root %s %s not found!' % (acl_type(direct_db_access), root_policy_path))
+ fsacl_sddl = fsacl.as_sddl(domainsid)
+ if fsacl_sddl != POLICIES_ACL:
+ raise ProvisioningError('%s ACL on policy root %s %s does not match expected value %s from provision' % (acl_type(direct_db_access), root_policy_path, fsacl_sddl, fsacl))
+ res = samdb.search(base="CN=Policies,CN=System,%s" %(domaindn),
+ attrs=["cn", "nTSecurityDescriptor"],
+ expression="", scope=ldb.SCOPE_ONELEVEL)
+
+ for policy in res:
+ acl = ndr_unpack(security.descriptor,
+ policy["nTSecurityDescriptor"][0]).as_sddl()
+ policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
+ check_dir_acl(policy_path, dsacl2fsacl(acl, domainsid), lp,
+ domainsid, direct_db_access)
+
+
+def checksysvolacl(samdb, netlogon, sysvol, domainsid, dnsdomain, domaindn,
+ lp):
+ """Set the ACL for the sysvol share and the subfolders
+
+ :param samdb: An LDB object on the SAM db
+ :param netlogon: Physical path for the netlogon folder
+ :param sysvol: Physical path for the sysvol folder
+ :param uid: The UID of the "Administrator" user
+ :param gid: The GID of the "Domain administrators" group
+ :param domainsid: The SID of the domain
+ :param dnsdomain: The DNS name of the domain
+ :param domaindn: The DN of the domain (ie. DC=...)
+ """
+
+ # This will ensure that the smbd code we are running when setting ACLs is initialised with the smb.conf
+ s3conf = s3param.get_context()
+ s3conf.load(lp.configfile)
+ # ensure we are using the right samba_dsdb passdb backend, no matter what
+ s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
+ # ensure that we init the samba_dsdb backend, so the domain sid is marked in secrets.tdb
+ s4_passdb = passdb.PDB(s3conf.get("passdb backend"))
+
+ # now ensure everything matches correctly, to avoid weird issues
+ if passdb.get_global_sam_sid() != domainsid:
+ raise ProvisioningError('SID as seen by smbd [%s] does not match SID as seen by the provision script [%s]!' % (passdb.get_global_sam_sid(), domainsid))
+
+ domain_info = s4_passdb.domain_info()
+ if domain_info["dom_sid"] != domainsid:
+ raise ProvisioningError('SID as seen by pdb_samba_dsdb [%s] does not match SID as seen by the provision script [%s]!' % (domain_info["dom_sid"], domainsid))
+
+ if domain_info["dns_domain"].upper() != dnsdomain.upper():
+ raise ProvisioningError('Realm as seen by pdb_samba_dsdb [%s] does not match Realm as seen by the provision script [%s]!' % (domain_info["dns_domain"].upper(), dnsdomain.upper()))
+
+ # Ensure we can read this directly, and via the smbd VFS
+ session_info = system_session_unix()
+ for direct_db_access in [True, False]:
+ # Check the SYSVOL_ACL on the sysvol folder and subfolder (first level)
+ for dir_path in [os.path.join(sysvol, dnsdomain), netlogon]:
+ fsacl = getntacl(lp, dir_path, session_info, direct_db_access=direct_db_access, service=SYSVOL_SERVICE)
+ if fsacl is None:
+ raise ProvisioningError('%s ACL on sysvol directory %s not found!' % (acl_type(direct_db_access), dir_path))
+ fsacl_sddl = fsacl.as_sddl(domainsid)
+ if fsacl_sddl != SYSVOL_ACL:
+ raise ProvisioningError('%s ACL on sysvol directory %s %s does not match expected value %s from provision' % (acl_type(direct_db_access), dir_path, fsacl_sddl, SYSVOL_ACL))
+
+ # Check acls on Policy folder and policies folders
+ check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp,
+ direct_db_access)
+
+
+def interface_ips_v4(lp, all_interfaces=False):
+ """return only IPv4 IPs"""
+ ips = samba.interface_ips(lp, all_interfaces)
+ ret = []
+ for i in ips:
+ if i.find(':') == -1:
+ ret.append(i)
+ return ret
+
+
+def interface_ips_v6(lp):
+ """return only IPv6 IPs"""
+ ips = samba.interface_ips(lp, False)
+ ret = []
+ for i in ips:
+ if i.find(':') != -1:
+ ret.append(i)
+ return ret
+
+
+def provision_fill(samdb, secrets_ldb, logger, names, paths,
+ schema=None,
+ samdb_fill=FILL_FULL,
+ hostip=None, hostip6=None,
+ next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None,
+ domainguid=None, policyguid=None, policyguid_dc=None,
+ invocationid=None, machinepass=None, ntdsguid=None,
+ dns_backend=None, dnspass=None,
+ serverrole=None, dom_for_fun_level=None,
+ lp=None, use_ntvfs=False,
+ skip_sysvolacl=False):
+ # create/adapt the group policy GUIDs
+ # Default GUID for default policy are described at
+ # "How Core Group Policy Works"
+ # 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 invocationid is None:
+ invocationid = str(uuid.uuid4())
+
+ if krbtgtpass is None:
+ # Note that the machinepass value is ignored
+ # as the backend (password_hash.c) will generate its
+ # own random values for the krbtgt keys
+ krbtgtpass = samba.generate_random_machine_password(128, 255)
+ if machinepass is None:
+ machinepass = samba.generate_random_machine_password(120, 120)
+ if dnspass is None:
+ dnspass = samba.generate_random_password(120, 120)
+
+ samdb.transaction_start()
+ try:
+ samdb = fill_samdb(samdb, lp, names, logger=logger,
+ schema=schema,
+ policyguid=policyguid, policyguid_dc=policyguid_dc,
+ fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
+ invocationid=invocationid, machinepass=machinepass,
+ dns_backend=dns_backend, dnspass=dnspass,
+ ntdsguid=ntdsguid,
+ dom_for_fun_level=dom_for_fun_level,
+ next_rid=next_rid, dc_rid=dc_rid)
+
+ # Set up group policies (domain policy and domain controller
+ # policy)
+ if serverrole == "active directory domain controller":
+ create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
+ policyguid_dc)
+ except:
+ samdb.transaction_cancel()
+ raise
+ else:
+ samdb.transaction_commit()
+
+ if serverrole == "active directory domain controller":
+ # Continue setting up sysvol for GPO. This appears to require being
+ # outside a transaction.
+ if not skip_sysvolacl:
+ setsysvolacl(samdb, paths.sysvol, paths.root_uid,
+ paths.root_gid, names.domainsid, names.dnsdomain,
+ names.domaindn, lp, use_ntvfs)
+ else:
+ logger.info("Setting acl on sysvol skipped")
+
+ secretsdb_self_join(secrets_ldb, domain=names.domain,
+ realm=names.realm, dnsdomain=names.dnsdomain,
+ netbiosname=names.netbiosname, domainsid=names.domainsid,
+ machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
+
+ # Now set up the right msDS-SupportedEncryptionTypes into the DB
+ # In future, this might be determined from some configuration
+ 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).decode('utf8')))
+ msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(
+ elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE,
+ name="msDS-SupportedEncryptionTypes")
+ samdb.modify(msg)
+ except ldb.LdbError as e:
+ (enum, estr) = e.args
+ if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
+ # It might be that this attribute does not exist in this schema
+ raise
+
+ setup_ad_dns(samdb, secrets_ldb, names, paths, logger,
+ hostip=hostip, hostip6=hostip6, dns_backend=dns_backend,
+ dnspass=dnspass, os_level=dom_for_fun_level,
+ fill_level=samdb_fill)
+
+ domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
+ attribute="objectGUID").decode('utf8')
+ assert isinstance(domainguid, str)
+
+ 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, invocationid, 1)
+ else:
+ set_provision_usn(samdb, 0, maxUSN, invocationid)
+
+ logger.info("Setting up sam.ldb rootDSE marking as synchronized")
+ setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
+ {'NTDSGUID': names.ntdsguid})
+
+ # fix any dangling GUIDs from the provision
+ logger.info("Fixing provision GUIDs")
+ chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True,
+ quiet=True)
+ samdb.transaction_start()
+ try:
+ # a small number of GUIDs are missing because of ordering issues in the
+ # provision code
+ for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
+ chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
+ scope=ldb.SCOPE_BASE,
+ attrs=['defaultObjectCategory'])
+ chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
+ scope=ldb.SCOPE_ONELEVEL,
+ attrs=['ipsecOwnersReference',
+ 'ipsecFilterReference',
+ 'ipsecISAKMPReference',
+ 'ipsecNegotiationPolicyReference',
+ 'ipsecNFAReference'])
+ if chk.check_database(DN=names.schemadn, scope=ldb.SCOPE_SUBTREE,
+ attrs=['attributeId', 'governsId']) != 0:
+ raise ProvisioningError("Duplicate attributeId or governsId in schema. Must be fixed manually!!")
+ except:
+ samdb.transaction_cancel()
+ raise
+ else:
+ samdb.transaction_commit()
+
+
+_ROLES_MAP = {
+ "ROLE_STANDALONE": "standalone server",
+ "ROLE_DOMAIN_MEMBER": "member server",
+ "ROLE_DOMAIN_BDC": "active directory domain controller",
+ "ROLE_DOMAIN_PDC": "active directory domain controller",
+ "dc": "active directory domain controller",
+ "member": "member server",
+ "domain controller": "active directory domain controller",
+ "active directory domain controller": "active directory domain controller",
+ "member server": "member server",
+ "standalone": "standalone server",
+ "standalone server": "standalone server",
+}
+
+
+def sanitize_server_role(role):
+ """Sanitize a server role name.
+
+ :param role: Server role
+ :raise ValueError: If the role can not be interpreted
+ :return: Sanitized server role (one of "member server",
+ "active directory domain controller", "standalone server")
+ """
+ try:
+ return _ROLES_MAP[role]
+ except KeyError:
+ raise ValueError(role)
+
+
+def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain):
+ """Create AD entries for the fake ypserver.
+
+ This is needed for being able to manipulate posix attrs via ADUC.
+ """
+ samdb.transaction_start()
+ try:
+ logger.info("Setting up fake yp server settings")
+ setup_add_ldif(samdb, setup_path("ypServ30.ldif"), {
+ "DOMAINDN": domaindn,
+ "NETBIOSNAME": netbiosname,
+ "NISDOMAIN": nisdomain,
+ })
+ except:
+ samdb.transaction_cancel()
+ raise
+ else:
+ samdb.transaction_commit()
+
+
+def directory_create_or_exists(path, mode=0o755):
+ if not os.path.exists(path):
+ try:
+ os.mkdir(path, mode)
+ except OSError as e:
+ if e.errno in [errno.EEXIST]:
+ pass
+ else:
+ raise ProvisioningError("Failed to create directory %s: %s" % (path, e.strerror))
+
+
+def determine_host_ip(logger, lp, hostip=None):
+ if hostip is None:
+ logger.info("Looking up IPv4 addresses")
+ hostips = interface_ips_v4(lp)
+ if len(hostips) > 0:
+ hostip = hostips[0]
+ if len(hostips) > 1:
+ logger.warning("More than one IPv4 address found. Using %s",
+ hostip)
+ if hostip == "127.0.0.1":
+ hostip = None
+ if hostip is None:
+ logger.warning("No IPv4 address will be assigned")
+
+ return hostip
+
+
+def determine_host_ip6(logger, lp, hostip6=None):
+ if hostip6 is None:
+ logger.info("Looking up IPv6 addresses")
+ hostips = interface_ips_v6(lp)
+ if hostips:
+ hostip6 = hostips[0]
+ if len(hostips) > 1:
+ logger.warning("More than one IPv6 address found. Using %s", hostip6)
+ if hostip6 is None:
+ logger.warning("No IPv6 address will be assigned")
+
+ return hostip6
+
+
+def provision(logger, session_info, smbconf=None,
+ targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
+ domaindn=None, schemadn=None, configdn=None, serverdn=None,
+ domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
+ next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None,
+ krbtgtpass=None, domainguid=None, policyguid=None, policyguid_dc=None,
+ dns_backend=None, dns_forwarder=None, dnspass=None,
+ invocationid=None, machinepass=None, ntdsguid=None,
+ root=None, nobody=None, users=None,
+ sitename=None, serverrole=None, dom_for_fun_level=None,
+ useeadb=False, am_rodc=False, lp=None, use_ntvfs=False,
+ use_rfc2307=False, skip_sysvolacl=True,
+ base_schema="2019", adprep_level=DS_DOMAIN_FUNCTION_2016,
+ plaintext_secrets=False, backend_store=None,
+ backend_store_size=None, batch_mode=False):
+ """Provision samba4
+
+ :note: caution, this wipes all existing data!
+ """
+
+ try:
+ serverrole = sanitize_server_role(serverrole)
+ except ValueError:
+ raise ProvisioningError('server role (%s) should be one of "active directory domain controller", "member server", "standalone server"' % serverrole)
+
+ if dom_for_fun_level is None:
+ dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
+
+ if base_schema in ["2008_R2", "2008_R2_old"]:
+ max_adprep_level = DS_DOMAIN_FUNCTION_2008_R2
+ elif base_schema in ["2012"]:
+ max_adprep_level = DS_DOMAIN_FUNCTION_2012
+ elif base_schema in ["2012_R2"]:
+ max_adprep_level = DS_DOMAIN_FUNCTION_2012_R2
+ else:
+ max_adprep_level = DS_DOMAIN_FUNCTION_2016
+
+ if max_adprep_level < dom_for_fun_level:
+ raise ProvisioningError('dom_for_fun_level[%u] incompatible with base_schema[%s]' %
+ (dom_for_fun_level, base_schema))
+
+ if adprep_level is not None and max_adprep_level < adprep_level:
+ raise ProvisioningError('base_schema[%s] incompatible with adprep_level[%u]' %
+ (base_schema, adprep_level))
+
+ if adprep_level is not None and adprep_level < dom_for_fun_level:
+ raise ProvisioningError('dom_for_fun_level[%u] incompatible with adprep_level[%u]' %
+ (dom_for_fun_level, adprep_level))
+
+ 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_store is None:
+ backend_store = get_default_backend_store()
+
+ if domainsid is None:
+ domainsid = security.random_sid()
+
+ root_uid = get_root_uid([root or "root"], logger)
+ nobody_uid = findnss_uid([nobody or "nobody"])
+ users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
+ root_gid = pwd.getpwuid(root_uid).pw_gid
+
+ 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))
+
+ server_services = []
+ global_param = {}
+ if use_rfc2307:
+ global_param["idmap_ldb:use rfc2307"] = ["yes"]
+
+ if dns_backend != "SAMBA_INTERNAL":
+ server_services.append("-dns")
+ else:
+ if dns_forwarder is not None:
+ global_param["dns forwarder"] = [dns_forwarder]
+
+ if use_ntvfs:
+ server_services.append("+smb")
+ server_services.append("-s3fs")
+ global_param["dcerpc endpoint servers"] = ["+winreg", "+srvsvc"]
+
+ if len(server_services) > 0:
+ global_param["server services"] = server_services
+
+ # 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
+ f = open(smbconf, 'r')
+ try:
+ data = f.read().lstrip()
+ finally:
+ f.close()
+ if data is None or data == "":
+ make_smbconf(smbconf, hostname, domain, realm,
+ targetdir, serverrole=serverrole,
+ eadb=useeadb, use_ntvfs=use_ntvfs,
+ lp=lp, global_param=global_param)
+ else:
+ make_smbconf(smbconf, hostname, domain, realm, targetdir,
+ serverrole=serverrole,
+ eadb=useeadb, use_ntvfs=use_ntvfs, lp=lp, global_param=global_param)
+
+ 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, rootdn=rootdn, domain_names_forced=(samdb_fill == FILL_DRS))
+ paths = provision_paths_from_lp(lp, names.dnsdomain)
+
+ paths.bind_gid = bind_gid
+ paths.root_uid = root_uid
+ paths.root_gid = root_gid
+
+ hostip = determine_host_ip(logger, lp, hostip)
+ hostip6 = determine_host_ip6(logger, lp, hostip6)
+ names.hostip = hostip
+ names.hostip6 = hostip6
+ names.domainguid = domainguid
+ names.domainsid = domainsid
+ names.forestsid = domainsid
+
+ if serverrole is None:
+ serverrole = lp.get("server role")
+
+ directory_create_or_exists(paths.private_dir, 0o700)
+ directory_create_or_exists(paths.binddns_dir, 0o770)
+ directory_create_or_exists(os.path.join(paths.private_dir, "tls"))
+ directory_create_or_exists(paths.state_dir)
+ if not plaintext_secrets:
+ setup_encrypted_secrets_key(paths.encrypted_secrets_key_path)
+
+ if paths.sysvol and not os.path.exists(paths.sysvol):
+ os.makedirs(paths.sysvol, 0o775)
+
+ schema = Schema(domainsid, invocationid=invocationid,
+ schemadn=names.schemadn, base_schema=base_schema)
+
+ provision_backend = LDBBackend(paths=paths,
+ lp=lp,
+ names=names, logger=logger)
+
+ 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,
+ session_info=session_info, lp=lp)
+
+ try:
+ logger.info("Setting up the registry")
+ setup_registry(paths.hklm, session_info, lp=lp)
+
+ logger.info("Setting up the privileges database")
+ setup_privileges(paths.privilege, session_info, lp=lp)
+
+ logger.info("Setting up idmap db")
+ idmap = setup_idmapdb(paths.idmapdb, session_info=session_info, lp=lp)
+
+ setup_name_mappings(idmap, sid=str(domainsid),
+ root_uid=root_uid, nobody_uid=nobody_uid,
+ users_gid=users_gid)
+
+ logger.info("Setting up SAM db")
+ samdb = setup_samdb(paths.samdb, session_info,
+ provision_backend, lp, names, logger=logger,
+ serverrole=serverrole,
+ schema=schema, am_rodc=am_rodc,
+ plaintext_secrets=plaintext_secrets,
+ backend_store=backend_store,
+ backend_store_size=backend_store_size,
+ batch_mode=batch_mode)
+
+ if serverrole == "active directory domain controller":
+ if paths.netlogon is None:
+ raise MissingShareError("netlogon", paths.smbconf)
+
+ if paths.sysvol is None:
+ raise MissingShareError("sysvol", paths.smbconf)
+
+ if not os.path.isdir(paths.netlogon):
+ os.makedirs(paths.netlogon, 0o755)
+
+ if adminpass is None:
+ adminpass = samba.generate_random_password(12, 32)
+ adminpass_generated = True
+ else:
+ if isinstance(adminpass, bytes):
+ adminpass = adminpass.decode('utf-8')
+ adminpass_generated = False
+
+ if samdb_fill == FILL_FULL:
+ provision_fill(samdb, secrets_ldb, logger, names, paths,
+ schema=schema, samdb_fill=samdb_fill,
+ hostip=hostip, hostip6=hostip6,
+ next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass,
+ krbtgtpass=krbtgtpass,
+ policyguid=policyguid, policyguid_dc=policyguid_dc,
+ invocationid=invocationid, machinepass=machinepass,
+ ntdsguid=ntdsguid, dns_backend=dns_backend,
+ dnspass=dnspass, serverrole=serverrole,
+ dom_for_fun_level=dom_for_fun_level,
+ lp=lp, use_ntvfs=use_ntvfs,
+ skip_sysvolacl=skip_sysvolacl)
+
+ if adprep_level is not None:
+ updates_allowed_overridden = False
+ if lp.get("dsdb:schema update allowed") is None:
+ lp.set("dsdb:schema update allowed", "yes")
+ print("Temporarily overriding 'dsdb:schema update allowed' setting")
+ updates_allowed_overridden = True
+
+ samdb.transaction_start()
+ try:
+ from samba.forest_update import ForestUpdate
+ forest = ForestUpdate(samdb, fix=True)
+
+ forest.check_updates_iterator([11, 54, 79, 80, 81, 82, 83])
+ forest.check_updates_functional_level(adprep_level,
+ DS_DOMAIN_FUNCTION_2008_R2,
+ update_revision=True)
+
+ samdb.transaction_commit()
+ except Exception as e:
+ samdb.transaction_cancel()
+ raise e
+
+ samdb.transaction_start()
+ try:
+ from samba.domain_update import DomainUpdate
+
+ DomainUpdate(samdb, fix=True).check_updates_functional_level(
+ adprep_level,
+ DS_DOMAIN_FUNCTION_2008,
+ update_revision=True,
+ )
+
+ samdb.transaction_commit()
+ except Exception as e:
+ samdb.transaction_cancel()
+ raise e
+
+ if updates_allowed_overridden:
+ lp.set("dsdb:schema update allowed", "no")
+
+ if not is_heimdal_built():
+ create_kdc_conf(paths.kdcconf, realm, domain, os.path.dirname(lp.get("log file")))
+ logger.info("The Kerberos KDC configuration for Samba AD is "
+ "located at %s", paths.kdcconf)
+
+ create_krb5_conf(paths.krb5conf,
+ dnsdomain=names.dnsdomain, hostname=names.hostname,
+ realm=names.realm)
+ logger.info("A Kerberos configuration suitable for Samba AD has been "
+ "generated at %s", paths.krb5conf)
+ logger.info("Merge the contents of this file with your system "
+ "krb5.conf or replace it with this one. Do not create a "
+ "symlink!")
+
+ if serverrole == "active directory domain controller":
+ create_dns_update_list(paths)
+
+ backend_result = provision_backend.post_setup()
+ provision_backend.shutdown()
+
+ except:
+ secrets_ldb.transaction_cancel()
+ raise
+
+ # Now commit the secrets.ldb to disk
+ secrets_ldb.transaction_commit()
+
+ # the commit creates the dns.keytab in the private directory
+ create_dns_dir_keytab_link(logger, paths)
+
+ result = ProvisionResult()
+ result.server_role = serverrole
+ result.domaindn = domaindn
+ result.paths = paths
+ result.names = names
+ result.lp = lp
+ result.samdb = samdb
+ result.idmap = idmap
+ result.domainsid = str(domainsid)
+
+ if samdb_fill == FILL_FULL:
+ result.adminpass_generated = adminpass_generated
+ result.adminpass = adminpass
+ else:
+ result.adminpass_generated = False
+ result.adminpass = None
+
+ result.backend_result = backend_result
+
+ if use_rfc2307:
+ provision_fake_ypserver(logger=logger, samdb=samdb,
+ domaindn=names.domaindn, netbiosname=names.netbiosname,
+ nisdomain=names.domain.lower())
+
+ return result
+
+
+def provision_become_dc(smbconf=None, targetdir=None, realm=None,
+ rootdn=None, domaindn=None, schemadn=None,
+ configdn=None, serverdn=None, domain=None,
+ hostname=None, domainsid=None,
+ machinepass=None, dnspass=None,
+ dns_backend=None, sitename=None, debuglevel=1,
+ use_ntvfs=False):
+
+ logger = logging.getLogger("provision")
+ samba.set_debug_level(debuglevel)
+
+ res = provision(logger, system_session(),
+ smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
+ realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
+ configdn=configdn, serverdn=serverdn, domain=domain,
+ hostname=hostname, hostip=None, domainsid=domainsid,
+ machinepass=machinepass,
+ serverrole="active directory domain controller",
+ sitename=sitename, dns_backend=dns_backend, dnspass=dnspass,
+ use_ntvfs=use_ntvfs)
+ res.lp.set("debuglevel", str(debuglevel))
+ return res
+
+
+def create_krb5_conf(path, dnsdomain, hostname, realm):
+ """Write out a file containing a valid krb5.conf file
+
+ :param path: Path of the new krb5.conf file.
+ :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().__init__(
+ "The name '%r' is not a valid NetBIOS name" % name)
+
+
+class MissingShareError(ProvisioningError):
+
+ def __init__(self, name, smbconf):
+ super().__init__(
+ "Existing smb.conf does not have a [%s] share, but you are "
+ "configuring a DC. Please remove %s or add the share manually." %
+ (name, smbconf))
diff --git a/python/samba/provision/backend.py b/python/samba/provision/backend.py
new file mode 100644
index 0000000..4ffe308
--- /dev/null
+++ b/python/samba/provision/backend.py
@@ -0,0 +1,87 @@
+#
+# Unix SMB/CIFS implementation.
+# backend code for provisioning a Samba4 server
+
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
+# 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 (LDB and LDAP backends)."""
+
+import shutil
+
+class BackendResult(object):
+
+ def report_logger(self, logger):
+ """Rerport this result to a particular logger.
+
+ """
+ raise NotImplementedError(self.report_logger)
+
+
+class ProvisionBackend(object):
+
+ def __init__(self, paths=None, lp=None,
+ names=None, logger=None):
+ """Provision a backend for samba4"""
+ self.paths = paths
+ self.lp = lp
+ self.names = names
+ self.logger = logger
+
+ self.type = "ldb"
+
+ 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.
+
+ :return: A BackendResult or None
+ """
+ raise NotImplementedError(self.post_setup)
+
+
+class LDBBackend(ProvisionBackend):
+
+ def init(self):
+
+ # 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
+
+
diff --git a/python/samba/provision/common.py b/python/samba/provision/common.py
new file mode 100644
index 0000000..a6851b7
--- /dev/null
+++ b/python/samba/provision/common.py
@@ -0,0 +1,91 @@
+
+# Unix SMB/CIFS implementation.
+# utility functions 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"
+
+import os
+from samba import read_and_sub_file
+from samba.param import setup_dir
+
+FILL_FULL = "FULL"
+FILL_SUBDOMAIN = "SUBDOMAIN"
+FILL_NT4SYNC = "NT4SYNC"
+FILL_DRS = "DRS"
+
+
+def setup_path(file):
+ """Return an absolute path to the provision template file specified by file"""
+ return os.path.join(setup_dir(), file)
+
+
+def setup_add_ldif(ldb, ldif_path, subst_vars=None, controls=None):
+ """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 substitute in LDIF.
+ :param nocontrols: Optional list of controls, can be None for no controls
+ """
+ if controls is None:
+ controls = ["relax:0"]
+ 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=None):
+ """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.
+ """
+ if controls is None:
+ controls = ["relax:0"]
+ 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()
diff --git a/python/samba/provision/kerberos.py b/python/samba/provision/kerberos.py
new file mode 100644
index 0000000..665c031
--- /dev/null
+++ b/python/samba/provision/kerberos.py
@@ -0,0 +1,104 @@
+# Unix SMB/CIFS implementation
+#
+# Backend code for provisioning a Samba AD server
+#
+# Copyright (c) 2015 Andreas Schneider <asn@samba.org>
+#
+# 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/>.
+#
+
+from samba.provision.kerberos_implementation import (
+ kdb_modules_dir)
+from samba import is_heimdal_built
+import os
+
+
+def create_kdc_conf(kdcconf, realm, domain, logdir):
+
+ if is_heimdal_built():
+ return
+
+ # Do nothing if kdc.conf has been set
+ if 'KRB5_KDC_PROFILE' in os.environ:
+ return
+
+ # We are in selftest
+ if 'SAMBA_SELFTEST' in os.environ and 'MITKRB5' in os.environ:
+ return
+
+ assert kdcconf is not None
+
+ assert domain is not None
+ domain = domain.upper()
+
+ assert realm is not None
+ realm = realm.upper()
+
+ f = open(kdcconf, 'w')
+ try:
+ f.write("[kdcdefaults]\n")
+
+ f.write("\tkdc_ports = 88\n")
+ f.write("\tkdc_tcp_ports = 88\n")
+ f.write("\tkadmind_port = 464\n")
+ f.write("\trestrict_anonymous_to_tgt = true\n")
+ f.write("\n")
+
+ f.write("[realms]\n")
+
+ f.write("\t%s = {\n" % realm)
+ f.write("\t\tmaster_key_type = aes256-cts\n")
+ f.write("\t\tdefault_principal_flags = +preauth\n")
+ f.write("\t}\n")
+ f.write("\n")
+
+ f.write("\t%s = {\n" % realm.lower())
+ f.write("\t\tmaster_key_type = aes256-cts\n")
+ f.write("\t\tdefault_principal_flags = +preauth\n")
+ f.write("\t}\n")
+ f.write("\n")
+
+ f.write("\t%s = {\n" % domain)
+ f.write("\t\tmaster_key_type = aes256-cts\n")
+ f.write("\t\tdefault_principal_flags = +preauth\n")
+ f.write("\t}\n")
+ f.write("\n")
+
+ f.write("[dbmodules]\n")
+
+ f.write("\tdb_module_dir = %s\n" % kdb_modules_dir)
+ f.write("\n")
+
+ f.write("\t%s = {\n" % realm)
+ f.write("\t\tdb_library = samba\n")
+ f.write("\t}\n")
+ f.write("\n")
+
+ f.write("\t%s = {\n" % realm.lower())
+ f.write("\t\tdb_library = samba\n")
+ f.write("\t}\n")
+ f.write("\n")
+
+ f.write("\t%s = {\n" % domain)
+ f.write("\t\tdb_library = samba\n")
+ f.write("\t}\n")
+ f.write("\n")
+
+ f.write("[logging]\n")
+
+ f.write("\tkdc = FILE:%s/mit_kdc.log\n" % logdir)
+ f.write("\tadmin_server = FILE:%s/mit_kadmin.log\n" % logdir)
+ f.write("\n")
+ finally:
+ f.close()
diff --git a/python/samba/provision/sambadns.py b/python/samba/provision/sambadns.py
new file mode 100644
index 0000000..01398bb
--- /dev/null
+++ b/python/samba/provision/sambadns.py
@@ -0,0 +1,1329 @@
+# Unix SMB/CIFS implementation.
+# backend code for provisioning DNS for a Samba4 server
+#
+# Copyright (C) Kai Blin <kai@samba.org> 2011
+# Copyright (C) Amitay Isaacs <amitay@gmail.com> 2011
+#
+# 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/>.
+#
+
+"""DNS-related provisioning"""
+
+import os
+import uuid
+import shutil
+import time
+import ldb
+from base64 import b64encode
+import subprocess
+import samba
+from samba.tdb_util import tdb_copy
+from samba.mdb_util import mdb_copy
+from samba.ndr import ndr_pack, ndr_unpack
+from samba import setup_file
+from samba.dcerpc import dnsp, misc, security
+from samba.dsdb import (
+ DS_DOMAIN_FUNCTION_2000,
+ DS_DOMAIN_FUNCTION_2003,
+ DS_DOMAIN_FUNCTION_2016,
+ DS_GUID_USERS_CONTAINER
+)
+from samba.descriptor import (
+ get_deletedobjects_descriptor,
+ get_domain_descriptor,
+ get_domain_delete_protected1_descriptor,
+ get_domain_delete_protected2_descriptor,
+ get_dns_partition_descriptor,
+ get_dns_forest_microsoft_dns_descriptor,
+ get_dns_domain_microsoft_dns_descriptor
+)
+from samba.provision.common import (
+ setup_path,
+ setup_add_ldif,
+ setup_modify_ldif,
+ setup_ldb,
+ FILL_FULL,
+ FILL_SUBDOMAIN,
+)
+
+from samba.samdb import get_default_backend_store
+from samba.common import get_string
+
+def get_domainguid(samdb, domaindn):
+ res = samdb.search(base=domaindn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
+ domainguid = str(ndr_unpack(misc.GUID, res[0]["objectGUID"][0]))
+ return domainguid
+
+
+def get_dnsadmins_sid(samdb, domaindn):
+ base_dn = "CN=DnsAdmins,%s" % samdb.get_wellknown_dn(ldb.Dn(samdb,
+ domaindn), DS_GUID_USERS_CONTAINER)
+ res = samdb.search(base=base_dn, scope=ldb.SCOPE_BASE, attrs=["objectSid"])
+ dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
+ return dnsadmins_sid
+
+
+# Note: these classes are not quite the same as similar looking ones
+# in ../dnsserver.py -- those ones are based on
+# dnsserver.DNS_RPC_RECORD ([MS-DNSP]2.2.2.2.5 "DNS_RPC_RECORD"),
+# these are based on dnsp.DnssrvRpcRecord ([MS-DNSP] 2.3.2.2
+# "DnsRecord").
+#
+# They are not interchangeable or mergeable. If you're talking over
+# the wire you want those other ones; these are the on-disk format.
+
+class ARecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, ip_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
+ super().__init__()
+ self.wType = dnsp.DNS_TYPE_A
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self.data = ip_addr
+
+
+class AAAARecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, ip6_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
+ super().__init__()
+ self.wType = dnsp.DNS_TYPE_AAAA
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self.data = ip6_addr
+
+
+class CNAMERecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, cname, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
+ super().__init__()
+ self.wType = dnsp.DNS_TYPE_CNAME
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self.data = cname
+
+
+class NSRecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, dns_server, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
+ super().__init__()
+ self.wType = dnsp.DNS_TYPE_NS
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self.data = dns_server
+
+
+class SOARecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, mname, rname, serial=1, refresh=900, retry=600,
+ expire=86400, minimum=3600, ttl=3600, rank=dnsp.DNS_RANK_ZONE):
+ super().__init__()
+ self.wType = dnsp.DNS_TYPE_SOA
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ soa = dnsp.soa()
+ soa.serial = serial
+ soa.refresh = refresh
+ soa.retry = retry
+ soa.expire = expire
+ soa.mname = mname
+ soa.rname = rname
+ soa.minimum = minimum
+ self.data = soa
+
+
+class SRVRecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, target, port, priority=0, weight=100, serial=1, ttl=900,
+ rank=dnsp.DNS_RANK_ZONE):
+ super().__init__()
+ self.wType = dnsp.DNS_TYPE_SRV
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ srv = dnsp.srv()
+ srv.nameTarget = target
+ srv.wPort = port
+ srv.wPriority = priority
+ srv.wWeight = weight
+ self.data = srv
+
+
+class TXTRecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, slist, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
+ super().__init__()
+ self.wType = dnsp.DNS_TYPE_TXT
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ stringlist = dnsp.string_list()
+ stringlist.count = len(slist)
+ stringlist.str = slist
+ self.data = stringlist
+
+
+class TypeProperty(dnsp.DnsProperty):
+
+ def __init__(self, zone_type=dnsp.DNS_ZONE_TYPE_PRIMARY):
+ super().__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_TYPE
+ self.data = zone_type
+
+
+class AllowUpdateProperty(dnsp.DnsProperty):
+
+ def __init__(self, allow_update=dnsp.DNS_ZONE_UPDATE_SECURE):
+ super().__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_ALLOW_UPDATE
+ self.data = allow_update
+
+
+class SecureTimeProperty(dnsp.DnsProperty):
+
+ def __init__(self, secure_time=0):
+ super().__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_SECURE_TIME
+ self.data = secure_time
+
+
+class NorefreshIntervalProperty(dnsp.DnsProperty):
+
+ def __init__(self, norefresh_interval=0):
+ super().__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_NOREFRESH_INTERVAL
+ self.data = norefresh_interval
+
+
+class RefreshIntervalProperty(dnsp.DnsProperty):
+
+ def __init__(self, refresh_interval=0):
+ super().__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_REFRESH_INTERVAL
+ self.data = refresh_interval
+
+
+class AgingStateProperty(dnsp.DnsProperty):
+
+ def __init__(self, aging_enabled=0):
+ super().__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_AGING_STATE
+ self.data = aging_enabled
+
+
+class AgingEnabledTimeProperty(dnsp.DnsProperty):
+
+ def __init__(self, next_cycle_hours=0):
+ super().__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_AGING_ENABLED_TIME
+ self.data = next_cycle_hours
+
+
+def setup_dns_partitions(samdb, domainsid, domaindn, forestdn, configdn,
+ serverdn, fill_level):
+ domainzone_dn = "DC=DomainDnsZones,%s" % domaindn
+ forestzone_dn = "DC=ForestDnsZones,%s" % forestdn
+ descriptor = get_dns_partition_descriptor(domainsid)
+ deletedobjects_desc = get_deletedobjects_descriptor(domainsid)
+
+ setup_add_ldif(samdb, setup_path("provision_dnszones_partitions.ldif"), {
+ "ZONE_DN": domainzone_dn,
+ "SECDESC": b64encode(descriptor).decode('utf8')
+ })
+ if fill_level != FILL_SUBDOMAIN:
+ setup_add_ldif(samdb, setup_path("provision_dnszones_partitions.ldif"), {
+ "ZONE_DN": forestzone_dn,
+ "SECDESC": b64encode(descriptor).decode('utf8')
+ })
+
+ domainzone_guid = str(uuid.uuid4())
+ domainzone_dns = ldb.Dn(samdb, domainzone_dn).canonical_ex_str().strip()
+
+ protected1_desc = get_domain_delete_protected1_descriptor(domainsid)
+ protected2_desc = get_domain_delete_protected2_descriptor(domainsid)
+ setup_add_ldif(samdb, setup_path("provision_dnszones_add.ldif"), {
+ "ZONE_DN": domainzone_dn,
+ "ZONE_GUID": domainzone_guid,
+ "ZONE_DNS": domainzone_dns,
+ "CONFIGDN": configdn,
+ "SERVERDN": serverdn,
+ "DELETEDOBJECTS_DESCRIPTOR": b64encode(deletedobjects_desc).decode('utf8'),
+ "LOSTANDFOUND_DESCRIPTOR": b64encode(protected2_desc).decode('utf8'),
+ "INFRASTRUCTURE_DESCRIPTOR": b64encode(protected1_desc).decode('utf8'),
+ })
+ setup_modify_ldif(samdb, setup_path("provision_dnszones_modify.ldif"), {
+ "CONFIGDN": configdn,
+ "SERVERDN": serverdn,
+ "ZONE_DN": domainzone_dn,
+ })
+
+ if fill_level != FILL_SUBDOMAIN:
+ forestzone_guid = str(uuid.uuid4())
+ forestzone_dns = ldb.Dn(samdb, forestzone_dn).canonical_ex_str().strip()
+
+ setup_add_ldif(samdb, setup_path("provision_dnszones_add.ldif"), {
+ "ZONE_DN": forestzone_dn,
+ "ZONE_GUID": forestzone_guid,
+ "ZONE_DNS": forestzone_dns,
+ "CONFIGDN": configdn,
+ "SERVERDN": serverdn,
+ "DELETEDOBJECTS_DESCRIPTOR": b64encode(deletedobjects_desc).decode('utf8'),
+ "LOSTANDFOUND_DESCRIPTOR": b64encode(protected2_desc).decode('utf8'),
+ "INFRASTRUCTURE_DESCRIPTOR": b64encode(protected1_desc).decode('utf8'),
+ })
+ setup_modify_ldif(samdb, setup_path("provision_dnszones_modify.ldif"), {
+ "CONFIGDN": configdn,
+ "SERVERDN": serverdn,
+ "ZONE_DN": forestzone_dn,
+ })
+
+
+def add_dns_accounts(samdb, domaindn):
+ setup_add_ldif(samdb, setup_path("provision_dns_accounts_add.ldif"), {
+ "DOMAINDN": domaindn,
+ })
+
+
+def add_dns_container(samdb, domaindn, prefix, domain_sid, dnsadmins_sid, forest=False):
+ name_map = {'DnsAdmins': str(dnsadmins_sid)}
+ if forest is True:
+ sd_val = get_dns_forest_microsoft_dns_descriptor(domain_sid,
+ name_map=name_map)
+ else:
+ sd_val = get_dns_domain_microsoft_dns_descriptor(domain_sid,
+ name_map=name_map)
+ # CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ msg = ldb.Message(ldb.Dn(samdb, "CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)))
+ msg["objectClass"] = ["top", "container"]
+ msg["nTSecurityDescriptor"] = \
+ ldb.MessageElement(sd_val, ldb.FLAG_MOD_ADD,
+ "nTSecurityDescriptor")
+ samdb.add(msg)
+
+
+def add_rootservers(samdb, domaindn, prefix):
+ # https://www.internic.net/zones/named.root
+ rootservers = {}
+ rootservers["a.root-servers.net"] = "198.41.0.4"
+ rootservers["b.root-servers.net"] = "192.228.79.201"
+ rootservers["c.root-servers.net"] = "192.33.4.12"
+ rootservers["d.root-servers.net"] = "199.7.91.13"
+ rootservers["e.root-servers.net"] = "192.203.230.10"
+ rootservers["f.root-servers.net"] = "192.5.5.241"
+ rootservers["g.root-servers.net"] = "192.112.36.4"
+ rootservers["h.root-servers.net"] = "198.97.190.53"
+ rootservers["i.root-servers.net"] = "192.36.148.17"
+ rootservers["j.root-servers.net"] = "192.58.128.30"
+ rootservers["k.root-servers.net"] = "193.0.14.129"
+ rootservers["l.root-servers.net"] = "199.7.83.42"
+ rootservers["m.root-servers.net"] = "202.12.27.33"
+
+ rootservers_v6 = {}
+ rootservers_v6["a.root-servers.net"] = "2001:503:ba3e::2:30"
+ rootservers_v6["b.root-servers.net"] = "2001:500:84::b"
+ rootservers_v6["c.root-servers.net"] = "2001:500:2::c"
+ rootservers_v6["d.root-servers.net"] = "2001:500:2d::d"
+ rootservers_v6["e.root-servers.net"] = "2001:500:a8::e"
+ rootservers_v6["f.root-servers.net"] = "2001:500:2f::f"
+ rootservers_v6["g.root-servers.net"] = "2001:500:12::d0d"
+ rootservers_v6["h.root-servers.net"] = "2001:500:1::53"
+ rootservers_v6["i.root-servers.net"] = "2001:7fe::53"
+ rootservers_v6["j.root-servers.net"] = "2001:503:c27::2:30"
+ rootservers_v6["k.root-servers.net"] = "2001:7fd::1"
+ rootservers_v6["l.root-servers.net"] = "2001:500:9f::42"
+ rootservers_v6["m.root-servers.net"] = "2001:dc3::35"
+
+ container_dn = "DC=RootDNSServers,CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)
+
+ # Add DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ msg = ldb.Message(ldb.Dn(samdb, container_dn))
+ props = []
+ props.append(ndr_pack(TypeProperty(zone_type=dnsp.DNS_ZONE_TYPE_CACHE)))
+ props.append(ndr_pack(AllowUpdateProperty(allow_update=dnsp.DNS_ZONE_UPDATE_OFF)))
+ props.append(ndr_pack(SecureTimeProperty()))
+ props.append(ndr_pack(NorefreshIntervalProperty()))
+ props.append(ndr_pack(RefreshIntervalProperty()))
+ props.append(ndr_pack(AgingStateProperty()))
+ props.append(ndr_pack(AgingEnabledTimeProperty()))
+ msg["objectClass"] = ["top", "dnsZone"]
+ msg["cn"] = ldb.MessageElement("Zone", ldb.FLAG_MOD_ADD, "cn")
+ msg["dNSProperty"] = ldb.MessageElement(props, ldb.FLAG_MOD_ADD, "dNSProperty")
+ samdb.add(msg)
+
+ # Add DC=@,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ record = []
+ for rserver in rootservers:
+ record.append(ndr_pack(NSRecord(rserver, serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT)))
+
+ msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+ # Add DC=<rootserver>,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ for rserver in rootservers:
+ record = [ndr_pack(ARecord(rootservers[rserver], serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT))]
+ # Add AAAA record as well (How does W2K* add IPv6 records?)
+ # if rserver in rootservers_v6:
+ # record.append(ndr_pack(AAAARecord(rootservers_v6[rserver], serial=0, ttl=0)))
+ msg = ldb.Message(ldb.Dn(samdb, "DC=%s,%s" % (rserver, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_at_record(samdb, container_dn, prefix, hostname, dnsdomain, hostip, hostip6):
+
+ fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
+
+ at_records = []
+
+ # SOA record
+ at_soa_record = SOARecord(fqdn_hostname, "hostmaster.%s" % dnsdomain)
+ at_records.append(ndr_pack(at_soa_record))
+
+ # NS record
+ at_ns_record = NSRecord(fqdn_hostname)
+ at_records.append(ndr_pack(at_ns_record))
+
+ if hostip is not None:
+ # A record
+ at_a_record = ARecord(hostip)
+ at_records.append(ndr_pack(at_a_record))
+
+ if hostip6 is not None:
+ # AAAA record
+ at_aaaa_record = AAAARecord(hostip6)
+ at_records.append(ndr_pack(at_aaaa_record))
+
+ msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(at_records, ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_srv_record(samdb, container_dn, prefix, host, port):
+ srv_record = SRVRecord(host, port)
+ msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(ndr_pack(srv_record), ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_ns_record(samdb, container_dn, prefix, host):
+ ns_record = NSRecord(host)
+ msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_ns_glue_record(samdb, container_dn, prefix, host):
+ ns_record = NSRecord(host, rank=dnsp.DNS_RANK_NS_GLUE)
+ msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_cname_record(samdb, container_dn, prefix, host):
+ cname_record = CNAMERecord(host)
+ msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(ndr_pack(cname_record), ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_host_record(samdb, container_dn, prefix, hostip, hostip6):
+ host_records = []
+ if hostip:
+ a_record = ARecord(hostip)
+ host_records.append(ndr_pack(a_record))
+ if hostip6:
+ aaaa_record = AAAARecord(hostip6)
+ host_records.append(ndr_pack(aaaa_record))
+ if host_records:
+ msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(host_records, ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_domain_record(samdb, domaindn, prefix, dnsdomain, domainsid, dnsadmins_sid):
+ # DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ sddl = "O:SYG:BAD:AI" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)" \
+ "(A;;CC;;;AU)" \
+ "(A;;RPLCLORC;;;WD)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
+ "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)" \
+ "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
+ "(OA;CIID;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
+ "(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
+ "(A;CIID;LC;;;RU)" \
+ "(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
+ "S:AI" % dnsadmins_sid
+ sec = security.descriptor.from_sddl(sddl, domainsid)
+ props = []
+ props.append(ndr_pack(TypeProperty()))
+ props.append(ndr_pack(AllowUpdateProperty()))
+ props.append(ndr_pack(SecureTimeProperty()))
+ props.append(ndr_pack(NorefreshIntervalProperty(norefresh_interval=168)))
+ props.append(ndr_pack(RefreshIntervalProperty(refresh_interval=168)))
+ props.append(ndr_pack(AgingStateProperty()))
+ props.append(ndr_pack(AgingEnabledTimeProperty()))
+ msg = ldb.Message(ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" % (dnsdomain, prefix, domaindn)))
+ msg["objectClass"] = ["top", "dnsZone"]
+ msg["ntSecurityDescriptor"] = \
+ ldb.MessageElement(ndr_pack(sec),
+ ldb.FLAG_MOD_ADD,
+ "nTSecurityDescriptor")
+ msg["dNSProperty"] = ldb.MessageElement(props, ldb.FLAG_MOD_ADD, "dNSProperty")
+ samdb.add(msg)
+
+
+def add_msdcs_record(samdb, forestdn, prefix, dnsforest):
+ # DC=_msdcs.<DNSFOREST>,CN=MicrosoftDNS,<PREFIX>,<FORESTDN>
+ msg = ldb.Message(ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
+ (dnsforest, prefix, forestdn)))
+ msg["objectClass"] = ["top", "dnsZone"]
+ samdb.add(msg)
+
+
+def add_dc_domain_records(samdb, domaindn, prefix, site, dnsdomain, hostname,
+ hostip, hostip6):
+
+ fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
+
+ # Set up domain container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ domain_container_dn = ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" %
+ (dnsdomain, prefix, domaindn))
+
+ # DC=@ record
+ add_at_record(samdb, domain_container_dn, "DC=@", hostname, dnsdomain,
+ hostip, hostip6)
+
+ # DC=<HOSTNAME> record
+ add_host_record(samdb, domain_container_dn, "DC=%s" % hostname, hostip,
+ hostip6)
+
+ # DC=_kerberos._tcp record
+ add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp",
+ fqdn_hostname, 88)
+
+ # DC=_kerberos._tcp.<SITENAME>._sites record
+ add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp.%s._sites" %
+ site, fqdn_hostname, 88)
+
+ # DC=_kerberos._udp record
+ add_srv_record(samdb, domain_container_dn, "DC=_kerberos._udp",
+ fqdn_hostname, 88)
+
+ # DC=_kpasswd._tcp record
+ add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._tcp",
+ fqdn_hostname, 464)
+
+ # DC=_kpasswd._udp record
+ add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._udp",
+ fqdn_hostname, 464)
+
+ # DC=_ldap._tcp record
+ add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp", fqdn_hostname,
+ 389)
+
+ # DC=_ldap._tcp.<SITENAME>._sites record
+ add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites" %
+ site, fqdn_hostname, 389)
+
+ # FIXME: The number of SRV records depend on the various roles this DC has.
+ # _gc and _msdcs records are added if the we are the forest dc and not subdomain dc
+ #
+ # Assumption: current DC is GC and add all the entries
+
+ # DC=_gc._tcp record
+ add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp", fqdn_hostname,
+ 3268)
+
+ # DC=_gc._tcp.<SITENAME>,_sites record
+ add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp.%s._sites" % site,
+ fqdn_hostname, 3268)
+
+ # DC=_msdcs record
+ add_ns_glue_record(samdb, domain_container_dn, "DC=_msdcs", fqdn_hostname)
+
+ # FIXME: Following entries are added only if DomainDnsZones and ForestDnsZones partitions
+ # are created
+ #
+ # Assumption: Additional entries won't hurt on os_level = 2000
+
+ # DC=_ldap._tcp.<SITENAME>._sites.DomainDnsZones
+ add_srv_record(samdb, domain_container_dn,
+ "DC=_ldap._tcp.%s._sites.DomainDnsZones" % site, fqdn_hostname,
+ 389)
+
+ # DC=_ldap._tcp.<SITENAME>._sites.ForestDnsZones
+ add_srv_record(samdb, domain_container_dn,
+ "DC=_ldap._tcp.%s._sites.ForestDnsZones" % site, fqdn_hostname,
+ 389)
+
+ # DC=_ldap._tcp.DomainDnsZones
+ add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.DomainDnsZones",
+ fqdn_hostname, 389)
+
+ # DC=_ldap._tcp.ForestDnsZones
+ add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.ForestDnsZones",
+ fqdn_hostname, 389)
+
+ # DC=DomainDnsZones
+ add_host_record(samdb, domain_container_dn, "DC=DomainDnsZones", hostip,
+ hostip6)
+
+ # DC=ForestDnsZones
+ add_host_record(samdb, domain_container_dn, "DC=ForestDnsZones", hostip,
+ hostip6)
+
+
+def add_dc_msdcs_records(samdb, forestdn, prefix, site, dnsforest, hostname,
+ hostip, hostip6, domainguid, ntdsguid):
+
+ fqdn_hostname = "%s.%s" % (hostname, dnsforest)
+
+ # Set up forest container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ forest_container_dn = ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
+ (dnsforest, prefix, forestdn))
+
+ # DC=@ record
+ add_at_record(samdb, forest_container_dn, "DC=@", hostname, dnsforest,
+ None, None)
+
+ # DC=_kerberos._tcp.dc record
+ add_srv_record(samdb, forest_container_dn, "DC=_kerberos._tcp.dc",
+ fqdn_hostname, 88)
+
+ # DC=_kerberos._tcp.<SITENAME>._sites.dc record
+ add_srv_record(samdb, forest_container_dn,
+ "DC=_kerberos._tcp.%s._sites.dc" % site, fqdn_hostname, 88)
+
+ # DC=_ldap._tcp.dc record
+ add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.dc",
+ fqdn_hostname, 389)
+
+ # DC=_ldap._tcp.<SITENAME>._sites.dc record
+ add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.dc" %
+ site, fqdn_hostname, 389)
+
+ # DC=_ldap._tcp.<SITENAME>._sites.gc record
+ add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.gc" %
+ site, fqdn_hostname, 3268)
+
+ # DC=_ldap._tcp.gc record
+ add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.gc",
+ fqdn_hostname, 3268)
+
+ # DC=_ldap._tcp.pdc record
+ add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.pdc",
+ fqdn_hostname, 389)
+
+ # DC=gc record
+ add_host_record(samdb, forest_container_dn, "DC=gc", hostip, hostip6)
+
+ # DC=_ldap._tcp.<DOMAINGUID>.domains record
+ add_srv_record(samdb, forest_container_dn,
+ "DC=_ldap._tcp.%s.domains" % domainguid, fqdn_hostname, 389)
+
+ # DC=<NTDSGUID>
+ add_cname_record(samdb, forest_container_dn, "DC=%s" % ntdsguid,
+ fqdn_hostname)
+
+
+def secretsdb_setup_dns(secretsdb, names, private_dir, binddns_dir, realm,
+ dnsdomain, dns_keytab_path, dnspass, key_version_number):
+ """Add DNS specific bits to a secrets database.
+
+ :param secretsdb: Ldb Handle to the secrets database
+ :param names: Names shortcut
+ :param machinepass: Machine password
+ """
+ try:
+ os.unlink(os.path.join(private_dir, dns_keytab_path))
+ os.unlink(os.path.join(binddns_dir, dns_keytab_path))
+ except OSError:
+ pass
+
+ if key_version_number is None:
+ key_version_number = 1
+
+ # This will create the dns.keytab file in the private_dir when it is
+ # committed!
+ setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
+ "REALM": realm,
+ "DNSDOMAIN": dnsdomain,
+ "DNS_KEYTAB": dns_keytab_path,
+ "DNSPASS_B64": b64encode(dnspass.encode('utf-8')).decode('utf8'),
+ "KEY_VERSION_NUMBER": str(key_version_number),
+ "HOSTNAME": names.hostname,
+ "DNSNAME": '%s.%s' % (
+ names.netbiosname.lower(), names.dnsdomain.lower())
+ })
+
+
+def create_dns_dir(logger, paths):
+ """(Re)create the DNS directory and chown it to bind.
+
+ :param logger: Logger object
+ :param paths: paths object
+ """
+ dns_dir = os.path.dirname(paths.dns)
+
+ try:
+ shutil.rmtree(dns_dir, True)
+ except OSError:
+ pass
+
+ os.mkdir(dns_dir, 0o770)
+
+ if paths.bind_gid is not None:
+ try:
+ os.chown(dns_dir, -1, paths.bind_gid)
+ # chmod needed to cope with umask
+ os.chmod(dns_dir, 0o770)
+ except OSError:
+ if 'SAMBA_SELFTEST' not in os.environ:
+ logger.error("Failed to chown %s to bind gid %u" % (
+ dns_dir, paths.bind_gid))
+
+
+def create_dns_dir_keytab_link(logger, paths):
+ """Create link for BIND to DNS keytab
+
+ :param logger: Logger object
+ :param paths: paths object
+ """
+ private_dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
+ bind_dns_keytab_path = os.path.join(paths.binddns_dir, paths.dns_keytab)
+
+ if os.path.isfile(private_dns_keytab_path):
+ if os.path.isfile(bind_dns_keytab_path):
+ try:
+ os.unlink(bind_dns_keytab_path)
+ except OSError as e:
+ logger.error("Failed to remove %s: %s" %
+ (bind_dns_keytab_path, e.strerror))
+
+ # link the dns.keytab to the bind-dns directory
+ try:
+ os.link(private_dns_keytab_path, bind_dns_keytab_path)
+ except OSError as e:
+ logger.error("Failed to create link %s -> %s: %s" %
+ (private_dns_keytab_path, bind_dns_keytab_path, e.strerror))
+
+ # chown the dns.keytab in the bind-dns directory
+ if paths.bind_gid is not None:
+ try:
+ os.chmod(paths.binddns_dir, 0o770)
+ os.chown(paths.binddns_dir, -1, paths.bind_gid)
+ except OSError:
+ if 'SAMBA_SELFTEST' not in os.environ:
+ logger.info("Failed to chown %s to bind gid %u",
+ paths.binddns_dir, paths.bind_gid)
+ try:
+ os.chmod(bind_dns_keytab_path, 0o640)
+ os.chown(bind_dns_keytab_path, -1, paths.bind_gid)
+ except OSError:
+ if 'SAMBA_SELFTEST' not in os.environ:
+ logger.info("Failed to chown %s to bind gid %u",
+ bind_dns_keytab_path, paths.bind_gid)
+
+
+def create_zone_file(logger, paths, dnsdomain,
+ hostip, hostip6, hostname, realm, domainguid,
+ ntdsguid, site):
+ """Write out a DNS zone file, from the info in the current database.
+
+ :param paths: paths object
+ :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 = ""
+
+ 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": site,
+ "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,
+ })
+
+ if paths.bind_gid is not None:
+ try:
+ os.chown(paths.dns, -1, paths.bind_gid)
+ # chmod needed to cope with umask
+ os.chmod(paths.dns, 0o664)
+ except OSError:
+ if 'SAMBA_SELFTEST' not in os.environ:
+ logger.error("Failed to chown %s to bind gid %u" % (
+ paths.dns, paths.bind_gid))
+
+
+def create_samdb_copy(samdb, logger, paths, names, domainsid, domainguid):
+ """Create a copy of samdb and give write permissions to named for dns partitions
+ """
+ private_dir = paths.private_dir
+ samldb_dir = os.path.join(private_dir, "sam.ldb.d")
+ dns_dir = os.path.dirname(paths.dns)
+ dns_samldb_dir = os.path.join(dns_dir, "sam.ldb.d")
+
+ # Find the partitions and corresponding filenames
+ partfile = {}
+ res = samdb.search(base="@PARTITION",
+ scope=ldb.SCOPE_BASE,
+ attrs=["partition", "backendStore"])
+ for tmp in res[0]["partition"]:
+ (nc, fname) = str(tmp).split(':')
+ partfile[nc.upper()] = fname
+
+ backend_store = get_default_backend_store()
+ if "backendStore" in res[0]:
+ backend_store = str(res[0]["backendStore"][0])
+
+ # Create empty domain partition
+
+ domaindn = names.domaindn.upper()
+ domainpart_file = os.path.join(dns_dir, partfile[domaindn])
+ try:
+ os.mkdir(dns_samldb_dir)
+ open(domainpart_file, 'w').close()
+
+ # Fill the basedn and @OPTION records in domain partition
+ dom_url = "%s://%s" % (backend_store, domainpart_file)
+ dom_ldb = samba.Ldb(dom_url)
+
+ # We need the dummy main-domain DB to have the correct @INDEXLIST
+ index_res = samdb.search(base="@INDEXLIST", scope=ldb.SCOPE_BASE)
+ dom_ldb.add(index_res[0])
+
+ domainguid_line = "objectGUID: %s\n-" % domainguid
+ descr = b64encode(get_domain_descriptor(domainsid)).decode('utf8')
+ setup_add_ldif(dom_ldb, setup_path("provision_basedn.ldif"), {
+ "DOMAINDN": names.domaindn,
+ "DOMAINGUID": domainguid_line,
+ "DOMAINSID": str(domainsid),
+ "DESCRIPTOR": descr})
+ setup_add_ldif(dom_ldb,
+ setup_path("provision_basedn_options.ldif"), None)
+
+ except:
+ logger.error(
+ "Failed to setup database for BIND, AD based DNS cannot be used")
+ raise
+
+ # This line is critical to the security of the whole scheme.
+ # We assume there is no secret data in the (to be left out of
+ # date and essentially read-only) config, schema and metadata partitions.
+ #
+ # Only the stub of the domain partition is created above.
+ #
+ # That way, things like the krbtgt key do not leak.
+ del partfile[domaindn]
+
+ # Link dns partitions and metadata
+ domainzonedn = "DC=DOMAINDNSZONES,%s" % names.domaindn.upper()
+ forestzonedn = "DC=FORESTDNSZONES,%s" % names.rootdn.upper()
+
+ domainzone_file = partfile[domainzonedn]
+ forestzone_file = partfile.get(forestzonedn)
+
+ metadata_file = "metadata.tdb"
+ try:
+ os.link(os.path.join(samldb_dir, metadata_file),
+ os.path.join(dns_samldb_dir, metadata_file))
+ os.link(os.path.join(private_dir, domainzone_file),
+ os.path.join(dns_dir, domainzone_file))
+ if backend_store == "mdb":
+ # If the file is an lmdb data file need to link the
+ # lock file as well
+ os.link(os.path.join(private_dir, domainzone_file + "-lock"),
+ os.path.join(dns_dir, domainzone_file + "-lock"))
+ if forestzone_file:
+ os.link(os.path.join(private_dir, forestzone_file),
+ os.path.join(dns_dir, forestzone_file))
+ if backend_store == "mdb":
+ # If the database file is an lmdb data file need to link the
+ # lock file as well
+ os.link(os.path.join(private_dir, forestzone_file + "-lock"),
+ os.path.join(dns_dir, forestzone_file + "-lock"))
+ except OSError:
+ logger.error(
+ "Failed to setup database for BIND, AD based DNS cannot be used")
+ raise
+ del partfile[domainzonedn]
+ if forestzone_file:
+ del partfile[forestzonedn]
+
+ # Copy root, config, schema partitions (and any other if any)
+ # Since samdb is open in the current process, copy them in a child process
+ try:
+ tdb_copy(os.path.join(private_dir, "sam.ldb"),
+ os.path.join(dns_dir, "sam.ldb"))
+ for nc in partfile:
+ pfile = partfile[nc]
+ if backend_store == "mdb":
+ mdb_copy(os.path.join(private_dir, pfile),
+ os.path.join(dns_dir, pfile))
+ else:
+ tdb_copy(os.path.join(private_dir, pfile),
+ os.path.join(dns_dir, pfile))
+ except:
+ logger.error(
+ "Failed to setup database for BIND, AD based DNS cannot be used")
+ raise
+
+ # Give bind read/write permissions dns partitions
+ if paths.bind_gid is not None:
+ try:
+ for dirname, dirs, files in os.walk(dns_dir):
+ for d in dirs:
+ dpath = os.path.join(dirname, d)
+ os.chown(dpath, -1, paths.bind_gid)
+ os.chmod(dpath, 0o770)
+ for f in files:
+ if f.endswith(('.ldb', '.tdb', 'ldb-lock')):
+ fpath = os.path.join(dirname, f)
+ os.chown(fpath, -1, paths.bind_gid)
+ os.chmod(fpath, 0o660)
+ except OSError:
+ if 'SAMBA_SELFTEST' not in os.environ:
+ logger.error(
+ "Failed to set permissions to sam.ldb* files, fix manually")
+ else:
+ if 'SAMBA_SELFTEST' not in os.environ:
+ logger.warning("""Unable to find group id for BIND,
+ set permissions to sam.ldb* files manually""")
+
+
+def create_dns_update_list(paths):
+ """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, samba_spnupdate
+ 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, realm, dnsdomain, dns_backend, logger):
+ """Write out a file containing zone statements suitable for inclusion in a
+ named.conf file (including GSS-TSIG configuration).
+
+ :param paths: all paths
+ :param realm: Realm name
+ :param dnsdomain: DNS Domain name
+ :param dns_backend: DNS backend type
+ :param keytab_name: File name of DNS keytab file
+ :param logger: Logger object
+ """
+
+ # TODO: This really should have been done as a top level import.
+ # It is done here to avoid a dependency loop. That is, we move
+ # ProvisioningError to another file, and have all the provision
+ # scripts import it from there.
+
+ from samba.provision import ProvisioningError
+
+ if dns_backend == "BIND9_FLATFILE":
+ 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)
+
+ elif dns_backend == "BIND9_DLZ":
+ bind_info = subprocess.Popen(['named -V'], shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ cwd='.').communicate()[0]
+ bind_info = get_string(bind_info)
+ bind9_8 = '#'
+ bind9_9 = '#'
+ bind9_10 = '#'
+ bind9_11 = '#'
+ bind9_12 = '#'
+ bind9_14 = '#'
+ bind9_16 = '#'
+ bind9_18 = '#'
+ if bind_info.upper().find('BIND 9.8') != -1:
+ bind9_8 = ''
+ elif bind_info.upper().find('BIND 9.9') != -1:
+ bind9_9 = ''
+ elif bind_info.upper().find('BIND 9.10') != -1:
+ bind9_10 = ''
+ elif bind_info.upper().find('BIND 9.11') != -1:
+ bind9_11 = ''
+ elif bind_info.upper().find('BIND 9.12') != -1:
+ bind9_12 = ''
+ elif bind_info.upper().find('BIND 9.14') != -1:
+ bind9_14 = ''
+ elif bind_info.upper().find('BIND 9.16') != -1:
+ bind9_16 = ''
+ elif bind_info.upper().find('BIND 9.18') != -1:
+ bind9_18 = ''
+ elif bind_info.upper().find('BIND 9.7') != -1:
+ raise ProvisioningError("DLZ option incompatible with BIND 9.7.")
+ elif bind_info.upper().find('BIND_9.13') != -1:
+ raise ProvisioningError("Only stable/esv releases of BIND are supported.")
+ elif bind_info.upper().find('BIND_9.15') != -1:
+ raise ProvisioningError("Only stable/esv releases of BIND are supported.")
+ elif bind_info.upper().find('BIND_9.17') != -1:
+ raise ProvisioningError("Only stable/esv releases of BIND are supported.")
+ else:
+ logger.warning("BIND version unknown, please modify %s manually." % paths.namedconf)
+ setup_file(setup_path("named.conf.dlz"), paths.namedconf, {
+ "NAMED_CONF": paths.namedconf,
+ "MODULESDIR": samba.param.modules_dir(),
+ "BIND9_8": bind9_8,
+ "BIND9_9": bind9_9,
+ "BIND9_10": bind9_10,
+ "BIND9_11": bind9_11,
+ "BIND9_12": bind9_12,
+ "BIND9_14": bind9_14,
+ "BIND9_16": bind9_16,
+ "BIND9_18": bind9_18
+ })
+
+
+def create_named_txt(path, realm, dnsdomain, dnsname, binddns_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 realm: Realm name
+ :param dnsdomain: DNS Domain name
+ :param binddns_dir: Path to bind dns directory
+ :param keytab_name: File name of DNS keytab file
+ """
+ setup_file(setup_path("named.txt"), path, {
+ "DNSDOMAIN": dnsdomain,
+ "DNSNAME": dnsname,
+ "REALM": realm,
+ "DNS_KEYTAB": keytab_name,
+ "DNS_KEYTAB_ABS": os.path.join(binddns_dir, keytab_name),
+ "PRIVATE_DIR": binddns_dir
+ })
+
+
+def is_valid_dns_backend(dns_backend):
+ return dns_backend in ("BIND9_FLATFILE", "BIND9_DLZ", "SAMBA_INTERNAL", "NONE")
+
+
+def is_valid_os_level(os_level):
+ return DS_DOMAIN_FUNCTION_2000 <= os_level <= DS_DOMAIN_FUNCTION_2016
+
+
+def create_dns_legacy(samdb, domainsid, forestdn, dnsadmins_sid):
+ # Set up MicrosoftDNS container
+ add_dns_container(samdb, forestdn, "CN=System", domainsid, dnsadmins_sid)
+ # Add root servers
+ add_rootservers(samdb, forestdn, "CN=System")
+
+
+def fill_dns_data_legacy(samdb, domainsid, forestdn, dnsdomain, site, hostname,
+ hostip, hostip6, dnsadmins_sid):
+ # Add domain record
+ add_domain_record(samdb, forestdn, "CN=System", dnsdomain, domainsid,
+ dnsadmins_sid)
+
+ # Add DNS records for a DC in domain
+ add_dc_domain_records(samdb, forestdn, "CN=System", site, dnsdomain,
+ hostname, hostip, hostip6)
+
+
+def create_dns_partitions(samdb, domainsid, names, domaindn, forestdn,
+ dnsadmins_sid, fill_level):
+ # Set up additional partitions (DomainDnsZones, ForstDnsZones)
+ setup_dns_partitions(samdb, domainsid, domaindn, forestdn,
+ names.configdn, names.serverdn, fill_level)
+
+ # Set up MicrosoftDNS containers
+ add_dns_container(samdb, domaindn, "DC=DomainDnsZones", domainsid,
+ dnsadmins_sid)
+ if fill_level != FILL_SUBDOMAIN:
+ add_dns_container(samdb, forestdn, "DC=ForestDnsZones", domainsid,
+ dnsadmins_sid, forest=True)
+
+
+def fill_dns_data_partitions(samdb, domainsid, site, domaindn, forestdn,
+ dnsdomain, dnsforest, hostname, hostip, hostip6,
+ domainguid, ntdsguid, dnsadmins_sid, autofill=True,
+ fill_level=FILL_FULL, add_root=True):
+ """Fill data in various AD partitions
+
+ :param samdb: LDB object connected to sam.ldb file
+ :param domainsid: Domain SID (as dom_sid object)
+ :param site: Site name to create hostnames in
+ :param domaindn: DN of the domain
+ :param forestdn: DN of the forest
+ :param dnsdomain: DNS name of the domain
+ :param dnsforest: DNS name of the forest
+ :param hostname: Host name of this DC
+ :param hostip: IPv4 addresses
+ :param hostip6: IPv6 addresses
+ :param domainguid: Domain GUID
+ :param ntdsguid: NTDS GUID
+ :param dnsadmins_sid: SID for DnsAdmins group
+ :param autofill: Create DNS records (using fixed template)
+ """
+
+ # Set up DC=DomainDnsZones,<DOMAINDN>
+ # Add rootserver records
+ if add_root:
+ add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
+
+ # Add domain record
+ add_domain_record(samdb, domaindn, "DC=DomainDnsZones", dnsdomain,
+ domainsid, dnsadmins_sid)
+
+ # Add DNS records for a DC in domain
+ if autofill:
+ add_dc_domain_records(samdb, domaindn, "DC=DomainDnsZones", site,
+ dnsdomain, hostname, hostip, hostip6)
+
+ if fill_level != FILL_SUBDOMAIN:
+ # Set up DC=ForestDnsZones,<FORESTDN>
+ # Add _msdcs record
+ add_msdcs_record(samdb, forestdn, "DC=ForestDnsZones", dnsforest)
+
+ # Add DNS records for a DC in forest
+ if autofill:
+ add_dc_msdcs_records(samdb, forestdn, "DC=ForestDnsZones", site,
+ dnsforest, hostname, hostip, hostip6,
+ domainguid, ntdsguid)
+
+
+def setup_ad_dns(samdb, secretsdb, names, paths, logger,
+ dns_backend, os_level, dnspass=None, hostip=None, hostip6=None,
+ fill_level=FILL_FULL):
+ """Provision DNS information (assuming GC role)
+
+ :param samdb: LDB object connected to sam.ldb file
+ :param secretsdb: LDB object connected to secrets.ldb file
+ :param names: Names shortcut
+ :param paths: Paths shortcut
+ :param logger: Logger object
+ :param dns_backend: Type of DNS backend
+ :param os_level: Functional level (treated as os level)
+ :param dnspass: Password for bind's DNS account
+ :param hostip: IPv4 address
+ :param hostip6: IPv6 address
+ """
+
+ if not is_valid_dns_backend(dns_backend):
+ raise Exception("Invalid dns backend: %r" % dns_backend)
+
+ if not is_valid_os_level(os_level):
+ raise Exception("Invalid os level: %r" % os_level)
+
+ if dns_backend == "NONE":
+ logger.info("No DNS backend set, not configuring DNS")
+ return
+
+ # Add dns accounts (DnsAdmins, DnsUpdateProxy) in domain
+ logger.info("Adding DNS accounts")
+ add_dns_accounts(samdb, names.domaindn)
+
+ # If dns_backend is BIND9_FLATFILE
+ # Populate only CN=MicrosoftDNS,CN=System,<DOMAINDN>
+ #
+ # If dns_backend is SAMBA_INTERNAL or BIND9_DLZ
+ # Populate DNS partitions
+
+ # If os_level < 2003 (DS_DOMAIN_FUNCTION_2000)
+ # All dns records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
+ #
+ # If os_level >= 2003 (DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008,
+ # DS_DOMAIN_FUNCTION_2008_R2)
+ # Root server records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
+ # Domain records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
+ # Domain records are in CN=MicrosoftDNS,DC=DomainDnsZones,<DOMAINDN>
+ # Forest records are in CN=MicrosoftDNS,DC=ForestDnsZones,<FORESTDN>
+ domaindn = names.domaindn
+ forestdn = samdb.get_root_basedn().get_linearized()
+
+ dnsdomain = names.dnsdomain.lower()
+ dnsforest = dnsdomain
+
+ site = names.sitename
+
+ hostname = names.netbiosname.lower()
+
+ dnsadmins_sid = get_dnsadmins_sid(samdb, domaindn)
+ domainguid = get_domainguid(samdb, domaindn)
+
+ samdb.transaction_start()
+ try:
+ # Create CN=System
+ logger.info("Creating CN=MicrosoftDNS,CN=System,%s" % domaindn)
+ create_dns_legacy(samdb, names.domainsid, domaindn, dnsadmins_sid)
+
+ if os_level == DS_DOMAIN_FUNCTION_2000:
+ # Populating legacy dns
+ logger.info("Populating CN=MicrosoftDNS,CN=System,%s" % domaindn)
+ fill_dns_data_legacy(samdb, names.domainsid, domaindn, dnsdomain, site,
+ hostname, hostip, hostip6, dnsadmins_sid)
+
+ elif dns_backend in ("SAMBA_INTERNAL", "BIND9_DLZ") and \
+ os_level >= DS_DOMAIN_FUNCTION_2003:
+
+ # Create DNS partitions
+ logger.info("Creating DomainDnsZones and ForestDnsZones partitions")
+ create_dns_partitions(samdb, names.domainsid, names, domaindn, forestdn,
+ dnsadmins_sid, fill_level)
+
+ # Populating dns partitions
+ logger.info("Populating DomainDnsZones and ForestDnsZones partitions")
+ fill_dns_data_partitions(samdb, names.domainsid, site, domaindn, forestdn,
+ dnsdomain, dnsforest, hostname, hostip, hostip6,
+ domainguid, names.ntdsguid, dnsadmins_sid,
+ fill_level=fill_level)
+
+ except:
+ samdb.transaction_cancel()
+ raise
+ else:
+ samdb.transaction_commit()
+
+ if dns_backend.startswith("BIND9_"):
+ setup_bind9_dns(samdb, secretsdb, names, paths, logger,
+ dns_backend, os_level, site=site, dnspass=dnspass, hostip=hostip,
+ hostip6=hostip6)
+
+
+def setup_bind9_dns(samdb, secretsdb, names, paths, logger,
+ dns_backend, os_level, site=None, dnspass=None, hostip=None,
+ hostip6=None, key_version_number=None):
+ """Provision DNS information (assuming BIND9 backend in DC role)
+
+ :param samdb: LDB object connected to sam.ldb file
+ :param secretsdb: LDB object connected to secrets.ldb file
+ :param names: Names shortcut
+ :param paths: Paths shortcut
+ :param logger: Logger object
+ :param dns_backend: Type of DNS backend
+ :param os_level: Functional level (treated as os level)
+ :param site: Site to create hostnames in
+ :param dnspass: Password for bind's DNS account
+ :param hostip: IPv4 address
+ :param hostip6: IPv6 address
+ """
+
+ if (not is_valid_dns_backend(dns_backend) or
+ not dns_backend.startswith("BIND9_")):
+ raise Exception("Invalid dns backend: %r" % dns_backend)
+
+ if not is_valid_os_level(os_level):
+ raise Exception("Invalid os level: %r" % os_level)
+
+ domaindn = names.domaindn
+
+ domainguid = get_domainguid(samdb, domaindn)
+
+ secretsdb_setup_dns(secretsdb, names,
+ paths.private_dir,
+ paths.binddns_dir,
+ realm=names.realm,
+ dnsdomain=names.dnsdomain,
+ dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
+ key_version_number=key_version_number)
+
+ create_dns_dir(logger, paths)
+ create_dns_dir_keytab_link(logger, paths)
+
+ if dns_backend == "BIND9_FLATFILE":
+ create_zone_file(logger, paths, site=site,
+ dnsdomain=names.dnsdomain, hostip=hostip,
+ hostip6=hostip6, hostname=names.hostname,
+ realm=names.realm, domainguid=domainguid,
+ ntdsguid=names.ntdsguid)
+
+ if dns_backend == "BIND9_DLZ" and os_level >= DS_DOMAIN_FUNCTION_2003:
+ create_samdb_copy(samdb, logger, paths,
+ names, names.domainsid, domainguid)
+
+ create_named_conf(paths, realm=names.realm,
+ dnsdomain=names.dnsdomain, dns_backend=dns_backend,
+ logger=logger)
+
+ create_named_txt(paths.namedtxt,
+ realm=names.realm, dnsdomain=names.dnsdomain,
+ dnsname="%s.%s" % (names.hostname, names.dnsdomain),
+ binddns_dir=paths.binddns_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)