import samba
from samba import version, Ldb, substitute_var, valid_netbios_name
from samba import check_all_substituted, read_and_sub_file, setup_file
-from samba.dsdb import DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008_R2
+from samba.dsdb import DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008_R2, ENC_ALL_TYPES
from samba.dcerpc import security
from samba.dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
from samba.idmap import IDmapDB
from samba.samdb import SamDB
__docformat__ = "restructuredText"
+DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
+DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9"
def find_setup_dir():
"""Find the setup directory used by provision."""
# This is stored without path prefix for the "privateKeytab" attribute in
# "secrets_dns.ldif".
paths.dns_keytab = "dns.keytab"
+ paths.keytab = "secrets.keytab"
paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
domain = domain.upper()
if lp.get("workgroup").upper() != domain:
- raise ProvisioningError("guess_names: Workgroup '%s' in %s must match chosen domain '%s'! Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile))
+ raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'! Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile))
if domaindn is None:
domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
else:
sid_generator_line = "sid generator = " + sid_generator
+ used_setup_dir = setup_path("")
+ default_setup_dir = default_lp.get("setup directory")
+ setupdir_line = ""
+ if used_setup_dir != default_setup_dir:
+ setupdir_line = "setup directory = %s" % used_setup_dir
+ default_lp.set("setup directory", used_setup_dir)
+
sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
netlogon = os.path.join(sysvol, realm.lower(), "scripts")
"SERVERROLE": serverrole,
"NETLOGONPATH": netlogon,
"SYSVOLPATH": sysvol,
+ "SETUPDIRECTORY_LINE": setupdir_line,
"SIDGENERATOR_LINE": sid_generator_line,
"PRIVATEDIR_LINE": privatedir_line,
"LOCKDIR_LINE": lockdir_line,
"priorChanged",
"krb5Keytab",
"privateKeytab"]
-
- msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
- msg["secureChannelType"] = str(secure_channel_type)
- msg["flatname"] = [domain]
- msg["objectClass"] = ["top", "primaryDomain"]
if realm is not None:
if dnsdomain is None:
- dnsdomain = realm.lower()
+ 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@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
+ msg["realm"] = [realm]
+ msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())]
msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
msg["privateKeytab"] = ["secrets.keytab"]
-
msg["secret"] = [machinepass]
msg["samAccountName"] = ["%s$" % netbiosname]
msg["secureChannelType"] = [str(secure_channel_type)]
if domainsid is not None:
msg["objectSid"] = [ndr_pack(domainsid)]
+ # This complex expression tries to ensure that we don't have more
+ # than one record for this SID, realm or netbios domain at a time,
+ # but we don't delete the old record that we are about to modify,
+ # because that would delete the keytab and previous password.
res = secretsdb.search(base="cn=Primary Domains",
attrs=attrs,
- expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))),
+ expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
scope=ldb.SCOPE_ONELEVEL)
for del_msg in res:
- if del_msg.dn is not msg.dn:
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"]
- msg["priorWhenChanged"] = res[0]["whenChanged"]
+ msg["priorSecret"] = [res[0]["secret"][0]]
+ msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
- if res["privateKeytab"] is not None:
- msg["privateKeytab"] = res[0]["privateKeytab"]
+ try:
+ msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
+ except KeyError:
+ pass
- if res["krb5Keytab"] is not None:
- msg["krb5Keytab"] = res[0]["krb5Keytab"]
+ try:
+ msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]]
+ except KeyError:
+ pass
for el in msg:
- el.set_flags(ldb.FLAG_MOD_REPLACE)
- secretsdb.modify(msg)
+ if el != 'dn':
+ msg[el].set_flags(ldb.FLAG_MOD_REPLACE)
+ secretsdb.modify(msg)
+ secretsdb.rename(res[0].dn, msg.dn)
else:
+ spn = [ 'HOST/%s' % shortname ]
+ if secure_channel_type == SEC_CHAN_BDC and dnsname is not None:
+ # we are a domain controller then we add servicePrincipalName entries
+ # for the keytab code to update
+ spn.extend([ 'HOST/%s' % dnsname ])
+ msg["servicePrincipalName"] = spn
+
secretsdb.add(msg)
-def secretsdb_setup_dns(secretsdb, setup_path, private_dir,
+def secretsdb_setup_dns(secretsdb, setup_path, names, private_dir,
realm, dnsdomain,
dns_keytab_path, dnspass):
"""Add DNS specific bits to a secrets database.
"DNSDOMAIN": dnsdomain,
"DNS_KEYTAB": dns_keytab_path,
"DNSPASS_B64": b64encode(dnspass),
+ "HOSTNAME": names.hostname,
+ "DNSNAME" : '%s.%s' % (names.netbiosname.lower(), names.dnsdomain.lower())
})
-def setup_secretsdb(path, setup_path, session_info, backend_credentials, lp):
+def setup_secretsdb(paths, setup_path, session_info, backend_credentials, lp):
"""Setup the secrets database.
:note: This function does not handle exceptions and transaction on purpose,
:param lp: Loadparm context
:return: LDB handle for the created secrets database
"""
- if os.path.exists(path):
- os.unlink(path)
+ if os.path.exists(paths.secrets):
+ os.unlink(paths.secrets)
+
+ keytab_path = os.path.join(paths.private_dir, paths.keytab)
+ if os.path.exists(keytab_path):
+ os.unlink(keytab_path)
+
+ dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
+ if os.path.exists(dns_keytab_path):
+ os.unlink(dns_keytab_path)
+
+ path = paths.secrets
+
secrets_ldb = Ldb(path, session_info=session_info,
lp=lp)
secrets_ldb.erase()
"""
setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
"SCHEMADN": names.schemadn,
- "NETBIOSNAME": names.netbiosname,
- "DNSDOMAIN": names.dnsdomain,
- "REALM": names.realm,
- "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
"DOMAINDN": names.domaindn,
"ROOTDN": names.rootdn,
"CONFIGDN": names.configdn,
"DOMAINDN": names.domaindn})
# add the NTDSGUID based SPNs
- ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration,%s" % (names.hostname, names.sitename, names.domaindn)
+ ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
expression="", scope=ldb.SCOPE_BASE)
assert isinstance(names.ntdsguid, str)
"SERVERDN": names.serverdn,
"NETBIOSNAME": names.netbiosname,
"NTDSGUID": names.ntdsguid,
- "DNSPASS_B64": b64encode(dnspass),
"RIDALLOCATIONSTART": str(next_rid + 100),
"RIDALLOCATIONEND": str(next_rid + 100 + 499),
})
+ # This is partially Samba4 specific and should be replaced by the correct
+ # DNS AD-style setup
+ setup_add_ldif(samdb, setup_path("provision_dns_add.ldif"), {
+ "DNSDOMAIN": names.dnsdomain,
+ "DOMAINDN": names.domaindn,
+ "DNSPASS_B64": b64encode(dnspass),
+ "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):
- os.makedirs(policy_path, 0755)
+ if not os.path.exists(policy_path):
+ os.makedirs(policy_path, 0775)
open(os.path.join(policy_path, "GPT.INI"), 'w').write(
- "[General]\r\nVersion=65543")
- os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
- os.makedirs(os.path.join(policy_path, "USER"), 0755)
-
+ "[General]\r\nVersion=0")
+ p = os.path.join(policy_path, "MACHINE")
+ if not os.path.exists(p):
+ os.makedirs(p, 0775)
+ p = os.path.join(policy_path, "USER")
+ if not os.path.exists(p):
+ os.makedirs(p, 0775)
+
+
+def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
+ """Create the default GPO for a domain
+
+ :param sysvolpath: Physical path for the sysvol folder
+ :param dnsdomain: DNS domain name of the AD domain
+ :param policyguid: GUID of the default domain policy
+ :param policyguid_dc: GUID of the default domain controler policy
+ """
-def setup_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
create_gpo_struct(policy_path)
# Load the schema from the one we computed earlier
samdb.set_schema(schema)
+ # Set the NTDS settings DN manually - in order to have it already around
+ # before the provisioned tree exists and we connect
+ samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
+
# And now we can connect to the DB - the schema won't be loaded from the DB
samdb.connect(path)
samdb.set_domain_sid(str(domainsid))
samdb.set_invocation_id(invocationid)
- samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
logger.info("Adding DomainDN: %s" % names.domaindn)
else:
samdb.transaction_commit()
- samdb = SamDB(session_info=admin_session_info,
+ samdb = SamDB(session_info=admin_session_info, auto_connect=False,
credentials=provision_backend.credentials, lp=lp,
global_schema=False, am_rodc=am_rodc)
+
+ # Set the NTDS settings DN manually - in order to have it already around
+ # before the provisioned tree exists and we connect
+ samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
+
samdb.connect(path)
+
samdb.transaction_start()
try:
samdb.invocation_id = invocationid
domainControllerFunctionality=domainControllerFunctionality,
ntdsguid=ntdsguid)
- ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration,%s" % (names.hostname, names.sitename, names.domaindn)
+ ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
names.ntdsguid = samdb.searchone(basedn=ntds_dn,
attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
assert isinstance(names.ntdsguid, str)
setntacl(lp, os.path.join(root, name), acl, domsid)
-def set_gpo_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
- # Set ACL for GPO
- policy_path = os.path.join(sysvol, dnsdomain, "Policies")
- set_dir_acl(policy_path,dsacl2fsacl(POLICIES_ACL, str(domainsid)),
- lp, str(domainsid))
+def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
+ """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")
+ setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid))
+
res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
attrs=["cn", "nTSecurityDescriptor"],
expression="", scope=ldb.SCOPE_ONELEVEL)
+
for policy in res:
acl = ndr_unpack(security.descriptor,
str(policy["nTSecurityDescriptor"])).as_sddl()
- policy_path = getpolicypath(sysvol,dnsdomain,str(policy["cn"]))
+ policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
str(domainsid))
def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
lp):
+ """Set the ACL for the sysvol share and the subfolders
+
+ :param samdb: An LDB object on the SAM db
+ :param netlogon: Physical path for the netlogon folder
+ :param sysvol: Physical path for the sysvol folder
+ :param gid: The GID of the "Domain adminstrators" group
+ :param domainsid: The SID of the domain
+ :param dnsdomain: The DNS name of the domain
+ :param domaindn: The DN of the domain (ie. DC=...)
+ """
+
try:
os.chown(sysvol,-1,gid)
except:
else:
canchown = True
- setntacl(lp,sysvol,SYSVOL_ACL,str(domainsid))
+ # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
+ setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid))
for root, dirs, files in os.walk(sysvol, topdown=False):
for name in files:
if canchown:
- os.chown(os.path.join(root, name),-1,gid)
- setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
+ os.chown(os.path.join(root, name), -1, gid)
+ setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
for name in dirs:
if canchown:
- os.chown(os.path.join(root, name),-1,gid)
- setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
- set_gpo_acl(sysvol,dnsdomain,domainsid,domaindn,samdb,lp)
+ os.chown(os.path.join(root, name), -1, gid)
+ setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
+
+ # Set acls on Policy folder and policies folders
+ set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
def provision(setup_dir, logger, session_info,
domainsid = security.dom_sid(domainsid)
# create/adapt the group policy GUIDs
+ # Default GUID for default policy are described at
+ # "How Core Group Policy Works"
+ # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx
if policyguid is None:
- policyguid = str(uuid.uuid4())
+ policyguid = DEFAULT_POLICY_GUID
policyguid = policyguid.upper()
if policyguid_dc is None:
- policyguid_dc = str(uuid.uuid4())
+ policyguid_dc = DEFAULT_DC_POLICY_GUID
policyguid_dc = policyguid_dc.upper()
if adminpass is None:
lp=lp)
share_ldb.load_ldif_file_add(setup_path("share.ldif"))
-
logger.info("Setting up secrets.ldb")
- secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
+ secrets_ldb = setup_secretsdb(paths, setup_path,
session_info=session_info,
backend_credentials=provision_backend.secrets_credentials, lp=lp)
if serverrole == "domain controller":
# Set up group policies (domain policy and domain controller policy)
- setup_gpo(paths.sysvol, names.dnsdomain, policyguid, policyguid_dc)
+ create_default_gpo(paths.sysvol, names.dnsdomain, policyguid, policyguid_dc)
setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid,
domainsid, names.dnsdomain, names.domaindn, lp)
machinepass=machinepass,
secure_channel_type=SEC_CHAN_BDC)
+ # Now set up the right msDS-SupportedEncryptionTypes into the DB
+ # In future, this might be determined from some configuration
+ kerberos_enctypes = str(ENC_ALL_TYPES)
+
+ try:
+ msg = ldb.Message(ldb.Dn(samdb, samdb.searchone("distinguishedName", expression="samAccountName=%s$" % names.netbiosname, scope=ldb.SCOPE_SUBTREE)))
+ msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(elements=kerberos_enctypes,
+ flags=ldb.FLAG_MOD_REPLACE,
+ name="msDS-SupportedEncryptionTypes")
+ samdb.modify(msg)
+ except ldb.LdbError, (ldb.ERR_NO_SUCH_ATTRIBUTE, _):
+ # It might be that this attribute does not exist in this schema
+ pass
+
+
if serverrole == "domain controller":
- secretsdb_setup_dns(secrets_ldb, setup_path,
+ secretsdb_setup_dns(secrets_ldb, setup_path, names,
paths.private_dir,
realm=names.realm, dnsdomain=names.dnsdomain,
dns_keytab_path=paths.dns_keytab,
logger.info("and %s for further documentation required for secure DNS "
"updates", paths.namedtxt)
- create_krb5_conf(paths.krb5conf, setup_path,
- dnsdomain=names.dnsdomain, hostname=names.hostname,
- realm=names.realm)
- logger.info("A Kerberos configuration suitable for Samba 4 has been "
- "generated at %s", paths.krb5conf)
-
lastProvisionUSNs = get_last_provision_usn(samdb)
maxUSN = get_max_usn(samdb, str(names.rootdn))
if lastProvisionUSNs is not None:
else:
set_provision_usn(samdb, 0, maxUSN)
+ create_krb5_conf(paths.krb5conf, setup_path,
+ dnsdomain=names.dnsdomain, hostname=names.hostname,
+ realm=names.realm)
+ logger.info("A Kerberos configuration suitable for Samba 4 has been "
+ "generated at %s", paths.krb5conf)
+
if serverrole == "domain controller":
create_dns_update_list(lp, logger, paths, setup_path)
provision_backend.post_setup()
provision_backend.shutdown()
-
+
create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
ldapi_url)
except:
os.chmod(dns_keytab_path, 0640)
os.chown(dns_keytab_path, -1, paths.bind_gid)
except OSError:
- logger.info("Failed to chown %s to bind gid %u", dns_keytab_path,
- paths.bind_gid)
+ if not os.environ.has_key('SAMBA_SELFTEST'):
+ logger.info("Failed to chown %s to bind gid %u", dns_keytab_path,
+ paths.bind_gid)
logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php",
logger = logging.getLogger("provision")
samba.set_debug_level(debuglevel)
- return provision(setup_dir, logger, system_session(), None,
- smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
- realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
- configdn=configdn, serverdn=serverdn, domain=domain,
- hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
- machinepass=machinepass, serverrole="domain controller",
- sitename=sitename)
+ res = provision(setup_dir, logger, system_session(), None,
+ smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
+ realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
+ configdn=configdn, serverdn=serverdn, domain=domain,
+ hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
+ machinepass=machinepass, serverrole="domain controller",
+ sitename=sitename)
+ res.lp.set("debuglevel", str(debuglevel))
+ return res
def create_phpldapadmin_config(path, setup_path, ldapi_uri):
os.chmod(dns_dir, 0775)
os.chmod(paths.dns, 0664)
except OSError:
- logger.error("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
+ if not os.environ.has_key('SAMBA_SELFTEST'):
+ logger.error("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
if targetdir is None:
os.system(rndc + " unfreeze " + lp.get("realm"))