2 # Copyright Andrew Tridgell 2010
3 # Copyright Andrew Bartlett 2010
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 from __future__ import print_function
20 """Joining a domain."""
22 from samba.auth import system_session
23 from samba.samdb import SamDB
24 from samba import gensec, Ldb, drs_utils, arcfour_encrypt, string_to_byte_array
25 import ldb, samba, sys, uuid
26 from samba.ndr import ndr_pack, ndr_unpack
27 from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp
28 from samba.dsdb import DS_DOMAIN_FUNCTION_2003
29 from samba.credentials import Credentials, DONT_USE_KERBEROS
30 from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN
31 from samba.provision.common import setup_path
32 from samba.schema import Schema
33 from samba import descriptor
34 from samba.net import Net
35 from samba.provision.sambadns import setup_bind9_dns
36 from samba import read_and_sub_file
37 from samba import werror
38 from base64 import b64encode
39 from samba import WERRORError, NTSTATUSError
40 from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord
41 from samba import sd_utils
47 class DCJoinException(Exception):
49 def __init__(self, msg):
50 super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
53 class DCJoinContext(object):
54 """Perform a DC join."""
56 def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
57 netbios_name=None, targetdir=None, domain=None,
58 machinepass=None, use_ntvfs=False, dns_backend=None,
59 promote_existing=False, clone_only=False,
60 plaintext_secrets=False, backend_store=None):
62 site = "Default-First-Site-Name"
64 ctx.clone_only=clone_only
70 ctx.targetdir = targetdir
71 ctx.use_ntvfs = use_ntvfs
72 ctx.plaintext_secrets = plaintext_secrets
73 ctx.backend_store = backend_store
75 ctx.promote_existing = promote_existing
76 ctx.promote_from_dn = None
81 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
82 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
84 if server is not None:
87 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
88 ctx.server = ctx.find_dc(domain)
89 ctx.logger.info("Found DC %s" % ctx.server)
91 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
92 session_info=system_session(),
93 credentials=ctx.creds, lp=ctx.lp)
96 ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
97 except ldb.LdbError as e4:
98 (enum, estr) = e4.args
99 raise DCJoinException(estr)
102 ctx.base_dn = str(ctx.samdb.get_default_basedn())
103 ctx.root_dn = str(ctx.samdb.get_root_basedn())
104 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
105 ctx.config_dn = str(ctx.samdb.get_config_basedn())
106 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
107 ctx.forestsid = ctx.domsid
108 ctx.domain_name = ctx.get_domain_name()
109 ctx.forest_domain_name = ctx.get_forest_domain_name()
110 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
112 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
113 ctx.dc_dnsHostName = ctx.get_dnsHostName()
114 ctx.behavior_version = ctx.get_behavior_version()
116 if machinepass is not None:
117 ctx.acct_pass = machinepass
119 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
121 ctx.dnsdomain = ctx.samdb.domain_dns_name()
123 # As we don't want to create or delete these DNs, we set them to None
127 ctx.myname = ctx.server.split('.')[0]
129 ctx.rid_manager_dn = None
132 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
134 # work out the DNs of all the objects we will be adding
135 ctx.myname = netbios_name
136 ctx.samname = "%s$" % ctx.myname
137 ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
138 ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
139 ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
140 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
141 ctx.dnsforest = ctx.samdb.forest_dns_name()
143 topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
144 if ctx.dn_exists(topology_base):
145 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
147 ctx.topology_dn = None
149 ctx.SPNs = [ "HOST/%s" % ctx.myname,
150 "HOST/%s" % ctx.dnshostname,
151 "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
153 res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
154 attrs=["rIDManagerReference"],
157 ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
159 ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
160 ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
162 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
163 res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
165 base=ctx.samdb.get_partitions_dn(),
167 if dns_backend is None:
168 ctx.dns_backend = "NONE"
170 if len(res_domaindns) == 0:
171 ctx.dns_backend = "NONE"
172 print("NO DNS zone information found in source domain, not replicating DNS")
174 ctx.dns_backend = dns_backend
176 ctx.realm = ctx.dnsdomain
180 ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
181 drsuapi.DRSUAPI_DRS_PER_SYNC |
182 drsuapi.DRSUAPI_DRS_GET_ANC |
183 drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
184 drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
186 # these elements are optional
187 ctx.never_reveal_sid = None
188 ctx.reveal_sid = None
189 ctx.connection_dn = None
194 ctx.subdomain = False
196 ctx.partition_dn = None
199 ctx.dns_cname_dn = None
201 # Do not normally register 127. addresses but allow override for selftest
202 ctx.force_all_ips = False
204 def del_noerror(ctx, dn, recursive=False):
207 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
211 ctx.del_noerror(r.dn, recursive=True)
214 print("Deleted %s" % dn)
218 def cleanup_old_accounts(ctx, force=False):
219 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
220 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
221 attrs=["msDS-krbTgtLink", "objectSID"])
226 creds = Credentials()
229 creds.set_machine_account(ctx.lp)
230 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
231 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
232 session_info=system_session(),
233 credentials=creds, lp=ctx.lp)
237 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
238 if token_res[0]["tokenGroups"][0] \
239 == res[0]["objectSID"][0]:
240 raise DCJoinException("Not removing account %s which "
241 "looks like a Samba DC account "
242 "matching the password we already have. "
243 "To override, remove secrets.ldb and secrets.tdb"
246 ctx.del_noerror(res[0].dn, recursive=True)
248 if "msDS-Krbtgtlink" in res[0]:
249 new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
250 ctx.del_noerror(ctx.new_krbtgt_dn)
252 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
253 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
254 (ldb.binary_encode("dns-%s" % ctx.myname),
255 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
258 ctx.del_noerror(res[0].dn, recursive=True)
260 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
261 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
264 raise DCJoinException("Not removing account %s which looks like "
265 "a Samba DNS service account but does not "
266 "have servicePrincipalName=%s" %
267 (ldb.binary_encode("dns-%s" % ctx.myname),
268 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
271 def cleanup_old_join(ctx, force=False):
272 """Remove any DNs from a previous join."""
273 # find the krbtgt link
274 if not ctx.subdomain:
275 ctx.cleanup_old_accounts(force=force)
277 if ctx.connection_dn is not None:
278 ctx.del_noerror(ctx.connection_dn)
279 if ctx.krbtgt_dn is not None:
280 ctx.del_noerror(ctx.krbtgt_dn)
281 ctx.del_noerror(ctx.ntds_dn)
282 ctx.del_noerror(ctx.server_dn, recursive=True)
284 ctx.del_noerror(ctx.topology_dn)
286 ctx.del_noerror(ctx.partition_dn)
289 binding_options = "sign"
290 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
293 objectAttr = lsa.ObjectAttribute()
294 objectAttr.sec_qos = lsa.QosInfo()
296 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
297 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
300 name.string = ctx.realm
301 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
303 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
306 name.string = ctx.forest_domain_name
307 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
309 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
312 ctx.del_noerror(ctx.dns_a_dn)
315 ctx.del_noerror(ctx.dns_cname_dn)
319 def promote_possible(ctx):
320 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
322 # This shouldn't happen
323 raise Exception("Can not promote into a subdomain")
325 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
326 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
327 attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
329 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
330 if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
331 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
332 if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
333 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
335 ctx.promote_from_dn = res[0].dn
338 def find_dc(ctx, domain):
339 """find a writeable DC for the given domain"""
341 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
342 except NTSTATUSError as error:
343 raise Exception("Failed to find a writeable DC for domain '%s': %s" %
346 raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
347 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
348 ctx.site = ctx.cldap_ret.client_site
349 return ctx.cldap_ret.pdc_dns_name
352 def get_behavior_version(ctx):
353 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
354 if "msDS-Behavior-Version" in res[0]:
355 return int(res[0]["msDS-Behavior-Version"][0])
357 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
359 def get_dnsHostName(ctx):
360 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
361 return res[0]["dnsHostName"][0]
363 def get_domain_name(ctx):
364 '''get netbios name of the domain from the partitions record'''
365 partitions_dn = ctx.samdb.get_partitions_dn()
366 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
367 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
368 return res[0]["nETBIOSName"][0]
370 def get_forest_domain_name(ctx):
371 '''get netbios name of the domain from the partitions record'''
372 partitions_dn = ctx.samdb.get_partitions_dn()
373 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
374 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
375 return res[0]["nETBIOSName"][0]
377 def get_parent_partition_dn(ctx):
378 '''get the parent domain partition DN from parent DNS name'''
379 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
380 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
381 (ldb.binary_encode(ctx.parent_dnsdomain),
382 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
383 return str(res[0].dn)
385 def get_naming_master(ctx):
386 '''get the parent domain partition DN from parent DNS name'''
387 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
388 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
389 if not 'fSMORoleOwner' in res[0]:
390 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
392 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
394 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
396 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
400 '''get the SID of the connected user. Only works with w2k8 and later,
401 so only used for RODC join'''
402 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
403 binsid = res[0]["tokenGroups"][0]
404 return ctx.samdb.schema_format_value("objectSID", binsid)
406 def dn_exists(ctx, dn):
407 '''check if a DN exists'''
409 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
410 except ldb.LdbError as e5:
411 (enum, estr) = e5.args
412 if enum == ldb.ERR_NO_SUCH_OBJECT:
417 def add_krbtgt_account(ctx):
418 '''RODCs need a special krbtgt account'''
419 print("Adding %s" % ctx.krbtgt_dn)
421 "dn" : ctx.krbtgt_dn,
422 "objectclass" : "user",
423 "useraccountcontrol" : str(samba.dsdb.UF_NORMAL_ACCOUNT |
424 samba.dsdb.UF_ACCOUNTDISABLE),
425 "showinadvancedviewonly" : "TRUE",
426 "description" : "krbtgt for %s" % ctx.samname}
427 ctx.samdb.add(rec, ["rodc_join:1:1"])
429 # now we need to search for the samAccountName attribute on the krbtgt DN,
430 # as this will have been magically set to the krbtgt number
431 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
432 ctx.krbtgt_name = res[0]["samAccountName"][0]
434 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
437 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
438 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
439 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
442 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
443 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
444 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
446 def drsuapi_connect(ctx):
447 '''make a DRSUAPI connection to the naming master'''
448 binding_options = "seal"
449 if ctx.lp.log_level() >= 9:
450 binding_options += ",print"
451 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
452 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
453 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
455 def create_tmp_samdb(ctx):
456 '''create a temporary samdb object for schema queries'''
457 ctx.tmp_schema = Schema(ctx.domsid,
458 schemadn=ctx.schema_dn)
459 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
460 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
462 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
464 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
465 '''build a DsReplicaAttributeCtr object'''
466 r = drsuapi.DsReplicaAttribute()
467 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
471 def DsAddEntry(ctx, recs):
472 '''add a record via the DRSUAPI DsAddEntry call'''
473 if ctx.drsuapi is None:
474 ctx.drsuapi_connect()
475 if ctx.tmp_samdb is None:
476 ctx.create_tmp_samdb()
480 id = drsuapi.DsReplicaObjectIdentifier()
487 if not isinstance(rec[a], list):
491 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
494 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
495 attribute_ctr.num_attributes = len(attrs)
496 attribute_ctr.attributes = attrs
498 object = drsuapi.DsReplicaObject()
499 object.identifier = id
500 object.attribute_ctr = attribute_ctr
502 list_object = drsuapi.DsReplicaObjectListItem()
503 list_object.object = object
504 objects.append(list_object)
506 req2 = drsuapi.DsAddEntryRequest2()
507 req2.first_object = objects[0]
508 prev = req2.first_object
509 for o in objects[1:]:
513 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
515 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
516 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
517 raise RuntimeError("DsAddEntry failed")
518 if ctr.extended_err[0] != werror.WERR_SUCCESS:
519 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
520 raise RuntimeError("DsAddEntry failed")
523 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
524 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
525 if ctr.err_data.info is None:
526 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
528 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
529 ctr.err_data.info.extended_err))
530 raise RuntimeError("DsAddEntry failed")
531 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
532 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
533 raise RuntimeError("DsAddEntry failed")
537 def join_ntdsdsa_obj(ctx):
538 '''return the ntdsdsa object to add'''
540 print("Adding %s" % ctx.ntds_dn)
543 "objectclass" : "nTDSDSA",
544 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
545 "dMDLocation" : ctx.schema_dn}
547 nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
549 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
550 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
552 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
553 rec["msDS-HasDomainNCs"] = ctx.base_dn
556 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
557 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
558 rec["options"] = "37"
560 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
561 rec["HasMasterNCs"] = []
563 if nc in ctx.full_nc_list:
564 rec["HasMasterNCs"].append(nc)
565 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
566 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
568 rec["invocationId"] = ndr_pack(ctx.invocation_id)
572 def join_add_ntdsdsa(ctx):
573 '''add the ntdsdsa object'''
575 rec = ctx.join_ntdsdsa_obj()
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):
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"] = []
615 if ctx.promote_existing:
616 if ctx.promote_from_dn != ctx.acct_dn:
617 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
618 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
623 ctx.add_krbtgt_account()
626 print("Adding %s" % ctx.server_dn)
629 "objectclass" : "server",
630 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
631 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
632 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
633 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
634 # windows seems to add the dnsHostName later
635 "dnsHostName" : ctx.dnshostname}
638 rec["serverReference"] = ctx.acct_dn
643 # the rest is done after replication
648 ctx.join_add_ntdsdsa()
650 # Add the Replica-Locations or RO-Replica-Locations attributes
651 # TODO Is this supposed to be for the schema partition too?
652 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
653 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
655 base=ctx.samdb.get_partitions_dn(),
656 expression=expr), ctx.domaindns_zone)
658 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
659 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
661 base=ctx.samdb.get_partitions_dn(),
662 expression=expr), ctx.forestdns_zone)
664 for part, zone in (domain, forest):
665 if zone not in ctx.nc_list:
671 attr = "msDS-NC-Replica-Locations"
673 attr = "msDS-NC-RO-Replica-Locations"
675 m[attr] = ldb.MessageElement(ctx.ntds_dn,
676 ldb.FLAG_MOD_ADD, attr)
679 if ctx.connection_dn is not None:
680 print("Adding %s" % ctx.connection_dn)
682 "dn" : ctx.connection_dn,
683 "objectclass" : "nTDSConnection",
684 "enabledconnection" : "TRUE",
686 "fromServer" : ctx.dc_ntds_dn}
690 print("Adding SPNs to %s" % ctx.acct_dn)
692 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
693 for i in range(len(ctx.SPNs)):
694 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
695 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
696 ldb.FLAG_MOD_REPLACE,
697 "servicePrincipalName")
700 # The account password set operation should normally be done over
701 # LDAP. Windows 2000 DCs however allow this only with SSL
702 # connections which are hard to set up and otherwise refuse with
703 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
705 print("Setting account password for %s" % ctx.samname)
707 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
708 % ldb.binary_encode(ctx.samname),
710 force_change_at_next_login=False,
711 username=ctx.samname)
712 except ldb.LdbError as e2:
714 if num != ldb.ERR_UNWILLING_TO_PERFORM:
716 ctx.net.set_password(account_name=ctx.samname,
717 domain_name=ctx.domain_name,
718 newpassword=ctx.acct_pass.encode('utf-8'))
720 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
721 attrs=["msDS-KeyVersionNumber",
723 if "msDS-KeyVersionNumber" in res[0]:
724 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
726 ctx.key_version_number = None
728 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
729 res[0]["objectSid"][0])
731 print("Enabling account")
733 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
734 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
735 ldb.FLAG_MOD_REPLACE,
736 "userAccountControl")
739 if ctx.dns_backend.startswith("BIND9_"):
740 ctx.dnspass = samba.generate_random_password(128, 255)
742 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
743 {"DNSDOMAIN": ctx.dnsdomain,
744 "DOMAINDN": ctx.base_dn,
745 "HOSTNAME" : ctx.myname,
746 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
747 "DNSNAME" : ctx.dnshostname}))
748 for changetype, msg in recs:
749 assert changetype == ldb.CHANGETYPE_NONE
750 dns_acct_dn = msg["dn"]
751 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
753 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
754 del msg["clearTextPassword"]
755 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
756 del msg["isCriticalSystemObject"]
757 # Disable account until password is set
758 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
759 samba.dsdb.UF_ACCOUNTDISABLE)
762 except ldb.LdbError as e:
764 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
767 # The account password set operation should normally be done over
768 # LDAP. Windows 2000 DCs however allow this only with SSL
769 # connections which are hard to set up and otherwise refuse with
770 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
772 print("Setting account password for dns-%s" % ctx.myname)
774 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
775 % ldb.binary_encode(ctx.myname),
777 force_change_at_next_login=False,
778 username=ctx.samname)
779 except ldb.LdbError as e3:
781 if num != ldb.ERR_UNWILLING_TO_PERFORM:
783 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
784 domain_name=ctx.domain_name,
785 newpassword=ctx.dnspass)
787 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
788 attrs=["msDS-KeyVersionNumber"])
789 if "msDS-KeyVersionNumber" in res[0]:
790 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
792 ctx.dns_key_version_number = None
794 def join_add_objects2(ctx):
795 """add the various objects needed for the join, for subdomains post replication"""
797 print("Adding %s" % ctx.partition_dn)
798 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
799 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
801 "dn" : ctx.partition_dn,
802 "objectclass" : "crossRef",
803 "objectCategory" : "CN=Cross-Ref,%s" % ctx.schema_dn,
804 "nCName" : ctx.base_dn,
805 "nETBIOSName" : ctx.domain_name,
806 "dnsRoot": ctx.dnsdomain,
807 "trustParent" : ctx.parent_partition_dn,
808 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
809 "ntSecurityDescriptor" : sd_binary,
812 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
813 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
815 rec2 = ctx.join_ntdsdsa_obj()
817 objects = ctx.DsAddEntry([rec, rec2])
818 if len(objects) != 2:
819 raise DCJoinException("Expected 2 objects from DsAddEntry")
821 ctx.ntds_guid = objects[1].guid
823 print("Replicating partition DN")
824 ctx.repl.replicate(ctx.partition_dn,
825 misc.GUID("00000000-0000-0000-0000-000000000000"),
827 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
828 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
830 print("Replicating NTDS DN")
831 ctx.repl.replicate(ctx.ntds_dn,
832 misc.GUID("00000000-0000-0000-0000-000000000000"),
834 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
835 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
837 def join_provision(ctx):
838 """Provision the local SAM."""
840 print("Calling bare provision")
842 smbconf = ctx.lp.configfile
844 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
845 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
846 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
847 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
848 serverdn=ctx.server_dn, domain=ctx.domain_name,
849 hostname=ctx.myname, domainsid=ctx.domsid,
850 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
851 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
852 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
853 plaintext_secrets=ctx.plaintext_secrets,
854 backend_store=ctx.backend_store
856 print("Provision OK for domain DN %s" % presult.domaindn)
857 ctx.local_samdb = presult.samdb
859 ctx.paths = presult.paths
860 ctx.names = presult.names
862 # Fix up the forestsid, it may be different if we are joining as a subdomain
863 ctx.names.forestsid = ctx.forestsid
865 def join_provision_own_domain(ctx):
866 """Provision the local SAM."""
868 # we now operate exclusively on the local database, which
869 # we need to reopen in order to get the newly created schema
870 print("Reconnecting to local samdb")
871 ctx.samdb = SamDB(url=ctx.local_samdb.url,
872 session_info=system_session(),
873 lp=ctx.local_samdb.lp,
875 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
876 ctx.local_samdb = ctx.samdb
878 ctx.logger.info("Finding domain GUID from ncName")
879 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
880 controls=["extended_dn:1:1", "reveal_internals:0"])
882 if 'nCName' not in res[0]:
883 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
886 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
888 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
890 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
892 ctx.logger.info("Calling own domain provision")
894 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
896 presult = provision_fill(ctx.local_samdb, secrets_ldb,
897 ctx.logger, ctx.names, ctx.paths,
898 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
899 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
900 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
901 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
902 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
903 print("Provision OK for domain %s" % ctx.names.dnsdomain)
905 def join_replicate(ctx):
906 """Replicate the SAM."""
908 print("Starting replication")
909 ctx.local_samdb.transaction_start()
911 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
912 if ctx.ntds_guid is None:
913 print("Using DS_BIND_GUID_W2K3")
914 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
916 destination_dsa_guid = ctx.ntds_guid
919 repl_creds = Credentials()
920 repl_creds.guess(ctx.lp)
921 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
922 repl_creds.set_username(ctx.samname)
923 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
925 repl_creds = ctx.creds
927 binding_options = "seal"
928 if ctx.lp.log_level() >= 9:
929 binding_options += ",print"
930 repl = drs_utils.drs_Replicate(
931 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
932 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
934 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
935 destination_dsa_guid, schema=True, rodc=ctx.RODC,
936 replica_flags=ctx.replica_flags)
937 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
938 destination_dsa_guid, rodc=ctx.RODC,
939 replica_flags=ctx.replica_flags)
940 if not ctx.subdomain:
941 # Replicate first the critical object for the basedn
942 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
943 print("Replicating critical objects from the base DN of the domain")
944 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
945 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
946 destination_dsa_guid, rodc=ctx.RODC,
947 replica_flags=ctx.domain_replica_flags)
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 print("Done with always replicated NC (base, config, schema)")
954 # At this point we should already have an entry in the ForestDNS
955 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
956 # indicate that we hold a replica for this NC.
957 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
958 if nc in ctx.nc_list:
959 print("Replicating %s" % (str(nc)))
960 repl.replicate(nc, source_dsa_invocation_id,
961 destination_dsa_guid, rodc=ctx.RODC,
962 replica_flags=ctx.replica_flags)
965 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
966 destination_dsa_guid,
967 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
968 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
969 destination_dsa_guid,
970 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
971 elif ctx.rid_manager_dn != None:
972 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
974 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
975 destination_dsa_guid,
976 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
977 except samba.DsExtendedError as e1:
978 (enum, estr) = e1.args
979 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
980 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
981 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
986 ctx.source_dsa_invocation_id = source_dsa_invocation_id
987 ctx.destination_dsa_guid = destination_dsa_guid
989 print("Committing SAM database")
991 ctx.local_samdb.transaction_cancel()
994 ctx.local_samdb.transaction_commit()
996 def send_DsReplicaUpdateRefs(ctx, dn):
997 r = drsuapi.DsReplicaUpdateRefsRequest1()
998 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
999 r.naming_context.dn = str(dn)
1000 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
1001 r.naming_context.sid = security.dom_sid("S-0-0")
1002 r.dest_dsa_guid = ctx.ntds_guid
1003 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1004 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1006 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1008 if ctx.drsuapi is None:
1009 ctx.drsuapi_connect()
1011 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1013 def join_add_dns_records(ctx):
1014 """Remotely Add a DNS record to the target DC. We assume that if we
1015 replicate DNS that the server holds the DNS roles and can accept
1018 This avoids issues getting replication going after the DC
1019 first starts as the rest of the domain does not have to
1020 wait for samba_dnsupdate to run successfully.
1022 Specifically, we add the records implied by the DsReplicaUpdateRefs
1025 We do not just run samba_dnsupdate as we want to strictly
1026 operate against the DC we just joined:
1027 - We do not want to query another DNS server
1028 - We do not want to obtain a Kerberos ticket
1029 (as the KDC we select may not be the DC we just joined,
1030 and so may not be in sync with the password we just set)
1031 - We do not wish to set the _ldap records until we have started
1032 - We do not wish to use NTLM (the --use-samba-tool mode forces
1037 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1038 record_type = dnsp.DNS_TYPE_A
1039 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1040 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1042 zone = ctx.dnsdomain
1043 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1045 msdcs_cname = str(ctx.ntds_guid)
1046 cname_target = "%s.%s" % (name, zone)
1047 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1049 ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
1050 (len(IPs), name, zone))
1052 binding_options = "sign"
1053 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1059 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1061 change_owner_sd = security.descriptor()
1062 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1063 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1065 security.DOMAIN_RID_DCS))
1067 # TODO: Remove any old records from the primary DNS name
1070 = dns_conn.DnssrvEnumRecords2(client_version,
1080 except WERRORError as e:
1081 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1087 for record in rec.records:
1088 if record.wType == dnsp.DNS_TYPE_A or \
1089 record.wType == dnsp.DNS_TYPE_AAAA:
1091 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1092 del_rec_buf.rec = record
1094 dns_conn.DnssrvUpdateRecord2(client_version,
1101 except WERRORError as e:
1102 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1108 if IP.find(':') != -1:
1109 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1111 rec = AAAARecord(IP)
1113 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1118 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1119 add_rec_buf.rec = rec
1120 dns_conn.DnssrvUpdateRecord2(client_version,
1129 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1130 (ctx.dns_a_dn, ldap_record) \
1131 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1132 dns_partition=domaindns_zone_dn)
1134 # Make the DC own the DNS record, not the administrator
1135 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1136 controls=["sd_flags:1:%d"
1137 % (security.SECINFO_OWNER
1138 | security.SECINFO_GROUP)])
1142 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1143 % (msdcs_cname, msdcs_zone, cname_target))
1145 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1146 rec = CNameRecord(cname_target)
1147 add_rec_buf.rec = rec
1148 dns_conn.DnssrvUpdateRecord2(client_version,
1156 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1157 (ctx.dns_cname_dn, ldap_record) \
1158 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1159 dns_partition=forestdns_zone_dn)
1161 # Make the DC own the DNS record, not the administrator
1162 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1163 controls=["sd_flags:1:%d"
1164 % (security.SECINFO_OWNER
1165 | security.SECINFO_GROUP)])
1167 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1168 "will be created samba_dnsupdate on first startup")
1171 def join_replicate_new_dns_records(ctx):
1172 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1173 if nc in ctx.nc_list:
1174 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1175 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1176 ctx.ntds_guid, rodc=ctx.RODC,
1177 replica_flags=ctx.replica_flags,
1182 def join_finalise(ctx):
1183 """Finalise the join, mark us synchronised and setup secrets db."""
1185 # FIXME we shouldn't do this in all cases
1187 # If for some reasons we joined in another site than the one of
1188 # DC we just replicated from then we don't need to send the updatereplicateref
1189 # as replication between sites is time based and on the initiative of the
1191 if not ctx.clone_only:
1192 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1193 for nc in ctx.nc_list:
1194 ctx.send_DsReplicaUpdateRefs(nc)
1196 if not ctx.clone_only and ctx.RODC:
1197 print("Setting RODC invocationId")
1198 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1199 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1200 ctx.behavior_version)
1202 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1203 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1204 ldb.FLAG_MOD_REPLACE,
1206 ctx.local_samdb.modify(m)
1208 # Note: as RODC the invocationId is only stored
1209 # on the RODC itself, the other DCs never see it.
1211 # Thats is why we fix up the replPropertyMetaData stamp
1212 # for the 'invocationId' attribute, we need to change
1213 # the 'version' to '0', this is what windows 2008r2 does as RODC
1215 # This means if the object on a RWDC ever gets a invocationId
1216 # attribute, it will have version '1' (or higher), which will
1217 # will overwrite the RODC local value.
1218 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1222 ctx.logger.info("Setting isSynchronized and dsServiceName")
1224 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1225 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1227 # We want to appear to be the server we just cloned
1229 guid = ctx.remote_dc_ntds_guid
1231 guid = ctx.ntds_guid
1233 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1234 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1235 ctx.local_samdb.modify(m)
1237 if ctx.clone_only or ctx.subdomain:
1240 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1242 ctx.logger.info("Setting up secrets database")
1243 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1245 dnsdomain=ctx.dnsdomain,
1246 netbiosname=ctx.myname,
1247 domainsid=ctx.domsid,
1248 machinepass=ctx.acct_pass,
1249 secure_channel_type=ctx.secure_channel_type,
1250 key_version_number=ctx.key_version_number)
1252 if ctx.dns_backend.startswith("BIND9_"):
1253 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1254 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1255 dns_backend=ctx.dns_backend,
1256 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1257 targetdir=ctx.targetdir,
1258 key_version_number=ctx.dns_key_version_number)
1260 def join_setup_trusts(ctx):
1261 """provision the local SAM."""
1263 print("Setup domain trusts with server %s" % ctx.server)
1264 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1265 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1268 objectAttr = lsa.ObjectAttribute()
1269 objectAttr.sec_qos = lsa.QosInfo()
1271 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1272 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1274 info = lsa.TrustDomainInfoInfoEx()
1275 info.domain_name.string = ctx.dnsdomain
1276 info.netbios_name.string = ctx.domain_name
1277 info.sid = ctx.domsid
1278 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1279 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1280 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1283 oldname = lsa.String()
1284 oldname.string = ctx.dnsdomain
1285 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1286 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1287 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1288 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1289 except RuntimeError:
1292 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1294 clear_value = drsblobs.AuthInfoClear()
1295 clear_value.size = len(password_blob)
1296 clear_value.password = password_blob
1298 clear_authentication_information = drsblobs.AuthenticationInformation()
1299 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1300 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1301 clear_authentication_information.AuthInfo = clear_value
1303 authentication_information_array = drsblobs.AuthenticationInformationArray()
1304 authentication_information_array.count = 1
1305 authentication_information_array.array = [clear_authentication_information]
1307 outgoing = drsblobs.trustAuthInOutBlob()
1309 outgoing.current = authentication_information_array
1311 trustpass = drsblobs.trustDomainPasswords()
1312 confounder = [3] * 512
1314 for i in range(512):
1315 confounder[i] = random.randint(0, 255)
1317 trustpass.confounder = confounder
1319 trustpass.outgoing = outgoing
1320 trustpass.incoming = outgoing
1322 trustpass_blob = ndr_pack(trustpass)
1324 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1326 auth_blob = lsa.DATA_BUF2()
1327 auth_blob.size = len(encrypted_trustpass)
1328 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1330 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1331 auth_info.auth_blob = auth_blob
1333 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1336 security.SEC_STD_DELETE)
1339 "dn" : "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1340 "objectclass" : "trustedDomain",
1341 "trustType" : str(info.trust_type),
1342 "trustAttributes" : str(info.trust_attributes),
1343 "trustDirection" : str(info.trust_direction),
1344 "flatname" : ctx.forest_domain_name,
1345 "trustPartner" : ctx.dnsforest,
1346 "trustAuthIncoming" : ndr_pack(outgoing),
1347 "trustAuthOutgoing" : ndr_pack(outgoing),
1348 "securityIdentifier" : ndr_pack(ctx.forestsid)
1350 ctx.local_samdb.add(rec)
1353 "dn" : "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1354 "objectclass" : "user",
1355 "userAccountControl" : str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1356 "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le'),
1357 "samAccountName" : "%s$" % ctx.forest_domain_name
1359 ctx.local_samdb.add(rec)
1363 # nc_list is the list of naming context (NC) for which we will
1364 # replicate in and send a updateRef command to the partner DC
1366 # full_nc_list is the list of naming context (NC) we hold
1367 # read/write copies of. These are not subsets of each other.
1368 ctx.nc_list = [ ctx.config_dn, ctx.schema_dn ]
1369 ctx.full_nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
1371 if ctx.subdomain and ctx.dns_backend != "NONE":
1372 ctx.full_nc_list += [ctx.domaindns_zone]
1374 elif not ctx.subdomain:
1375 ctx.nc_list += [ctx.base_dn]
1377 if ctx.dns_backend != "NONE":
1378 ctx.nc_list += [ctx.domaindns_zone]
1379 ctx.nc_list += [ctx.forestdns_zone]
1380 ctx.full_nc_list += [ctx.domaindns_zone]
1381 ctx.full_nc_list += [ctx.forestdns_zone]
1383 if not ctx.clone_only:
1384 if ctx.promote_existing:
1385 ctx.promote_possible()
1387 ctx.cleanup_old_join()
1390 if not ctx.clone_only:
1391 ctx.join_add_objects()
1392 ctx.join_provision()
1393 ctx.join_replicate()
1394 if (not ctx.clone_only and ctx.subdomain):
1395 ctx.join_add_objects2()
1396 ctx.join_provision_own_domain()
1397 ctx.join_setup_trusts()
1399 if not ctx.clone_only and ctx.dns_backend != "NONE":
1400 ctx.join_add_dns_records()
1401 ctx.join_replicate_new_dns_records()
1406 print("Join failed - cleaning up")
1409 if not ctx.clone_only:
1410 ctx.cleanup_old_join()
1414 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1415 targetdir=None, domain=None, domain_critical_only=False,
1416 machinepass=None, use_ntvfs=False, dns_backend=None,
1417 promote_existing=False, plaintext_secrets=False,
1418 backend_store=None):
1419 """Join as a RODC."""
1421 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1422 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1423 promote_existing, plaintext_secrets,
1424 backend_store=backend_store)
1426 lp.set("workgroup", ctx.domain_name)
1427 logger.info("workgroup is %s" % ctx.domain_name)
1429 lp.set("realm", ctx.realm)
1430 logger.info("realm is %s" % ctx.realm)
1432 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1434 # setup some defaults for accounts that should be replicated to this RODC
1435 ctx.never_reveal_sid = [
1436 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1437 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1438 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1439 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1440 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1441 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1443 mysid = ctx.get_mysid()
1444 admin_dn = "<SID=%s>" % mysid
1445 ctx.managedby = admin_dn
1447 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1448 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1449 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1451 ctx.SPNs.extend([ "RestrictedKrbHost/%s" % ctx.myname,
1452 "RestrictedKrbHost/%s" % ctx.dnshostname ])
1454 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1455 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1457 ctx.replica_flags |= ( drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1458 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1459 ctx.domain_replica_flags = ctx.replica_flags
1460 if domain_critical_only:
1461 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1465 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1468 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1469 targetdir=None, domain=None, domain_critical_only=False,
1470 machinepass=None, use_ntvfs=False, dns_backend=None,
1471 promote_existing=False, plaintext_secrets=False,
1472 backend_store=None):
1474 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1475 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1476 promote_existing, plaintext_secrets,
1477 backend_store=backend_store)
1479 lp.set("workgroup", ctx.domain_name)
1480 logger.info("workgroup is %s" % ctx.domain_name)
1482 lp.set("realm", ctx.realm)
1483 logger.info("realm is %s" % ctx.realm)
1485 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1487 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1488 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1490 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1491 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1492 ctx.domain_replica_flags = ctx.replica_flags
1493 if domain_critical_only:
1494 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1497 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1499 def join_clone(logger=None, server=None, creds=None, lp=None,
1500 targetdir=None, domain=None, include_secrets=False,
1501 dns_backend="NONE"):
1503 ctx = DCJoinContext(logger, server, creds, lp, site=None, netbios_name=None,
1504 targetdir=targetdir, domain=domain, machinepass=None,
1505 use_ntvfs=False, dns_backend=dns_backend,
1506 promote_existing=False, clone_only=True)
1508 lp.set("workgroup", ctx.domain_name)
1509 logger.info("workgroup is %s" % ctx.domain_name)
1511 lp.set("realm", ctx.realm)
1512 logger.info("realm is %s" % ctx.realm)
1514 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1515 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1516 if not include_secrets:
1517 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1518 ctx.domain_replica_flags = ctx.replica_flags
1521 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1523 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1524 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1525 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1526 dns_backend=None, plaintext_secrets=False,
1527 backend_store=None):
1529 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1530 targetdir, parent_domain, machinepass, use_ntvfs,
1531 dns_backend, plaintext_secrets,
1532 backend_store=backend_store)
1533 ctx.subdomain = True
1534 if adminpass is None:
1535 ctx.adminpass = samba.generate_random_password(12, 32)
1537 ctx.adminpass = adminpass
1538 ctx.parent_domain_name = ctx.domain_name
1539 ctx.domain_name = netbios_domain
1540 ctx.realm = dnsdomain
1541 ctx.parent_dnsdomain = ctx.dnsdomain
1542 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1543 ctx.dnsdomain = dnsdomain
1544 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1545 ctx.naming_master = ctx.get_naming_master()
1546 if ctx.naming_master != ctx.server:
1547 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1548 ctx.server = ctx.naming_master
1549 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1550 session_info=system_session(),
1551 credentials=ctx.creds, lp=ctx.lp)
1552 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1554 ctx.server = res[0]["dnsHostName"]
1555 logger.info("DNS name of new naming master is %s" % ctx.server)
1557 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1558 ctx.forestsid = ctx.domsid
1559 ctx.domsid = security.random_sid()
1561 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1562 # Windows uses 240 bytes as UTF16 so we do
1563 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1565 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1567 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1568 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1570 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1571 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1572 ctx.domain_replica_flags = ctx.replica_flags
1575 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))