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 DCJoinContext(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, plaintext_secrets=False,
62 site = "Default-First-Site-Name"
68 ctx.targetdir = targetdir
69 ctx.use_ntvfs = use_ntvfs
70 ctx.plaintext_secrets = plaintext_secrets
71 ctx.backend_store = backend_store
73 ctx.promote_existing = promote_existing
74 ctx.promote_from_dn = None
79 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
80 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
82 if server is not None:
85 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
86 ctx.server = ctx.find_dc(domain)
87 ctx.logger.info("Found DC %s" % ctx.server)
89 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
90 session_info=system_session(),
91 credentials=ctx.creds, lp=ctx.lp)
94 ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
95 except ldb.LdbError as e4:
96 (enum, estr) = e4.args
97 raise DCJoinException(estr)
100 ctx.base_dn = str(ctx.samdb.get_default_basedn())
101 ctx.root_dn = str(ctx.samdb.get_root_basedn())
102 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
103 ctx.config_dn = str(ctx.samdb.get_config_basedn())
104 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
105 ctx.forestsid = ctx.domsid
106 ctx.domain_name = ctx.get_domain_name()
107 ctx.forest_domain_name = ctx.get_forest_domain_name()
108 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
110 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
111 ctx.dc_dnsHostName = ctx.get_dnsHostName()
112 ctx.behavior_version = ctx.get_behavior_version()
114 if machinepass is not None:
115 ctx.acct_pass = machinepass
117 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
119 ctx.dnsdomain = ctx.samdb.domain_dns_name()
121 # the following are all dependent on the new DC's netbios_name (which
122 # we expect to always be specified, except when cloning a DC)
124 # work out the DNs of all the objects we will be adding
125 ctx.myname = netbios_name
126 ctx.samname = "%s$" % ctx.myname
127 ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
128 ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
129 ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
130 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
131 ctx.dnsforest = ctx.samdb.forest_dns_name()
133 topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
134 if ctx.dn_exists(topology_base):
135 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
137 ctx.topology_dn = None
139 ctx.SPNs = [ "HOST/%s" % ctx.myname,
140 "HOST/%s" % ctx.dnshostname,
141 "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
143 res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
144 attrs=["rIDManagerReference"],
147 ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
149 ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
150 ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
152 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
153 res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
155 base=ctx.samdb.get_partitions_dn(),
157 if dns_backend is None:
158 ctx.dns_backend = "NONE"
160 if len(res_domaindns) == 0:
161 ctx.dns_backend = "NONE"
162 print("NO DNS zone information found in source domain, not replicating DNS")
164 ctx.dns_backend = dns_backend
166 ctx.realm = ctx.dnsdomain
170 ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
171 drsuapi.DRSUAPI_DRS_PER_SYNC |
172 drsuapi.DRSUAPI_DRS_GET_ANC |
173 drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
174 drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
176 # these elements are optional
177 ctx.never_reveal_sid = None
178 ctx.reveal_sid = None
179 ctx.connection_dn = None
184 ctx.subdomain = False
186 ctx.partition_dn = None
189 ctx.dns_cname_dn = None
191 # Do not normally register 127. addresses but allow override for selftest
192 ctx.force_all_ips = False
194 def del_noerror(ctx, dn, recursive=False):
197 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
201 ctx.del_noerror(r.dn, recursive=True)
204 print("Deleted %s" % dn)
208 def cleanup_old_accounts(ctx, force=False):
209 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
210 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
211 attrs=["msDS-krbTgtLink", "objectSID"])
216 creds = Credentials()
219 creds.set_machine_account(ctx.lp)
220 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
221 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
222 session_info=system_session(),
223 credentials=creds, lp=ctx.lp)
227 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
228 if token_res[0]["tokenGroups"][0] \
229 == res[0]["objectSID"][0]:
230 raise DCJoinException("Not removing account %s which "
231 "looks like a Samba DC account "
232 "matching the password we already have. "
233 "To override, remove secrets.ldb and secrets.tdb"
236 ctx.del_noerror(res[0].dn, recursive=True)
238 if "msDS-Krbtgtlink" in res[0]:
239 new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
240 ctx.del_noerror(ctx.new_krbtgt_dn)
242 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
243 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
244 (ldb.binary_encode("dns-%s" % ctx.myname),
245 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
248 ctx.del_noerror(res[0].dn, recursive=True)
250 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
251 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
254 raise DCJoinException("Not removing account %s which looks like "
255 "a Samba DNS service account but does not "
256 "have servicePrincipalName=%s" %
257 (ldb.binary_encode("dns-%s" % ctx.myname),
258 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
261 def cleanup_old_join(ctx, force=False):
262 """Remove any DNs from a previous join."""
263 # find the krbtgt link
264 if not ctx.subdomain:
265 ctx.cleanup_old_accounts(force=force)
267 if ctx.connection_dn is not None:
268 ctx.del_noerror(ctx.connection_dn)
269 if ctx.krbtgt_dn is not None:
270 ctx.del_noerror(ctx.krbtgt_dn)
271 ctx.del_noerror(ctx.ntds_dn)
272 ctx.del_noerror(ctx.server_dn, recursive=True)
274 ctx.del_noerror(ctx.topology_dn)
276 ctx.del_noerror(ctx.partition_dn)
279 binding_options = "sign"
280 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
283 objectAttr = lsa.ObjectAttribute()
284 objectAttr.sec_qos = lsa.QosInfo()
286 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
287 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
290 name.string = ctx.realm
291 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
293 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
296 name.string = ctx.forest_domain_name
297 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
299 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
302 ctx.del_noerror(ctx.dns_a_dn)
305 ctx.del_noerror(ctx.dns_cname_dn)
309 def promote_possible(ctx):
310 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
312 # This shouldn't happen
313 raise Exception("Can not promote into a subdomain")
315 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
316 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
317 attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
319 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
320 if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
321 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
322 if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
323 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
325 ctx.promote_from_dn = res[0].dn
328 def find_dc(ctx, domain):
329 """find a writeable DC for the given domain"""
331 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
332 except NTSTATUSError as error:
333 raise Exception("Failed to find a writeable DC for domain '%s': %s" %
336 raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
337 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
338 ctx.site = ctx.cldap_ret.client_site
339 return ctx.cldap_ret.pdc_dns_name
342 def get_behavior_version(ctx):
343 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
344 if "msDS-Behavior-Version" in res[0]:
345 return int(res[0]["msDS-Behavior-Version"][0])
347 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
349 def get_dnsHostName(ctx):
350 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
351 return res[0]["dnsHostName"][0]
353 def get_domain_name(ctx):
354 '''get netbios name of the domain from the partitions record'''
355 partitions_dn = ctx.samdb.get_partitions_dn()
356 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
357 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
358 return res[0]["nETBIOSName"][0]
360 def get_forest_domain_name(ctx):
361 '''get netbios name of the domain from the partitions record'''
362 partitions_dn = ctx.samdb.get_partitions_dn()
363 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
364 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
365 return res[0]["nETBIOSName"][0]
367 def get_parent_partition_dn(ctx):
368 '''get the parent domain partition DN from parent DNS name'''
369 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
370 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
371 (ldb.binary_encode(ctx.parent_dnsdomain),
372 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
373 return str(res[0].dn)
375 def get_naming_master(ctx):
376 '''get the parent domain partition DN from parent DNS name'''
377 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
378 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
379 if not 'fSMORoleOwner' in res[0]:
380 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
382 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
384 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
386 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
390 '''get the SID of the connected user. Only works with w2k8 and later,
391 so only used for RODC join'''
392 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
393 binsid = res[0]["tokenGroups"][0]
394 return ctx.samdb.schema_format_value("objectSID", binsid)
396 def dn_exists(ctx, dn):
397 '''check if a DN exists'''
399 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
400 except ldb.LdbError as e5:
401 (enum, estr) = e5.args
402 if enum == ldb.ERR_NO_SUCH_OBJECT:
407 def add_krbtgt_account(ctx):
408 '''RODCs need a special krbtgt account'''
409 print("Adding %s" % ctx.krbtgt_dn)
411 "dn" : ctx.krbtgt_dn,
412 "objectclass" : "user",
413 "useraccountcontrol" : str(samba.dsdb.UF_NORMAL_ACCOUNT |
414 samba.dsdb.UF_ACCOUNTDISABLE),
415 "showinadvancedviewonly" : "TRUE",
416 "description" : "krbtgt for %s" % ctx.samname}
417 ctx.samdb.add(rec, ["rodc_join:1:1"])
419 # now we need to search for the samAccountName attribute on the krbtgt DN,
420 # as this will have been magically set to the krbtgt number
421 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
422 ctx.krbtgt_name = res[0]["samAccountName"][0]
424 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
427 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
428 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
429 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
432 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
433 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
434 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
436 def drsuapi_connect(ctx):
437 '''make a DRSUAPI connection to the naming master'''
438 binding_options = "seal"
439 if ctx.lp.log_level() >= 9:
440 binding_options += ",print"
441 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
442 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
443 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
445 def create_tmp_samdb(ctx):
446 '''create a temporary samdb object for schema queries'''
447 ctx.tmp_schema = Schema(ctx.domsid,
448 schemadn=ctx.schema_dn)
449 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
450 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
452 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
454 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
455 '''build a DsReplicaAttributeCtr object'''
456 r = drsuapi.DsReplicaAttribute()
457 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
461 def DsAddEntry(ctx, recs):
462 '''add a record via the DRSUAPI DsAddEntry call'''
463 if ctx.drsuapi is None:
464 ctx.drsuapi_connect()
465 if ctx.tmp_samdb is None:
466 ctx.create_tmp_samdb()
470 id = drsuapi.DsReplicaObjectIdentifier()
477 if not isinstance(rec[a], list):
481 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
484 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
485 attribute_ctr.num_attributes = len(attrs)
486 attribute_ctr.attributes = attrs
488 object = drsuapi.DsReplicaObject()
489 object.identifier = id
490 object.attribute_ctr = attribute_ctr
492 list_object = drsuapi.DsReplicaObjectListItem()
493 list_object.object = object
494 objects.append(list_object)
496 req2 = drsuapi.DsAddEntryRequest2()
497 req2.first_object = objects[0]
498 prev = req2.first_object
499 for o in objects[1:]:
503 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
505 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
506 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
507 raise RuntimeError("DsAddEntry failed")
508 if ctr.extended_err[0] != werror.WERR_SUCCESS:
509 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
510 raise RuntimeError("DsAddEntry failed")
513 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
514 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
515 if ctr.err_data.info is None:
516 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
518 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
519 ctr.err_data.info.extended_err))
520 raise RuntimeError("DsAddEntry failed")
521 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
522 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
523 raise RuntimeError("DsAddEntry failed")
527 def join_ntdsdsa_obj(ctx):
528 '''return the ntdsdsa object to add'''
530 print("Adding %s" % ctx.ntds_dn)
533 "objectclass" : "nTDSDSA",
534 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
535 "dMDLocation" : ctx.schema_dn}
537 nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
539 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
540 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
542 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
543 rec["msDS-HasDomainNCs"] = ctx.base_dn
546 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
547 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
548 rec["options"] = "37"
550 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
551 rec["HasMasterNCs"] = []
553 if nc in ctx.full_nc_list:
554 rec["HasMasterNCs"].append(nc)
555 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
556 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
558 rec["invocationId"] = ndr_pack(ctx.invocation_id)
562 def join_add_ntdsdsa(ctx):
563 '''add the ntdsdsa object'''
565 rec = ctx.join_ntdsdsa_obj()
567 ctx.samdb.add(rec, ["rodc_join:1:1"])
569 ctx.DsAddEntry([rec])
571 # find the GUID of our NTDS DN
572 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
573 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
575 def join_add_objects(ctx):
576 '''add the various objects needed for the join'''
578 print("Adding %s" % ctx.acct_dn)
581 "objectClass": "computer",
582 "displayname": ctx.samname,
583 "samaccountname" : ctx.samname,
584 "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
585 "dnshostname" : ctx.dnshostname}
586 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
587 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
588 elif ctx.promote_existing:
589 rec['msDS-SupportedEncryptionTypes'] = []
591 rec["managedby"] = ctx.managedby
592 elif ctx.promote_existing:
593 rec["managedby"] = []
595 if ctx.never_reveal_sid:
596 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
597 elif ctx.promote_existing:
598 rec["msDS-NeverRevealGroup"] = []
601 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
602 elif ctx.promote_existing:
603 rec["msDS-RevealOnDemandGroup"] = []
605 if ctx.promote_existing:
606 if ctx.promote_from_dn != ctx.acct_dn:
607 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
608 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
613 ctx.add_krbtgt_account()
616 print("Adding %s" % ctx.server_dn)
619 "objectclass" : "server",
620 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
621 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
622 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
623 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
624 # windows seems to add the dnsHostName later
625 "dnsHostName" : ctx.dnshostname}
628 rec["serverReference"] = ctx.acct_dn
633 # the rest is done after replication
638 ctx.join_add_ntdsdsa()
640 # Add the Replica-Locations or RO-Replica-Locations attributes
641 # TODO Is this supposed to be for the schema partition too?
642 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
643 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
645 base=ctx.samdb.get_partitions_dn(),
646 expression=expr), ctx.domaindns_zone)
648 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
649 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
651 base=ctx.samdb.get_partitions_dn(),
652 expression=expr), ctx.forestdns_zone)
654 for part, zone in (domain, forest):
655 if zone not in ctx.nc_list:
661 attr = "msDS-NC-Replica-Locations"
663 attr = "msDS-NC-RO-Replica-Locations"
665 m[attr] = ldb.MessageElement(ctx.ntds_dn,
666 ldb.FLAG_MOD_ADD, attr)
669 if ctx.connection_dn is not None:
670 print("Adding %s" % ctx.connection_dn)
672 "dn" : ctx.connection_dn,
673 "objectclass" : "nTDSConnection",
674 "enabledconnection" : "TRUE",
676 "fromServer" : ctx.dc_ntds_dn}
680 print("Adding SPNs to %s" % ctx.acct_dn)
682 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
683 for i in range(len(ctx.SPNs)):
684 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
685 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
686 ldb.FLAG_MOD_REPLACE,
687 "servicePrincipalName")
690 # The account password set operation should normally be done over
691 # LDAP. Windows 2000 DCs however allow this only with SSL
692 # connections which are hard to set up and otherwise refuse with
693 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
695 print("Setting account password for %s" % ctx.samname)
697 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
698 % ldb.binary_encode(ctx.samname),
700 force_change_at_next_login=False,
701 username=ctx.samname)
702 except ldb.LdbError as e2:
704 if num != ldb.ERR_UNWILLING_TO_PERFORM:
706 ctx.net.set_password(account_name=ctx.samname,
707 domain_name=ctx.domain_name,
708 newpassword=ctx.acct_pass.encode('utf-8'))
710 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
711 attrs=["msDS-KeyVersionNumber",
713 if "msDS-KeyVersionNumber" in res[0]:
714 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
716 ctx.key_version_number = None
718 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
719 res[0]["objectSid"][0])
721 print("Enabling account")
723 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
724 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
725 ldb.FLAG_MOD_REPLACE,
726 "userAccountControl")
729 if ctx.dns_backend.startswith("BIND9_"):
730 ctx.dnspass = samba.generate_random_password(128, 255)
732 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
733 {"DNSDOMAIN": ctx.dnsdomain,
734 "DOMAINDN": ctx.base_dn,
735 "HOSTNAME" : ctx.myname,
736 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
737 "DNSNAME" : ctx.dnshostname}))
738 for changetype, msg in recs:
739 assert changetype == ldb.CHANGETYPE_NONE
740 dns_acct_dn = msg["dn"]
741 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
743 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
744 del msg["clearTextPassword"]
745 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
746 del msg["isCriticalSystemObject"]
747 # Disable account until password is set
748 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
749 samba.dsdb.UF_ACCOUNTDISABLE)
752 except ldb.LdbError as e:
754 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
757 # The account password set operation should normally be done over
758 # LDAP. Windows 2000 DCs however allow this only with SSL
759 # connections which are hard to set up and otherwise refuse with
760 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
762 print("Setting account password for dns-%s" % ctx.myname)
764 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
765 % ldb.binary_encode(ctx.myname),
767 force_change_at_next_login=False,
768 username=ctx.samname)
769 except ldb.LdbError as e3:
771 if num != ldb.ERR_UNWILLING_TO_PERFORM:
773 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
774 domain_name=ctx.domain_name,
775 newpassword=ctx.dnspass)
777 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
778 attrs=["msDS-KeyVersionNumber"])
779 if "msDS-KeyVersionNumber" in res[0]:
780 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
782 ctx.dns_key_version_number = None
784 def join_add_objects2(ctx):
785 """add the various objects needed for the join, for subdomains post replication"""
787 print("Adding %s" % ctx.partition_dn)
788 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
789 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
791 "dn" : ctx.partition_dn,
792 "objectclass" : "crossRef",
793 "objectCategory" : "CN=Cross-Ref,%s" % ctx.schema_dn,
794 "nCName" : ctx.base_dn,
795 "nETBIOSName" : ctx.domain_name,
796 "dnsRoot": ctx.dnsdomain,
797 "trustParent" : ctx.parent_partition_dn,
798 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
799 "ntSecurityDescriptor" : sd_binary,
802 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
803 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
805 rec2 = ctx.join_ntdsdsa_obj()
807 objects = ctx.DsAddEntry([rec, rec2])
808 if len(objects) != 2:
809 raise DCJoinException("Expected 2 objects from DsAddEntry")
811 ctx.ntds_guid = objects[1].guid
813 print("Replicating partition DN")
814 ctx.repl.replicate(ctx.partition_dn,
815 misc.GUID("00000000-0000-0000-0000-000000000000"),
817 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
818 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
820 print("Replicating NTDS DN")
821 ctx.repl.replicate(ctx.ntds_dn,
822 misc.GUID("00000000-0000-0000-0000-000000000000"),
824 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
825 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
827 def join_provision(ctx):
828 """Provision the local SAM."""
830 print("Calling bare provision")
832 smbconf = ctx.lp.configfile
834 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
835 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
836 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
837 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
838 serverdn=ctx.server_dn, domain=ctx.domain_name,
839 hostname=ctx.myname, domainsid=ctx.domsid,
840 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
841 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
842 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
843 plaintext_secrets=ctx.plaintext_secrets,
844 backend_store=ctx.backend_store
846 print("Provision OK for domain DN %s" % presult.domaindn)
847 ctx.local_samdb = presult.samdb
849 ctx.paths = presult.paths
850 ctx.names = presult.names
852 # Fix up the forestsid, it may be different if we are joining as a subdomain
853 ctx.names.forestsid = ctx.forestsid
855 def join_provision_own_domain(ctx):
856 """Provision the local SAM."""
858 # we now operate exclusively on the local database, which
859 # we need to reopen in order to get the newly created schema
860 print("Reconnecting to local samdb")
861 ctx.samdb = SamDB(url=ctx.local_samdb.url,
862 session_info=system_session(),
863 lp=ctx.local_samdb.lp,
865 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
866 ctx.local_samdb = ctx.samdb
868 ctx.logger.info("Finding domain GUID from ncName")
869 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
870 controls=["extended_dn:1:1", "reveal_internals:0"])
872 if 'nCName' not in res[0]:
873 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
876 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
878 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
880 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
882 ctx.logger.info("Calling own domain provision")
884 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
886 presult = provision_fill(ctx.local_samdb, secrets_ldb,
887 ctx.logger, ctx.names, ctx.paths,
888 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
889 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
890 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
891 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
892 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
893 print("Provision OK for domain %s" % ctx.names.dnsdomain)
895 def join_replicate(ctx):
896 """Replicate the SAM."""
898 print("Starting replication")
899 ctx.local_samdb.transaction_start()
901 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
902 if ctx.ntds_guid is None:
903 print("Using DS_BIND_GUID_W2K3")
904 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
906 destination_dsa_guid = ctx.ntds_guid
909 repl_creds = Credentials()
910 repl_creds.guess(ctx.lp)
911 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
912 repl_creds.set_username(ctx.samname)
913 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
915 repl_creds = ctx.creds
917 binding_options = "seal"
918 if ctx.lp.log_level() >= 9:
919 binding_options += ",print"
920 repl = drs_utils.drs_Replicate(
921 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
922 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
924 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
925 destination_dsa_guid, schema=True, rodc=ctx.RODC,
926 replica_flags=ctx.replica_flags)
927 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
928 destination_dsa_guid, rodc=ctx.RODC,
929 replica_flags=ctx.replica_flags)
930 if not ctx.subdomain:
931 # Replicate first the critical object for the basedn
932 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
933 print("Replicating critical objects from the base DN of the domain")
934 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
935 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
936 destination_dsa_guid, rodc=ctx.RODC,
937 replica_flags=ctx.domain_replica_flags)
938 ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
939 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
940 destination_dsa_guid, rodc=ctx.RODC,
941 replica_flags=ctx.domain_replica_flags)
942 print("Done with always replicated NC (base, config, schema)")
944 # At this point we should already have an entry in the ForestDNS
945 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
946 # indicate that we hold a replica for this NC.
947 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
948 if nc in ctx.nc_list:
949 print("Replicating %s" % (str(nc)))
950 repl.replicate(nc, source_dsa_invocation_id,
951 destination_dsa_guid, rodc=ctx.RODC,
952 replica_flags=ctx.replica_flags)
955 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
956 destination_dsa_guid,
957 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
958 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
959 destination_dsa_guid,
960 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
961 elif ctx.rid_manager_dn != None:
962 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
964 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
965 destination_dsa_guid,
966 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
967 except samba.DsExtendedError as e1:
968 (enum, estr) = e1.args
969 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
970 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
971 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
976 ctx.source_dsa_invocation_id = source_dsa_invocation_id
977 ctx.destination_dsa_guid = destination_dsa_guid
979 print("Committing SAM database")
981 ctx.local_samdb.transaction_cancel()
984 ctx.local_samdb.transaction_commit()
986 def send_DsReplicaUpdateRefs(ctx, dn):
987 r = drsuapi.DsReplicaUpdateRefsRequest1()
988 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
989 r.naming_context.dn = str(dn)
990 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
991 r.naming_context.sid = security.dom_sid("S-0-0")
992 r.dest_dsa_guid = ctx.ntds_guid
993 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
994 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
996 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
998 if ctx.drsuapi is None:
999 ctx.drsuapi_connect()
1001 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1003 def join_add_dns_records(ctx):
1004 """Remotely Add a DNS record to the target DC. We assume that if we
1005 replicate DNS that the server holds the DNS roles and can accept
1008 This avoids issues getting replication going after the DC
1009 first starts as the rest of the domain does not have to
1010 wait for samba_dnsupdate to run successfully.
1012 Specifically, we add the records implied by the DsReplicaUpdateRefs
1015 We do not just run samba_dnsupdate as we want to strictly
1016 operate against the DC we just joined:
1017 - We do not want to query another DNS server
1018 - We do not want to obtain a Kerberos ticket
1019 (as the KDC we select may not be the DC we just joined,
1020 and so may not be in sync with the password we just set)
1021 - We do not wish to set the _ldap records until we have started
1022 - We do not wish to use NTLM (the --use-samba-tool mode forces
1027 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1028 record_type = dnsp.DNS_TYPE_A
1029 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1030 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1032 zone = ctx.dnsdomain
1033 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1035 msdcs_cname = str(ctx.ntds_guid)
1036 cname_target = "%s.%s" % (name, zone)
1037 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1039 ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
1040 (len(IPs), name, zone))
1042 binding_options = "sign"
1043 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1049 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1051 change_owner_sd = security.descriptor()
1052 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1053 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1055 security.DOMAIN_RID_DCS))
1057 # TODO: Remove any old records from the primary DNS name
1060 = dns_conn.DnssrvEnumRecords2(client_version,
1070 except WERRORError as e:
1071 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1077 for record in rec.records:
1078 if record.wType == dnsp.DNS_TYPE_A or \
1079 record.wType == dnsp.DNS_TYPE_AAAA:
1081 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1082 del_rec_buf.rec = record
1084 dns_conn.DnssrvUpdateRecord2(client_version,
1091 except WERRORError as e:
1092 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1098 if IP.find(':') != -1:
1099 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1101 rec = AAAARecord(IP)
1103 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1108 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1109 add_rec_buf.rec = rec
1110 dns_conn.DnssrvUpdateRecord2(client_version,
1119 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1120 (ctx.dns_a_dn, ldap_record) \
1121 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1122 dns_partition=domaindns_zone_dn)
1124 # Make the DC own the DNS record, not the administrator
1125 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1126 controls=["sd_flags:1:%d"
1127 % (security.SECINFO_OWNER
1128 | security.SECINFO_GROUP)])
1132 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1133 % (msdcs_cname, msdcs_zone, cname_target))
1135 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1136 rec = CNameRecord(cname_target)
1137 add_rec_buf.rec = rec
1138 dns_conn.DnssrvUpdateRecord2(client_version,
1146 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1147 (ctx.dns_cname_dn, ldap_record) \
1148 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1149 dns_partition=forestdns_zone_dn)
1151 # Make the DC own the DNS record, not the administrator
1152 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1153 controls=["sd_flags:1:%d"
1154 % (security.SECINFO_OWNER
1155 | security.SECINFO_GROUP)])
1157 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1158 "will be created samba_dnsupdate on first startup")
1161 def join_replicate_new_dns_records(ctx):
1162 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1163 if nc in ctx.nc_list:
1164 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1165 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1166 ctx.ntds_guid, rodc=ctx.RODC,
1167 replica_flags=ctx.replica_flags,
1172 def join_finalise(ctx):
1173 """Finalise the join, mark us synchronised and setup secrets db."""
1175 # FIXME we shouldn't do this in all cases
1177 # If for some reasons we joined in another site than the one of
1178 # DC we just replicated from then we don't need to send the updatereplicateref
1179 # as replication between sites is time based and on the initiative of the
1181 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1182 for nc in ctx.nc_list:
1183 ctx.send_DsReplicaUpdateRefs(nc)
1186 print("Setting RODC invocationId")
1187 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1188 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1189 ctx.behavior_version)
1191 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1192 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1193 ldb.FLAG_MOD_REPLACE,
1195 ctx.local_samdb.modify(m)
1197 # Note: as RODC the invocationId is only stored
1198 # on the RODC itself, the other DCs never see it.
1200 # Thats is why we fix up the replPropertyMetaData stamp
1201 # for the 'invocationId' attribute, we need to change
1202 # the 'version' to '0', this is what windows 2008r2 does as RODC
1204 # This means if the object on a RWDC ever gets a invocationId
1205 # attribute, it will have version '1' (or higher), which will
1206 # will overwrite the RODC local value.
1207 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1211 ctx.logger.info("Setting isSynchronized and dsServiceName")
1213 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1214 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1216 guid = ctx.ntds_guid
1217 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1218 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1219 ctx.local_samdb.modify(m)
1224 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1226 ctx.logger.info("Setting up secrets database")
1227 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1229 dnsdomain=ctx.dnsdomain,
1230 netbiosname=ctx.myname,
1231 domainsid=ctx.domsid,
1232 machinepass=ctx.acct_pass,
1233 secure_channel_type=ctx.secure_channel_type,
1234 key_version_number=ctx.key_version_number)
1236 if ctx.dns_backend.startswith("BIND9_"):
1237 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1238 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1239 dns_backend=ctx.dns_backend,
1240 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1241 targetdir=ctx.targetdir,
1242 key_version_number=ctx.dns_key_version_number)
1244 def join_setup_trusts(ctx):
1245 """provision the local SAM."""
1247 print("Setup domain trusts with server %s" % ctx.server)
1248 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1249 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1252 objectAttr = lsa.ObjectAttribute()
1253 objectAttr.sec_qos = lsa.QosInfo()
1255 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1256 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1258 info = lsa.TrustDomainInfoInfoEx()
1259 info.domain_name.string = ctx.dnsdomain
1260 info.netbios_name.string = ctx.domain_name
1261 info.sid = ctx.domsid
1262 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1263 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1264 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1267 oldname = lsa.String()
1268 oldname.string = ctx.dnsdomain
1269 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1270 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1271 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1272 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1273 except RuntimeError:
1276 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1278 clear_value = drsblobs.AuthInfoClear()
1279 clear_value.size = len(password_blob)
1280 clear_value.password = password_blob
1282 clear_authentication_information = drsblobs.AuthenticationInformation()
1283 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1284 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1285 clear_authentication_information.AuthInfo = clear_value
1287 authentication_information_array = drsblobs.AuthenticationInformationArray()
1288 authentication_information_array.count = 1
1289 authentication_information_array.array = [clear_authentication_information]
1291 outgoing = drsblobs.trustAuthInOutBlob()
1293 outgoing.current = authentication_information_array
1295 trustpass = drsblobs.trustDomainPasswords()
1296 confounder = [3] * 512
1298 for i in range(512):
1299 confounder[i] = random.randint(0, 255)
1301 trustpass.confounder = confounder
1303 trustpass.outgoing = outgoing
1304 trustpass.incoming = outgoing
1306 trustpass_blob = ndr_pack(trustpass)
1308 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1310 auth_blob = lsa.DATA_BUF2()
1311 auth_blob.size = len(encrypted_trustpass)
1312 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1314 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1315 auth_info.auth_blob = auth_blob
1317 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1320 security.SEC_STD_DELETE)
1323 "dn" : "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1324 "objectclass" : "trustedDomain",
1325 "trustType" : str(info.trust_type),
1326 "trustAttributes" : str(info.trust_attributes),
1327 "trustDirection" : str(info.trust_direction),
1328 "flatname" : ctx.forest_domain_name,
1329 "trustPartner" : ctx.dnsforest,
1330 "trustAuthIncoming" : ndr_pack(outgoing),
1331 "trustAuthOutgoing" : ndr_pack(outgoing),
1332 "securityIdentifier" : ndr_pack(ctx.forestsid)
1334 ctx.local_samdb.add(rec)
1337 "dn" : "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1338 "objectclass" : "user",
1339 "userAccountControl" : str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1340 "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le'),
1341 "samAccountName" : "%s$" % ctx.forest_domain_name
1343 ctx.local_samdb.add(rec)
1346 def build_nc_lists(ctx):
1347 # nc_list is the list of naming context (NC) for which we will
1348 # replicate in and send a updateRef command to the partner DC
1350 # full_nc_list is the list of naming context (NC) we hold
1351 # read/write copies of. These are not subsets of each other.
1352 ctx.nc_list = [ ctx.config_dn, ctx.schema_dn ]
1353 ctx.full_nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
1355 if ctx.subdomain and ctx.dns_backend != "NONE":
1356 ctx.full_nc_list += [ctx.domaindns_zone]
1358 elif not ctx.subdomain:
1359 ctx.nc_list += [ctx.base_dn]
1361 if ctx.dns_backend != "NONE":
1362 ctx.nc_list += [ctx.domaindns_zone]
1363 ctx.nc_list += [ctx.forestdns_zone]
1364 ctx.full_nc_list += [ctx.domaindns_zone]
1365 ctx.full_nc_list += [ctx.forestdns_zone]
1368 ctx.build_nc_lists()
1370 if ctx.promote_existing:
1371 ctx.promote_possible()
1373 ctx.cleanup_old_join()
1376 ctx.join_add_objects()
1377 ctx.join_provision()
1378 ctx.join_replicate()
1380 ctx.join_add_objects2()
1381 ctx.join_provision_own_domain()
1382 ctx.join_setup_trusts()
1384 if ctx.dns_backend != "NONE":
1385 ctx.join_add_dns_records()
1386 ctx.join_replicate_new_dns_records()
1391 print("Join failed - cleaning up")
1394 ctx.cleanup_old_join()
1398 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1399 targetdir=None, domain=None, domain_critical_only=False,
1400 machinepass=None, use_ntvfs=False, dns_backend=None,
1401 promote_existing=False, plaintext_secrets=False,
1402 backend_store=None):
1403 """Join as a RODC."""
1405 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1406 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1407 promote_existing, plaintext_secrets,
1408 backend_store=backend_store)
1410 lp.set("workgroup", ctx.domain_name)
1411 logger.info("workgroup is %s" % ctx.domain_name)
1413 lp.set("realm", ctx.realm)
1414 logger.info("realm is %s" % ctx.realm)
1416 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1418 # setup some defaults for accounts that should be replicated to this RODC
1419 ctx.never_reveal_sid = [
1420 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1421 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1422 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1423 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1424 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1425 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1427 mysid = ctx.get_mysid()
1428 admin_dn = "<SID=%s>" % mysid
1429 ctx.managedby = admin_dn
1431 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1432 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1433 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1435 ctx.SPNs.extend([ "RestrictedKrbHost/%s" % ctx.myname,
1436 "RestrictedKrbHost/%s" % ctx.dnshostname ])
1438 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1439 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1441 ctx.replica_flags |= ( drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1442 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1443 ctx.domain_replica_flags = ctx.replica_flags
1444 if domain_critical_only:
1445 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1449 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1452 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1453 targetdir=None, domain=None, domain_critical_only=False,
1454 machinepass=None, use_ntvfs=False, dns_backend=None,
1455 promote_existing=False, plaintext_secrets=False,
1456 backend_store=None):
1458 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1459 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1460 promote_existing, plaintext_secrets,
1461 backend_store=backend_store)
1463 lp.set("workgroup", ctx.domain_name)
1464 logger.info("workgroup is %s" % ctx.domain_name)
1466 lp.set("realm", ctx.realm)
1467 logger.info("realm is %s" % ctx.realm)
1469 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1471 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1472 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1474 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1475 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1476 ctx.domain_replica_flags = ctx.replica_flags
1477 if domain_critical_only:
1478 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1481 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1483 def join_clone(logger=None, server=None, creds=None, lp=None,
1484 targetdir=None, domain=None, include_secrets=False,
1485 dns_backend="NONE"):
1486 """Creates a local clone of a remote DC."""
1487 ctx = DCCloneContext(logger, server, creds, lp, targetdir=targetdir,
1488 domain=domain, dns_backend=dns_backend,
1489 include_secrets=include_secrets)
1491 lp.set("workgroup", ctx.domain_name)
1492 logger.info("workgroup is %s" % ctx.domain_name)
1494 lp.set("realm", ctx.realm)
1495 logger.info("realm is %s" % ctx.realm)
1498 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1500 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1501 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1502 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1503 dns_backend=None, plaintext_secrets=False,
1504 backend_store=None):
1506 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1507 targetdir, parent_domain, machinepass, use_ntvfs,
1508 dns_backend, plaintext_secrets,
1509 backend_store=backend_store)
1510 ctx.subdomain = True
1511 if adminpass is None:
1512 ctx.adminpass = samba.generate_random_password(12, 32)
1514 ctx.adminpass = adminpass
1515 ctx.parent_domain_name = ctx.domain_name
1516 ctx.domain_name = netbios_domain
1517 ctx.realm = dnsdomain
1518 ctx.parent_dnsdomain = ctx.dnsdomain
1519 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1520 ctx.dnsdomain = dnsdomain
1521 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1522 ctx.naming_master = ctx.get_naming_master()
1523 if ctx.naming_master != ctx.server:
1524 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1525 ctx.server = ctx.naming_master
1526 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1527 session_info=system_session(),
1528 credentials=ctx.creds, lp=ctx.lp)
1529 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1531 ctx.server = res[0]["dnsHostName"]
1532 logger.info("DNS name of new naming master is %s" % ctx.server)
1534 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1535 ctx.forestsid = ctx.domsid
1536 ctx.domsid = security.random_sid()
1538 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1539 # Windows uses 240 bytes as UTF16 so we do
1540 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1542 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1544 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1545 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1547 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1548 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1549 ctx.domain_replica_flags = ctx.replica_flags
1552 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1555 class DCCloneContext(DCJoinContext):
1556 """Clones a remote DC."""
1558 def __init__(ctx, logger=None, server=None, creds=None, lp=None,
1559 targetdir=None, domain=None, dns_backend=None,
1560 include_secrets=False):
1561 super(DCCloneContext, ctx).__init__(logger, server, creds, lp,
1562 targetdir=targetdir, domain=domain,
1563 dns_backend=dns_backend)
1565 # As we don't want to create or delete these DNs, we set them to None
1566 ctx.server_dn = None
1569 ctx.myname = ctx.server.split('.')[0]
1570 ctx.ntds_guid = None
1571 ctx.rid_manager_dn = None
1574 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
1576 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1577 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1578 if not include_secrets:
1579 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1580 ctx.domain_replica_flags = ctx.replica_flags
1582 def join_finalise(ctx):
1583 ctx.logger.info("Setting isSynchronized and dsServiceName")
1585 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1586 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE,
1589 # We want to appear to be the server we just cloned
1590 guid = ctx.remote_dc_ntds_guid
1591 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1592 ldb.FLAG_MOD_REPLACE,
1594 ctx.local_samdb.modify(m)
1597 ctx.build_nc_lists()
1599 # When cloning a DC, we just want to provision a DC locally, then
1600 # grab the remote DC's entire DB via DRS replication
1601 ctx.join_provision()
1602 ctx.join_replicate()