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
29 from samba.ndr import ndr_pack, ndr_unpack
30 from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp
31 from samba.dsdb import DS_DOMAIN_FUNCTION_2003
32 from samba.credentials import Credentials, DONT_USE_KERBEROS
33 from samba.provision import (secretsdb_self_join, provision, provision_fill,
34 FILL_DRS, FILL_SUBDOMAIN, DEFAULTSITE)
35 from samba.provision.common import setup_path
36 from samba.schema import Schema
37 from samba import descriptor
38 from samba.net import Net
39 from samba.provision.sambadns import setup_bind9_dns
40 from samba import read_and_sub_file
41 from samba import werror
42 from base64 import b64encode
43 from samba import WERRORError, NTSTATUSError
44 from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord
45 from samba import sd_utils
53 from samba.compat import text_type
54 from samba.compat import get_string
57 class DCJoinException(Exception):
59 def __init__(self, msg):
60 super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
63 class DCJoinContext(object):
64 """Perform a DC join."""
66 def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
67 netbios_name=None, targetdir=None, domain=None,
68 machinepass=None, use_ntvfs=False, dns_backend=None,
69 promote_existing=False, plaintext_secrets=False,
70 backend_store=None, forced_local_samdb=None):
78 ctx.targetdir = targetdir
79 ctx.use_ntvfs = use_ntvfs
80 ctx.plaintext_secrets = plaintext_secrets
81 ctx.backend_store = backend_store
83 ctx.promote_existing = promote_existing
84 ctx.promote_from_dn = None
89 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
90 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
93 ctx.forced_local_samdb = forced_local_samdb
95 if forced_local_samdb:
96 ctx.samdb = forced_local_samdb
97 ctx.server = ctx.samdb.url
100 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
101 ctx.server = ctx.find_dc(domain)
102 ctx.logger.info("Found DC %s" % ctx.server)
103 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
104 session_info=system_session(),
105 credentials=ctx.creds, lp=ctx.lp)
108 ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
109 except ldb.LdbError as e4:
110 (enum, estr) = e4.args
111 raise DCJoinException(estr)
113 ctx.base_dn = str(ctx.samdb.get_default_basedn())
114 ctx.root_dn = str(ctx.samdb.get_root_basedn())
115 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
116 ctx.config_dn = str(ctx.samdb.get_config_basedn())
117 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
118 ctx.forestsid = ctx.domsid
119 ctx.domain_name = ctx.get_domain_name()
120 ctx.forest_domain_name = ctx.get_forest_domain_name()
121 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
123 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
124 ctx.dc_dnsHostName = ctx.get_dnsHostName()
125 ctx.behavior_version = ctx.get_behavior_version()
127 if machinepass is not None:
128 ctx.acct_pass = machinepass
130 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
132 ctx.dnsdomain = ctx.samdb.domain_dns_name()
134 # the following are all dependent on the new DC's netbios_name (which
135 # we expect to always be specified, except when cloning a DC)
137 # work out the DNs of all the objects we will be adding
138 ctx.myname = netbios_name
139 ctx.samname = "%s$" % ctx.myname
140 ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
141 ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
142 ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
143 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
144 ctx.dnsforest = ctx.samdb.forest_dns_name()
146 topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
147 if ctx.dn_exists(topology_base):
148 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
150 ctx.topology_dn = None
152 ctx.SPNs = ["HOST/%s" % ctx.myname,
153 "HOST/%s" % ctx.dnshostname,
154 "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest)]
156 res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
157 attrs=["rIDManagerReference"],
160 ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
162 ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
163 ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
165 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
166 res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
168 base=ctx.samdb.get_partitions_dn(),
170 if dns_backend is None:
171 ctx.dns_backend = "NONE"
173 if len(res_domaindns) == 0:
174 ctx.dns_backend = "NONE"
175 print("NO DNS zone information found in source domain, not replicating DNS")
177 ctx.dns_backend = dns_backend
179 ctx.realm = ctx.dnsdomain
183 ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
184 drsuapi.DRSUAPI_DRS_PER_SYNC |
185 drsuapi.DRSUAPI_DRS_GET_ANC |
186 drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
187 drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
189 # these elements are optional
190 ctx.never_reveal_sid = None
191 ctx.reveal_sid = None
192 ctx.connection_dn = None
197 ctx.subdomain = False
199 ctx.partition_dn = None
202 ctx.dns_cname_dn = None
204 # Do not normally register 127. addresses but allow override for selftest
205 ctx.force_all_ips = False
207 def del_noerror(ctx, dn, recursive=False):
210 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
214 ctx.del_noerror(r.dn, recursive=True)
217 print("Deleted %s" % dn)
221 def cleanup_old_accounts(ctx, force=False):
222 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
223 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
224 attrs=["msDS-krbTgtLink", "objectSID"])
229 creds = Credentials()
232 creds.set_machine_account(ctx.lp)
233 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
234 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
235 session_info=system_session(),
236 credentials=creds, lp=ctx.lp)
240 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
241 if token_res[0]["tokenGroups"][0] \
242 == res[0]["objectSID"][0]:
243 raise DCJoinException("Not removing account %s which "
244 "looks like a Samba DC account "
245 "matching the password we already have. "
246 "To override, remove secrets.ldb and secrets.tdb"
249 ctx.del_noerror(res[0].dn, recursive=True)
251 if "msDS-Krbtgtlink" in res[0]:
252 new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
253 ctx.del_noerror(ctx.new_krbtgt_dn)
255 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
256 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
257 (ldb.binary_encode("dns-%s" % ctx.myname),
258 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
261 ctx.del_noerror(res[0].dn, recursive=True)
263 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
264 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
267 raise DCJoinException("Not removing account %s which looks like "
268 "a Samba DNS service account but does not "
269 "have servicePrincipalName=%s" %
270 (ldb.binary_encode("dns-%s" % ctx.myname),
271 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
273 def cleanup_old_join(ctx, force=False):
274 """Remove any DNs from a previous join."""
275 # find the krbtgt link
276 if not ctx.subdomain:
277 ctx.cleanup_old_accounts(force=force)
279 if ctx.connection_dn is not None:
280 ctx.del_noerror(ctx.connection_dn)
281 if ctx.krbtgt_dn is not None:
282 ctx.del_noerror(ctx.krbtgt_dn)
283 ctx.del_noerror(ctx.ntds_dn)
284 ctx.del_noerror(ctx.server_dn, recursive=True)
286 ctx.del_noerror(ctx.topology_dn)
288 ctx.del_noerror(ctx.partition_dn)
291 binding_options = "sign"
292 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
295 objectAttr = lsa.ObjectAttribute()
296 objectAttr.sec_qos = lsa.QosInfo()
298 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
299 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
302 name.string = ctx.realm
303 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
305 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
308 name.string = ctx.forest_domain_name
309 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
311 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
314 ctx.del_noerror(ctx.dns_a_dn)
317 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 |
333 samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
334 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
336 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
351 def get_behavior_version(ctx):
352 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
353 if "msDS-Behavior-Version" in res[0]:
354 return int(res[0]["msDS-Behavior-Version"][0])
356 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
358 def get_dnsHostName(ctx):
359 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
360 return str(res[0]["dnsHostName"][0])
362 def get_domain_name(ctx):
363 '''get netbios name of the domain from the partitions record'''
364 partitions_dn = ctx.samdb.get_partitions_dn()
365 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
366 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
367 return str(res[0]["nETBIOSName"][0])
369 def get_forest_domain_name(ctx):
370 '''get netbios name of the domain from the partitions record'''
371 partitions_dn = ctx.samdb.get_partitions_dn()
372 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
373 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
374 return str(res[0]["nETBIOSName"][0])
376 def get_parent_partition_dn(ctx):
377 '''get the parent domain partition DN from parent DNS name'''
378 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
379 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
380 (ldb.binary_encode(ctx.parent_dnsdomain),
381 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
382 return str(res[0].dn)
384 def get_naming_master(ctx):
385 '''get the parent domain partition DN from parent DNS name'''
386 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
387 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
388 if 'fSMORoleOwner' not in res[0]:
389 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
391 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
393 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
395 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
399 '''get the SID of the connected user. Only works with w2k8 and later,
400 so only used for RODC join'''
401 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
402 binsid = res[0]["tokenGroups"][0]
403 return get_string(ctx.samdb.schema_format_value("objectSID", binsid))
405 def dn_exists(ctx, dn):
406 '''check if a DN exists'''
408 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
409 except ldb.LdbError as e5:
410 (enum, estr) = e5.args
411 if enum == ldb.ERR_NO_SUCH_OBJECT:
416 def add_krbtgt_account(ctx):
417 '''RODCs need a special krbtgt account'''
418 print("Adding %s" % ctx.krbtgt_dn)
421 "objectclass": "user",
422 "useraccountcontrol": str(samba.dsdb.UF_NORMAL_ACCOUNT |
423 samba.dsdb.UF_ACCOUNTDISABLE),
424 "showinadvancedviewonly": "TRUE",
425 "description": "krbtgt for %s" % ctx.samname}
426 ctx.samdb.add(rec, ["rodc_join:1:1"])
428 # now we need to search for the samAccountName attribute on the krbtgt DN,
429 # as this will have been magically set to the krbtgt number
430 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
431 ctx.krbtgt_name = res[0]["samAccountName"][0]
433 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
436 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
437 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
438 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
441 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
442 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
443 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
445 def drsuapi_connect(ctx):
446 '''make a DRSUAPI connection to the naming master'''
447 binding_options = "seal"
448 if ctx.lp.log_level() >= 9:
449 binding_options += ",print"
450 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
451 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
452 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
454 def create_tmp_samdb(ctx):
455 '''create a temporary samdb object for schema queries'''
456 ctx.tmp_schema = Schema(ctx.domsid,
457 schemadn=ctx.schema_dn)
458 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
459 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
461 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
463 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
464 '''build a DsReplicaAttributeCtr object'''
465 r = drsuapi.DsReplicaAttribute()
466 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
469 def DsAddEntry(ctx, recs):
470 '''add a record via the DRSUAPI DsAddEntry call'''
471 if ctx.drsuapi is None:
472 ctx.drsuapi_connect()
473 if ctx.tmp_samdb is None:
474 ctx.create_tmp_samdb()
478 id = drsuapi.DsReplicaObjectIdentifier()
485 if not isinstance(rec[a], list):
489 v = [x.encode('utf8') if isinstance(x, text_type) else x for x in v]
490 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
493 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
494 attribute_ctr.num_attributes = len(attrs)
495 attribute_ctr.attributes = attrs
497 object = drsuapi.DsReplicaObject()
498 object.identifier = id
499 object.attribute_ctr = attribute_ctr
501 list_object = drsuapi.DsReplicaObjectListItem()
502 list_object.object = object
503 objects.append(list_object)
505 req2 = drsuapi.DsAddEntryRequest2()
506 req2.first_object = objects[0]
507 prev = req2.first_object
508 for o in objects[1:]:
512 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
514 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
515 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
516 raise RuntimeError("DsAddEntry failed")
517 if ctr.extended_err[0] != werror.WERR_SUCCESS:
518 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
519 raise RuntimeError("DsAddEntry failed")
522 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
523 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
524 if ctr.err_data.info is None:
525 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
527 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
528 ctr.err_data.info.extended_err))
529 raise RuntimeError("DsAddEntry failed")
530 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
531 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
532 raise RuntimeError("DsAddEntry failed")
536 def join_ntdsdsa_obj(ctx):
537 '''return the ntdsdsa object to add'''
539 print("Adding %s" % ctx.ntds_dn)
542 "objectclass": "nTDSDSA",
543 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
544 "dMDLocation": ctx.schema_dn}
546 nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
548 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
549 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
551 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
552 rec["msDS-HasDomainNCs"] = ctx.base_dn
555 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
556 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
557 rec["options"] = "37"
559 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
560 rec["HasMasterNCs"] = []
562 if nc in ctx.full_nc_list:
563 rec["HasMasterNCs"].append(nc)
564 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
565 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
567 rec["invocationId"] = ndr_pack(ctx.invocation_id)
571 def join_add_ntdsdsa(ctx):
572 '''add the ntdsdsa object'''
574 rec = ctx.join_ntdsdsa_obj()
575 if ctx.forced_local_samdb:
576 ctx.samdb.add(rec, controls=["relax:0"])
578 ctx.samdb.add(rec, ["rodc_join:1:1"])
580 ctx.DsAddEntry([rec])
582 # find the GUID of our NTDS DN
583 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
584 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
586 def join_add_objects(ctx, specified_sid=None):
587 '''add the various objects needed for the join'''
589 print("Adding %s" % ctx.acct_dn)
592 "objectClass": "computer",
593 "displayname": ctx.samname,
594 "samaccountname": ctx.samname,
595 "userAccountControl": str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
596 "dnshostname": ctx.dnshostname}
597 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
598 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
599 elif ctx.promote_existing:
600 rec['msDS-SupportedEncryptionTypes'] = []
602 rec["managedby"] = ctx.managedby
603 elif ctx.promote_existing:
604 rec["managedby"] = []
606 if ctx.never_reveal_sid:
607 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
608 elif ctx.promote_existing:
609 rec["msDS-NeverRevealGroup"] = []
612 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
613 elif ctx.promote_existing:
614 rec["msDS-RevealOnDemandGroup"] = []
617 rec["objectSid"] = ndr_pack(specified_sid)
619 if ctx.promote_existing:
620 if ctx.promote_from_dn != ctx.acct_dn:
621 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
622 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
625 if specified_sid is not None:
626 controls = ["relax:0"]
627 ctx.samdb.add(rec, controls=controls)
630 ctx.add_krbtgt_account()
633 print("Adding %s" % ctx.server_dn)
636 "objectclass": "server",
637 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
638 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
639 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
640 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
641 # windows seems to add the dnsHostName later
642 "dnsHostName": ctx.dnshostname}
645 rec["serverReference"] = ctx.acct_dn
650 # the rest is done after replication
655 ctx.join_add_ntdsdsa()
657 # Add the Replica-Locations or RO-Replica-Locations attributes
658 # TODO Is this supposed to be for the schema partition too?
659 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
660 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
662 base=ctx.samdb.get_partitions_dn(),
663 expression=expr), ctx.domaindns_zone)
665 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
666 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
668 base=ctx.samdb.get_partitions_dn(),
669 expression=expr), ctx.forestdns_zone)
671 for part, zone in (domain, forest):
672 if zone not in ctx.nc_list:
678 attr = "msDS-NC-Replica-Locations"
680 attr = "msDS-NC-RO-Replica-Locations"
682 m[attr] = ldb.MessageElement(ctx.ntds_dn,
683 ldb.FLAG_MOD_ADD, attr)
686 if ctx.connection_dn is not None:
687 print("Adding %s" % ctx.connection_dn)
689 "dn": ctx.connection_dn,
690 "objectclass": "nTDSConnection",
691 "enabledconnection": "TRUE",
693 "fromServer": ctx.dc_ntds_dn}
697 print("Adding SPNs to %s" % ctx.acct_dn)
699 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
700 for i in range(len(ctx.SPNs)):
701 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
702 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
703 ldb.FLAG_MOD_REPLACE,
704 "servicePrincipalName")
707 # The account password set operation should normally be done over
708 # LDAP. Windows 2000 DCs however allow this only with SSL
709 # connections which are hard to set up and otherwise refuse with
710 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
712 print("Setting account password for %s" % ctx.samname)
714 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
715 % ldb.binary_encode(ctx.samname),
717 force_change_at_next_login=False,
718 username=ctx.samname)
719 except ldb.LdbError as e2:
721 if num != ldb.ERR_UNWILLING_TO_PERFORM:
723 ctx.net.set_password(account_name=ctx.samname,
724 domain_name=ctx.domain_name,
725 newpassword=ctx.acct_pass.encode('utf-8'))
727 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
728 attrs=["msDS-KeyVersionNumber",
730 if "msDS-KeyVersionNumber" in res[0]:
731 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
733 ctx.key_version_number = None
735 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
736 res[0]["objectSid"][0])
738 print("Enabling account")
740 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
741 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
742 ldb.FLAG_MOD_REPLACE,
743 "userAccountControl")
746 if ctx.dns_backend.startswith("BIND9_"):
747 ctx.dnspass = samba.generate_random_password(128, 255)
749 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
750 {"DNSDOMAIN": ctx.dnsdomain,
751 "DOMAINDN": ctx.base_dn,
752 "HOSTNAME": ctx.myname,
753 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
754 "DNSNAME": ctx.dnshostname}))
755 for changetype, msg in recs:
756 assert changetype == ldb.CHANGETYPE_NONE
757 dns_acct_dn = msg["dn"]
758 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
760 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
761 del msg["clearTextPassword"]
762 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
763 del msg["isCriticalSystemObject"]
764 # Disable account until password is set
765 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
766 samba.dsdb.UF_ACCOUNTDISABLE)
769 except ldb.LdbError as e:
771 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
774 # The account password set operation should normally be done over
775 # LDAP. Windows 2000 DCs however allow this only with SSL
776 # connections which are hard to set up and otherwise refuse with
777 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
779 print("Setting account password for dns-%s" % ctx.myname)
781 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
782 % ldb.binary_encode(ctx.myname),
784 force_change_at_next_login=False,
785 username=ctx.samname)
786 except ldb.LdbError as e3:
788 if num != ldb.ERR_UNWILLING_TO_PERFORM:
790 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
791 domain_name=ctx.domain_name,
792 newpassword=ctx.dnspass)
794 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
795 attrs=["msDS-KeyVersionNumber"])
796 if "msDS-KeyVersionNumber" in res[0]:
797 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
799 ctx.dns_key_version_number = None
801 def join_add_objects2(ctx):
802 """add the various objects needed for the join, for subdomains post replication"""
804 print("Adding %s" % ctx.partition_dn)
805 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
806 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
808 "dn": ctx.partition_dn,
809 "objectclass": "crossRef",
810 "objectCategory": "CN=Cross-Ref,%s" % ctx.schema_dn,
811 "nCName": ctx.base_dn,
812 "nETBIOSName": ctx.domain_name,
813 "dnsRoot": ctx.dnsdomain,
814 "trustParent": ctx.parent_partition_dn,
815 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC |samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
816 "ntSecurityDescriptor": sd_binary,
819 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
820 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
822 rec2 = ctx.join_ntdsdsa_obj()
824 objects = ctx.DsAddEntry([rec, rec2])
825 if len(objects) != 2:
826 raise DCJoinException("Expected 2 objects from DsAddEntry")
828 ctx.ntds_guid = objects[1].guid
830 print("Replicating partition DN")
831 ctx.repl.replicate(ctx.partition_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 print("Replicating NTDS DN")
838 ctx.repl.replicate(ctx.ntds_dn,
839 misc.GUID("00000000-0000-0000-0000-000000000000"),
841 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
842 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
844 def join_provision(ctx):
845 """Provision the local SAM."""
847 print("Calling bare provision")
849 smbconf = ctx.lp.configfile
851 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
852 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
853 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
854 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
855 serverdn=ctx.server_dn, domain=ctx.domain_name,
856 hostname=ctx.myname, domainsid=ctx.domsid,
857 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
858 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
859 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
860 plaintext_secrets=ctx.plaintext_secrets,
861 backend_store=ctx.backend_store
863 print("Provision OK for domain DN %s" % presult.domaindn)
864 ctx.local_samdb = presult.samdb
866 ctx.paths = presult.paths
867 ctx.names = presult.names
869 # Fix up the forestsid, it may be different if we are joining as a subdomain
870 ctx.names.forestsid = ctx.forestsid
872 def join_provision_own_domain(ctx):
873 """Provision the local SAM."""
875 # we now operate exclusively on the local database, which
876 # we need to reopen in order to get the newly created schema
877 print("Reconnecting to local samdb")
878 ctx.samdb = SamDB(url=ctx.local_samdb.url,
879 session_info=system_session(),
880 lp=ctx.local_samdb.lp,
882 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
883 ctx.local_samdb = ctx.samdb
885 ctx.logger.info("Finding domain GUID from ncName")
886 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
887 controls=["extended_dn:1:1", "reveal_internals:0"])
889 if 'nCName' not in res[0]:
890 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
893 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
895 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
897 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
899 ctx.logger.info("Calling own domain provision")
901 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
903 presult = provision_fill(ctx.local_samdb, secrets_ldb,
904 ctx.logger, ctx.names, ctx.paths,
905 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
906 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
907 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
908 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
909 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
910 print("Provision OK for domain %s" % ctx.names.dnsdomain)
912 def create_replicator(ctx, repl_creds, binding_options):
913 '''Creates a new DRS object for managing replications'''
914 return drs_utils.drs_Replicate(
915 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
916 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
918 def join_replicate(ctx):
919 """Replicate the SAM."""
921 print("Starting replication")
922 ctx.local_samdb.transaction_start()
924 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
925 if ctx.ntds_guid is None:
926 print("Using DS_BIND_GUID_W2K3")
927 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
929 destination_dsa_guid = ctx.ntds_guid
932 repl_creds = Credentials()
933 repl_creds.guess(ctx.lp)
934 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
935 repl_creds.set_username(ctx.samname)
936 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
938 repl_creds = ctx.creds
940 binding_options = "seal"
941 if ctx.lp.log_level() >= 9:
942 binding_options += ",print"
944 repl = ctx.create_replicator(repl_creds, binding_options)
946 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
947 destination_dsa_guid, schema=True, rodc=ctx.RODC,
948 replica_flags=ctx.replica_flags)
949 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
950 destination_dsa_guid, rodc=ctx.RODC,
951 replica_flags=ctx.replica_flags)
952 if not ctx.subdomain:
953 # Replicate first the critical object for the basedn
954 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
955 print("Replicating critical objects from the base DN of the domain")
956 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
957 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
958 destination_dsa_guid, rodc=ctx.RODC,
959 replica_flags=ctx.domain_replica_flags)
960 ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
961 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
962 destination_dsa_guid, rodc=ctx.RODC,
963 replica_flags=ctx.domain_replica_flags)
964 print("Done with always replicated NC (base, config, schema)")
966 # At this point we should already have an entry in the ForestDNS
967 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
968 # indicate that we hold a replica for this NC.
969 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
970 if nc in ctx.nc_list:
971 print("Replicating %s" % (str(nc)))
972 repl.replicate(nc, source_dsa_invocation_id,
973 destination_dsa_guid, rodc=ctx.RODC,
974 replica_flags=ctx.replica_flags)
977 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
978 destination_dsa_guid,
979 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
980 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
981 destination_dsa_guid,
982 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
983 elif ctx.rid_manager_dn is not None:
984 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
986 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
987 destination_dsa_guid,
988 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
989 except samba.DsExtendedError as e1:
990 (enum, estr) = e1.args
991 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
992 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
993 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
998 ctx.source_dsa_invocation_id = source_dsa_invocation_id
999 ctx.destination_dsa_guid = destination_dsa_guid
1001 print("Committing SAM database")
1003 ctx.local_samdb.transaction_cancel()
1006 ctx.local_samdb.transaction_commit()
1008 def send_DsReplicaUpdateRefs(ctx, dn):
1009 r = drsuapi.DsReplicaUpdateRefsRequest1()
1010 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
1011 r.naming_context.dn = str(dn)
1012 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
1013 r.naming_context.sid = security.dom_sid("S-0-0")
1014 r.dest_dsa_guid = ctx.ntds_guid
1015 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1016 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1018 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1020 if ctx.drsuapi is None:
1021 ctx.drsuapi_connect()
1023 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1025 def join_add_dns_records(ctx):
1026 """Remotely Add a DNS record to the target DC. We assume that if we
1027 replicate DNS that the server holds the DNS roles and can accept
1030 This avoids issues getting replication going after the DC
1031 first starts as the rest of the domain does not have to
1032 wait for samba_dnsupdate to run successfully.
1034 Specifically, we add the records implied by the DsReplicaUpdateRefs
1037 We do not just run samba_dnsupdate as we want to strictly
1038 operate against the DC we just joined:
1039 - We do not want to query another DNS server
1040 - We do not want to obtain a Kerberos ticket
1041 (as the KDC we select may not be the DC we just joined,
1042 and so may not be in sync with the password we just set)
1043 - We do not wish to set the _ldap records until we have started
1044 - We do not wish to use NTLM (the --use-samba-tool mode forces
1049 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1050 record_type = dnsp.DNS_TYPE_A
1051 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1052 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1054 zone = ctx.dnsdomain
1055 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1057 msdcs_cname = str(ctx.ntds_guid)
1058 cname_target = "%s.%s" % (name, zone)
1059 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1061 ctx.logger.info("Adding %d remote DNS records for %s.%s" %
1062 (len(IPs), name, zone))
1064 binding_options = "sign"
1065 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1070 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1072 change_owner_sd = security.descriptor()
1073 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1074 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1076 security.DOMAIN_RID_DCS))
1078 # TODO: Remove any old records from the primary DNS name
1081 = dns_conn.DnssrvEnumRecords2(client_version,
1091 except WERRORError as e:
1092 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1098 for record in rec.records:
1099 if record.wType == dnsp.DNS_TYPE_A or \
1100 record.wType == dnsp.DNS_TYPE_AAAA:
1102 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1103 del_rec_buf.rec = record
1105 dns_conn.DnssrvUpdateRecord2(client_version,
1112 except WERRORError as e:
1113 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1119 if IP.find(':') != -1:
1120 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1122 rec = AAAARecord(IP)
1124 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1129 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1130 add_rec_buf.rec = rec
1131 dns_conn.DnssrvUpdateRecord2(client_version,
1140 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1141 (ctx.dns_a_dn, ldap_record) \
1142 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1143 dns_partition=domaindns_zone_dn)
1145 # Make the DC own the DNS record, not the administrator
1146 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1147 controls=["sd_flags:1:%d"
1148 % (security.SECINFO_OWNER
1149 | security.SECINFO_GROUP)])
1152 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1153 % (msdcs_cname, msdcs_zone, cname_target))
1155 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1156 rec = CNameRecord(cname_target)
1157 add_rec_buf.rec = rec
1158 dns_conn.DnssrvUpdateRecord2(client_version,
1166 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1167 (ctx.dns_cname_dn, ldap_record) \
1168 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1169 dns_partition=forestdns_zone_dn)
1171 # Make the DC own the DNS record, not the administrator
1172 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1173 controls=["sd_flags:1:%d"
1174 % (security.SECINFO_OWNER
1175 | security.SECINFO_GROUP)])
1177 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1178 "will be created samba_dnsupdate on first startup")
1180 def join_replicate_new_dns_records(ctx):
1181 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1182 if nc in ctx.nc_list:
1183 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1184 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1185 ctx.ntds_guid, rodc=ctx.RODC,
1186 replica_flags=ctx.replica_flags,
1189 def join_finalise(ctx):
1190 """Finalise the join, mark us synchronised and setup secrets db."""
1192 # FIXME we shouldn't do this in all cases
1194 # If for some reasons we joined in another site than the one of
1195 # DC we just replicated from then we don't need to send the updatereplicateref
1196 # as replication between sites is time based and on the initiative of the
1198 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1199 for nc in ctx.nc_list:
1200 ctx.send_DsReplicaUpdateRefs(nc)
1203 print("Setting RODC invocationId")
1204 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1205 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1206 ctx.behavior_version)
1208 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1209 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1210 ldb.FLAG_MOD_REPLACE,
1212 ctx.local_samdb.modify(m)
1214 # Note: as RODC the invocationId is only stored
1215 # on the RODC itself, the other DCs never see it.
1217 # Thats is why we fix up the replPropertyMetaData stamp
1218 # for the 'invocationId' attribute, we need to change
1219 # the 'version' to '0', this is what windows 2008r2 does as RODC
1221 # This means if the object on a RWDC ever gets a invocationId
1222 # attribute, it will have version '1' (or higher), which will
1223 # will overwrite the RODC local value.
1224 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1228 ctx.logger.info("Setting isSynchronized and dsServiceName")
1230 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1231 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1233 guid = ctx.ntds_guid
1234 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1235 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1236 ctx.local_samdb.modify(m)
1241 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1243 ctx.logger.info("Setting up secrets database")
1244 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1246 dnsdomain=ctx.dnsdomain,
1247 netbiosname=ctx.myname,
1248 domainsid=ctx.domsid,
1249 machinepass=ctx.acct_pass,
1250 secure_channel_type=ctx.secure_channel_type,
1251 key_version_number=ctx.key_version_number)
1253 if ctx.dns_backend.startswith("BIND9_"):
1254 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1255 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1256 dns_backend=ctx.dns_backend,
1257 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1258 targetdir=ctx.targetdir,
1259 key_version_number=ctx.dns_key_version_number)
1261 def join_setup_trusts(ctx):
1262 """provision the local SAM."""
1264 print("Setup domain trusts with server %s" % ctx.server)
1265 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1266 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1269 objectAttr = lsa.ObjectAttribute()
1270 objectAttr.sec_qos = lsa.QosInfo()
1272 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1273 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1275 info = lsa.TrustDomainInfoInfoEx()
1276 info.domain_name.string = ctx.dnsdomain
1277 info.netbios_name.string = ctx.domain_name
1278 info.sid = ctx.domsid
1279 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1280 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1281 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1284 oldname = lsa.String()
1285 oldname.string = ctx.dnsdomain
1286 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1287 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1288 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1289 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1290 except RuntimeError:
1293 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1295 clear_value = drsblobs.AuthInfoClear()
1296 clear_value.size = len(password_blob)
1297 clear_value.password = password_blob
1299 clear_authentication_information = drsblobs.AuthenticationInformation()
1300 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1301 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1302 clear_authentication_information.AuthInfo = clear_value
1304 authentication_information_array = drsblobs.AuthenticationInformationArray()
1305 authentication_information_array.count = 1
1306 authentication_information_array.array = [clear_authentication_information]
1308 outgoing = drsblobs.trustAuthInOutBlob()
1310 outgoing.current = authentication_information_array
1312 trustpass = drsblobs.trustDomainPasswords()
1313 confounder = [3] * 512
1315 for i in range(512):
1316 confounder[i] = random.randint(0, 255)
1318 trustpass.confounder = confounder
1320 trustpass.outgoing = outgoing
1321 trustpass.incoming = outgoing
1323 trustpass_blob = ndr_pack(trustpass)
1325 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1327 auth_blob = lsa.DATA_BUF2()
1328 auth_blob.size = len(encrypted_trustpass)
1329 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1331 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1332 auth_info.auth_blob = auth_blob
1334 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1337 security.SEC_STD_DELETE)
1340 "dn": "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1341 "objectclass": "trustedDomain",
1342 "trustType": str(info.trust_type),
1343 "trustAttributes": str(info.trust_attributes),
1344 "trustDirection": str(info.trust_direction),
1345 "flatname": ctx.forest_domain_name,
1346 "trustPartner": ctx.dnsforest,
1347 "trustAuthIncoming": ndr_pack(outgoing),
1348 "trustAuthOutgoing": ndr_pack(outgoing),
1349 "securityIdentifier": ndr_pack(ctx.forestsid)
1351 ctx.local_samdb.add(rec)
1354 "dn": "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1355 "objectclass": "user",
1356 "userAccountControl": str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1357 "clearTextPassword": ctx.trustdom_pass.encode('utf-16-le'),
1358 "samAccountName": "%s$" % ctx.forest_domain_name
1360 ctx.local_samdb.add(rec)
1362 def build_nc_lists(ctx):
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]
1384 ctx.build_nc_lists()
1386 if ctx.promote_existing:
1387 ctx.promote_possible()
1389 ctx.cleanup_old_join()
1392 ctx.join_add_objects()
1393 ctx.join_provision()
1394 ctx.join_replicate()
1396 ctx.join_add_objects2()
1397 ctx.join_provision_own_domain()
1398 ctx.join_setup_trusts()
1400 if ctx.dns_backend != "NONE":
1401 ctx.join_add_dns_records()
1402 ctx.join_replicate_new_dns_records()
1407 print("Join failed - cleaning up")
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))
1500 def join_clone(logger=None, server=None, creds=None, lp=None,
1501 targetdir=None, domain=None, include_secrets=False,
1502 dns_backend="NONE"):
1503 """Creates a local clone of a remote DC."""
1504 ctx = DCCloneContext(logger, server, creds, lp, targetdir=targetdir,
1505 domain=domain, dns_backend=dns_backend,
1506 include_secrets=include_secrets)
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)
1515 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 = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1526 targetdir, parent_domain, machinepass, use_ntvfs,
1527 dns_backend, plaintext_secrets,
1528 backend_store=backend_store)
1529 ctx.subdomain = True
1530 if adminpass is None:
1531 ctx.adminpass = samba.generate_random_password(12, 32)
1533 ctx.adminpass = adminpass
1534 ctx.parent_domain_name = ctx.domain_name
1535 ctx.domain_name = netbios_domain
1536 ctx.realm = dnsdomain
1537 ctx.parent_dnsdomain = ctx.dnsdomain
1538 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1539 ctx.dnsdomain = dnsdomain
1540 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1541 ctx.naming_master = ctx.get_naming_master()
1542 if ctx.naming_master != ctx.server:
1543 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1544 ctx.server = ctx.naming_master
1545 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1546 session_info=system_session(),
1547 credentials=ctx.creds, lp=ctx.lp)
1548 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1550 ctx.server = res[0]["dnsHostName"]
1551 logger.info("DNS name of new naming master is %s" % ctx.server)
1553 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1554 ctx.forestsid = ctx.domsid
1555 ctx.domsid = security.random_sid()
1557 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1558 # Windows uses 240 bytes as UTF16 so we do
1559 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1561 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1563 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1564 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1566 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1567 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1568 ctx.domain_replica_flags = ctx.replica_flags
1571 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1574 class DCCloneContext(DCJoinContext):
1575 """Clones a remote DC."""
1577 def __init__(ctx, logger=None, server=None, creds=None, lp=None,
1578 targetdir=None, domain=None, dns_backend=None,
1579 include_secrets=False):
1580 super(DCCloneContext, ctx).__init__(logger, server, creds, lp,
1581 targetdir=targetdir, domain=domain,
1582 dns_backend=dns_backend)
1584 # As we don't want to create or delete these DNs, we set them to None
1585 ctx.server_dn = None
1588 ctx.myname = ctx.server.split('.')[0]
1589 ctx.ntds_guid = None
1590 ctx.rid_manager_dn = None
1593 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
1595 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1596 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1597 if not include_secrets:
1598 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1599 ctx.domain_replica_flags = ctx.replica_flags
1601 def join_finalise(ctx):
1602 ctx.logger.info("Setting isSynchronized and dsServiceName")
1604 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1605 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE,
1608 # We want to appear to be the server we just cloned
1609 guid = ctx.remote_dc_ntds_guid
1610 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1611 ldb.FLAG_MOD_REPLACE,
1613 ctx.local_samdb.modify(m)
1616 ctx.build_nc_lists()
1618 # When cloning a DC, we just want to provision a DC locally, then
1619 # grab the remote DC's entire DB via DRS replication
1620 ctx.join_provision()
1621 ctx.join_replicate()
1625 # Used to create a renamed backup of a DC. Renaming the domain means that the
1626 # cloned/backup DC can be started without interfering with the production DC.
1627 class DCCloneAndRenameContext(DCCloneContext):
1628 """Clones a remote DC, renaming the domain along the way."""
1630 def __init__(ctx, new_base_dn, new_domain_name, new_realm, logger=None,
1631 server=None, creds=None, lp=None, targetdir=None, domain=None,
1632 dns_backend=None, include_secrets=True):
1633 super(DCCloneAndRenameContext, ctx).__init__(logger, server, creds, lp,
1634 targetdir=targetdir,
1636 dns_backend=dns_backend,
1637 include_secrets=include_secrets)
1638 # store the new DN (etc) that we want the cloned DB to use
1639 ctx.new_base_dn = new_base_dn
1640 ctx.new_domain_name = new_domain_name
1641 ctx.new_realm = new_realm
1643 def create_replicator(ctx, repl_creds, binding_options):
1644 """Creates a new DRS object for managing replications"""
1646 # We want to rename all the domain objects, and the simplest way to do
1647 # this is during replication. This is because the base DN of the top-
1648 # level replicated object will flow through to all the objects below it
1649 binding_str = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
1650 return drs_utils.drs_ReplicateRenamer(binding_str, ctx.lp, repl_creds,
1653 ctx.base_dn, ctx.new_base_dn)
1655 def create_non_global_lp(ctx, global_lp):
1656 '''Creates a non-global LoadParm based on the global LP's settings'''
1658 # the samba code shares a global LoadParm by default. Here we create a
1659 # new LoadParm that retains the global settings, but any changes we
1660 # make to it won't automatically affect the rest of the samba code.
1661 # The easiest way to do this is to dump the global settings to a
1662 # temporary smb.conf file, and then load the temp file into a new
1663 # non-global LoadParm
1664 fd, tmp_file = tempfile.mkstemp()
1665 global_lp.dump(False, tmp_file)
1666 local_lp = samba.param.LoadParm(filename_for_non_global_lp=tmp_file)
1670 def rename_dn(ctx, dn_str):
1671 '''Uses string substitution to replace the base DN'''
1672 old_base_dn = ctx.base_dn
1673 return re.sub('%s$' % old_base_dn, ctx.new_base_dn, dn_str)
1675 # we want to override the normal DCCloneContext's join_provision() so that
1676 # use the new domain DNs during the provision. We do this because:
1677 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1678 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1679 # we couldn't apply the renamed DRS objects during replication)
1680 def join_provision(ctx):
1681 """Provision the local (renamed) SAM."""
1683 print("Provisioning the new (renamed) domain...")
1685 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1686 # to create a new smb.conf. By default, it uses the global LoadParm to
1687 # do this, and so it would overwrite the realm/domain values globally.
1688 # We still need the global LoadParm to retain the old domain's details,
1689 # so we can connect to (and clone) the existing DC.
1690 # So, copy the global settings into a non-global LoadParm, which we can
1691 # then pass into provision(). This generates a new smb.conf correctly,
1692 # without overwriting the global realm/domain values just yet.
1693 non_global_lp = ctx.create_non_global_lp(ctx.lp)
1695 # do the provision with the new/renamed domain DN values
1696 presult = provision(ctx.logger, system_session(),
1697 targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
1698 realm=ctx.new_realm, lp=non_global_lp,
1699 rootdn=ctx.rename_dn(ctx.root_dn), domaindn=ctx.new_base_dn,
1700 schemadn=ctx.rename_dn(ctx.schema_dn),
1701 configdn=ctx.rename_dn(ctx.config_dn),
1702 domain=ctx.new_domain_name, domainsid=ctx.domsid,
1703 serverrole="active directory domain controller",
1704 dns_backend=ctx.dns_backend)
1706 print("Provision OK for renamed domain DN %s" % presult.domaindn)
1707 ctx.local_samdb = presult.samdb
1708 ctx.paths = presult.paths