X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=python%2Fsamba%2Fjoin.py;h=fa87f0bb32f316d55f773f6e20faebe099fd683d;hb=75eb2e3a09ed7ab5beac4593d93e6ea0e506f857;hp=cdfe45207cf811353e5f71ce1dcfd24017827e8b;hpb=d1d5ec311a6dd2fabe92b7dd89c156a4288490e4;p=samba.git diff --git a/python/samba/join.py b/python/samba/join.py index cdfe45207cf..fa87f0bb32f 100644 --- a/python/samba/join.py +++ b/python/samba/join.py @@ -20,10 +20,10 @@ from samba.auth import system_session from samba.samdb import SamDB -from samba import gensec, Ldb, drs_utils +from samba import gensec, Ldb, drs_utils, arcfour_encrypt, string_to_byte_array import ldb, samba, sys, uuid -from samba.ndr import ndr_pack -from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs +from samba.ndr import ndr_pack, ndr_unpack +from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp 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 @@ -33,15 +33,16 @@ from samba import descriptor 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 +from samba import WERRORError +from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord +from samba import sd_utils import logging import talloc import random import time -# this makes debugging easier -talloc.enable_null_tracking() - class DCJoinException(Exception): def __init__(self, msg): @@ -55,6 +56,9 @@ class dc_join(object): netbios_name=None, targetdir=None, domain=None, machinepass=None, use_ntvfs=False, dns_backend=None, promote_existing=False, clone_only=False): + if site is None: + site = "Default-First-Site-Name" + ctx.clone_only=clone_only ctx.logger = logger @@ -107,7 +111,7 @@ class dc_join(object): 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: @@ -117,6 +121,10 @@ class dc_join(object): 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 @@ -137,13 +145,20 @@ class dc_join(object): "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: @@ -157,6 +172,12 @@ class dc_join(object): 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 @@ -167,6 +188,13 @@ class dc_join(object): ctx.managedby = None ctx.subdomain = False ctx.adminpass = None + ctx.partition_dn = None + + ctx.dns_a_dn = None + ctx.dns_cname_dn = None + + # Do not normally register 127. addresses but allow override for selftest + ctx.force_all_ips = False def del_noerror(ctx, dn, recursive=False): if recursive: @@ -182,71 +210,106 @@ class dc_join(object): except Exception: pass - 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 + def cleanup_old_accounts(ctx, force=False): + 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 + + if not force: + creds = Credentials() + creds.guess(ctx.lp) + try: + creds.set_machine_account(ctx.lp) + creds.set_kerberos_state(ctx.creds.get_kerberos_state()) + machine_samdb = SamDB(url="ldap://%s" % ctx.server, + session_info=system_session(), + credentials=creds, lp=ctx.lp) + except: + pass 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) + 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] + ctx.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, force=False): + """Remove any DNs from a previous join.""" + # find the krbtgt link + if not ctx.subdomain: + ctx.cleanup_old_accounts(force=force) + + 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) + + if ctx.dns_a_dn: + ctx.del_noerror(ctx.dns_a_dn) + + if ctx.dns_cname_dn: + ctx.del_noerror(ctx.dns_cname_dn) + - 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""" @@ -293,21 +356,22 @@ class dc_join(object): '''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): @@ -373,7 +437,7 @@ class dc_join(object): 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) @@ -442,15 +506,18 @@ class dc_join(object): 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) @@ -571,6 +638,35 @@ class dc_join(object): 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 = { @@ -609,15 +705,19 @@ class dc_join(object): 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"]) + attrs=["msDS-KeyVersionNumber", + "objectSID"]) if "msDS-KeyVersionNumber" in res[0]: ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0]) else: ctx.key_version_number = None + ctx.new_dc_account_sid = ndr_unpack(security.dom_sid, + res[0]["objectSid"][0]) + print("Enabling account") m = ldb.Message() m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn) @@ -633,7 +733,7 @@ class dc_join(object): {"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 @@ -805,12 +905,12 @@ class dc_join(object): 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), @@ -826,18 +926,19 @@ class dc_join(object): # 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)) @@ -845,10 +946,6 @@ class dc_join(object): 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, @@ -856,6 +953,19 @@ class dc_join(object): 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 @@ -879,8 +989,179 @@ class dc_join(object): if not ctx.RODC: r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP - if ctx.drsuapi: - ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r) + if ctx.drsuapi is None: + ctx.drsuapi_connect() + + ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r) + + def join_add_dns_records(ctx): + """Remotely Add a DNS record to the target DC. We assume that if we + replicate DNS that the server holds the DNS roles and can accept + updates. + + This avoids issues getting replication going after the DC + first starts as the rest of the domain does not have to + wait for samba_dnsupdate to run successfully. + + Specifically, we add the records implied by the DsReplicaUpdateRefs + call above. + + We do not just run samba_dnsupdate as we want to strictly + operate against the DC we just joined: + - We do not want to query another DNS server + - We do not want to obtain a Kerberos ticket + (as the KDC we select may not be the DC we just joined, + and so may not be in sync with the password we just set) + - We do not wish to set the _ldap records until we have started + - We do not wish to use NTLM (the --use-samba-tool mode forces + NTLM) + + """ + + client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN + record_type = dnsp.DNS_TYPE_A + select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\ + dnsserver.DNS_RPC_VIEW_NO_CHILDREN + + zone = ctx.dnsdomain + msdcs_zone = "_msdcs.%s" % ctx.dnsforest + name = ctx.myname + msdcs_cname = str(ctx.ntds_guid) + cname_target = "%s.%s" % (name, zone) + IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips) + + ctx.logger.info("Adding %d remote DNS records for %s.%s" % \ + (len(IPs), name, zone)) + + binding_options = "sign" + dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options), + ctx.lp, ctx.creds) + + + name_found = True + + sd_helper = samba.sd_utils.SDUtils(ctx.samdb) + + change_owner_sd = security.descriptor() + change_owner_sd.owner_sid = ctx.new_dc_account_sid + change_owner_sd.group_sid = security.dom_sid("%s-%d" % + (str(ctx.domsid), + security.DOMAIN_RID_DCS)) + + # TODO: Remove any old records from the primary DNS name + try: + (buflen, res) \ + = dns_conn.DnssrvEnumRecords2(client_version, + 0, + ctx.server, + zone, + name, + None, + dnsp.DNS_TYPE_ALL, + select_flags, + None, + None) + except WERRORError as e: + if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST: + name_found = False + pass + + if name_found: + for rec in res.rec: + for record in rec.records: + if record.wType == dnsp.DNS_TYPE_A or \ + record.wType == dnsp.DNS_TYPE_AAAA: + # delete record + del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + del_rec_buf.rec = record + try: + dns_conn.DnssrvUpdateRecord2(client_version, + 0, + ctx.server, + zone, + name, + None, + del_rec_buf) + except WERRORError as e: + if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST: + pass + else: + raise + + for IP in IPs: + if IP.find(':') != -1: + ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s" + % (name, zone, IP)) + rec = AAAARecord(IP) + else: + ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s" + % (name, zone, IP)) + rec = ARecord(IP) + + # Add record + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + dns_conn.DnssrvUpdateRecord2(client_version, + 0, + ctx.server, + zone, + name, + add_rec_buf, + None) + + if (len(IPs) > 0): + domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone) + (ctx.dns_a_dn, ldap_record) \ + = ctx.samdb.dns_lookup("%s.%s" % (name, zone), + dns_partition=domaindns_zone_dn) + + # Make the DC own the DNS record, not the administrator + sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd, + controls=["sd_flags:1:%d" + % (security.SECINFO_OWNER + | security.SECINFO_GROUP)]) + + + # Add record + ctx.logger.info("Adding DNS CNAME record %s.%s for %s" + % (msdcs_cname, msdcs_zone, cname_target)) + + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + rec = CNameRecord(cname_target) + add_rec_buf.rec = rec + dns_conn.DnssrvUpdateRecord2(client_version, + 0, + ctx.server, + msdcs_zone, + msdcs_cname, + add_rec_buf, + None) + + forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone) + (ctx.dns_cname_dn, ldap_record) \ + = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone), + dns_partition=forestdns_zone_dn) + + # Make the DC own the DNS record, not the administrator + sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd, + controls=["sd_flags:1:%d" + % (security.SECINFO_OWNER + | security.SECINFO_GROUP)]) + + ctx.logger.info("All other DNS records (like _ldap SRV records) " + + "will be created samba_dnsupdate on first startup") + + + def join_replicate_new_dns_records(ctx): + for nc in (ctx.domaindns_zone, ctx.forestdns_zone): + if nc in ctx.nc_list: + ctx.logger.info("Replicating new DNS records in %s" % (str(nc))) + ctx.repl.replicate(nc, ctx.source_dsa_invocation_id, + ctx.ntds_guid, rodc=ctx.RODC, + replica_flags=ctx.replica_flags, + full_sync=False) + + def join_finalise(ctx): """Finalise the join, mark us synchronised and setup secrets db.""" @@ -929,7 +1210,7 @@ class dc_join(object): # 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 @@ -963,19 +1244,6 @@ class dc_join(object): def join_setup_trusts(ctx): """provision the local SAM.""" - def arcfour_encrypt(key, data): - from Crypto.Cipher import ARC4 - c = ARC4.new(key) - return c.encrypt(data) - - def string_to_array(string): - blob = [0] * len(string) - - for i in range(len(string)): - blob[i] = ord(string[i]) - - return blob - print "Setup domain trusts with server %s" % ctx.server binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options), @@ -1005,7 +1273,7 @@ class dc_join(object): except RuntimeError: pass - password_blob = string_to_array(ctx.trustdom_pass.encode('utf-16-le')) + password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le')) clear_value = drsblobs.AuthInfoClear() clear_value.size = len(password_blob) @@ -1041,7 +1309,7 @@ class dc_join(object): auth_blob = lsa.DATA_BUF2() auth_blob.size = len(encrypted_trustpass) - auth_blob.data = string_to_array(encrypted_trustpass) + auth_blob.data = string_to_byte_array(encrypted_trustpass) auth_info = lsa.TrustDomainInfoAuthInfoInternal() auth_info.auth_blob = auth_blob @@ -1111,9 +1379,17 @@ class dc_join(object): ctx.join_add_objects2() ctx.join_provision_own_domain() ctx.join_setup_trusts() + + if not ctx.clone_only and ctx.dns_backend != "NONE": + ctx.join_add_dns_records() + ctx.join_replicate_new_dns_records() + 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 @@ -1159,11 +1435,7 @@ def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_ 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: @@ -1193,11 +1465,8 @@ def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_na 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 @@ -1217,11 +1486,8 @@ def join_clone(logger=None, server=None, creds=None, lp=None, 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 @@ -1265,18 +1531,16 @@ def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None, 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()