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)
108 ctx.base_dn = str(ctx.samdb.get_default_basedn())
109 ctx.root_dn = str(ctx.samdb.get_root_basedn())
110 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
111 ctx.config_dn = str(ctx.samdb.get_config_basedn())
112 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
113 ctx.forestsid = ctx.domsid
114 ctx.domain_name = ctx.get_domain_name()
115 ctx.forest_domain_name = ctx.get_forest_domain_name()
116 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
118 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
119 ctx.dc_dnsHostName = ctx.get_dnsHostName()
120 ctx.behavior_version = ctx.get_behavior_version()
122 if machinepass is not None:
123 ctx.acct_pass = machinepass
125 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
127 ctx.dnsdomain = ctx.samdb.domain_dns_name()
129 # the following are all dependent on the new DC's netbios_name (which
130 # we expect to always be specified, except when cloning a DC)
132 # work out the DNs of all the objects we will be adding
133 ctx.myname = netbios_name
134 ctx.samname = "%s$" % ctx.myname
135 ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
136 ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
137 ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
138 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
139 ctx.dnsforest = ctx.samdb.forest_dns_name()
141 topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
142 if ctx.dn_exists(topology_base):
143 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
145 ctx.topology_dn = None
147 ctx.SPNs = ["HOST/%s" % ctx.myname,
148 "HOST/%s" % ctx.dnshostname,
149 "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest)]
151 res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
152 attrs=["rIDManagerReference"],
155 ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
157 ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
158 ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
160 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
161 res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
163 base=ctx.samdb.get_partitions_dn(),
165 if dns_backend is None:
166 ctx.dns_backend = "NONE"
168 if len(res_domaindns) == 0:
169 ctx.dns_backend = "NONE"
170 print("NO DNS zone information found in source domain, not replicating DNS")
172 ctx.dns_backend = dns_backend
174 ctx.realm = ctx.dnsdomain
178 ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
179 drsuapi.DRSUAPI_DRS_PER_SYNC |
180 drsuapi.DRSUAPI_DRS_GET_ANC |
181 drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
182 drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
184 # these elements are optional
185 ctx.never_reveal_sid = None
186 ctx.reveal_sid = None
187 ctx.connection_dn = None
192 ctx.subdomain = False
194 ctx.partition_dn = None
197 ctx.dns_cname_dn = None
199 # Do not normally register 127. addresses but allow override for selftest
200 ctx.force_all_ips = False
202 def del_noerror(ctx, dn, recursive=False):
205 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
209 ctx.del_noerror(r.dn, recursive=True)
212 print("Deleted %s" % dn)
216 def cleanup_old_accounts(ctx, force=False):
217 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
218 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
219 attrs=["msDS-krbTgtLink", "objectSID"])
224 creds = Credentials()
227 creds.set_machine_account(ctx.lp)
228 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
229 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
230 session_info=system_session(),
231 credentials=creds, lp=ctx.lp)
235 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
236 if token_res[0]["tokenGroups"][0] \
237 == res[0]["objectSID"][0]:
238 raise DCJoinException("Not removing account %s which "
239 "looks like a Samba DC account "
240 "matching the password we already have. "
241 "To override, remove secrets.ldb and secrets.tdb"
244 ctx.del_noerror(res[0].dn, recursive=True)
246 if "msDS-Krbtgtlink" in res[0]:
247 new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
248 ctx.del_noerror(ctx.new_krbtgt_dn)
250 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
251 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
252 (ldb.binary_encode("dns-%s" % ctx.myname),
253 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
256 ctx.del_noerror(res[0].dn, recursive=True)
258 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
259 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
262 raise DCJoinException("Not removing account %s which looks like "
263 "a Samba DNS service account but does not "
264 "have servicePrincipalName=%s" %
265 (ldb.binary_encode("dns-%s" % ctx.myname),
266 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
269 def cleanup_old_join(ctx, force=False):
270 """Remove any DNs from a previous join."""
271 # find the krbtgt link
272 if not ctx.subdomain:
273 ctx.cleanup_old_accounts(force=force)
275 if ctx.connection_dn is not None:
276 ctx.del_noerror(ctx.connection_dn)
277 if ctx.krbtgt_dn is not None:
278 ctx.del_noerror(ctx.krbtgt_dn)
279 ctx.del_noerror(ctx.ntds_dn)
280 ctx.del_noerror(ctx.server_dn, recursive=True)
282 ctx.del_noerror(ctx.topology_dn)
284 ctx.del_noerror(ctx.partition_dn)
287 binding_options = "sign"
288 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
291 objectAttr = lsa.ObjectAttribute()
292 objectAttr.sec_qos = lsa.QosInfo()
294 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
295 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
298 name.string = ctx.realm
299 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
301 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
304 name.string = ctx.forest_domain_name
305 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
307 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
310 ctx.del_noerror(ctx.dns_a_dn)
313 ctx.del_noerror(ctx.dns_cname_dn)
317 def promote_possible(ctx):
318 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
320 # This shouldn't happen
321 raise Exception("Can not promote into a subdomain")
323 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
324 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
325 attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
327 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
328 if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
329 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
330 if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
331 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
333 ctx.promote_from_dn = res[0].dn
336 def find_dc(ctx, domain):
337 """find a writeable DC for the given domain"""
339 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
340 except NTSTATUSError as error:
341 raise Exception("Failed to find a writeable DC for domain '%s': %s" %
344 raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
345 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
346 ctx.site = ctx.cldap_ret.client_site
347 return ctx.cldap_ret.pdc_dns_name
350 def get_behavior_version(ctx):
351 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
352 if "msDS-Behavior-Version" in res[0]:
353 return int(res[0]["msDS-Behavior-Version"][0])
355 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
357 def get_dnsHostName(ctx):
358 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
359 return res[0]["dnsHostName"][0]
361 def get_domain_name(ctx):
362 '''get netbios name of the domain from the partitions record'''
363 partitions_dn = ctx.samdb.get_partitions_dn()
364 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
365 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
366 return res[0]["nETBIOSName"][0]
368 def get_forest_domain_name(ctx):
369 '''get netbios name of the domain from the partitions record'''
370 partitions_dn = ctx.samdb.get_partitions_dn()
371 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
372 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
373 return res[0]["nETBIOSName"][0]
375 def get_parent_partition_dn(ctx):
376 '''get the parent domain partition DN from parent DNS name'''
377 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
378 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
379 (ldb.binary_encode(ctx.parent_dnsdomain),
380 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
381 return str(res[0].dn)
383 def get_naming_master(ctx):
384 '''get the parent domain partition DN from parent DNS name'''
385 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
386 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
387 if not 'fSMORoleOwner' in res[0]:
388 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
390 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
392 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
394 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
398 '''get the SID of the connected user. Only works with w2k8 and later,
399 so only used for RODC join'''
400 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
401 binsid = res[0]["tokenGroups"][0]
402 return ctx.samdb.schema_format_value("objectSID", binsid)
404 def dn_exists(ctx, dn):
405 '''check if a DN exists'''
407 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
408 except ldb.LdbError as e5:
409 (enum, estr) = e5.args
410 if enum == ldb.ERR_NO_SUCH_OBJECT:
415 def add_krbtgt_account(ctx):
416 '''RODCs need a special krbtgt account'''
417 print("Adding %s" % ctx.krbtgt_dn)
420 "objectclass": "user",
421 "useraccountcontrol": str(samba.dsdb.UF_NORMAL_ACCOUNT |
422 samba.dsdb.UF_ACCOUNTDISABLE),
423 "showinadvancedviewonly": "TRUE",
424 "description": "krbtgt for %s" % ctx.samname}
425 ctx.samdb.add(rec, ["rodc_join:1:1"])
427 # now we need to search for the samAccountName attribute on the krbtgt DN,
428 # as this will have been magically set to the krbtgt number
429 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
430 ctx.krbtgt_name = res[0]["samAccountName"][0]
432 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
435 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
436 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
437 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
440 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
441 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
442 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
444 def drsuapi_connect(ctx):
445 '''make a DRSUAPI connection to the naming master'''
446 binding_options = "seal"
447 if ctx.lp.log_level() >= 9:
448 binding_options += ",print"
449 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
450 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
451 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
453 def create_tmp_samdb(ctx):
454 '''create a temporary samdb object for schema queries'''
455 ctx.tmp_schema = Schema(ctx.domsid,
456 schemadn=ctx.schema_dn)
457 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
458 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
460 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
462 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
463 '''build a DsReplicaAttributeCtr object'''
464 r = drsuapi.DsReplicaAttribute()
465 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
469 def DsAddEntry(ctx, recs):
470 '''add a record via the DRSUAPI DsAddEntry call'''
471 if ctx.drsuapi is None:
472 ctx.drsuapi_connect()
473 if ctx.tmp_samdb is None:
474 ctx.create_tmp_samdb()
478 id = drsuapi.DsReplicaObjectIdentifier()
485 if not isinstance(rec[a], list):
489 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
492 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
493 attribute_ctr.num_attributes = len(attrs)
494 attribute_ctr.attributes = attrs
496 object = drsuapi.DsReplicaObject()
497 object.identifier = id
498 object.attribute_ctr = attribute_ctr
500 list_object = drsuapi.DsReplicaObjectListItem()
501 list_object.object = object
502 objects.append(list_object)
504 req2 = drsuapi.DsAddEntryRequest2()
505 req2.first_object = objects[0]
506 prev = req2.first_object
507 for o in objects[1:]:
511 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
513 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
514 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
515 raise RuntimeError("DsAddEntry failed")
516 if ctr.extended_err[0] != werror.WERR_SUCCESS:
517 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
518 raise RuntimeError("DsAddEntry failed")
521 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
522 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
523 if ctr.err_data.info is None:
524 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
526 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
527 ctr.err_data.info.extended_err))
528 raise RuntimeError("DsAddEntry failed")
529 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
530 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
531 raise RuntimeError("DsAddEntry failed")
535 def join_ntdsdsa_obj(ctx):
536 '''return the ntdsdsa object to add'''
538 print("Adding %s" % ctx.ntds_dn)
541 "objectclass": "nTDSDSA",
542 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
543 "dMDLocation": ctx.schema_dn}
545 nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
547 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
548 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
550 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
551 rec["msDS-HasDomainNCs"] = ctx.base_dn
554 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
555 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
556 rec["options"] = "37"
558 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
559 rec["HasMasterNCs"] = []
561 if nc in ctx.full_nc_list:
562 rec["HasMasterNCs"].append(nc)
563 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
564 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
566 rec["invocationId"] = ndr_pack(ctx.invocation_id)
570 def join_add_ntdsdsa(ctx):
571 '''add the ntdsdsa object'''
573 rec = ctx.join_ntdsdsa_obj()
574 if ctx.forced_local_samdb:
575 ctx.samdb.add(rec, controls=["relax:0"])
577 ctx.samdb.add(rec, ["rodc_join:1:1"])
579 ctx.DsAddEntry([rec])
581 # find the GUID of our NTDS DN
582 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
583 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
585 def join_add_objects(ctx, specified_sid=None):
586 '''add the various objects needed for the join'''
588 print("Adding %s" % ctx.acct_dn)
591 "objectClass": "computer",
592 "displayname": ctx.samname,
593 "samaccountname": ctx.samname,
594 "userAccountControl": str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
595 "dnshostname": ctx.dnshostname}
596 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
597 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
598 elif ctx.promote_existing:
599 rec['msDS-SupportedEncryptionTypes'] = []
601 rec["managedby"] = ctx.managedby
602 elif ctx.promote_existing:
603 rec["managedby"] = []
605 if ctx.never_reveal_sid:
606 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
607 elif ctx.promote_existing:
608 rec["msDS-NeverRevealGroup"] = []
611 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
612 elif ctx.promote_existing:
613 rec["msDS-RevealOnDemandGroup"] = []
616 rec["objectSid"] = ndr_pack(specified_sid)
618 if ctx.promote_existing:
619 if ctx.promote_from_dn != ctx.acct_dn:
620 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
621 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
624 if specified_sid is not None:
625 controls = ["relax:0"]
626 ctx.samdb.add(rec, controls=controls)
629 ctx.add_krbtgt_account()
632 print("Adding %s" % ctx.server_dn)
635 "objectclass": "server",
636 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
637 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
638 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
639 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
640 # windows seems to add the dnsHostName later
641 "dnsHostName": ctx.dnshostname}
644 rec["serverReference"] = ctx.acct_dn
649 # the rest is done after replication
654 ctx.join_add_ntdsdsa()
656 # Add the Replica-Locations or RO-Replica-Locations attributes
657 # TODO Is this supposed to be for the schema partition too?
658 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
659 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
661 base=ctx.samdb.get_partitions_dn(),
662 expression=expr), ctx.domaindns_zone)
664 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
665 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
667 base=ctx.samdb.get_partitions_dn(),
668 expression=expr), ctx.forestdns_zone)
670 for part, zone in (domain, forest):
671 if zone not in ctx.nc_list:
677 attr = "msDS-NC-Replica-Locations"
679 attr = "msDS-NC-RO-Replica-Locations"
681 m[attr] = ldb.MessageElement(ctx.ntds_dn,
682 ldb.FLAG_MOD_ADD, attr)
685 if ctx.connection_dn is not None:
686 print("Adding %s" % ctx.connection_dn)
688 "dn": ctx.connection_dn,
689 "objectclass": "nTDSConnection",
690 "enabledconnection": "TRUE",
692 "fromServer": ctx.dc_ntds_dn}
696 print("Adding SPNs to %s" % ctx.acct_dn)
698 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
699 for i in range(len(ctx.SPNs)):
700 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
701 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
702 ldb.FLAG_MOD_REPLACE,
703 "servicePrincipalName")
706 # The account password set operation should normally be done over
707 # LDAP. Windows 2000 DCs however allow this only with SSL
708 # connections which are hard to set up and otherwise refuse with
709 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
711 print("Setting account password for %s" % ctx.samname)
713 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
714 % ldb.binary_encode(ctx.samname),
716 force_change_at_next_login=False,
717 username=ctx.samname)
718 except ldb.LdbError as e2:
720 if num != ldb.ERR_UNWILLING_TO_PERFORM:
722 ctx.net.set_password(account_name=ctx.samname,
723 domain_name=ctx.domain_name,
724 newpassword=ctx.acct_pass.encode('utf-8'))
726 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
727 attrs=["msDS-KeyVersionNumber",
729 if "msDS-KeyVersionNumber" in res[0]:
730 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
732 ctx.key_version_number = None
734 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
735 res[0]["objectSid"][0])
737 print("Enabling account")
739 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
740 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
741 ldb.FLAG_MOD_REPLACE,
742 "userAccountControl")
745 if ctx.dns_backend.startswith("BIND9_"):
746 ctx.dnspass = samba.generate_random_password(128, 255)
748 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
749 {"DNSDOMAIN": ctx.dnsdomain,
750 "DOMAINDN": ctx.base_dn,
751 "HOSTNAME": ctx.myname,
752 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
753 "DNSNAME": ctx.dnshostname}))
754 for changetype, msg in recs:
755 assert changetype == ldb.CHANGETYPE_NONE
756 dns_acct_dn = msg["dn"]
757 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
759 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
760 del msg["clearTextPassword"]
761 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
762 del msg["isCriticalSystemObject"]
763 # Disable account until password is set
764 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
765 samba.dsdb.UF_ACCOUNTDISABLE)
768 except ldb.LdbError as e:
770 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
773 # The account password set operation should normally be done over
774 # LDAP. Windows 2000 DCs however allow this only with SSL
775 # connections which are hard to set up and otherwise refuse with
776 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
778 print("Setting account password for dns-%s" % ctx.myname)
780 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
781 % ldb.binary_encode(ctx.myname),
783 force_change_at_next_login=False,
784 username=ctx.samname)
785 except ldb.LdbError as e3:
787 if num != ldb.ERR_UNWILLING_TO_PERFORM:
789 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
790 domain_name=ctx.domain_name,
791 newpassword=ctx.dnspass)
793 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
794 attrs=["msDS-KeyVersionNumber"])
795 if "msDS-KeyVersionNumber" in res[0]:
796 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
798 ctx.dns_key_version_number = None
800 def join_add_objects2(ctx):
801 """add the various objects needed for the join, for subdomains post replication"""
803 print("Adding %s" % ctx.partition_dn)
804 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
805 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
807 "dn": ctx.partition_dn,
808 "objectclass": "crossRef",
809 "objectCategory": "CN=Cross-Ref,%s" % ctx.schema_dn,
810 "nCName": ctx.base_dn,
811 "nETBIOSName": ctx.domain_name,
812 "dnsRoot": ctx.dnsdomain,
813 "trustParent": ctx.parent_partition_dn,
814 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC |samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
815 "ntSecurityDescriptor": sd_binary,
818 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
819 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
821 rec2 = ctx.join_ntdsdsa_obj()
823 objects = ctx.DsAddEntry([rec, rec2])
824 if len(objects) != 2:
825 raise DCJoinException("Expected 2 objects from DsAddEntry")
827 ctx.ntds_guid = objects[1].guid
829 print("Replicating partition DN")
830 ctx.repl.replicate(ctx.partition_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 print("Replicating NTDS DN")
837 ctx.repl.replicate(ctx.ntds_dn,
838 misc.GUID("00000000-0000-0000-0000-000000000000"),
840 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
841 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
843 def join_provision(ctx):
844 """Provision the local SAM."""
846 print("Calling bare provision")
848 smbconf = ctx.lp.configfile
850 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
851 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
852 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
853 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
854 serverdn=ctx.server_dn, domain=ctx.domain_name,
855 hostname=ctx.myname, domainsid=ctx.domsid,
856 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
857 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
858 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
859 plaintext_secrets=ctx.plaintext_secrets,
860 backend_store=ctx.backend_store
862 print("Provision OK for domain DN %s" % presult.domaindn)
863 ctx.local_samdb = presult.samdb
865 ctx.paths = presult.paths
866 ctx.names = presult.names
868 # Fix up the forestsid, it may be different if we are joining as a subdomain
869 ctx.names.forestsid = ctx.forestsid
871 def join_provision_own_domain(ctx):
872 """Provision the local SAM."""
874 # we now operate exclusively on the local database, which
875 # we need to reopen in order to get the newly created schema
876 print("Reconnecting to local samdb")
877 ctx.samdb = SamDB(url=ctx.local_samdb.url,
878 session_info=system_session(),
879 lp=ctx.local_samdb.lp,
881 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
882 ctx.local_samdb = ctx.samdb
884 ctx.logger.info("Finding domain GUID from ncName")
885 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
886 controls=["extended_dn:1:1", "reveal_internals:0"])
888 if 'nCName' not in res[0]:
889 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
892 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
894 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
896 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
898 ctx.logger.info("Calling own domain provision")
900 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
902 presult = provision_fill(ctx.local_samdb, secrets_ldb,
903 ctx.logger, ctx.names, ctx.paths,
904 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
905 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
906 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
907 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
908 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
909 print("Provision OK for domain %s" % ctx.names.dnsdomain)
911 def create_replicator(ctx, repl_creds, binding_options):
912 '''Creates a new DRS object for managing replications'''
913 return drs_utils.drs_Replicate(
914 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
915 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
917 def join_replicate(ctx):
918 """Replicate the SAM."""
920 print("Starting replication")
921 ctx.local_samdb.transaction_start()
923 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
924 if ctx.ntds_guid is None:
925 print("Using DS_BIND_GUID_W2K3")
926 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
928 destination_dsa_guid = ctx.ntds_guid
931 repl_creds = Credentials()
932 repl_creds.guess(ctx.lp)
933 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
934 repl_creds.set_username(ctx.samname)
935 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
937 repl_creds = ctx.creds
939 binding_options = "seal"
940 if ctx.lp.log_level() >= 9:
941 binding_options += ",print"
943 repl = ctx.create_replicator(repl_creds, binding_options)
945 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
946 destination_dsa_guid, schema=True, rodc=ctx.RODC,
947 replica_flags=ctx.replica_flags)
948 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
949 destination_dsa_guid, rodc=ctx.RODC,
950 replica_flags=ctx.replica_flags)
951 if not ctx.subdomain:
952 # Replicate first the critical object for the basedn
953 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
954 print("Replicating critical objects from the base DN of the domain")
955 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
956 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
957 destination_dsa_guid, rodc=ctx.RODC,
958 replica_flags=ctx.domain_replica_flags)
959 ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
960 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
961 destination_dsa_guid, rodc=ctx.RODC,
962 replica_flags=ctx.domain_replica_flags)
963 print("Done with always replicated NC (base, config, schema)")
965 # At this point we should already have an entry in the ForestDNS
966 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
967 # indicate that we hold a replica for this NC.
968 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
969 if nc in ctx.nc_list:
970 print("Replicating %s" % (str(nc)))
971 repl.replicate(nc, source_dsa_invocation_id,
972 destination_dsa_guid, rodc=ctx.RODC,
973 replica_flags=ctx.replica_flags)
976 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
977 destination_dsa_guid,
978 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
979 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
980 destination_dsa_guid,
981 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
982 elif ctx.rid_manager_dn != None:
983 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
985 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
986 destination_dsa_guid,
987 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
988 except samba.DsExtendedError as e1:
989 (enum, estr) = e1.args
990 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
991 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
992 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
997 ctx.source_dsa_invocation_id = source_dsa_invocation_id
998 ctx.destination_dsa_guid = destination_dsa_guid
1000 print("Committing SAM database")
1002 ctx.local_samdb.transaction_cancel()
1005 ctx.local_samdb.transaction_commit()
1007 def send_DsReplicaUpdateRefs(ctx, dn):
1008 r = drsuapi.DsReplicaUpdateRefsRequest1()
1009 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
1010 r.naming_context.dn = str(dn)
1011 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
1012 r.naming_context.sid = security.dom_sid("S-0-0")
1013 r.dest_dsa_guid = ctx.ntds_guid
1014 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1015 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1017 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1019 if ctx.drsuapi is None:
1020 ctx.drsuapi_connect()
1022 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1024 def join_add_dns_records(ctx):
1025 """Remotely Add a DNS record to the target DC. We assume that if we
1026 replicate DNS that the server holds the DNS roles and can accept
1029 This avoids issues getting replication going after the DC
1030 first starts as the rest of the domain does not have to
1031 wait for samba_dnsupdate to run successfully.
1033 Specifically, we add the records implied by the DsReplicaUpdateRefs
1036 We do not just run samba_dnsupdate as we want to strictly
1037 operate against the DC we just joined:
1038 - We do not want to query another DNS server
1039 - We do not want to obtain a Kerberos ticket
1040 (as the KDC we select may not be the DC we just joined,
1041 and so may not be in sync with the password we just set)
1042 - We do not wish to set the _ldap records until we have started
1043 - We do not wish to use NTLM (the --use-samba-tool mode forces
1048 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1049 record_type = dnsp.DNS_TYPE_A
1050 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1051 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1053 zone = ctx.dnsdomain
1054 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1056 msdcs_cname = str(ctx.ntds_guid)
1057 cname_target = "%s.%s" % (name, zone)
1058 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1060 ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
1061 (len(IPs), name, zone))
1063 binding_options = "sign"
1064 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1070 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1072 change_owner_sd = security.descriptor()
1073 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1074 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1076 security.DOMAIN_RID_DCS))
1078 # TODO: Remove any old records from the primary DNS name
1081 = dns_conn.DnssrvEnumRecords2(client_version,
1091 except WERRORError as e:
1092 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1098 for record in rec.records:
1099 if record.wType == dnsp.DNS_TYPE_A or \
1100 record.wType == dnsp.DNS_TYPE_AAAA:
1102 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1103 del_rec_buf.rec = record
1105 dns_conn.DnssrvUpdateRecord2(client_version,
1112 except WERRORError as e:
1113 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1119 if IP.find(':') != -1:
1120 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1122 rec = AAAARecord(IP)
1124 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1129 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1130 add_rec_buf.rec = rec
1131 dns_conn.DnssrvUpdateRecord2(client_version,
1140 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1141 (ctx.dns_a_dn, ldap_record) \
1142 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1143 dns_partition=domaindns_zone_dn)
1145 # Make the DC own the DNS record, not the administrator
1146 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1147 controls=["sd_flags:1:%d"
1148 % (security.SECINFO_OWNER
1149 | security.SECINFO_GROUP)])
1153 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1154 % (msdcs_cname, msdcs_zone, cname_target))
1156 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1157 rec = CNameRecord(cname_target)
1158 add_rec_buf.rec = rec
1159 dns_conn.DnssrvUpdateRecord2(client_version,
1167 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1168 (ctx.dns_cname_dn, ldap_record) \
1169 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1170 dns_partition=forestdns_zone_dn)
1172 # Make the DC own the DNS record, not the administrator
1173 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1174 controls=["sd_flags:1:%d"
1175 % (security.SECINFO_OWNER
1176 | security.SECINFO_GROUP)])
1178 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1179 "will be created samba_dnsupdate on first startup")
1182 def join_replicate_new_dns_records(ctx):
1183 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1184 if nc in ctx.nc_list:
1185 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1186 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1187 ctx.ntds_guid, rodc=ctx.RODC,
1188 replica_flags=ctx.replica_flags,
1193 def join_finalise(ctx):
1194 """Finalise the join, mark us synchronised and setup secrets db."""
1196 # FIXME we shouldn't do this in all cases
1198 # If for some reasons we joined in another site than the one of
1199 # DC we just replicated from then we don't need to send the updatereplicateref
1200 # as replication between sites is time based and on the initiative of the
1202 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1203 for nc in ctx.nc_list:
1204 ctx.send_DsReplicaUpdateRefs(nc)
1207 print("Setting RODC invocationId")
1208 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1209 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1210 ctx.behavior_version)
1212 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1213 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1214 ldb.FLAG_MOD_REPLACE,
1216 ctx.local_samdb.modify(m)
1218 # Note: as RODC the invocationId is only stored
1219 # on the RODC itself, the other DCs never see it.
1221 # Thats is why we fix up the replPropertyMetaData stamp
1222 # for the 'invocationId' attribute, we need to change
1223 # the 'version' to '0', this is what windows 2008r2 does as RODC
1225 # This means if the object on a RWDC ever gets a invocationId
1226 # attribute, it will have version '1' (or higher), which will
1227 # will overwrite the RODC local value.
1228 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1232 ctx.logger.info("Setting isSynchronized and dsServiceName")
1234 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1235 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1237 guid = ctx.ntds_guid
1238 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1239 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1240 ctx.local_samdb.modify(m)
1245 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1247 ctx.logger.info("Setting up secrets database")
1248 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1250 dnsdomain=ctx.dnsdomain,
1251 netbiosname=ctx.myname,
1252 domainsid=ctx.domsid,
1253 machinepass=ctx.acct_pass,
1254 secure_channel_type=ctx.secure_channel_type,
1255 key_version_number=ctx.key_version_number)
1257 if ctx.dns_backend.startswith("BIND9_"):
1258 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1259 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1260 dns_backend=ctx.dns_backend,
1261 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1262 targetdir=ctx.targetdir,
1263 key_version_number=ctx.dns_key_version_number)
1265 def join_setup_trusts(ctx):
1266 """provision the local SAM."""
1268 print("Setup domain trusts with server %s" % ctx.server)
1269 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1270 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1273 objectAttr = lsa.ObjectAttribute()
1274 objectAttr.sec_qos = lsa.QosInfo()
1276 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1277 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1279 info = lsa.TrustDomainInfoInfoEx()
1280 info.domain_name.string = ctx.dnsdomain
1281 info.netbios_name.string = ctx.domain_name
1282 info.sid = ctx.domsid
1283 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1284 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1285 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1288 oldname = lsa.String()
1289 oldname.string = ctx.dnsdomain
1290 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1291 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1292 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1293 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1294 except RuntimeError:
1297 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1299 clear_value = drsblobs.AuthInfoClear()
1300 clear_value.size = len(password_blob)
1301 clear_value.password = password_blob
1303 clear_authentication_information = drsblobs.AuthenticationInformation()
1304 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1305 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1306 clear_authentication_information.AuthInfo = clear_value
1308 authentication_information_array = drsblobs.AuthenticationInformationArray()
1309 authentication_information_array.count = 1
1310 authentication_information_array.array = [clear_authentication_information]
1312 outgoing = drsblobs.trustAuthInOutBlob()
1314 outgoing.current = authentication_information_array
1316 trustpass = drsblobs.trustDomainPasswords()
1317 confounder = [3] * 512
1319 for i in range(512):
1320 confounder[i] = random.randint(0, 255)
1322 trustpass.confounder = confounder
1324 trustpass.outgoing = outgoing
1325 trustpass.incoming = outgoing
1327 trustpass_blob = ndr_pack(trustpass)
1329 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1331 auth_blob = lsa.DATA_BUF2()
1332 auth_blob.size = len(encrypted_trustpass)
1333 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1335 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1336 auth_info.auth_blob = auth_blob
1338 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1341 security.SEC_STD_DELETE)
1344 "dn": "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1345 "objectclass": "trustedDomain",
1346 "trustType": str(info.trust_type),
1347 "trustAttributes": str(info.trust_attributes),
1348 "trustDirection": str(info.trust_direction),
1349 "flatname": ctx.forest_domain_name,
1350 "trustPartner": ctx.dnsforest,
1351 "trustAuthIncoming": ndr_pack(outgoing),
1352 "trustAuthOutgoing": ndr_pack(outgoing),
1353 "securityIdentifier": ndr_pack(ctx.forestsid)
1355 ctx.local_samdb.add(rec)
1358 "dn": "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1359 "objectclass": "user",
1360 "userAccountControl": str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1361 "clearTextPassword": ctx.trustdom_pass.encode('utf-16-le'),
1362 "samAccountName": "%s$" % ctx.forest_domain_name
1364 ctx.local_samdb.add(rec)
1367 def build_nc_lists(ctx):
1368 # nc_list is the list of naming context (NC) for which we will
1369 # replicate in and send a updateRef command to the partner DC
1371 # full_nc_list is the list of naming context (NC) we hold
1372 # read/write copies of. These are not subsets of each other.
1373 ctx.nc_list = [ctx.config_dn, ctx.schema_dn]
1374 ctx.full_nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
1376 if ctx.subdomain and ctx.dns_backend != "NONE":
1377 ctx.full_nc_list += [ctx.domaindns_zone]
1379 elif not ctx.subdomain:
1380 ctx.nc_list += [ctx.base_dn]
1382 if ctx.dns_backend != "NONE":
1383 ctx.nc_list += [ctx.domaindns_zone]
1384 ctx.nc_list += [ctx.forestdns_zone]
1385 ctx.full_nc_list += [ctx.domaindns_zone]
1386 ctx.full_nc_list += [ctx.forestdns_zone]
1389 ctx.build_nc_lists()
1391 if ctx.promote_existing:
1392 ctx.promote_possible()
1394 ctx.cleanup_old_join()
1397 ctx.join_add_objects()
1398 ctx.join_provision()
1399 ctx.join_replicate()
1401 ctx.join_add_objects2()
1402 ctx.join_provision_own_domain()
1403 ctx.join_setup_trusts()
1405 if ctx.dns_backend != "NONE":
1406 ctx.join_add_dns_records()
1407 ctx.join_replicate_new_dns_records()
1412 print("Join failed - cleaning up")
1415 ctx.cleanup_old_join()
1419 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1420 targetdir=None, domain=None, domain_critical_only=False,
1421 machinepass=None, use_ntvfs=False, dns_backend=None,
1422 promote_existing=False, plaintext_secrets=False,
1423 backend_store=None):
1424 """Join as a RODC."""
1426 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1427 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1428 promote_existing, plaintext_secrets,
1429 backend_store=backend_store)
1431 lp.set("workgroup", ctx.domain_name)
1432 logger.info("workgroup is %s" % ctx.domain_name)
1434 lp.set("realm", ctx.realm)
1435 logger.info("realm is %s" % ctx.realm)
1437 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1439 # setup some defaults for accounts that should be replicated to this RODC
1440 ctx.never_reveal_sid = [
1441 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1442 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1443 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1444 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1445 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1446 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1448 mysid = ctx.get_mysid()
1449 admin_dn = "<SID=%s>" % mysid
1450 ctx.managedby = admin_dn
1452 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1453 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1454 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1456 ctx.SPNs.extend(["RestrictedKrbHost/%s" % ctx.myname,
1457 "RestrictedKrbHost/%s" % ctx.dnshostname])
1459 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1460 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1462 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1463 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1464 ctx.domain_replica_flags = ctx.replica_flags
1465 if domain_critical_only:
1466 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1470 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1473 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1474 targetdir=None, domain=None, domain_critical_only=False,
1475 machinepass=None, use_ntvfs=False, dns_backend=None,
1476 promote_existing=False, plaintext_secrets=False,
1477 backend_store=None):
1479 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1480 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1481 promote_existing, plaintext_secrets,
1482 backend_store=backend_store)
1484 lp.set("workgroup", ctx.domain_name)
1485 logger.info("workgroup is %s" % ctx.domain_name)
1487 lp.set("realm", ctx.realm)
1488 logger.info("realm is %s" % ctx.realm)
1490 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1492 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1493 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1495 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1496 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1497 ctx.domain_replica_flags = ctx.replica_flags
1498 if domain_critical_only:
1499 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1502 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1505 def join_clone(logger=None, server=None, creds=None, lp=None,
1506 targetdir=None, domain=None, include_secrets=False,
1507 dns_backend="NONE"):
1508 """Creates a local clone of a remote DC."""
1509 ctx = DCCloneContext(logger, server, creds, lp, targetdir=targetdir,
1510 domain=domain, dns_backend=dns_backend,
1511 include_secrets=include_secrets)
1513 lp.set("workgroup", ctx.domain_name)
1514 logger.info("workgroup is %s" % ctx.domain_name)
1516 lp.set("realm", ctx.realm)
1517 logger.info("realm is %s" % ctx.realm)
1520 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1524 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1525 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1526 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1527 dns_backend=None, plaintext_secrets=False,
1528 backend_store=None):
1530 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1531 targetdir, parent_domain, machinepass, use_ntvfs,
1532 dns_backend, plaintext_secrets,
1533 backend_store=backend_store)
1534 ctx.subdomain = True
1535 if adminpass is None:
1536 ctx.adminpass = samba.generate_random_password(12, 32)
1538 ctx.adminpass = adminpass
1539 ctx.parent_domain_name = ctx.domain_name
1540 ctx.domain_name = netbios_domain
1541 ctx.realm = dnsdomain
1542 ctx.parent_dnsdomain = ctx.dnsdomain
1543 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1544 ctx.dnsdomain = dnsdomain
1545 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1546 ctx.naming_master = ctx.get_naming_master()
1547 if ctx.naming_master != ctx.server:
1548 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1549 ctx.server = ctx.naming_master
1550 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1551 session_info=system_session(),
1552 credentials=ctx.creds, lp=ctx.lp)
1553 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1555 ctx.server = res[0]["dnsHostName"]
1556 logger.info("DNS name of new naming master is %s" % ctx.server)
1558 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1559 ctx.forestsid = ctx.domsid
1560 ctx.domsid = security.random_sid()
1562 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1563 # Windows uses 240 bytes as UTF16 so we do
1564 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1566 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1568 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1569 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1571 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1572 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1573 ctx.domain_replica_flags = ctx.replica_flags
1576 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1579 class DCCloneContext(DCJoinContext):
1580 """Clones a remote DC."""
1582 def __init__(ctx, logger=None, server=None, creds=None, lp=None,
1583 targetdir=None, domain=None, dns_backend=None,
1584 include_secrets=False):
1585 super(DCCloneContext, ctx).__init__(logger, server, creds, lp,
1586 targetdir=targetdir, domain=domain,
1587 dns_backend=dns_backend)
1589 # As we don't want to create or delete these DNs, we set them to None
1590 ctx.server_dn = None
1593 ctx.myname = ctx.server.split('.')[0]
1594 ctx.ntds_guid = None
1595 ctx.rid_manager_dn = None
1598 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
1600 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1601 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1602 if not include_secrets:
1603 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1604 ctx.domain_replica_flags = ctx.replica_flags
1606 def join_finalise(ctx):
1607 ctx.logger.info("Setting isSynchronized and dsServiceName")
1609 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1610 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE,
1613 # We want to appear to be the server we just cloned
1614 guid = ctx.remote_dc_ntds_guid
1615 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1616 ldb.FLAG_MOD_REPLACE,
1618 ctx.local_samdb.modify(m)
1621 ctx.build_nc_lists()
1623 # When cloning a DC, we just want to provision a DC locally, then
1624 # grab the remote DC's entire DB via DRS replication
1625 ctx.join_provision()
1626 ctx.join_replicate()
1630 # Used to create a renamed backup of a DC. Renaming the domain means that the
1631 # cloned/backup DC can be started without interfering with the production DC.
1632 class DCCloneAndRenameContext(DCCloneContext):
1633 """Clones a remote DC, renaming the domain along the way."""
1635 def __init__(ctx, new_base_dn, new_domain_name, new_realm, logger=None,
1636 server=None, creds=None, lp=None, targetdir=None, domain=None,
1637 dns_backend=None, include_secrets=True):
1638 super(DCCloneAndRenameContext, ctx).__init__(logger, server, creds, lp,
1639 targetdir=targetdir,
1641 dns_backend=dns_backend,
1642 include_secrets=include_secrets)
1643 # store the new DN (etc) that we want the cloned DB to use
1644 ctx.new_base_dn = new_base_dn
1645 ctx.new_domain_name = new_domain_name
1646 ctx.new_realm = new_realm
1648 def create_replicator(ctx, repl_creds, binding_options):
1649 """Creates a new DRS object for managing replications"""
1651 # We want to rename all the domain objects, and the simplest way to do
1652 # this is during replication. This is because the base DN of the top-
1653 # level replicated object will flow through to all the objects below it
1654 binding_str = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
1655 return drs_utils.drs_ReplicateRenamer(binding_str, ctx.lp, repl_creds,
1658 ctx.base_dn, ctx.new_base_dn)
1660 def create_non_global_lp(ctx, global_lp):
1661 '''Creates a non-global LoadParm based on the global LP's settings'''
1663 # the samba code shares a global LoadParm by default. Here we create a
1664 # new LoadParm that retains the global settings, but any changes we
1665 # make to it won't automatically affect the rest of the samba code.
1666 # The easiest way to do this is to dump the global settings to a
1667 # temporary smb.conf file, and then load the temp file into a new
1668 # non-global LoadParm
1669 fd, tmp_file = tempfile.mkstemp()
1670 global_lp.dump(False, tmp_file)
1671 local_lp = samba.param.LoadParm(filename_for_non_global_lp=tmp_file)
1675 def rename_dn(ctx, dn_str):
1676 '''Uses string substitution to replace the base DN'''
1677 old_base_dn = ctx.base_dn
1678 return re.sub('%s$' % old_base_dn, ctx.new_base_dn, dn_str)
1680 # we want to override the normal DCCloneContext's join_provision() so that
1681 # use the new domain DNs during the provision. We do this because:
1682 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1683 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1684 # we couldn't apply the renamed DRS objects during replication)
1685 def join_provision(ctx):
1686 """Provision the local (renamed) SAM."""
1688 print("Provisioning the new (renamed) domain...")
1690 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1691 # to create a new smb.conf. By default, it uses the global LoadParm to
1692 # do this, and so it would overwrite the realm/domain values globally.
1693 # We still need the global LoadParm to retain the old domain's details,
1694 # so we can connect to (and clone) the existing DC.
1695 # So, copy the global settings into a non-global LoadParm, which we can
1696 # then pass into provision(). This generates a new smb.conf correctly,
1697 # without overwriting the global realm/domain values just yet.
1698 non_global_lp = ctx.create_non_global_lp(ctx.lp)
1700 # do the provision with the new/renamed domain DN values
1701 presult = provision(ctx.logger, system_session(),
1702 targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
1703 realm=ctx.new_realm, lp=non_global_lp,
1704 rootdn=ctx.rename_dn(ctx.root_dn), domaindn=ctx.new_base_dn,
1705 schemadn=ctx.rename_dn(ctx.schema_dn),
1706 configdn=ctx.rename_dn(ctx.config_dn),
1707 domain=ctx.new_domain_name, domainsid=ctx.domsid,
1708 serverrole="active directory domain controller",
1709 dns_backend=ctx.dns_backend)
1711 print("Provision OK for renamed domain DN %s" % presult.domaindn)
1712 ctx.local_samdb = presult.samdb
1713 ctx.paths = presult.paths