from samba.net import Net
from samba.provision.sambadns import setup_bind9_dns
from samba import read_and_sub_file
+from samba import werror
from base64 import b64encode
import logging
import talloc
import random
import time
-# this makes debugging easier
-talloc.enable_null_tracking()
-
class DCJoinException(Exception):
def __init__(self, msg):
if machinepass is not None:
ctx.acct_pass = machinepass
else:
- ctx.acct_pass = samba.generate_random_password(32, 40)
+ ctx.acct_pass = samba.generate_random_machine_password(128, 255)
ctx.dnsdomain = ctx.samdb.domain_dns_name()
if clone_only:
ctx.acct_dn = None
ctx.myname = ctx.server.split('.')[0]
ctx.ntds_guid = None
+ ctx.rid_manager_dn = None
+
+ # Save this early
+ ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
else:
# work out the DNs of all the objects we will be adding
ctx.myname = netbios_name
"HOST/%s" % ctx.dnshostname,
"GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
+ res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
+ attrs=["rIDManagerReference"],
+ base=ctx.base_dn)
+
+ ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
+
ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
+ expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
attrs=[],
base=ctx.samdb.get_partitions_dn(),
- expression="(&(objectClass=crossRef)(ncName=%s))" % ctx.domaindns_zone)
+ expression=expr)
if dns_backend is None:
ctx.dns_backend = "NONE"
else:
ctx.tmp_samdb = None
+ ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
+ drsuapi.DRSUAPI_DRS_PER_SYNC |
+ drsuapi.DRSUAPI_DRS_GET_ANC |
+ drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
+ drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
+
# these elements are optional
ctx.never_reveal_sid = None
ctx.reveal_sid = None
ctx.managedby = None
ctx.subdomain = False
ctx.adminpass = None
+ ctx.partition_dn = None
def del_noerror(ctx, dn, recursive=False):
if recursive:
except Exception:
pass
+ def cleanup_old_accounts(ctx):
+ res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
+ expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
+ attrs=["msDS-krbTgtLink", "objectSID"])
+ if len(res) == 0:
+ return
+
+ creds = Credentials()
+ creds.guess(ctx.lp)
+ try:
+ creds.set_machine_account(ctx.lp)
+ machine_samdb = SamDB(url="ldap://%s" % ctx.server,
+ session_info=system_session(),
+ credentials=creds, lp=ctx.lp)
+ except:
+ pass
+ else:
+ token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
+ if token_res[0]["tokenGroups"][0] \
+ == res[0]["objectSID"][0]:
+ raise DCJoinException("Not removing account %s which "
+ "looks like a Samba DC account "
+ "maching the password we already have. "
+ "To override, remove secrets.ldb and secrets.tdb"
+ % ctx.samname)
+
+ ctx.del_noerror(res[0].dn, recursive=True)
+
+ if "msDS-Krbtgtlink" in res[0]:
+ new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
+ del_noerror(ctx.new_krbtgt_dn)
+
+ res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
+ expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
+ (ldb.binary_encode("dns-%s" % ctx.myname),
+ ldb.binary_encode("dns/%s" % ctx.dnshostname)),
+ attrs=[])
+ if res:
+ ctx.del_noerror(res[0].dn, recursive=True)
+
+ res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
+ expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
+ attrs=[])
+ if res:
+ raise DCJoinException("Not removing account %s which looks like "
+ "a Samba DNS service account but does not "
+ "have servicePrincipalName=%s" %
+ (ldb.binary_encode("dns-%s" % ctx.myname),
+ ldb.binary_encode("dns/%s" % ctx.dnshostname)))
+
+
def cleanup_old_join(ctx):
"""Remove any DNs from a previous join."""
- try:
- # find the krbtgt link
- print("checking sAMAccountName")
- if ctx.subdomain:
- res = None
- else:
- res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
- expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
- attrs=["msDS-krbTgtLink"])
- if res:
- ctx.del_noerror(res[0].dn, recursive=True)
-
- res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
- expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' % (ldb.binary_encode("dns-%s" % ctx.myname), ldb.binary_encode("dns/%s" % ctx.dnshostname)),
- attrs=[])
- if res:
- ctx.del_noerror(res[0].dn, recursive=True)
-
- res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
- expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
- attrs=[])
- if res:
- raise RuntimeError("Not removing account %s which looks like a Samba DNS service account but does not have servicePrincipalName=%s" % (ldb.binary_encode("dns-%s" % ctx.myname), ldb.binary_encode("dns/%s" % ctx.dnshostname)))
-
- if ctx.connection_dn is not None:
- ctx.del_noerror(ctx.connection_dn)
- if ctx.krbtgt_dn is not None:
- ctx.del_noerror(ctx.krbtgt_dn)
- ctx.del_noerror(ctx.ntds_dn)
- ctx.del_noerror(ctx.server_dn, recursive=True)
- if ctx.topology_dn:
- ctx.del_noerror(ctx.topology_dn)
- if ctx.partition_dn:
- ctx.del_noerror(ctx.partition_dn)
- if res:
- ctx.new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
- ctx.del_noerror(ctx.new_krbtgt_dn)
-
- if ctx.subdomain:
- binding_options = "sign"
- lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
- ctx.lp, ctx.creds)
-
- objectAttr = lsa.ObjectAttribute()
- objectAttr.sec_qos = lsa.QosInfo()
-
- pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
- objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
-
- name = lsa.String()
- name.string = ctx.realm
- info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
-
- lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
-
- name = lsa.String()
- name.string = ctx.forest_domain_name
- info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
-
- lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
+ # find the krbtgt link
+ if not ctx.subdomain:
+ ctx.cleanup_old_accounts()
+
+ if ctx.connection_dn is not None:
+ ctx.del_noerror(ctx.connection_dn)
+ if ctx.krbtgt_dn is not None:
+ ctx.del_noerror(ctx.krbtgt_dn)
+ ctx.del_noerror(ctx.ntds_dn)
+ ctx.del_noerror(ctx.server_dn, recursive=True)
+ if ctx.topology_dn:
+ ctx.del_noerror(ctx.topology_dn)
+ if ctx.partition_dn:
+ ctx.del_noerror(ctx.partition_dn)
+
+ if ctx.subdomain:
+ binding_options = "sign"
+ lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
+ ctx.lp, ctx.creds)
+
+ objectAttr = lsa.ObjectAttribute()
+ objectAttr.sec_qos = lsa.QosInfo()
+
+ pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
+ objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
+
+ name = lsa.String()
+ name.string = ctx.realm
+ info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
+
+ lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
+
+ name = lsa.String()
+ name.string = ctx.forest_domain_name
+ info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
+
+ lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
- except Exception:
- pass
def promote_possible(ctx):
"""confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
'''get netbios name of the domain from the partitions record'''
partitions_dn = ctx.samdb.get_partitions_dn()
res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
- expression='ncName=%s' % ctx.samdb.get_default_basedn())
+ expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
return res[0]["nETBIOSName"][0]
def get_forest_domain_name(ctx):
'''get netbios name of the domain from the partitions record'''
partitions_dn = ctx.samdb.get_partitions_dn()
res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
- expression='ncName=%s' % ctx.samdb.get_root_basedn())
+ expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
return res[0]["nETBIOSName"][0]
def get_parent_partition_dn(ctx):
'''get the parent domain partition DN from parent DNS name'''
res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
- (ctx.parent_dnsdomain, ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
+ (ldb.binary_encode(ctx.parent_dnsdomain),
+ ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
return str(res[0].dn)
def get_naming_master(ctx):
def drsuapi_connect(ctx):
'''make a DRSUAPI connection to the naming master'''
binding_options = "seal"
- if int(ctx.lp.get("log level")) >= 4:
+ if ctx.lp.log_level() >= 4:
binding_options += ",print"
binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
raise RuntimeError("DsAddEntry failed")
- if ctr.extended_err != (0, 'WERR_OK'):
+ if ctr.extended_err[0] != werror.WERR_SUCCESS:
print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
raise RuntimeError("DsAddEntry failed")
if level == 3:
if ctr.err_ver != 1:
raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
- if ctr.err_data.status != (0, 'WERR_OK'):
- print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status,
- ctr.err_data.info.extended_err))
+ if ctr.err_data.status[0] != werror.WERR_SUCCESS:
+ if ctr.err_data.info is None:
+ print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
+ else:
+ print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
+ ctr.err_data.info.extended_err))
raise RuntimeError("DsAddEntry failed")
if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
if ctx.ntds_dn:
ctx.join_add_ntdsdsa()
+ # Add the Replica-Locations or RO-Replica-Locations attributes
+ # TODO Is this supposed to be for the schema partition too?
+ expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
+ domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
+ attrs=[],
+ base=ctx.samdb.get_partitions_dn(),
+ expression=expr), ctx.domaindns_zone)
+
+ expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
+ forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
+ attrs=[],
+ base=ctx.samdb.get_partitions_dn(),
+ expression=expr), ctx.forestdns_zone)
+
+ for part, zone in (domain, forest):
+ if zone not in ctx.nc_list:
+ continue
+
+ if len(part) == 1:
+ m = ldb.Message()
+ m.dn = part[0].dn
+ attr = "msDS-NC-Replica-Locations"
+ if ctx.RODC:
+ attr = "msDS-NC-RO-Replica-Locations"
+
+ m[attr] = ldb.MessageElement(ctx.ntds_dn,
+ ldb.FLAG_MOD_ADD, attr)
+ ctx.samdb.modify(m)
+
if ctx.connection_dn is not None:
print "Adding %s" % ctx.connection_dn
rec = {
pass
ctx.net.set_password(account_name=ctx.samname,
domain_name=ctx.domain_name,
- newpassword=ctx.acct_pass)
+ newpassword=ctx.acct_pass.encode('utf-8'))
res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
attrs=["msDS-KeyVersionNumber"])
{"DNSDOMAIN": ctx.dnsdomain,
"DOMAINDN": ctx.base_dn,
"HOSTNAME" : ctx.myname,
- "DNSPASS_B64": b64encode(ctx.dnspass),
+ "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')),
"DNSNAME" : ctx.dnshostname}))
for changetype, msg in recs:
assert changetype == ldb.CHANGETYPE_NONE
repl_creds.guess(ctx.lp)
repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
repl_creds.set_username(ctx.samname)
- repl_creds.set_password(ctx.acct_pass)
+ repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
else:
repl_creds = ctx.creds
binding_options = "seal"
- if int(ctx.lp.get("log level")) >= 5:
+ if ctx.lp.log_level() >= 5:
binding_options += ",print"
repl = drs_utils.drs_Replicate(
"ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
# Replicate first the critical object for the basedn
if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
print "Replicating critical objects from the base DN of the domain"
- ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi.DRSUAPI_DRS_GET_ANC
+ ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
repl.replicate(ctx.base_dn, source_dsa_invocation_id,
destination_dsa_guid, rodc=ctx.RODC,
replica_flags=ctx.domain_replica_flags)
- ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi.DRSUAPI_DRS_GET_ANC
- else:
- ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_GET_ANC
+ ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
repl.replicate(ctx.base_dn, source_dsa_invocation_id,
destination_dsa_guid, rodc=ctx.RODC,
replica_flags=ctx.domain_replica_flags)
print "Done with always replicated NC (base, config, schema)"
+ # At this point we should already have 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.
for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
if nc in ctx.nc_list:
print "Replicating %s" % (str(nc))
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
-
if ctx.RODC:
repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
destination_dsa_guid,
repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
destination_dsa_guid,
exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
+ elif ctx.rid_manager_dn != None:
+ # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
+ try:
+ repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
+ destination_dsa_guid,
+ exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
+ except samba.DsExtendedError, (enum, estr):
+ if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
+ print "WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server
+ print "NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup."
+ else:
+ raise
+
ctx.repl = repl
ctx.source_dsa_invocation_id = source_dsa_invocation_id
ctx.destination_dsa_guid = destination_dsa_guid
# We want to appear to be the server we just cloned
if ctx.clone_only:
- guid = ctx.samdb.get_ntds_GUID()
+ guid = ctx.remote_dc_ntds_guid
else:
guid = ctx.ntds_guid
ctx.join_setup_trusts()
ctx.join_finalise()
except:
- print "Join failed - cleaning up"
+ try:
+ print "Join failed - cleaning up"
+ except IOError:
+ pass
if not ctx.clone_only:
ctx.cleanup_old_join()
raise
ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
ctx.secure_channel_type = misc.SEC_CHAN_RODC
ctx.RODC = True
- ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
- drsuapi.DRSUAPI_DRS_PER_SYNC |
- drsuapi.DRSUAPI_DRS_GET_ANC |
- drsuapi.DRSUAPI_DRS_NEVER_SYNCED |
- drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
+ ctx.replica_flags |= ( drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
ctx.domain_replica_flags = ctx.replica_flags
if domain_critical_only:
ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
ctx.secure_channel_type = misc.SEC_CHAN_BDC
- ctx.replica_flags = (drsuapi.DRSUAPI_DRS_WRIT_REP |
- drsuapi.DRSUAPI_DRS_INIT_SYNC |
- drsuapi.DRSUAPI_DRS_PER_SYNC |
- drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS |
- drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
+ ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
+ drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
ctx.domain_replica_flags = ctx.replica_flags
if domain_critical_only:
ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
lp.set("realm", ctx.realm)
logger.info("realm is %s" % ctx.realm)
- ctx.replica_flags = (drsuapi.DRSUAPI_DRS_WRIT_REP |
- drsuapi.DRSUAPI_DRS_INIT_SYNC |
- drsuapi.DRSUAPI_DRS_PER_SYNC |
- drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS |
- drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
+ ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
+ drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
if not include_secrets:
ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
ctx.domain_replica_flags = ctx.replica_flags
ctx.domsid = security.random_sid()
ctx.acct_dn = None
ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
- ctx.trustdom_pass = samba.generate_random_password(128, 128)
+ # Windows uses 240 bytes as UTF16 so we do
+ ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
ctx.secure_channel_type = misc.SEC_CHAN_BDC
- ctx.replica_flags = (drsuapi.DRSUAPI_DRS_WRIT_REP |
- drsuapi.DRSUAPI_DRS_INIT_SYNC |
- drsuapi.DRSUAPI_DRS_PER_SYNC |
- drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS |
- drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
+ ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
+ drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
ctx.domain_replica_flags = ctx.replica_flags
ctx.do_join()