2 # Copyright Andrew Tridgell 2010
3 # Copyright Andrew Bartlett 2010
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 from __future__ import print_function
20 """Joining a domain."""
22 from samba.auth import system_session
23 from samba.samdb import SamDB
24 from samba import gensec, Ldb, drs_utils, arcfour_encrypt, string_to_byte_array
25 import ldb, samba, sys, uuid
26 from samba.ndr import ndr_pack, ndr_unpack
27 from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp
28 from samba.dsdb import DS_DOMAIN_FUNCTION_2003
29 from samba.credentials import Credentials, DONT_USE_KERBEROS
30 from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN
31 from samba.provision.common import setup_path
32 from samba.schema import Schema
33 from samba import descriptor
34 from samba.net import Net
35 from samba.provision.sambadns import setup_bind9_dns
36 from samba import read_and_sub_file
37 from samba import werror
38 from base64 import b64encode
39 from samba import WERRORError, NTSTATUSError
40 from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord
41 from samba import sd_utils
47 class DCJoinException(Exception):
49 def __init__(self, msg):
50 super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
53 class dc_join(object):
54 """Perform a DC join."""
56 def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
57 netbios_name=None, targetdir=None, domain=None,
58 machinepass=None, use_ntvfs=False, dns_backend=None,
59 promote_existing=False, clone_only=False,
60 plaintext_secrets=False):
62 site = "Default-First-Site-Name"
64 ctx.clone_only=clone_only
70 ctx.targetdir = targetdir
71 ctx.use_ntvfs = use_ntvfs
72 ctx.plaintext_secrets = plaintext_secrets
74 ctx.promote_existing = promote_existing
75 ctx.promote_from_dn = None
80 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
81 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
83 if server is not None:
86 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
87 ctx.server = ctx.find_dc(domain)
88 ctx.logger.info("Found DC %s" % ctx.server)
90 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
91 session_info=system_session(),
92 credentials=ctx.creds, lp=ctx.lp)
95 ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
96 except ldb.LdbError as e4:
97 (enum, estr) = e4.args
98 raise DCJoinException(estr)
101 ctx.base_dn = str(ctx.samdb.get_default_basedn())
102 ctx.root_dn = str(ctx.samdb.get_root_basedn())
103 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
104 ctx.config_dn = str(ctx.samdb.get_config_basedn())
105 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
106 ctx.forestsid = ctx.domsid
107 ctx.domain_name = ctx.get_domain_name()
108 ctx.forest_domain_name = ctx.get_forest_domain_name()
109 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
111 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
112 ctx.dc_dnsHostName = ctx.get_dnsHostName()
113 ctx.behavior_version = ctx.get_behavior_version()
115 if machinepass is not None:
116 ctx.acct_pass = machinepass
118 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
120 ctx.dnsdomain = ctx.samdb.domain_dns_name()
122 # As we don't want to create or delete these DNs, we set them to None
126 ctx.myname = ctx.server.split('.')[0]
128 ctx.rid_manager_dn = None
131 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
133 # work out the DNs of all the objects we will be adding
134 ctx.myname = netbios_name
135 ctx.samname = "%s$" % ctx.myname
136 ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
137 ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
138 ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
139 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
140 ctx.dnsforest = ctx.samdb.forest_dns_name()
142 topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
143 if ctx.dn_exists(topology_base):
144 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
146 ctx.topology_dn = None
148 ctx.SPNs = [ "HOST/%s" % ctx.myname,
149 "HOST/%s" % ctx.dnshostname,
150 "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
152 res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
153 attrs=["rIDManagerReference"],
156 ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
158 ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
159 ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
161 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
162 res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
164 base=ctx.samdb.get_partitions_dn(),
166 if dns_backend is None:
167 ctx.dns_backend = "NONE"
169 if len(res_domaindns) == 0:
170 ctx.dns_backend = "NONE"
171 print("NO DNS zone information found in source domain, not replicating DNS")
173 ctx.dns_backend = dns_backend
175 ctx.realm = ctx.dnsdomain
179 ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
180 drsuapi.DRSUAPI_DRS_PER_SYNC |
181 drsuapi.DRSUAPI_DRS_GET_ANC |
182 drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
183 drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
185 # these elements are optional
186 ctx.never_reveal_sid = None
187 ctx.reveal_sid = None
188 ctx.connection_dn = None
193 ctx.subdomain = False
195 ctx.partition_dn = None
198 ctx.dns_cname_dn = None
200 # Do not normally register 127. addresses but allow override for selftest
201 ctx.force_all_ips = False
203 def del_noerror(ctx, dn, recursive=False):
206 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
210 ctx.del_noerror(r.dn, recursive=True)
213 print("Deleted %s" % dn)
217 def cleanup_old_accounts(ctx, force=False):
218 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
219 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
220 attrs=["msDS-krbTgtLink", "objectSID"])
225 creds = Credentials()
228 creds.set_machine_account(ctx.lp)
229 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
230 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
231 session_info=system_session(),
232 credentials=creds, lp=ctx.lp)
236 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
237 if token_res[0]["tokenGroups"][0] \
238 == res[0]["objectSID"][0]:
239 raise DCJoinException("Not removing account %s which "
240 "looks like a Samba DC account "
241 "maching the password we already have. "
242 "To override, remove secrets.ldb and secrets.tdb"
245 ctx.del_noerror(res[0].dn, recursive=True)
247 if "msDS-Krbtgtlink" in res[0]:
248 new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
249 ctx.del_noerror(ctx.new_krbtgt_dn)
251 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
252 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
253 (ldb.binary_encode("dns-%s" % ctx.myname),
254 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
257 ctx.del_noerror(res[0].dn, recursive=True)
259 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
260 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
263 raise DCJoinException("Not removing account %s which looks like "
264 "a Samba DNS service account but does not "
265 "have servicePrincipalName=%s" %
266 (ldb.binary_encode("dns-%s" % ctx.myname),
267 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
270 def cleanup_old_join(ctx, force=False):
271 """Remove any DNs from a previous join."""
272 # find the krbtgt link
273 if not ctx.subdomain:
274 ctx.cleanup_old_accounts(force=force)
276 if ctx.connection_dn is not None:
277 ctx.del_noerror(ctx.connection_dn)
278 if ctx.krbtgt_dn is not None:
279 ctx.del_noerror(ctx.krbtgt_dn)
280 ctx.del_noerror(ctx.ntds_dn)
281 ctx.del_noerror(ctx.server_dn, recursive=True)
283 ctx.del_noerror(ctx.topology_dn)
285 ctx.del_noerror(ctx.partition_dn)
288 binding_options = "sign"
289 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
292 objectAttr = lsa.ObjectAttribute()
293 objectAttr.sec_qos = lsa.QosInfo()
295 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
296 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
299 name.string = ctx.realm
300 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
302 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
305 name.string = ctx.forest_domain_name
306 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
308 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
311 ctx.del_noerror(ctx.dns_a_dn)
314 ctx.del_noerror(ctx.dns_cname_dn)
318 def promote_possible(ctx):
319 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
321 # This shouldn't happen
322 raise Exception("Can not promote into a subdomain")
324 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
325 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
326 attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
328 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
329 if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
330 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
331 if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
332 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
334 ctx.promote_from_dn = res[0].dn
337 def find_dc(ctx, domain):
338 """find a writeable DC for the given domain"""
340 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
341 except NTSTATUSError as error:
342 raise Exception("Failed to find a writeable DC for domain '%s': %s" %
345 raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
346 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
347 ctx.site = ctx.cldap_ret.client_site
348 return ctx.cldap_ret.pdc_dns_name
351 def get_behavior_version(ctx):
352 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
353 if "msDS-Behavior-Version" in res[0]:
354 return int(res[0]["msDS-Behavior-Version"][0])
356 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
358 def get_dnsHostName(ctx):
359 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
360 return res[0]["dnsHostName"][0]
362 def get_domain_name(ctx):
363 '''get netbios name of the domain from the partitions record'''
364 partitions_dn = ctx.samdb.get_partitions_dn()
365 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
366 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
367 return res[0]["nETBIOSName"][0]
369 def get_forest_domain_name(ctx):
370 '''get netbios name of the domain from the partitions record'''
371 partitions_dn = ctx.samdb.get_partitions_dn()
372 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
373 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
374 return res[0]["nETBIOSName"][0]
376 def get_parent_partition_dn(ctx):
377 '''get the parent domain partition DN from parent DNS name'''
378 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
379 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
380 (ldb.binary_encode(ctx.parent_dnsdomain),
381 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
382 return str(res[0].dn)
384 def get_naming_master(ctx):
385 '''get the parent domain partition DN from parent DNS name'''
386 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
387 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
388 if not 'fSMORoleOwner' in res[0]:
389 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
391 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
393 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
395 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
399 '''get the SID of the connected user. Only works with w2k8 and later,
400 so only used for RODC join'''
401 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
402 binsid = res[0]["tokenGroups"][0]
403 return ctx.samdb.schema_format_value("objectSID", binsid)
405 def dn_exists(ctx, dn):
406 '''check if a DN exists'''
408 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
409 except ldb.LdbError as e5:
410 (enum, estr) = e5.args
411 if enum == ldb.ERR_NO_SUCH_OBJECT:
416 def add_krbtgt_account(ctx):
417 '''RODCs need a special krbtgt account'''
418 print("Adding %s" % ctx.krbtgt_dn)
420 "dn" : ctx.krbtgt_dn,
421 "objectclass" : "user",
422 "useraccountcontrol" : str(samba.dsdb.UF_NORMAL_ACCOUNT |
423 samba.dsdb.UF_ACCOUNTDISABLE),
424 "showinadvancedviewonly" : "TRUE",
425 "description" : "krbtgt for %s" % ctx.samname}
426 ctx.samdb.add(rec, ["rodc_join:1:1"])
428 # now we need to search for the samAccountName attribute on the krbtgt DN,
429 # as this will have been magically set to the krbtgt number
430 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
431 ctx.krbtgt_name = res[0]["samAccountName"][0]
433 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
436 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
437 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
438 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
441 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
442 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
443 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
445 def drsuapi_connect(ctx):
446 '''make a DRSUAPI connection to the naming master'''
447 binding_options = "seal"
448 if ctx.lp.log_level() >= 9:
449 binding_options += ",print"
450 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
451 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
452 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
454 def create_tmp_samdb(ctx):
455 '''create a temporary samdb object for schema queries'''
456 ctx.tmp_schema = Schema(ctx.domsid,
457 schemadn=ctx.schema_dn)
458 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
459 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
461 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
463 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
464 '''build a DsReplicaAttributeCtr object'''
465 r = drsuapi.DsReplicaAttribute()
466 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
470 def DsAddEntry(ctx, recs):
471 '''add a record via the DRSUAPI DsAddEntry call'''
472 if ctx.drsuapi is None:
473 ctx.drsuapi_connect()
474 if ctx.tmp_samdb is None:
475 ctx.create_tmp_samdb()
479 id = drsuapi.DsReplicaObjectIdentifier()
486 if not isinstance(rec[a], list):
490 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
493 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
494 attribute_ctr.num_attributes = len(attrs)
495 attribute_ctr.attributes = attrs
497 object = drsuapi.DsReplicaObject()
498 object.identifier = id
499 object.attribute_ctr = attribute_ctr
501 list_object = drsuapi.DsReplicaObjectListItem()
502 list_object.object = object
503 objects.append(list_object)
505 req2 = drsuapi.DsAddEntryRequest2()
506 req2.first_object = objects[0]
507 prev = req2.first_object
508 for o in objects[1:]:
512 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
514 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
515 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
516 raise RuntimeError("DsAddEntry failed")
517 if ctr.extended_err[0] != werror.WERR_SUCCESS:
518 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
519 raise RuntimeError("DsAddEntry failed")
522 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
523 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
524 if ctr.err_data.info is None:
525 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
527 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
528 ctr.err_data.info.extended_err))
529 raise RuntimeError("DsAddEntry failed")
530 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
531 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
532 raise RuntimeError("DsAddEntry failed")
536 def join_ntdsdsa_obj(ctx):
537 '''return the ntdsdsa object to add'''
539 print("Adding %s" % ctx.ntds_dn)
542 "objectclass" : "nTDSDSA",
543 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
544 "dMDLocation" : ctx.schema_dn}
546 nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
548 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
549 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
551 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
552 rec["msDS-HasDomainNCs"] = ctx.base_dn
555 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
556 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
557 rec["options"] = "37"
559 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
560 rec["HasMasterNCs"] = []
562 if nc in ctx.full_nc_list:
563 rec["HasMasterNCs"].append(nc)
564 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
565 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
567 rec["invocationId"] = ndr_pack(ctx.invocation_id)
571 def join_add_ntdsdsa(ctx):
572 '''add the ntdsdsa object'''
574 rec = ctx.join_ntdsdsa_obj()
576 ctx.samdb.add(rec, ["rodc_join:1:1"])
578 ctx.DsAddEntry([rec])
580 # find the GUID of our NTDS DN
581 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
582 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
584 def join_add_objects(ctx):
585 '''add the various objects needed for the join'''
587 print("Adding %s" % ctx.acct_dn)
590 "objectClass": "computer",
591 "displayname": ctx.samname,
592 "samaccountname" : ctx.samname,
593 "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
594 "dnshostname" : ctx.dnshostname}
595 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
596 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
597 elif ctx.promote_existing:
598 rec['msDS-SupportedEncryptionTypes'] = []
600 rec["managedby"] = ctx.managedby
601 elif ctx.promote_existing:
602 rec["managedby"] = []
604 if ctx.never_reveal_sid:
605 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
606 elif ctx.promote_existing:
607 rec["msDS-NeverRevealGroup"] = []
610 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
611 elif ctx.promote_existing:
612 rec["msDS-RevealOnDemandGroup"] = []
614 if ctx.promote_existing:
615 if ctx.promote_from_dn != ctx.acct_dn:
616 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
617 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
622 ctx.add_krbtgt_account()
625 print("Adding %s" % ctx.server_dn)
628 "objectclass" : "server",
629 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
630 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
631 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
632 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
633 # windows seems to add the dnsHostName later
634 "dnsHostName" : ctx.dnshostname}
637 rec["serverReference"] = ctx.acct_dn
642 # the rest is done after replication
647 ctx.join_add_ntdsdsa()
649 # Add the Replica-Locations or RO-Replica-Locations attributes
650 # TODO Is this supposed to be for the schema partition too?
651 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
652 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
654 base=ctx.samdb.get_partitions_dn(),
655 expression=expr), ctx.domaindns_zone)
657 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
658 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
660 base=ctx.samdb.get_partitions_dn(),
661 expression=expr), ctx.forestdns_zone)
663 for part, zone in (domain, forest):
664 if zone not in ctx.nc_list:
670 attr = "msDS-NC-Replica-Locations"
672 attr = "msDS-NC-RO-Replica-Locations"
674 m[attr] = ldb.MessageElement(ctx.ntds_dn,
675 ldb.FLAG_MOD_ADD, attr)
678 if ctx.connection_dn is not None:
679 print("Adding %s" % ctx.connection_dn)
681 "dn" : ctx.connection_dn,
682 "objectclass" : "nTDSConnection",
683 "enabledconnection" : "TRUE",
685 "fromServer" : ctx.dc_ntds_dn}
689 print("Adding SPNs to %s" % ctx.acct_dn)
691 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
692 for i in range(len(ctx.SPNs)):
693 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
694 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
695 ldb.FLAG_MOD_REPLACE,
696 "servicePrincipalName")
699 # The account password set operation should normally be done over
700 # LDAP. Windows 2000 DCs however allow this only with SSL
701 # connections which are hard to set up and otherwise refuse with
702 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
704 print("Setting account password for %s" % ctx.samname)
706 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
707 % ldb.binary_encode(ctx.samname),
709 force_change_at_next_login=False,
710 username=ctx.samname)
711 except ldb.LdbError as e2:
713 if num != ldb.ERR_UNWILLING_TO_PERFORM:
715 ctx.net.set_password(account_name=ctx.samname,
716 domain_name=ctx.domain_name,
717 newpassword=ctx.acct_pass.encode('utf-8'))
719 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
720 attrs=["msDS-KeyVersionNumber",
722 if "msDS-KeyVersionNumber" in res[0]:
723 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
725 ctx.key_version_number = None
727 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
728 res[0]["objectSid"][0])
730 print("Enabling account")
732 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
733 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
734 ldb.FLAG_MOD_REPLACE,
735 "userAccountControl")
738 if ctx.dns_backend.startswith("BIND9_"):
739 ctx.dnspass = samba.generate_random_password(128, 255)
741 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
742 {"DNSDOMAIN": ctx.dnsdomain,
743 "DOMAINDN": ctx.base_dn,
744 "HOSTNAME" : ctx.myname,
745 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
746 "DNSNAME" : ctx.dnshostname}))
747 for changetype, msg in recs:
748 assert changetype == ldb.CHANGETYPE_NONE
749 dns_acct_dn = msg["dn"]
750 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
752 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
753 del msg["clearTextPassword"]
754 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
755 del msg["isCriticalSystemObject"]
756 # Disable account until password is set
757 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
758 samba.dsdb.UF_ACCOUNTDISABLE)
761 except ldb.LdbError as e:
763 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
766 # The account password set operation should normally be done over
767 # LDAP. Windows 2000 DCs however allow this only with SSL
768 # connections which are hard to set up and otherwise refuse with
769 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
771 print("Setting account password for dns-%s" % ctx.myname)
773 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
774 % ldb.binary_encode(ctx.myname),
776 force_change_at_next_login=False,
777 username=ctx.samname)
778 except ldb.LdbError as e3:
780 if num != ldb.ERR_UNWILLING_TO_PERFORM:
782 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
783 domain_name=ctx.domain_name,
784 newpassword=ctx.dnspass)
786 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
787 attrs=["msDS-KeyVersionNumber"])
788 if "msDS-KeyVersionNumber" in res[0]:
789 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
791 ctx.dns_key_version_number = None
793 def join_add_objects2(ctx):
794 """add the various objects needed for the join, for subdomains post replication"""
796 print("Adding %s" % ctx.partition_dn)
797 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
798 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
800 "dn" : ctx.partition_dn,
801 "objectclass" : "crossRef",
802 "objectCategory" : "CN=Cross-Ref,%s" % ctx.schema_dn,
803 "nCName" : ctx.base_dn,
804 "nETBIOSName" : ctx.domain_name,
805 "dnsRoot": ctx.dnsdomain,
806 "trustParent" : ctx.parent_partition_dn,
807 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
808 "ntSecurityDescriptor" : sd_binary,
811 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
812 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
814 rec2 = ctx.join_ntdsdsa_obj()
816 objects = ctx.DsAddEntry([rec, rec2])
817 if len(objects) != 2:
818 raise DCJoinException("Expected 2 objects from DsAddEntry")
820 ctx.ntds_guid = objects[1].guid
822 print("Replicating partition DN")
823 ctx.repl.replicate(ctx.partition_dn,
824 misc.GUID("00000000-0000-0000-0000-000000000000"),
826 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
827 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
829 print("Replicating NTDS DN")
830 ctx.repl.replicate(ctx.ntds_dn,
831 misc.GUID("00000000-0000-0000-0000-000000000000"),
833 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
834 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
836 def join_provision(ctx):
837 """Provision the local SAM."""
839 print("Calling bare provision")
841 smbconf = ctx.lp.configfile
843 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
844 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
845 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
846 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
847 serverdn=ctx.server_dn, domain=ctx.domain_name,
848 hostname=ctx.myname, domainsid=ctx.domsid,
849 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
850 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
851 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
852 plaintext_secrets=ctx.plaintext_secrets)
853 print("Provision OK for domain DN %s" % presult.domaindn)
854 ctx.local_samdb = presult.samdb
856 ctx.paths = presult.paths
857 ctx.names = presult.names
859 # Fix up the forestsid, it may be different if we are joining as a subdomain
860 ctx.names.forestsid = ctx.forestsid
862 def join_provision_own_domain(ctx):
863 """Provision the local SAM."""
865 # we now operate exclusively on the local database, which
866 # we need to reopen in order to get the newly created schema
867 print("Reconnecting to local samdb")
868 ctx.samdb = SamDB(url=ctx.local_samdb.url,
869 session_info=system_session(),
870 lp=ctx.local_samdb.lp,
872 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
873 ctx.local_samdb = ctx.samdb
875 ctx.logger.info("Finding domain GUID from ncName")
876 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
877 controls=["extended_dn:1:1", "reveal_internals:0"])
879 if 'nCName' not in res[0]:
880 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
883 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
885 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
887 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
889 ctx.logger.info("Calling own domain provision")
891 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
893 presult = provision_fill(ctx.local_samdb, secrets_ldb,
894 ctx.logger, ctx.names, ctx.paths,
895 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
896 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
897 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
898 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
899 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
900 print("Provision OK for domain %s" % ctx.names.dnsdomain)
902 def join_replicate(ctx):
903 """Replicate the SAM."""
905 print("Starting replication")
906 ctx.local_samdb.transaction_start()
908 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
909 if ctx.ntds_guid is None:
910 print("Using DS_BIND_GUID_W2K3")
911 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
913 destination_dsa_guid = ctx.ntds_guid
916 repl_creds = Credentials()
917 repl_creds.guess(ctx.lp)
918 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
919 repl_creds.set_username(ctx.samname)
920 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
922 repl_creds = ctx.creds
924 binding_options = "seal"
925 if ctx.lp.log_level() >= 9:
926 binding_options += ",print"
927 repl = drs_utils.drs_Replicate(
928 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
929 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
931 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
932 destination_dsa_guid, schema=True, rodc=ctx.RODC,
933 replica_flags=ctx.replica_flags)
934 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
935 destination_dsa_guid, rodc=ctx.RODC,
936 replica_flags=ctx.replica_flags)
937 if not ctx.subdomain:
938 # Replicate first the critical object for the basedn
939 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
940 print("Replicating critical objects from the base DN of the domain")
941 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
942 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
943 destination_dsa_guid, rodc=ctx.RODC,
944 replica_flags=ctx.domain_replica_flags)
945 ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
946 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
947 destination_dsa_guid, rodc=ctx.RODC,
948 replica_flags=ctx.domain_replica_flags)
949 print("Done with always replicated NC (base, config, schema)")
951 # At this point we should already have an entry in the ForestDNS
952 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
953 # indicate that we hold a replica for this NC.
954 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
955 if nc in ctx.nc_list:
956 print("Replicating %s" % (str(nc)))
957 repl.replicate(nc, source_dsa_invocation_id,
958 destination_dsa_guid, rodc=ctx.RODC,
959 replica_flags=ctx.replica_flags)
962 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
963 destination_dsa_guid,
964 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
965 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
966 destination_dsa_guid,
967 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
968 elif ctx.rid_manager_dn != None:
969 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
971 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
972 destination_dsa_guid,
973 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
974 except samba.DsExtendedError as e1:
975 (enum, estr) = e1.args
976 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
977 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
978 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
983 ctx.source_dsa_invocation_id = source_dsa_invocation_id
984 ctx.destination_dsa_guid = destination_dsa_guid
986 print("Committing SAM database")
988 ctx.local_samdb.transaction_cancel()
991 ctx.local_samdb.transaction_commit()
993 def send_DsReplicaUpdateRefs(ctx, dn):
994 r = drsuapi.DsReplicaUpdateRefsRequest1()
995 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
996 r.naming_context.dn = str(dn)
997 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
998 r.naming_context.sid = security.dom_sid("S-0-0")
999 r.dest_dsa_guid = ctx.ntds_guid
1000 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1001 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1003 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1005 if ctx.drsuapi is None:
1006 ctx.drsuapi_connect()
1008 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1010 def join_add_dns_records(ctx):
1011 """Remotely Add a DNS record to the target DC. We assume that if we
1012 replicate DNS that the server holds the DNS roles and can accept
1015 This avoids issues getting replication going after the DC
1016 first starts as the rest of the domain does not have to
1017 wait for samba_dnsupdate to run successfully.
1019 Specifically, we add the records implied by the DsReplicaUpdateRefs
1022 We do not just run samba_dnsupdate as we want to strictly
1023 operate against the DC we just joined:
1024 - We do not want to query another DNS server
1025 - We do not want to obtain a Kerberos ticket
1026 (as the KDC we select may not be the DC we just joined,
1027 and so may not be in sync with the password we just set)
1028 - We do not wish to set the _ldap records until we have started
1029 - We do not wish to use NTLM (the --use-samba-tool mode forces
1034 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1035 record_type = dnsp.DNS_TYPE_A
1036 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1037 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1039 zone = ctx.dnsdomain
1040 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1042 msdcs_cname = str(ctx.ntds_guid)
1043 cname_target = "%s.%s" % (name, zone)
1044 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1046 ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
1047 (len(IPs), name, zone))
1049 binding_options = "sign"
1050 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1056 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1058 change_owner_sd = security.descriptor()
1059 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1060 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1062 security.DOMAIN_RID_DCS))
1064 # TODO: Remove any old records from the primary DNS name
1067 = dns_conn.DnssrvEnumRecords2(client_version,
1077 except WERRORError as e:
1078 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1084 for record in rec.records:
1085 if record.wType == dnsp.DNS_TYPE_A or \
1086 record.wType == dnsp.DNS_TYPE_AAAA:
1088 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1089 del_rec_buf.rec = record
1091 dns_conn.DnssrvUpdateRecord2(client_version,
1098 except WERRORError as e:
1099 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1105 if IP.find(':') != -1:
1106 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1108 rec = AAAARecord(IP)
1110 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1115 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1116 add_rec_buf.rec = rec
1117 dns_conn.DnssrvUpdateRecord2(client_version,
1126 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1127 (ctx.dns_a_dn, ldap_record) \
1128 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1129 dns_partition=domaindns_zone_dn)
1131 # Make the DC own the DNS record, not the administrator
1132 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1133 controls=["sd_flags:1:%d"
1134 % (security.SECINFO_OWNER
1135 | security.SECINFO_GROUP)])
1139 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1140 % (msdcs_cname, msdcs_zone, cname_target))
1142 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1143 rec = CNameRecord(cname_target)
1144 add_rec_buf.rec = rec
1145 dns_conn.DnssrvUpdateRecord2(client_version,
1153 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1154 (ctx.dns_cname_dn, ldap_record) \
1155 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1156 dns_partition=forestdns_zone_dn)
1158 # Make the DC own the DNS record, not the administrator
1159 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1160 controls=["sd_flags:1:%d"
1161 % (security.SECINFO_OWNER
1162 | security.SECINFO_GROUP)])
1164 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1165 "will be created samba_dnsupdate on first startup")
1168 def join_replicate_new_dns_records(ctx):
1169 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1170 if nc in ctx.nc_list:
1171 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1172 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1173 ctx.ntds_guid, rodc=ctx.RODC,
1174 replica_flags=ctx.replica_flags,
1179 def join_finalise(ctx):
1180 """Finalise the join, mark us synchronised and setup secrets db."""
1182 # FIXME we shouldn't do this in all cases
1184 # If for some reasons we joined in another site than the one of
1185 # DC we just replicated from then we don't need to send the updatereplicateref
1186 # as replication between sites is time based and on the initiative of the
1188 if not ctx.clone_only:
1189 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1190 for nc in ctx.nc_list:
1191 ctx.send_DsReplicaUpdateRefs(nc)
1193 if not ctx.clone_only and ctx.RODC:
1194 print("Setting RODC invocationId")
1195 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1196 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1197 ctx.behavior_version)
1199 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1200 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1201 ldb.FLAG_MOD_REPLACE,
1203 ctx.local_samdb.modify(m)
1205 # Note: as RODC the invocationId is only stored
1206 # on the RODC itself, the other DCs never see it.
1208 # Thats is why we fix up the replPropertyMetaData stamp
1209 # for the 'invocationId' attribute, we need to change
1210 # the 'version' to '0', this is what windows 2008r2 does as RODC
1212 # This means if the object on a RWDC ever gets a invocationId
1213 # attribute, it will have version '1' (or higher), which will
1214 # will overwrite the RODC local value.
1215 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1219 ctx.logger.info("Setting isSynchronized and dsServiceName")
1221 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1222 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1224 # We want to appear to be the server we just cloned
1226 guid = ctx.remote_dc_ntds_guid
1228 guid = ctx.ntds_guid
1230 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1231 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1232 ctx.local_samdb.modify(m)
1234 if ctx.clone_only or ctx.subdomain:
1237 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1239 ctx.logger.info("Setting up secrets database")
1240 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1242 dnsdomain=ctx.dnsdomain,
1243 netbiosname=ctx.myname,
1244 domainsid=ctx.domsid,
1245 machinepass=ctx.acct_pass,
1246 secure_channel_type=ctx.secure_channel_type,
1247 key_version_number=ctx.key_version_number)
1249 if ctx.dns_backend.startswith("BIND9_"):
1250 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1251 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1252 dns_backend=ctx.dns_backend,
1253 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1254 targetdir=ctx.targetdir,
1255 key_version_number=ctx.dns_key_version_number)
1257 def join_setup_trusts(ctx):
1258 """provision the local SAM."""
1260 print("Setup domain trusts with server %s" % ctx.server)
1261 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1262 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1265 objectAttr = lsa.ObjectAttribute()
1266 objectAttr.sec_qos = lsa.QosInfo()
1268 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1269 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1271 info = lsa.TrustDomainInfoInfoEx()
1272 info.domain_name.string = ctx.dnsdomain
1273 info.netbios_name.string = ctx.domain_name
1274 info.sid = ctx.domsid
1275 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1276 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1277 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1280 oldname = lsa.String()
1281 oldname.string = ctx.dnsdomain
1282 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1283 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1284 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1285 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1286 except RuntimeError:
1289 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1291 clear_value = drsblobs.AuthInfoClear()
1292 clear_value.size = len(password_blob)
1293 clear_value.password = password_blob
1295 clear_authentication_information = drsblobs.AuthenticationInformation()
1296 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1297 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1298 clear_authentication_information.AuthInfo = clear_value
1300 authentication_information_array = drsblobs.AuthenticationInformationArray()
1301 authentication_information_array.count = 1
1302 authentication_information_array.array = [clear_authentication_information]
1304 outgoing = drsblobs.trustAuthInOutBlob()
1306 outgoing.current = authentication_information_array
1308 trustpass = drsblobs.trustDomainPasswords()
1309 confounder = [3] * 512
1311 for i in range(512):
1312 confounder[i] = random.randint(0, 255)
1314 trustpass.confounder = confounder
1316 trustpass.outgoing = outgoing
1317 trustpass.incoming = outgoing
1319 trustpass_blob = ndr_pack(trustpass)
1321 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1323 auth_blob = lsa.DATA_BUF2()
1324 auth_blob.size = len(encrypted_trustpass)
1325 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1327 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1328 auth_info.auth_blob = auth_blob
1330 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1333 security.SEC_STD_DELETE)
1336 "dn" : "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1337 "objectclass" : "trustedDomain",
1338 "trustType" : str(info.trust_type),
1339 "trustAttributes" : str(info.trust_attributes),
1340 "trustDirection" : str(info.trust_direction),
1341 "flatname" : ctx.forest_domain_name,
1342 "trustPartner" : ctx.dnsforest,
1343 "trustAuthIncoming" : ndr_pack(outgoing),
1344 "trustAuthOutgoing" : ndr_pack(outgoing),
1345 "securityIdentifier" : ndr_pack(ctx.forestsid)
1347 ctx.local_samdb.add(rec)
1350 "dn" : "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1351 "objectclass" : "user",
1352 "userAccountControl" : str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1353 "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le'),
1354 "samAccountName" : "%s$" % ctx.forest_domain_name
1356 ctx.local_samdb.add(rec)
1360 # nc_list is the list of naming context (NC) for which we will
1361 # replicate in and send a updateRef command to the partner DC
1363 # full_nc_list is the list of naming context (NC) we hold
1364 # read/write copies of. These are not subsets of each other.
1365 ctx.nc_list = [ ctx.config_dn, ctx.schema_dn ]
1366 ctx.full_nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
1368 if ctx.subdomain and ctx.dns_backend != "NONE":
1369 ctx.full_nc_list += [ctx.domaindns_zone]
1371 elif not ctx.subdomain:
1372 ctx.nc_list += [ctx.base_dn]
1374 if ctx.dns_backend != "NONE":
1375 ctx.nc_list += [ctx.domaindns_zone]
1376 ctx.nc_list += [ctx.forestdns_zone]
1377 ctx.full_nc_list += [ctx.domaindns_zone]
1378 ctx.full_nc_list += [ctx.forestdns_zone]
1380 if not ctx.clone_only:
1381 if ctx.promote_existing:
1382 ctx.promote_possible()
1384 ctx.cleanup_old_join()
1387 if not ctx.clone_only:
1388 ctx.join_add_objects()
1389 ctx.join_provision()
1390 ctx.join_replicate()
1391 if (not ctx.clone_only and ctx.subdomain):
1392 ctx.join_add_objects2()
1393 ctx.join_provision_own_domain()
1394 ctx.join_setup_trusts()
1396 if not ctx.clone_only and ctx.dns_backend != "NONE":
1397 ctx.join_add_dns_records()
1398 ctx.join_replicate_new_dns_records()
1403 print("Join failed - cleaning up")
1406 if not ctx.clone_only:
1407 ctx.cleanup_old_join()
1411 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1412 targetdir=None, domain=None, domain_critical_only=False,
1413 machinepass=None, use_ntvfs=False, dns_backend=None,
1414 promote_existing=False, plaintext_secrets=False):
1415 """Join as a RODC."""
1417 ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
1418 machinepass, use_ntvfs, dns_backend, promote_existing,
1421 lp.set("workgroup", ctx.domain_name)
1422 logger.info("workgroup is %s" % ctx.domain_name)
1424 lp.set("realm", ctx.realm)
1425 logger.info("realm is %s" % ctx.realm)
1427 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1429 # setup some defaults for accounts that should be replicated to this RODC
1430 ctx.never_reveal_sid = [
1431 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1432 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1433 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1434 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1435 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1436 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1438 mysid = ctx.get_mysid()
1439 admin_dn = "<SID=%s>" % mysid
1440 ctx.managedby = admin_dn
1442 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1443 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1444 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1446 ctx.SPNs.extend([ "RestrictedKrbHost/%s" % ctx.myname,
1447 "RestrictedKrbHost/%s" % ctx.dnshostname ])
1449 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1450 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1452 ctx.replica_flags |= ( drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1453 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1454 ctx.domain_replica_flags = ctx.replica_flags
1455 if domain_critical_only:
1456 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1460 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1463 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1464 targetdir=None, domain=None, domain_critical_only=False,
1465 machinepass=None, use_ntvfs=False, dns_backend=None,
1466 promote_existing=False, plaintext_secrets=False):
1468 ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
1469 machinepass, use_ntvfs, dns_backend, promote_existing,
1472 lp.set("workgroup", ctx.domain_name)
1473 logger.info("workgroup is %s" % ctx.domain_name)
1475 lp.set("realm", ctx.realm)
1476 logger.info("realm is %s" % ctx.realm)
1478 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1480 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1481 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1483 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1484 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1485 ctx.domain_replica_flags = ctx.replica_flags
1486 if domain_critical_only:
1487 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1490 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1492 def join_clone(logger=None, server=None, creds=None, lp=None,
1493 targetdir=None, domain=None, include_secrets=False):
1495 ctx = dc_join(logger, server, creds, lp, site=None, netbios_name=None, targetdir=targetdir, domain=domain,
1496 machinepass=None, use_ntvfs=False, dns_backend="NONE", promote_existing=False, clone_only=True)
1498 lp.set("workgroup", ctx.domain_name)
1499 logger.info("workgroup is %s" % ctx.domain_name)
1501 lp.set("realm", ctx.realm)
1502 logger.info("realm is %s" % ctx.realm)
1504 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1505 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1506 if not include_secrets:
1507 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1508 ctx.domain_replica_flags = ctx.replica_flags
1511 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1513 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1514 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1515 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1516 dns_backend=None, plaintext_secrets=False):
1518 ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, parent_domain,
1519 machinepass, use_ntvfs, dns_backend, plaintext_secrets)
1520 ctx.subdomain = True
1521 if adminpass is None:
1522 ctx.adminpass = samba.generate_random_password(12, 32)
1524 ctx.adminpass = adminpass
1525 ctx.parent_domain_name = ctx.domain_name
1526 ctx.domain_name = netbios_domain
1527 ctx.realm = dnsdomain
1528 ctx.parent_dnsdomain = ctx.dnsdomain
1529 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1530 ctx.dnsdomain = dnsdomain
1531 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1532 ctx.naming_master = ctx.get_naming_master()
1533 if ctx.naming_master != ctx.server:
1534 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1535 ctx.server = ctx.naming_master
1536 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1537 session_info=system_session(),
1538 credentials=ctx.creds, lp=ctx.lp)
1539 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1541 ctx.server = res[0]["dnsHostName"]
1542 logger.info("DNS name of new naming master is %s" % ctx.server)
1544 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1545 ctx.forestsid = ctx.domsid
1546 ctx.domsid = security.random_sid()
1548 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1549 # Windows uses 240 bytes as UTF16 so we do
1550 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1552 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1554 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1555 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1557 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1558 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1559 ctx.domain_replica_flags = ctx.replica_flags
1562 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))