import ldb, samba, sys, uuid
from samba.ndr import ndr_pack
from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs
+from samba.dsdb import DS_DOMAIN_FUNCTION_2003
from samba.credentials import Credentials, DONT_USE_KERBEROS
from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN
from samba.provision.common import setup_path
from samba.schema import Schema
+from samba import descriptor
from samba.net import Net
from samba.provision.sambadns import setup_bind9_dns
from samba import read_and_sub_file
class dc_join(object):
"""Perform a DC join."""
- def __init__(ctx, server=None, creds=None, lp=None, site=None,
+ def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
netbios_name=None, targetdir=None, domain=None,
machinepass=None, use_ntvfs=False, dns_backend=None,
promote_existing=False):
+ ctx.logger = logger
ctx.creds = creds
ctx.lp = lp
ctx.site = site
if server is not None:
ctx.server = server
else:
- print("Finding a writeable DC for domain '%s'" % domain)
+ ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
ctx.server = ctx.find_dc(domain)
- print("Found DC %s" % ctx.server)
+ ctx.logger.info("Found DC %s" % ctx.server)
ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
session_info=system_session(),
ctx.root_dn = str(ctx.samdb.get_root_basedn())
ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
ctx.config_dn = str(ctx.samdb.get_config_basedn())
- ctx.domsid = ctx.samdb.get_domain_sid()
+ ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
+ ctx.forestsid = ctx.domsid
ctx.domain_name = ctx.get_domain_name()
ctx.forest_domain_name = ctx.get_forest_domain_name()
ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
ctx.dnsdomain = ctx.samdb.domain_dns_name()
ctx.dnsforest = ctx.samdb.forest_dns_name()
ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
- ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.base_dn
+ ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
attrs=[],
else:
ctx.dns_backend = dns_backend
- ctx.dnshostname = "%s.%s" % (ctx.myname, ctx.dnsdomain)
+ ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
ctx.realm = ctx.dnsdomain
def create_tmp_samdb(ctx):
'''create a temporary samdb object for schema queries'''
- ctx.tmp_schema = Schema(security.dom_sid(ctx.domsid),
+ ctx.tmp_schema = Schema(ctx.domsid,
schemadn=ctx.schema_dn)
ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
credentials=ctx.creds, lp=ctx.lp, global_schema=False,
return ctr.objects
- def join_add_ntdsdsa(ctx):
- '''add the ntdsdsa object'''
+ def join_ntdsdsa_obj(ctx):
+ '''return the ntdsdsa object to add'''
print "Adding %s" % ctx.ntds_dn
rec = {
if ctx.RODC:
rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
- rec["msDS-HasFullReplicaNCs"] = ctx.nc_list
+ rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
rec["options"] = "37"
- ctx.samdb.add(rec, ["rodc_join:1:1"])
else:
rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
- rec["HasMasterNCs"] = nc_list
+ rec["HasMasterNCs"] = []
+ for nc in nc_list:
+ if nc in ctx.full_nc_list:
+ rec["HasMasterNCs"].append(nc)
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
- rec["msDS-HasMasterNCs"] = ctx.nc_list
+ rec["msDS-HasMasterNCs"] = ctx.full_nc_list
rec["options"] = "1"
rec["invocationId"] = ndr_pack(ctx.invocation_id)
+
+ return rec
+
+ def join_add_ntdsdsa(ctx):
+ '''add the ntdsdsa object'''
+
+ rec = ctx.join_ntdsdsa_obj()
+ if ctx.RODC:
+ ctx.samdb.add(rec, ["rodc_join:1:1"])
+ else:
ctx.DsAddEntry([rec])
# find the GUID of our NTDS DN
"DNSNAME" : ctx.dnshostname}))
for changetype, msg in recs:
assert changetype == ldb.CHANGETYPE_NONE
+ dns_acct_dn = msg["dn"]
print "Adding DNS account %s with dns/ SPN" % msg["dn"]
# Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
del msg["clearTextPassword"]
# Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
del msg["isCriticalSystemObject"]
+ # Disable account until password is set
+ msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
+ samba.dsdb.UF_ACCOUNTDISABLE)
try:
ctx.samdb.add(msg)
- dns_acct_dn = msg["dn"]
except ldb.LdbError, (num, _):
if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
raise
# connections which are hard to set up and otherwise refuse with
# ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
# over SAMR.
- print "Setting account password for %s" % ctx.samname
+ print "Setting account password for dns-%s" % ctx.myname
try:
ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
% ldb.binary_encode(ctx.myname),
username=ctx.samname)
except ldb.LdbError, (num, _):
if num != ldb.ERR_UNWILLING_TO_PERFORM:
- pass
- ctx.net.set_password(account_name="dns-" % ctx.myname,
+ raise
+ ctx.net.set_password(account_name="dns-%s" % ctx.myname,
domain_name=ctx.domain_name,
newpassword=ctx.dnspass)
"""add the various objects needed for the join, for subdomains post replication"""
print "Adding %s" % ctx.partition_dn
- # NOTE: windows sends a ntSecurityDescriptor here, we
- # let it default
+ name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
+ sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
rec = {
"dn" : ctx.partition_dn,
"objectclass" : "crossRef",
"nETBIOSName" : ctx.domain_name,
"dnsRoot": ctx.dnsdomain,
"trustParent" : ctx.parent_partition_dn,
- "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN)}
- if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
- rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
-
- rec2 = {
- "dn" : ctx.ntds_dn,
- "objectclass" : "nTDSDSA",
- "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
- "dMDLocation" : ctx.schema_dn}
-
- nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
+ "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
+ "ntSecurityDescriptor" : sd_binary,
+ }
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
- rec2["msDS-Behavior-Version"] = str(ctx.behavior_version)
-
- if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
- rec2["msDS-HasDomainNCs"] = ctx.base_dn
+ rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
- rec2["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
- rec2["HasMasterNCs"] = nc_list
- if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
- rec2["msDS-HasMasterNCs"] = ctx.nc_list
- rec2["options"] = "1"
- rec2["invocationId"] = ndr_pack(ctx.invocation_id)
+ rec2 = ctx.join_ntdsdsa_obj()
objects = ctx.DsAddEntry([rec, rec2])
if len(objects) != 2:
print "Calling bare provision"
- logger = logging.getLogger("provision")
- logger.addHandler(logging.StreamHandler(sys.stdout))
smbconf = ctx.lp.configfile
- presult = provision(logger, system_session(), None, smbconf=smbconf,
+ presult = provision(ctx.logger, system_session(), smbconf=smbconf,
targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
rootdn=ctx.root_dn, domaindn=ctx.base_dn,
schemadn=ctx.schema_dn, configdn=ctx.config_dn,
serverdn=ctx.server_dn, domain=ctx.domain_name,
hostname=ctx.myname, domainsid=ctx.domsid,
- machinepass=ctx.acct_pass, serverrole="domain controller",
+ machinepass=ctx.acct_pass, serverrole="active directory domain controller",
sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend)
print "Provision OK for domain DN %s" % presult.domaindn
ctx.paths = presult.paths
ctx.names = presult.names
+ # Fix up the forestsid, it may be different if we are joining as a subdomain
+ ctx.names.forestsid = ctx.forestsid
+
def join_provision_own_domain(ctx):
"""Provision the local SAM."""
ctx.samdb.set_invocation_id(str(ctx.invocation_id))
ctx.local_samdb = ctx.samdb
- print("Finding domain GUID from ncName")
+ ctx.logger.info("Finding domain GUID from ncName")
res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
controls=["extended_dn:1:1", "reveal_internals:0"])
raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
try:
- domguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID')))
+ ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID')))
except KeyError:
raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
- print("Got domain GUID %s" % domguid)
+ ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
- print("Calling own domain provision")
-
- logger = logging.getLogger("provision")
- logger.addHandler(logging.StreamHandler(sys.stdout))
+ ctx.logger.info("Calling own domain provision")
secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
presult = provision_fill(ctx.local_samdb, secrets_ldb,
- logger, ctx.names, ctx.paths, domainsid=security.dom_sid(ctx.domsid),
- domainguid=domguid,
+ ctx.logger, ctx.names, ctx.paths,
+ dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
- machinepass=ctx.acct_pass, serverrole="domain controller",
+ machinepass=ctx.acct_pass, serverrole="active directory domain controller",
lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
- dns_backend=ctx.dns_backend)
+ dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
print("Provision OK for domain %s" % ctx.names.dnsdomain)
def join_replicate(ctx):
binding_options += ",print"
repl = drs_utils.drs_Replicate(
"ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
- ctx.lp, repl_creds, ctx.local_samdb)
+ ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
destination_dsa_guid, schema=True, rodc=ctx.RODC,
destination_dsa_guid, rodc=ctx.RODC,
replica_flags=ctx.replica_flags)
- if 'DC=ForestDnsZones,%s' % ctx.root_dn in ctx.nc_list:
- repl.replicate('DC=ForestDnsZones,%s' % ctx.root_dn, source_dsa_invocation_id,
- destination_dsa_guid, rodc=ctx.RODC,
- replica_flags=ctx.replica_flags)
# FIXME At this point we should add an entry in the forestdns and domaindns NC
# (those under CN=Partions,DC=...)
# in order to indicate that we hold a replica for this NC
def join_finalise(ctx):
"""Finalise the join, mark us synchronised and setup secrets db."""
- logger = logging.getLogger("provision")
- logger.addHandler(logging.StreamHandler(sys.stdout))
-
# FIXME we shouldn't do this in all cases
# If for some reasons we joined in another site than the one of
# DC we just replicated from then we don't need to send the updatereplicateref
# as replication between sites is time based and on the initiative of the
# requesting DC
- print "Sending DsReplicateUpdateRefs for all the replicated partitions"
- for nc in ctx.full_nc_list:
+ ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
+ for nc in ctx.nc_list:
ctx.send_DsReplicaUpdateRefs(nc)
if ctx.RODC:
"invocationId",
0)
- print "Setting isSynchronized and dsServiceName"
+ ctx.logger.info("Setting isSynchronized and dsServiceName")
m = ldb.Message()
m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
- print "Setting up secrets database"
+ ctx.logger.info("Setting up secrets database")
secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
realm=ctx.realm,
dnsdomain=ctx.dnsdomain,
netbiosname=ctx.myname,
- domainsid=security.dom_sid(ctx.domsid),
+ domainsid=ctx.domsid,
machinepass=ctx.acct_pass,
secure_channel_type=ctx.secure_channel_type,
key_version_number=ctx.key_version_number)
if ctx.dns_backend.startswith("BIND9_"):
- setup_bind9_dns(ctx.local_samdb, secrets_ldb, security.dom_sid(ctx.domsid),
- ctx.names, ctx.paths, ctx.lp, logger,
+ setup_bind9_dns(ctx.local_samdb, secrets_ldb,
+ ctx.names, ctx.paths, ctx.lp, ctx.logger,
dns_backend=ctx.dns_backend,
dnspass=ctx.dnspass, os_level=ctx.behavior_version,
targetdir=ctx.targetdir,
info = lsa.TrustDomainInfoInfoEx()
info.domain_name.string = ctx.dnsdomain
info.netbios_name.string = ctx.domain_name
- info.sid = security.dom_sid(ctx.domsid)
+ info.sid = ctx.domsid
info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
"flatname" : ctx.forest_domain_name,
"trustPartner" : ctx.dnsforest,
"trustAuthIncoming" : ndr_pack(outgoing),
- "trustAuthOutgoing" : ndr_pack(outgoing)
+ "trustAuthOutgoing" : ndr_pack(outgoing),
+ "securityIdentifier" : ndr_pack(ctx.forestsid)
}
ctx.local_samdb.add(rec)
"dn" : "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
"objectclass" : "user",
"userAccountControl" : str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
- "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le')
+ "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le'),
+ "samAccountName" : "%s$" % ctx.forest_domain_name
}
ctx.local_samdb.add(rec)
def do_join(ctx):
- # full_nc_list is the list of naming context (NC) for which we will
- # send a updateRef command to the partner DC
+ # nc_list is the list of naming context (NC) for which we will
+ # replicate in and send a updateRef command to the partner DC
+
+ # full_nc_list is the list of naming context (NC) we hold
+ # read/write copies of. These are not subsets of each other.
ctx.nc_list = [ ctx.config_dn, ctx.schema_dn ]
- ctx.full_nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
+ ctx.full_nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
- if not ctx.subdomain:
+ if ctx.subdomain and ctx.dns_backend != "NONE":
+ ctx.full_nc_list += [ctx.domaindns_zone]
+
+ elif not ctx.subdomain:
ctx.nc_list += [ctx.base_dn]
+
if ctx.dns_backend != "NONE":
ctx.nc_list += [ctx.domaindns_zone]
-
- if ctx.dns_backend != "NONE":
- if not ctx.subdomain:
- ctx.full_nc_list += ['DC=DomainDnsZones,%s' % ctx.base_dn]
- ctx.full_nc_list += ['DC=ForestDnsZones,%s' % ctx.root_dn]
- ctx.nc_list += ['DC=ForestDnsZones,%s' % ctx.root_dn]
+ ctx.nc_list += [ctx.forestdns_zone]
+ ctx.full_nc_list += [ctx.domaindns_zone]
+ ctx.full_nc_list += [ctx.forestdns_zone]
if ctx.promote_existing:
ctx.promote_possible()
raise
-def join_RODC(server=None, creds=None, lp=None, site=None, netbios_name=None,
+def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
targetdir=None, domain=None, domain_critical_only=False,
machinepass=None, use_ntvfs=False, dns_backend=None,
promote_existing=False):
"""Join as a RODC."""
- ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, domain,
+ ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
machinepass, use_ntvfs, dns_backend, promote_existing)
lp.set("workgroup", ctx.domain_name)
- print("workgroup is %s" % ctx.domain_name)
+ logger.info("workgroup is %s" % ctx.domain_name)
lp.set("realm", ctx.realm)
- print("realm is %s" % ctx.realm)
+ logger.info("realm is %s" % ctx.realm)
ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
ctx.do_join()
- print "Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid)
+ logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
-def join_DC(server=None, creds=None, lp=None, site=None, netbios_name=None,
+def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
targetdir=None, domain=None, domain_critical_only=False,
machinepass=None, use_ntvfs=False, dns_backend=None,
promote_existing=False):
"""Join as a DC."""
- ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, domain,
+ ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
machinepass, use_ntvfs, dns_backend, promote_existing)
lp.set("workgroup", ctx.domain_name)
- print("workgroup is %s" % ctx.domain_name)
+ logger.info("workgroup is %s" % ctx.domain_name)
lp.set("realm", ctx.realm)
- print("realm is %s" % ctx.realm)
+ logger.info("realm is %s" % ctx.realm)
ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
ctx.do_join()
- print "Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid)
+ logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
-def join_subdomain(server=None, creds=None, lp=None, site=None,
+def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
dns_backend=None):
"""Join as a DC."""
- ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, parent_domain,
+ ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, parent_domain,
machinepass, use_ntvfs, dns_backend)
ctx.subdomain = True
if adminpass is None:
ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
ctx.naming_master = ctx.get_naming_master()
if ctx.naming_master != ctx.server:
- print("Reconnecting to naming master %s" % ctx.naming_master)
+ logger.info("Reconnecting to naming master %s" % ctx.naming_master)
ctx.server = ctx.naming_master
ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
session_info=system_session(),
credentials=ctx.creds, lp=ctx.lp)
+ res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
+ controls=[])
+ ctx.server = res[0]["dnsHostName"]
+ logger.info("DNS name of new naming master is %s" % ctx.server)
ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
- ctx.domsid = str(security.random_sid())
+ ctx.forestsid = ctx.domsid
+ ctx.domsid = security.random_sid()
ctx.acct_dn = None
- ctx.dnshostname = "%s.%s" % (ctx.myname, ctx.dnsdomain)
+ ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
ctx.trustdom_pass = samba.generate_random_password(128, 128)
ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
ctx.domain_replica_flags = ctx.replica_flags
ctx.do_join()
- print "Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid)
+ ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))