2 # Copyright Andrew Tridgell 2010
3 # Copyright Andrew Bartlett 2010
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 from __future__ import print_function
20 """Joining a domain."""
22 from samba.auth import system_session
23 from samba.samdb import SamDB
24 from samba import gensec, Ldb, drs_utils, arcfour_encrypt, string_to_byte_array
25 import ldb, samba, sys, uuid
26 from samba.ndr import ndr_pack, ndr_unpack
27 from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp
28 from samba.dsdb import DS_DOMAIN_FUNCTION_2003
29 from samba.credentials import Credentials, DONT_USE_KERBEROS
30 from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN
31 from samba.provision.common import setup_path
32 from samba.schema import Schema
33 from samba import descriptor
34 from samba.net import Net
35 from samba.provision.sambadns import setup_bind9_dns
36 from samba import read_and_sub_file
37 from samba import werror
38 from base64 import b64encode
39 from samba import WERRORError, NTSTATUSError
40 from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord
41 from samba import sd_utils
47 class DCJoinException(Exception):
49 def __init__(self, msg):
50 super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
53 class dc_join(object):
54 """Perform a DC join."""
56 def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
57 netbios_name=None, targetdir=None, domain=None,
58 machinepass=None, use_ntvfs=False, dns_backend=None,
59 promote_existing=False, clone_only=False,
60 plaintext_secrets=False, 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 = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
1422 machinepass, use_ntvfs, dns_backend, promote_existing,
1423 plaintext_secrets, backend_store=backend_store)
1425 lp.set("workgroup", ctx.domain_name)
1426 logger.info("workgroup is %s" % ctx.domain_name)
1428 lp.set("realm", ctx.realm)
1429 logger.info("realm is %s" % ctx.realm)
1431 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1433 # setup some defaults for accounts that should be replicated to this RODC
1434 ctx.never_reveal_sid = [
1435 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1436 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1437 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1438 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1439 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1440 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1442 mysid = ctx.get_mysid()
1443 admin_dn = "<SID=%s>" % mysid
1444 ctx.managedby = admin_dn
1446 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1447 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1448 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1450 ctx.SPNs.extend([ "RestrictedKrbHost/%s" % ctx.myname,
1451 "RestrictedKrbHost/%s" % ctx.dnshostname ])
1453 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1454 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1456 ctx.replica_flags |= ( drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1457 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1458 ctx.domain_replica_flags = ctx.replica_flags
1459 if domain_critical_only:
1460 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1464 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1467 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1468 targetdir=None, domain=None, domain_critical_only=False,
1469 machinepass=None, use_ntvfs=False, dns_backend=None,
1470 promote_existing=False, plaintext_secrets=False,
1471 backend_store=None):
1473 ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
1474 machinepass, use_ntvfs, dns_backend, promote_existing,
1475 plaintext_secrets, backend_store=backend_store)
1477 lp.set("workgroup", ctx.domain_name)
1478 logger.info("workgroup is %s" % ctx.domain_name)
1480 lp.set("realm", ctx.realm)
1481 logger.info("realm is %s" % ctx.realm)
1483 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1485 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1486 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1488 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1489 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1490 ctx.domain_replica_flags = ctx.replica_flags
1491 if domain_critical_only:
1492 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1495 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1497 def join_clone(logger=None, server=None, creds=None, lp=None,
1498 targetdir=None, domain=None, include_secrets=False,
1499 dns_backend="NONE"):
1501 ctx = dc_join(logger, server, creds, lp, site=None, netbios_name=None, targetdir=targetdir, domain=domain,
1502 machinepass=None, use_ntvfs=False, dns_backend=dns_backend, promote_existing=False, clone_only=True)
1504 lp.set("workgroup", ctx.domain_name)
1505 logger.info("workgroup is %s" % ctx.domain_name)
1507 lp.set("realm", ctx.realm)
1508 logger.info("realm is %s" % ctx.realm)
1510 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1511 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1512 if not include_secrets:
1513 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1514 ctx.domain_replica_flags = ctx.replica_flags
1517 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1519 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1520 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1521 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1522 dns_backend=None, plaintext_secrets=False,
1523 backend_store=None):
1525 ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, parent_domain,
1526 machinepass, use_ntvfs, dns_backend, plaintext_secrets,
1527 backend_store=backend_store)
1528 ctx.subdomain = True
1529 if adminpass is None:
1530 ctx.adminpass = samba.generate_random_password(12, 32)
1532 ctx.adminpass = adminpass
1533 ctx.parent_domain_name = ctx.domain_name
1534 ctx.domain_name = netbios_domain
1535 ctx.realm = dnsdomain
1536 ctx.parent_dnsdomain = ctx.dnsdomain
1537 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1538 ctx.dnsdomain = dnsdomain
1539 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1540 ctx.naming_master = ctx.get_naming_master()
1541 if ctx.naming_master != ctx.server:
1542 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1543 ctx.server = ctx.naming_master
1544 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1545 session_info=system_session(),
1546 credentials=ctx.creds, lp=ctx.lp)
1547 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1549 ctx.server = res[0]["dnsHostName"]
1550 logger.info("DNS name of new naming master is %s" % ctx.server)
1552 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1553 ctx.forestsid = ctx.domsid
1554 ctx.domsid = security.random_sid()
1556 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1557 # Windows uses 240 bytes as UTF16 so we do
1558 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1560 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1562 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1563 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1565 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1566 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1567 ctx.domain_replica_flags = ctx.replica_flags
1570 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))