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
51 class DCJoinException(Exception):
53 def __init__(self, msg):
54 super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
57 class DCJoinContext(object):
58 """Perform a DC join."""
60 def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
61 netbios_name=None, targetdir=None, domain=None,
62 machinepass=None, use_ntvfs=False, dns_backend=None,
63 promote_existing=False, plaintext_secrets=False,
64 backend_store=None, forced_local_samdb=None):
66 site = "Default-First-Site-Name"
72 ctx.targetdir = targetdir
73 ctx.use_ntvfs = use_ntvfs
74 ctx.plaintext_secrets = plaintext_secrets
75 ctx.backend_store = backend_store
77 ctx.promote_existing = promote_existing
78 ctx.promote_from_dn = None
83 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
84 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
87 ctx.forced_local_samdb = forced_local_samdb
89 if forced_local_samdb:
90 ctx.samdb = forced_local_samdb
91 ctx.server = ctx.samdb.url
94 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
95 ctx.server = ctx.find_dc(domain)
96 ctx.logger.info("Found DC %s" % ctx.server)
97 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
98 session_info=system_session(),
99 credentials=ctx.creds, lp=ctx.lp)
102 ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
103 except ldb.LdbError as e4:
104 (enum, estr) = e4.args
105 raise DCJoinException(estr)
107 ctx.base_dn = str(ctx.samdb.get_default_basedn())
108 ctx.root_dn = str(ctx.samdb.get_root_basedn())
109 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
110 ctx.config_dn = str(ctx.samdb.get_config_basedn())
111 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
112 ctx.forestsid = ctx.domsid
113 ctx.domain_name = ctx.get_domain_name()
114 ctx.forest_domain_name = ctx.get_forest_domain_name()
115 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
117 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
118 ctx.dc_dnsHostName = ctx.get_dnsHostName()
119 ctx.behavior_version = ctx.get_behavior_version()
121 if machinepass is not None:
122 ctx.acct_pass = machinepass
124 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
126 ctx.dnsdomain = ctx.samdb.domain_dns_name()
128 # the following are all dependent on the new DC's netbios_name (which
129 # we expect to always be specified, except when cloning a DC)
131 # work out the DNs of all the objects we will be adding
132 ctx.myname = netbios_name
133 ctx.samname = "%s$" % ctx.myname
134 ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
135 ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
136 ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
137 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
138 ctx.dnsforest = ctx.samdb.forest_dns_name()
140 topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
141 if ctx.dn_exists(topology_base):
142 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
144 ctx.topology_dn = None
146 ctx.SPNs = ["HOST/%s" % ctx.myname,
147 "HOST/%s" % ctx.dnshostname,
148 "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest)]
150 res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
151 attrs=["rIDManagerReference"],
154 ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
156 ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
157 ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
159 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
160 res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
162 base=ctx.samdb.get_partitions_dn(),
164 if dns_backend is None:
165 ctx.dns_backend = "NONE"
167 if len(res_domaindns) == 0:
168 ctx.dns_backend = "NONE"
169 print("NO DNS zone information found in source domain, not replicating DNS")
171 ctx.dns_backend = dns_backend
173 ctx.realm = ctx.dnsdomain
177 ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
178 drsuapi.DRSUAPI_DRS_PER_SYNC |
179 drsuapi.DRSUAPI_DRS_GET_ANC |
180 drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
181 drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
183 # these elements are optional
184 ctx.never_reveal_sid = None
185 ctx.reveal_sid = None
186 ctx.connection_dn = None
191 ctx.subdomain = False
193 ctx.partition_dn = None
196 ctx.dns_cname_dn = None
198 # Do not normally register 127. addresses but allow override for selftest
199 ctx.force_all_ips = False
201 def del_noerror(ctx, dn, recursive=False):
204 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
208 ctx.del_noerror(r.dn, recursive=True)
211 print("Deleted %s" % dn)
215 def cleanup_old_accounts(ctx, force=False):
216 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
217 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
218 attrs=["msDS-krbTgtLink", "objectSID"])
223 creds = Credentials()
226 creds.set_machine_account(ctx.lp)
227 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
228 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
229 session_info=system_session(),
230 credentials=creds, lp=ctx.lp)
234 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
235 if token_res[0]["tokenGroups"][0] \
236 == res[0]["objectSID"][0]:
237 raise DCJoinException("Not removing account %s which "
238 "looks like a Samba DC account "
239 "matching the password we already have. "
240 "To override, remove secrets.ldb and secrets.tdb"
243 ctx.del_noerror(res[0].dn, recursive=True)
245 if "msDS-Krbtgtlink" in res[0]:
246 new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
247 ctx.del_noerror(ctx.new_krbtgt_dn)
249 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
250 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
251 (ldb.binary_encode("dns-%s" % ctx.myname),
252 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
255 ctx.del_noerror(res[0].dn, recursive=True)
257 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
258 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
261 raise DCJoinException("Not removing account %s which looks like "
262 "a Samba DNS service account but does not "
263 "have servicePrincipalName=%s" %
264 (ldb.binary_encode("dns-%s" % ctx.myname),
265 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
267 def cleanup_old_join(ctx, force=False):
268 """Remove any DNs from a previous join."""
269 # find the krbtgt link
270 if not ctx.subdomain:
271 ctx.cleanup_old_accounts(force=force)
273 if ctx.connection_dn is not None:
274 ctx.del_noerror(ctx.connection_dn)
275 if ctx.krbtgt_dn is not None:
276 ctx.del_noerror(ctx.krbtgt_dn)
277 ctx.del_noerror(ctx.ntds_dn)
278 ctx.del_noerror(ctx.server_dn, recursive=True)
280 ctx.del_noerror(ctx.topology_dn)
282 ctx.del_noerror(ctx.partition_dn)
285 binding_options = "sign"
286 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
289 objectAttr = lsa.ObjectAttribute()
290 objectAttr.sec_qos = lsa.QosInfo()
292 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
293 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
296 name.string = ctx.realm
297 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
299 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
302 name.string = ctx.forest_domain_name
303 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
305 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
308 ctx.del_noerror(ctx.dns_a_dn)
311 ctx.del_noerror(ctx.dns_cname_dn)
313 def promote_possible(ctx):
314 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
316 # This shouldn't happen
317 raise Exception("Can not promote into a subdomain")
319 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
320 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
321 attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
323 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
324 if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
325 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
326 if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
327 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
329 ctx.promote_from_dn = res[0].dn
331 def find_dc(ctx, domain):
332 """find a writeable DC for the given domain"""
334 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
335 except NTSTATUSError as error:
336 raise Exception("Failed to find a writeable DC for domain '%s': %s" %
339 raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
340 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
341 ctx.site = ctx.cldap_ret.client_site
342 return ctx.cldap_ret.pdc_dns_name
344 def get_behavior_version(ctx):
345 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
346 if "msDS-Behavior-Version" in res[0]:
347 return int(res[0]["msDS-Behavior-Version"][0])
349 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
351 def get_dnsHostName(ctx):
352 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
353 return res[0]["dnsHostName"][0]
355 def get_domain_name(ctx):
356 '''get netbios name of the domain from the partitions record'''
357 partitions_dn = ctx.samdb.get_partitions_dn()
358 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
359 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
360 return res[0]["nETBIOSName"][0]
362 def get_forest_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_root_basedn())))
367 return res[0]["nETBIOSName"][0]
369 def get_parent_partition_dn(ctx):
370 '''get the parent domain partition DN from parent DNS name'''
371 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
372 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
373 (ldb.binary_encode(ctx.parent_dnsdomain),
374 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
375 return str(res[0].dn)
377 def get_naming_master(ctx):
378 '''get the parent domain partition DN from parent DNS name'''
379 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
380 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
381 if not 'fSMORoleOwner' in res[0]:
382 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
384 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
386 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
388 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
392 '''get the SID of the connected user. Only works with w2k8 and later,
393 so only used for RODC join'''
394 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
395 binsid = res[0]["tokenGroups"][0]
396 return ctx.samdb.schema_format_value("objectSID", binsid)
398 def dn_exists(ctx, dn):
399 '''check if a DN exists'''
401 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
402 except ldb.LdbError as e5:
403 (enum, estr) = e5.args
404 if enum == ldb.ERR_NO_SUCH_OBJECT:
409 def add_krbtgt_account(ctx):
410 '''RODCs need a special krbtgt account'''
411 print("Adding %s" % ctx.krbtgt_dn)
414 "objectclass": "user",
415 "useraccountcontrol": str(samba.dsdb.UF_NORMAL_ACCOUNT |
416 samba.dsdb.UF_ACCOUNTDISABLE),
417 "showinadvancedviewonly": "TRUE",
418 "description": "krbtgt for %s" % ctx.samname}
419 ctx.samdb.add(rec, ["rodc_join:1:1"])
421 # now we need to search for the samAccountName attribute on the krbtgt DN,
422 # as this will have been magically set to the krbtgt number
423 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
424 ctx.krbtgt_name = res[0]["samAccountName"][0]
426 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
429 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
430 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
431 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
434 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
435 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
436 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
438 def drsuapi_connect(ctx):
439 '''make a DRSUAPI connection to the naming master'''
440 binding_options = "seal"
441 if ctx.lp.log_level() >= 9:
442 binding_options += ",print"
443 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
444 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
445 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
447 def create_tmp_samdb(ctx):
448 '''create a temporary samdb object for schema queries'''
449 ctx.tmp_schema = Schema(ctx.domsid,
450 schemadn=ctx.schema_dn)
451 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
452 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
454 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
456 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
457 '''build a DsReplicaAttributeCtr object'''
458 r = drsuapi.DsReplicaAttribute()
459 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
462 def DsAddEntry(ctx, recs):
463 '''add a record via the DRSUAPI DsAddEntry call'''
464 if ctx.drsuapi is None:
465 ctx.drsuapi_connect()
466 if ctx.tmp_samdb is None:
467 ctx.create_tmp_samdb()
471 id = drsuapi.DsReplicaObjectIdentifier()
478 if not isinstance(rec[a], list):
482 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
485 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
486 attribute_ctr.num_attributes = len(attrs)
487 attribute_ctr.attributes = attrs
489 object = drsuapi.DsReplicaObject()
490 object.identifier = id
491 object.attribute_ctr = attribute_ctr
493 list_object = drsuapi.DsReplicaObjectListItem()
494 list_object.object = object
495 objects.append(list_object)
497 req2 = drsuapi.DsAddEntryRequest2()
498 req2.first_object = objects[0]
499 prev = req2.first_object
500 for o in objects[1:]:
504 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
506 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
507 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
508 raise RuntimeError("DsAddEntry failed")
509 if ctr.extended_err[0] != werror.WERR_SUCCESS:
510 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
511 raise RuntimeError("DsAddEntry failed")
514 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
515 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
516 if ctr.err_data.info is None:
517 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
519 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
520 ctr.err_data.info.extended_err))
521 raise RuntimeError("DsAddEntry failed")
522 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
523 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
524 raise RuntimeError("DsAddEntry failed")
528 def join_ntdsdsa_obj(ctx):
529 '''return the ntdsdsa object to add'''
531 print("Adding %s" % ctx.ntds_dn)
534 "objectclass": "nTDSDSA",
535 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
536 "dMDLocation": ctx.schema_dn}
538 nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
540 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
541 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
543 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
544 rec["msDS-HasDomainNCs"] = ctx.base_dn
547 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
548 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
549 rec["options"] = "37"
551 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
552 rec["HasMasterNCs"] = []
554 if nc in ctx.full_nc_list:
555 rec["HasMasterNCs"].append(nc)
556 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
557 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
559 rec["invocationId"] = ndr_pack(ctx.invocation_id)
563 def join_add_ntdsdsa(ctx):
564 '''add the ntdsdsa object'''
566 rec = ctx.join_ntdsdsa_obj()
567 if ctx.forced_local_samdb:
568 ctx.samdb.add(rec, controls=["relax:0"])
570 ctx.samdb.add(rec, ["rodc_join:1:1"])
572 ctx.DsAddEntry([rec])
574 # find the GUID of our NTDS DN
575 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
576 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
578 def join_add_objects(ctx, specified_sid=None):
579 '''add the various objects needed for the join'''
581 print("Adding %s" % ctx.acct_dn)
584 "objectClass": "computer",
585 "displayname": ctx.samname,
586 "samaccountname": ctx.samname,
587 "userAccountControl": str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
588 "dnshostname": ctx.dnshostname}
589 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
590 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
591 elif ctx.promote_existing:
592 rec['msDS-SupportedEncryptionTypes'] = []
594 rec["managedby"] = ctx.managedby
595 elif ctx.promote_existing:
596 rec["managedby"] = []
598 if ctx.never_reveal_sid:
599 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
600 elif ctx.promote_existing:
601 rec["msDS-NeverRevealGroup"] = []
604 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
605 elif ctx.promote_existing:
606 rec["msDS-RevealOnDemandGroup"] = []
609 rec["objectSid"] = ndr_pack(specified_sid)
611 if ctx.promote_existing:
612 if ctx.promote_from_dn != ctx.acct_dn:
613 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
614 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
617 if specified_sid is not None:
618 controls = ["relax:0"]
619 ctx.samdb.add(rec, controls=controls)
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 backend_store=ctx.backend_store
855 print("Provision OK for domain DN %s" % presult.domaindn)
856 ctx.local_samdb = presult.samdb
858 ctx.paths = presult.paths
859 ctx.names = presult.names
861 # Fix up the forestsid, it may be different if we are joining as a subdomain
862 ctx.names.forestsid = ctx.forestsid
864 def join_provision_own_domain(ctx):
865 """Provision the local SAM."""
867 # we now operate exclusively on the local database, which
868 # we need to reopen in order to get the newly created schema
869 print("Reconnecting to local samdb")
870 ctx.samdb = SamDB(url=ctx.local_samdb.url,
871 session_info=system_session(),
872 lp=ctx.local_samdb.lp,
874 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
875 ctx.local_samdb = ctx.samdb
877 ctx.logger.info("Finding domain GUID from ncName")
878 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
879 controls=["extended_dn:1:1", "reveal_internals:0"])
881 if 'nCName' not in res[0]:
882 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
885 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
887 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
889 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
891 ctx.logger.info("Calling own domain provision")
893 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
895 presult = provision_fill(ctx.local_samdb, secrets_ldb,
896 ctx.logger, ctx.names, ctx.paths,
897 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
898 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
899 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
900 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
901 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
902 print("Provision OK for domain %s" % ctx.names.dnsdomain)
904 def create_replicator(ctx, repl_creds, binding_options):
905 '''Creates a new DRS object for managing replications'''
906 return drs_utils.drs_Replicate(
907 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
908 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
910 def join_replicate(ctx):
911 """Replicate the SAM."""
913 print("Starting replication")
914 ctx.local_samdb.transaction_start()
916 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
917 if ctx.ntds_guid is None:
918 print("Using DS_BIND_GUID_W2K3")
919 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
921 destination_dsa_guid = ctx.ntds_guid
924 repl_creds = Credentials()
925 repl_creds.guess(ctx.lp)
926 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
927 repl_creds.set_username(ctx.samname)
928 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
930 repl_creds = ctx.creds
932 binding_options = "seal"
933 if ctx.lp.log_level() >= 9:
934 binding_options += ",print"
936 repl = ctx.create_replicator(repl_creds, binding_options)
938 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
939 destination_dsa_guid, schema=True, rodc=ctx.RODC,
940 replica_flags=ctx.replica_flags)
941 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
942 destination_dsa_guid, rodc=ctx.RODC,
943 replica_flags=ctx.replica_flags)
944 if not ctx.subdomain:
945 # Replicate first the critical object for the basedn
946 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
947 print("Replicating critical objects from the base DN of the domain")
948 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
949 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
950 destination_dsa_guid, rodc=ctx.RODC,
951 replica_flags=ctx.domain_replica_flags)
952 ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
953 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
954 destination_dsa_guid, rodc=ctx.RODC,
955 replica_flags=ctx.domain_replica_flags)
956 print("Done with always replicated NC (base, config, schema)")
958 # At this point we should already have an entry in the ForestDNS
959 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
960 # indicate that we hold a replica for this NC.
961 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
962 if nc in ctx.nc_list:
963 print("Replicating %s" % (str(nc)))
964 repl.replicate(nc, source_dsa_invocation_id,
965 destination_dsa_guid, rodc=ctx.RODC,
966 replica_flags=ctx.replica_flags)
969 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
970 destination_dsa_guid,
971 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
972 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
973 destination_dsa_guid,
974 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
975 elif ctx.rid_manager_dn != None:
976 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
978 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
979 destination_dsa_guid,
980 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
981 except samba.DsExtendedError as e1:
982 (enum, estr) = e1.args
983 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
984 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
985 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
990 ctx.source_dsa_invocation_id = source_dsa_invocation_id
991 ctx.destination_dsa_guid = destination_dsa_guid
993 print("Committing SAM database")
995 ctx.local_samdb.transaction_cancel()
998 ctx.local_samdb.transaction_commit()
1000 def send_DsReplicaUpdateRefs(ctx, dn):
1001 r = drsuapi.DsReplicaUpdateRefsRequest1()
1002 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
1003 r.naming_context.dn = str(dn)
1004 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
1005 r.naming_context.sid = security.dom_sid("S-0-0")
1006 r.dest_dsa_guid = ctx.ntds_guid
1007 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1008 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1010 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1012 if ctx.drsuapi is None:
1013 ctx.drsuapi_connect()
1015 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1017 def join_add_dns_records(ctx):
1018 """Remotely Add a DNS record to the target DC. We assume that if we
1019 replicate DNS that the server holds the DNS roles and can accept
1022 This avoids issues getting replication going after the DC
1023 first starts as the rest of the domain does not have to
1024 wait for samba_dnsupdate to run successfully.
1026 Specifically, we add the records implied by the DsReplicaUpdateRefs
1029 We do not just run samba_dnsupdate as we want to strictly
1030 operate against the DC we just joined:
1031 - We do not want to query another DNS server
1032 - We do not want to obtain a Kerberos ticket
1033 (as the KDC we select may not be the DC we just joined,
1034 and so may not be in sync with the password we just set)
1035 - We do not wish to set the _ldap records until we have started
1036 - We do not wish to use NTLM (the --use-samba-tool mode forces
1041 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1042 record_type = dnsp.DNS_TYPE_A
1043 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1044 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1046 zone = ctx.dnsdomain
1047 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1049 msdcs_cname = str(ctx.ntds_guid)
1050 cname_target = "%s.%s" % (name, zone)
1051 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1053 ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
1054 (len(IPs), name, zone))
1056 binding_options = "sign"
1057 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1062 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1064 change_owner_sd = security.descriptor()
1065 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1066 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1068 security.DOMAIN_RID_DCS))
1070 # TODO: Remove any old records from the primary DNS name
1073 = dns_conn.DnssrvEnumRecords2(client_version,
1083 except WERRORError as e:
1084 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1090 for record in rec.records:
1091 if record.wType == dnsp.DNS_TYPE_A or \
1092 record.wType == dnsp.DNS_TYPE_AAAA:
1094 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1095 del_rec_buf.rec = record
1097 dns_conn.DnssrvUpdateRecord2(client_version,
1104 except WERRORError as e:
1105 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1111 if IP.find(':') != -1:
1112 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1114 rec = AAAARecord(IP)
1116 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1121 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1122 add_rec_buf.rec = rec
1123 dns_conn.DnssrvUpdateRecord2(client_version,
1132 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1133 (ctx.dns_a_dn, ldap_record) \
1134 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1135 dns_partition=domaindns_zone_dn)
1137 # Make the DC own the DNS record, not the administrator
1138 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1139 controls=["sd_flags:1:%d"
1140 % (security.SECINFO_OWNER
1141 | security.SECINFO_GROUP)])
1144 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1145 % (msdcs_cname, msdcs_zone, cname_target))
1147 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1148 rec = CNameRecord(cname_target)
1149 add_rec_buf.rec = rec
1150 dns_conn.DnssrvUpdateRecord2(client_version,
1158 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1159 (ctx.dns_cname_dn, ldap_record) \
1160 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1161 dns_partition=forestdns_zone_dn)
1163 # Make the DC own the DNS record, not the administrator
1164 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1165 controls=["sd_flags:1:%d"
1166 % (security.SECINFO_OWNER
1167 | security.SECINFO_GROUP)])
1169 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1170 "will be created samba_dnsupdate on first startup")
1172 def join_replicate_new_dns_records(ctx):
1173 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1174 if nc in ctx.nc_list:
1175 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1176 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1177 ctx.ntds_guid, rodc=ctx.RODC,
1178 replica_flags=ctx.replica_flags,
1181 def join_finalise(ctx):
1182 """Finalise the join, mark us synchronised and setup secrets db."""
1184 # FIXME we shouldn't do this in all cases
1186 # If for some reasons we joined in another site than the one of
1187 # DC we just replicated from then we don't need to send the updatereplicateref
1188 # as replication between sites is time based and on the initiative of the
1190 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1191 for nc in ctx.nc_list:
1192 ctx.send_DsReplicaUpdateRefs(nc)
1195 print("Setting RODC invocationId")
1196 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1197 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1198 ctx.behavior_version)
1200 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1201 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1202 ldb.FLAG_MOD_REPLACE,
1204 ctx.local_samdb.modify(m)
1206 # Note: as RODC the invocationId is only stored
1207 # on the RODC itself, the other DCs never see it.
1209 # Thats is why we fix up the replPropertyMetaData stamp
1210 # for the 'invocationId' attribute, we need to change
1211 # the 'version' to '0', this is what windows 2008r2 does as RODC
1213 # This means if the object on a RWDC ever gets a invocationId
1214 # attribute, it will have version '1' (or higher), which will
1215 # will overwrite the RODC local value.
1216 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1220 ctx.logger.info("Setting isSynchronized and dsServiceName")
1222 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1223 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1225 guid = ctx.ntds_guid
1226 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1227 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1228 ctx.local_samdb.modify(m)
1233 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1235 ctx.logger.info("Setting up secrets database")
1236 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1238 dnsdomain=ctx.dnsdomain,
1239 netbiosname=ctx.myname,
1240 domainsid=ctx.domsid,
1241 machinepass=ctx.acct_pass,
1242 secure_channel_type=ctx.secure_channel_type,
1243 key_version_number=ctx.key_version_number)
1245 if ctx.dns_backend.startswith("BIND9_"):
1246 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1247 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1248 dns_backend=ctx.dns_backend,
1249 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1250 targetdir=ctx.targetdir,
1251 key_version_number=ctx.dns_key_version_number)
1253 def join_setup_trusts(ctx):
1254 """provision the local SAM."""
1256 print("Setup domain trusts with server %s" % ctx.server)
1257 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1258 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1261 objectAttr = lsa.ObjectAttribute()
1262 objectAttr.sec_qos = lsa.QosInfo()
1264 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1265 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1267 info = lsa.TrustDomainInfoInfoEx()
1268 info.domain_name.string = ctx.dnsdomain
1269 info.netbios_name.string = ctx.domain_name
1270 info.sid = ctx.domsid
1271 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1272 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1273 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1276 oldname = lsa.String()
1277 oldname.string = ctx.dnsdomain
1278 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1279 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1280 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1281 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1282 except RuntimeError:
1285 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1287 clear_value = drsblobs.AuthInfoClear()
1288 clear_value.size = len(password_blob)
1289 clear_value.password = password_blob
1291 clear_authentication_information = drsblobs.AuthenticationInformation()
1292 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1293 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1294 clear_authentication_information.AuthInfo = clear_value
1296 authentication_information_array = drsblobs.AuthenticationInformationArray()
1297 authentication_information_array.count = 1
1298 authentication_information_array.array = [clear_authentication_information]
1300 outgoing = drsblobs.trustAuthInOutBlob()
1302 outgoing.current = authentication_information_array
1304 trustpass = drsblobs.trustDomainPasswords()
1305 confounder = [3] * 512
1307 for i in range(512):
1308 confounder[i] = random.randint(0, 255)
1310 trustpass.confounder = confounder
1312 trustpass.outgoing = outgoing
1313 trustpass.incoming = outgoing
1315 trustpass_blob = ndr_pack(trustpass)
1317 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1319 auth_blob = lsa.DATA_BUF2()
1320 auth_blob.size = len(encrypted_trustpass)
1321 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1323 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1324 auth_info.auth_blob = auth_blob
1326 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1329 security.SEC_STD_DELETE)
1332 "dn": "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1333 "objectclass": "trustedDomain",
1334 "trustType": str(info.trust_type),
1335 "trustAttributes": str(info.trust_attributes),
1336 "trustDirection": str(info.trust_direction),
1337 "flatname": ctx.forest_domain_name,
1338 "trustPartner": ctx.dnsforest,
1339 "trustAuthIncoming": ndr_pack(outgoing),
1340 "trustAuthOutgoing": ndr_pack(outgoing),
1341 "securityIdentifier": ndr_pack(ctx.forestsid)
1343 ctx.local_samdb.add(rec)
1346 "dn": "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1347 "objectclass": "user",
1348 "userAccountControl": str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1349 "clearTextPassword": ctx.trustdom_pass.encode('utf-16-le'),
1350 "samAccountName": "%s$" % ctx.forest_domain_name
1352 ctx.local_samdb.add(rec)
1354 def build_nc_lists(ctx):
1355 # nc_list is the list of naming context (NC) for which we will
1356 # replicate in and send a updateRef command to the partner DC
1358 # full_nc_list is the list of naming context (NC) we hold
1359 # read/write copies of. These are not subsets of each other.
1360 ctx.nc_list = [ctx.config_dn, ctx.schema_dn]
1361 ctx.full_nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
1363 if ctx.subdomain and ctx.dns_backend != "NONE":
1364 ctx.full_nc_list += [ctx.domaindns_zone]
1366 elif not ctx.subdomain:
1367 ctx.nc_list += [ctx.base_dn]
1369 if ctx.dns_backend != "NONE":
1370 ctx.nc_list += [ctx.domaindns_zone]
1371 ctx.nc_list += [ctx.forestdns_zone]
1372 ctx.full_nc_list += [ctx.domaindns_zone]
1373 ctx.full_nc_list += [ctx.forestdns_zone]
1376 ctx.build_nc_lists()
1378 if ctx.promote_existing:
1379 ctx.promote_possible()
1381 ctx.cleanup_old_join()
1384 ctx.join_add_objects()
1385 ctx.join_provision()
1386 ctx.join_replicate()
1388 ctx.join_add_objects2()
1389 ctx.join_provision_own_domain()
1390 ctx.join_setup_trusts()
1392 if ctx.dns_backend != "NONE":
1393 ctx.join_add_dns_records()
1394 ctx.join_replicate_new_dns_records()
1399 print("Join failed - cleaning up")
1402 ctx.cleanup_old_join()
1406 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1407 targetdir=None, domain=None, domain_critical_only=False,
1408 machinepass=None, use_ntvfs=False, dns_backend=None,
1409 promote_existing=False, plaintext_secrets=False,
1410 backend_store=None):
1411 """Join as a RODC."""
1413 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1414 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1415 promote_existing, plaintext_secrets,
1416 backend_store=backend_store)
1418 lp.set("workgroup", ctx.domain_name)
1419 logger.info("workgroup is %s" % ctx.domain_name)
1421 lp.set("realm", ctx.realm)
1422 logger.info("realm is %s" % ctx.realm)
1424 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1426 # setup some defaults for accounts that should be replicated to this RODC
1427 ctx.never_reveal_sid = [
1428 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1429 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1430 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1431 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1432 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1433 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1435 mysid = ctx.get_mysid()
1436 admin_dn = "<SID=%s>" % mysid
1437 ctx.managedby = admin_dn
1439 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1440 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1441 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1443 ctx.SPNs.extend(["RestrictedKrbHost/%s" % ctx.myname,
1444 "RestrictedKrbHost/%s" % ctx.dnshostname])
1446 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1447 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1449 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1450 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1451 ctx.domain_replica_flags = ctx.replica_flags
1452 if domain_critical_only:
1453 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1457 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1460 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1461 targetdir=None, domain=None, domain_critical_only=False,
1462 machinepass=None, use_ntvfs=False, dns_backend=None,
1463 promote_existing=False, plaintext_secrets=False,
1464 backend_store=None):
1466 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1467 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1468 promote_existing, plaintext_secrets,
1469 backend_store=backend_store)
1471 lp.set("workgroup", ctx.domain_name)
1472 logger.info("workgroup is %s" % ctx.domain_name)
1474 lp.set("realm", ctx.realm)
1475 logger.info("realm is %s" % ctx.realm)
1477 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1479 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1480 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1482 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1483 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1484 ctx.domain_replica_flags = ctx.replica_flags
1485 if domain_critical_only:
1486 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1489 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,
1494 dns_backend="NONE"):
1495 """Creates a local clone of a remote DC."""
1496 ctx = DCCloneContext(logger, server, creds, lp, targetdir=targetdir,
1497 domain=domain, dns_backend=dns_backend,
1498 include_secrets=include_secrets)
1500 lp.set("workgroup", ctx.domain_name)
1501 logger.info("workgroup is %s" % ctx.domain_name)
1503 lp.set("realm", ctx.realm)
1504 logger.info("realm is %s" % ctx.realm)
1507 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1511 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1512 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1513 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1514 dns_backend=None, plaintext_secrets=False,
1515 backend_store=None):
1517 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1518 targetdir, parent_domain, machinepass, use_ntvfs,
1519 dns_backend, plaintext_secrets,
1520 backend_store=backend_store)
1521 ctx.subdomain = True
1522 if adminpass is None:
1523 ctx.adminpass = samba.generate_random_password(12, 32)
1525 ctx.adminpass = adminpass
1526 ctx.parent_domain_name = ctx.domain_name
1527 ctx.domain_name = netbios_domain
1528 ctx.realm = dnsdomain
1529 ctx.parent_dnsdomain = ctx.dnsdomain
1530 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1531 ctx.dnsdomain = dnsdomain
1532 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1533 ctx.naming_master = ctx.get_naming_master()
1534 if ctx.naming_master != ctx.server:
1535 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1536 ctx.server = ctx.naming_master
1537 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1538 session_info=system_session(),
1539 credentials=ctx.creds, lp=ctx.lp)
1540 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1542 ctx.server = res[0]["dnsHostName"]
1543 logger.info("DNS name of new naming master is %s" % ctx.server)
1545 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1546 ctx.forestsid = ctx.domsid
1547 ctx.domsid = security.random_sid()
1549 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1550 # Windows uses 240 bytes as UTF16 so we do
1551 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1553 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1555 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1556 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1558 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1559 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1560 ctx.domain_replica_flags = ctx.replica_flags
1563 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1566 class DCCloneContext(DCJoinContext):
1567 """Clones a remote DC."""
1569 def __init__(ctx, logger=None, server=None, creds=None, lp=None,
1570 targetdir=None, domain=None, dns_backend=None,
1571 include_secrets=False):
1572 super(DCCloneContext, ctx).__init__(logger, server, creds, lp,
1573 targetdir=targetdir, domain=domain,
1574 dns_backend=dns_backend)
1576 # As we don't want to create or delete these DNs, we set them to None
1577 ctx.server_dn = None
1580 ctx.myname = ctx.server.split('.')[0]
1581 ctx.ntds_guid = None
1582 ctx.rid_manager_dn = None
1585 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
1587 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1588 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1589 if not include_secrets:
1590 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1591 ctx.domain_replica_flags = ctx.replica_flags
1593 def join_finalise(ctx):
1594 ctx.logger.info("Setting isSynchronized and dsServiceName")
1596 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1597 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE,
1600 # We want to appear to be the server we just cloned
1601 guid = ctx.remote_dc_ntds_guid
1602 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1603 ldb.FLAG_MOD_REPLACE,
1605 ctx.local_samdb.modify(m)
1608 ctx.build_nc_lists()
1610 # When cloning a DC, we just want to provision a DC locally, then
1611 # grab the remote DC's entire DB via DRS replication
1612 ctx.join_provision()
1613 ctx.join_replicate()
1617 # Used to create a renamed backup of a DC. Renaming the domain means that the
1618 # cloned/backup DC can be started without interfering with the production DC.
1619 class DCCloneAndRenameContext(DCCloneContext):
1620 """Clones a remote DC, renaming the domain along the way."""
1622 def __init__(ctx, new_base_dn, new_domain_name, new_realm, logger=None,
1623 server=None, creds=None, lp=None, targetdir=None, domain=None,
1624 dns_backend=None, include_secrets=True):
1625 super(DCCloneAndRenameContext, ctx).__init__(logger, server, creds, lp,
1626 targetdir=targetdir,
1628 dns_backend=dns_backend,
1629 include_secrets=include_secrets)
1630 # store the new DN (etc) that we want the cloned DB to use
1631 ctx.new_base_dn = new_base_dn
1632 ctx.new_domain_name = new_domain_name
1633 ctx.new_realm = new_realm
1635 def create_replicator(ctx, repl_creds, binding_options):
1636 """Creates a new DRS object for managing replications"""
1638 # We want to rename all the domain objects, and the simplest way to do
1639 # this is during replication. This is because the base DN of the top-
1640 # level replicated object will flow through to all the objects below it
1641 binding_str = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
1642 return drs_utils.drs_ReplicateRenamer(binding_str, ctx.lp, repl_creds,
1645 ctx.base_dn, ctx.new_base_dn)
1647 def create_non_global_lp(ctx, global_lp):
1648 '''Creates a non-global LoadParm based on the global LP's settings'''
1650 # the samba code shares a global LoadParm by default. Here we create a
1651 # new LoadParm that retains the global settings, but any changes we
1652 # make to it won't automatically affect the rest of the samba code.
1653 # The easiest way to do this is to dump the global settings to a
1654 # temporary smb.conf file, and then load the temp file into a new
1655 # non-global LoadParm
1656 fd, tmp_file = tempfile.mkstemp()
1657 global_lp.dump(False, tmp_file)
1658 local_lp = samba.param.LoadParm(filename_for_non_global_lp=tmp_file)
1662 def rename_dn(ctx, dn_str):
1663 '''Uses string substitution to replace the base DN'''
1664 old_base_dn = ctx.base_dn
1665 return re.sub('%s$' % old_base_dn, ctx.new_base_dn, dn_str)
1667 # we want to override the normal DCCloneContext's join_provision() so that
1668 # use the new domain DNs during the provision. We do this because:
1669 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1670 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1671 # we couldn't apply the renamed DRS objects during replication)
1672 def join_provision(ctx):
1673 """Provision the local (renamed) SAM."""
1675 print("Provisioning the new (renamed) domain...")
1677 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1678 # to create a new smb.conf. By default, it uses the global LoadParm to
1679 # do this, and so it would overwrite the realm/domain values globally.
1680 # We still need the global LoadParm to retain the old domain's details,
1681 # so we can connect to (and clone) the existing DC.
1682 # So, copy the global settings into a non-global LoadParm, which we can
1683 # then pass into provision(). This generates a new smb.conf correctly,
1684 # without overwriting the global realm/domain values just yet.
1685 non_global_lp = ctx.create_non_global_lp(ctx.lp)
1687 # do the provision with the new/renamed domain DN values
1688 presult = provision(ctx.logger, system_session(),
1689 targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
1690 realm=ctx.new_realm, lp=non_global_lp,
1691 rootdn=ctx.rename_dn(ctx.root_dn), domaindn=ctx.new_base_dn,
1692 schemadn=ctx.rename_dn(ctx.schema_dn),
1693 configdn=ctx.rename_dn(ctx.config_dn),
1694 domain=ctx.new_domain_name, domainsid=ctx.domsid,
1695 serverrole="active directory domain controller",
1696 dns_backend=ctx.dns_backend)
1698 print("Provision OK for renamed domain DN %s" % presult.domaindn)
1699 ctx.local_samdb = presult.samdb
1700 ctx.paths = presult.paths