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, FILL_DRS, FILL_SUBDOMAIN
34 from samba.provision.common import setup_path
35 from samba.schema import Schema
36 from samba import descriptor
37 from samba.net import Net
38 from samba.provision.sambadns import setup_bind9_dns
39 from samba import read_and_sub_file
40 from samba import werror
41 from base64 import b64encode
42 from samba import WERRORError, NTSTATUSError
43 from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord
44 from samba import sd_utils
54 class DCJoinException(Exception):
56 def __init__(self, msg):
57 super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
60 class DCJoinContext(object):
61 """Perform a DC join."""
63 def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
64 netbios_name=None, targetdir=None, domain=None,
65 machinepass=None, use_ntvfs=False, dns_backend=None,
66 promote_existing=False, plaintext_secrets=False,
67 backend_store=None, forced_local_samdb=None):
69 site = "Default-First-Site-Name"
75 ctx.targetdir = targetdir
76 ctx.use_ntvfs = use_ntvfs
77 ctx.plaintext_secrets = plaintext_secrets
78 ctx.backend_store = backend_store
80 ctx.promote_existing = promote_existing
81 ctx.promote_from_dn = None
86 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
87 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
90 ctx.forced_local_samdb = forced_local_samdb
92 if forced_local_samdb:
93 ctx.samdb = forced_local_samdb
94 ctx.server = ctx.samdb.url
97 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
98 ctx.server = ctx.find_dc(domain)
99 ctx.logger.info("Found DC %s" % ctx.server)
100 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
101 session_info=system_session(),
102 credentials=ctx.creds, lp=ctx.lp)
105 ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
106 except ldb.LdbError as e4:
107 (enum, estr) = e4.args
108 raise DCJoinException(estr)
110 ctx.base_dn = str(ctx.samdb.get_default_basedn())
111 ctx.root_dn = str(ctx.samdb.get_root_basedn())
112 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
113 ctx.config_dn = str(ctx.samdb.get_config_basedn())
114 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
115 ctx.forestsid = ctx.domsid
116 ctx.domain_name = ctx.get_domain_name()
117 ctx.forest_domain_name = ctx.get_forest_domain_name()
118 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
120 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
121 ctx.dc_dnsHostName = ctx.get_dnsHostName()
122 ctx.behavior_version = ctx.get_behavior_version()
124 if machinepass is not None:
125 ctx.acct_pass = machinepass
127 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
129 ctx.dnsdomain = ctx.samdb.domain_dns_name()
131 # the following are all dependent on the new DC's netbios_name (which
132 # we expect to always be specified, except when cloning a DC)
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)))
270 def cleanup_old_join(ctx, force=False):
271 """Remove any DNs from a previous join."""
272 # find the krbtgt link
273 if not ctx.subdomain:
274 ctx.cleanup_old_accounts(force=force)
276 if ctx.connection_dn is not None:
277 ctx.del_noerror(ctx.connection_dn)
278 if ctx.krbtgt_dn is not None:
279 ctx.del_noerror(ctx.krbtgt_dn)
280 ctx.del_noerror(ctx.ntds_dn)
281 ctx.del_noerror(ctx.server_dn, recursive=True)
283 ctx.del_noerror(ctx.topology_dn)
285 ctx.del_noerror(ctx.partition_dn)
288 binding_options = "sign"
289 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
292 objectAttr = lsa.ObjectAttribute()
293 objectAttr.sec_qos = lsa.QosInfo()
295 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
296 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
299 name.string = ctx.realm
300 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
302 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
305 name.string = ctx.forest_domain_name
306 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
308 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
311 ctx.del_noerror(ctx.dns_a_dn)
314 ctx.del_noerror(ctx.dns_cname_dn)
316 def promote_possible(ctx):
317 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
319 # This shouldn't happen
320 raise Exception("Can not promote into a subdomain")
322 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
323 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
324 attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
326 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
327 if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
328 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
329 if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
330 samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
331 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
333 ctx.promote_from_dn = res[0].dn
335 def find_dc(ctx, domain):
336 """find a writeable DC for the given domain"""
338 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
339 except NTSTATUSError as error:
340 raise Exception("Failed to find a writeable DC for domain '%s': %s" %
343 raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
344 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
345 ctx.site = ctx.cldap_ret.client_site
346 return ctx.cldap_ret.pdc_dns_name
348 def get_behavior_version(ctx):
349 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
350 if "msDS-Behavior-Version" in res[0]:
351 return int(res[0]["msDS-Behavior-Version"][0])
353 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
355 def get_dnsHostName(ctx):
356 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
357 return str(res[0]["dnsHostName"][0])
359 def get_domain_name(ctx):
360 '''get netbios name of the domain from the partitions record'''
361 partitions_dn = ctx.samdb.get_partitions_dn()
362 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
363 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
364 return str(res[0]["nETBIOSName"][0])
366 def get_forest_domain_name(ctx):
367 '''get netbios name of the domain from the partitions record'''
368 partitions_dn = ctx.samdb.get_partitions_dn()
369 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
370 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
371 return str(res[0]["nETBIOSName"][0])
373 def get_parent_partition_dn(ctx):
374 '''get the parent domain partition DN from parent DNS name'''
375 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
376 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
377 (ldb.binary_encode(ctx.parent_dnsdomain),
378 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
379 return str(res[0].dn)
381 def get_naming_master(ctx):
382 '''get the parent domain partition DN from parent DNS name'''
383 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
384 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
385 if 'fSMORoleOwner' not in res[0]:
386 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
388 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
390 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
392 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
396 '''get the SID of the connected user. Only works with w2k8 and later,
397 so only used for RODC join'''
398 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
399 binsid = res[0]["tokenGroups"][0]
400 return ctx.samdb.schema_format_value("objectSID", binsid)
402 def dn_exists(ctx, dn):
403 '''check if a DN exists'''
405 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
406 except ldb.LdbError as e5:
407 (enum, estr) = e5.args
408 if enum == ldb.ERR_NO_SUCH_OBJECT:
413 def add_krbtgt_account(ctx):
414 '''RODCs need a special krbtgt account'''
415 print("Adding %s" % ctx.krbtgt_dn)
418 "objectclass": "user",
419 "useraccountcontrol": str(samba.dsdb.UF_NORMAL_ACCOUNT |
420 samba.dsdb.UF_ACCOUNTDISABLE),
421 "showinadvancedviewonly": "TRUE",
422 "description": "krbtgt for %s" % ctx.samname}
423 ctx.samdb.add(rec, ["rodc_join:1:1"])
425 # now we need to search for the samAccountName attribute on the krbtgt DN,
426 # as this will have been magically set to the krbtgt number
427 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
428 ctx.krbtgt_name = res[0]["samAccountName"][0]
430 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
433 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
434 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
435 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
438 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
439 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
440 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
442 def drsuapi_connect(ctx):
443 '''make a DRSUAPI connection to the naming master'''
444 binding_options = "seal"
445 if ctx.lp.log_level() >= 9:
446 binding_options += ",print"
447 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
448 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
449 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
451 def create_tmp_samdb(ctx):
452 '''create a temporary samdb object for schema queries'''
453 ctx.tmp_schema = Schema(ctx.domsid,
454 schemadn=ctx.schema_dn)
455 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
456 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
458 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
460 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
461 '''build a DsReplicaAttributeCtr object'''
462 r = drsuapi.DsReplicaAttribute()
463 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
466 def DsAddEntry(ctx, recs):
467 '''add a record via the DRSUAPI DsAddEntry call'''
468 if ctx.drsuapi is None:
469 ctx.drsuapi_connect()
470 if ctx.tmp_samdb is None:
471 ctx.create_tmp_samdb()
475 id = drsuapi.DsReplicaObjectIdentifier()
482 if not isinstance(rec[a], list):
486 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
489 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
490 attribute_ctr.num_attributes = len(attrs)
491 attribute_ctr.attributes = attrs
493 object = drsuapi.DsReplicaObject()
494 object.identifier = id
495 object.attribute_ctr = attribute_ctr
497 list_object = drsuapi.DsReplicaObjectListItem()
498 list_object.object = object
499 objects.append(list_object)
501 req2 = drsuapi.DsAddEntryRequest2()
502 req2.first_object = objects[0]
503 prev = req2.first_object
504 for o in objects[1:]:
508 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
510 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
511 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
512 raise RuntimeError("DsAddEntry failed")
513 if ctr.extended_err[0] != werror.WERR_SUCCESS:
514 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
515 raise RuntimeError("DsAddEntry failed")
518 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
519 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
520 if ctr.err_data.info is None:
521 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
523 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
524 ctr.err_data.info.extended_err))
525 raise RuntimeError("DsAddEntry failed")
526 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
527 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
528 raise RuntimeError("DsAddEntry failed")
532 def join_ntdsdsa_obj(ctx):
533 '''return the ntdsdsa object to add'''
535 print("Adding %s" % ctx.ntds_dn)
538 "objectclass": "nTDSDSA",
539 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
540 "dMDLocation": ctx.schema_dn}
542 nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
544 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
545 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
547 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
548 rec["msDS-HasDomainNCs"] = ctx.base_dn
551 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
552 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
553 rec["options"] = "37"
555 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
556 rec["HasMasterNCs"] = []
558 if nc in ctx.full_nc_list:
559 rec["HasMasterNCs"].append(nc)
560 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
561 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
563 rec["invocationId"] = ndr_pack(ctx.invocation_id)
567 def join_add_ntdsdsa(ctx):
568 '''add the ntdsdsa object'''
570 rec = ctx.join_ntdsdsa_obj()
571 if ctx.forced_local_samdb:
572 ctx.samdb.add(rec, controls=["relax:0"])
574 ctx.samdb.add(rec, ["rodc_join:1:1"])
576 ctx.DsAddEntry([rec])
578 # find the GUID of our NTDS DN
579 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
580 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
582 def join_add_objects(ctx, specified_sid=None):
583 '''add the various objects needed for the join'''
585 print("Adding %s" % ctx.acct_dn)
588 "objectClass": "computer",
589 "displayname": ctx.samname,
590 "samaccountname": ctx.samname,
591 "userAccountControl": str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
592 "dnshostname": ctx.dnshostname}
593 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
594 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
595 elif ctx.promote_existing:
596 rec['msDS-SupportedEncryptionTypes'] = []
598 rec["managedby"] = ctx.managedby
599 elif ctx.promote_existing:
600 rec["managedby"] = []
602 if ctx.never_reveal_sid:
603 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
604 elif ctx.promote_existing:
605 rec["msDS-NeverRevealGroup"] = []
608 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
609 elif ctx.promote_existing:
610 rec["msDS-RevealOnDemandGroup"] = []
613 rec["objectSid"] = ndr_pack(specified_sid)
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))
621 if specified_sid is not None:
622 controls = ["relax:0"]
623 ctx.samdb.add(rec, controls=controls)
626 ctx.add_krbtgt_account()
629 print("Adding %s" % ctx.server_dn)
632 "objectclass": "server",
633 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
634 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
635 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
636 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
637 # windows seems to add the dnsHostName later
638 "dnsHostName": ctx.dnshostname}
641 rec["serverReference"] = ctx.acct_dn
646 # the rest is done after replication
651 ctx.join_add_ntdsdsa()
653 # Add the Replica-Locations or RO-Replica-Locations attributes
654 # TODO Is this supposed to be for the schema partition too?
655 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
656 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
658 base=ctx.samdb.get_partitions_dn(),
659 expression=expr), ctx.domaindns_zone)
661 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
662 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
664 base=ctx.samdb.get_partitions_dn(),
665 expression=expr), ctx.forestdns_zone)
667 for part, zone in (domain, forest):
668 if zone not in ctx.nc_list:
674 attr = "msDS-NC-Replica-Locations"
676 attr = "msDS-NC-RO-Replica-Locations"
678 m[attr] = ldb.MessageElement(ctx.ntds_dn,
679 ldb.FLAG_MOD_ADD, attr)
682 if ctx.connection_dn is not None:
683 print("Adding %s" % ctx.connection_dn)
685 "dn": ctx.connection_dn,
686 "objectclass": "nTDSConnection",
687 "enabledconnection": "TRUE",
689 "fromServer": ctx.dc_ntds_dn}
693 print("Adding SPNs to %s" % ctx.acct_dn)
695 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
696 for i in range(len(ctx.SPNs)):
697 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
698 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
699 ldb.FLAG_MOD_REPLACE,
700 "servicePrincipalName")
703 # The account password set operation should normally be done over
704 # LDAP. Windows 2000 DCs however allow this only with SSL
705 # connections which are hard to set up and otherwise refuse with
706 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
708 print("Setting account password for %s" % ctx.samname)
710 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
711 % ldb.binary_encode(ctx.samname),
713 force_change_at_next_login=False,
714 username=ctx.samname)
715 except ldb.LdbError as e2:
717 if num != ldb.ERR_UNWILLING_TO_PERFORM:
719 ctx.net.set_password(account_name=ctx.samname,
720 domain_name=ctx.domain_name,
721 newpassword=ctx.acct_pass.encode('utf-8'))
723 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
724 attrs=["msDS-KeyVersionNumber",
726 if "msDS-KeyVersionNumber" in res[0]:
727 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
729 ctx.key_version_number = None
731 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
732 res[0]["objectSid"][0])
734 print("Enabling account")
736 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
737 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
738 ldb.FLAG_MOD_REPLACE,
739 "userAccountControl")
742 if ctx.dns_backend.startswith("BIND9_"):
743 ctx.dnspass = samba.generate_random_password(128, 255)
745 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
746 {"DNSDOMAIN": ctx.dnsdomain,
747 "DOMAINDN": ctx.base_dn,
748 "HOSTNAME": ctx.myname,
749 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
750 "DNSNAME": ctx.dnshostname}))
751 for changetype, msg in recs:
752 assert changetype == ldb.CHANGETYPE_NONE
753 dns_acct_dn = msg["dn"]
754 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
756 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
757 del msg["clearTextPassword"]
758 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
759 del msg["isCriticalSystemObject"]
760 # Disable account until password is set
761 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
762 samba.dsdb.UF_ACCOUNTDISABLE)
765 except ldb.LdbError as e:
767 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
770 # The account password set operation should normally be done over
771 # LDAP. Windows 2000 DCs however allow this only with SSL
772 # connections which are hard to set up and otherwise refuse with
773 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
775 print("Setting account password for dns-%s" % ctx.myname)
777 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
778 % ldb.binary_encode(ctx.myname),
780 force_change_at_next_login=False,
781 username=ctx.samname)
782 except ldb.LdbError as e3:
784 if num != ldb.ERR_UNWILLING_TO_PERFORM:
786 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
787 domain_name=ctx.domain_name,
788 newpassword=ctx.dnspass)
790 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
791 attrs=["msDS-KeyVersionNumber"])
792 if "msDS-KeyVersionNumber" in res[0]:
793 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
795 ctx.dns_key_version_number = None
797 def join_add_objects2(ctx):
798 """add the various objects needed for the join, for subdomains post replication"""
800 print("Adding %s" % ctx.partition_dn)
801 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
802 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
804 "dn": ctx.partition_dn,
805 "objectclass": "crossRef",
806 "objectCategory": "CN=Cross-Ref,%s" % ctx.schema_dn,
807 "nCName": ctx.base_dn,
808 "nETBIOSName": ctx.domain_name,
809 "dnsRoot": ctx.dnsdomain,
810 "trustParent": ctx.parent_partition_dn,
811 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC |samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
812 "ntSecurityDescriptor": sd_binary,
815 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
816 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
818 rec2 = ctx.join_ntdsdsa_obj()
820 objects = ctx.DsAddEntry([rec, rec2])
821 if len(objects) != 2:
822 raise DCJoinException("Expected 2 objects from DsAddEntry")
824 ctx.ntds_guid = objects[1].guid
826 print("Replicating partition DN")
827 ctx.repl.replicate(ctx.partition_dn,
828 misc.GUID("00000000-0000-0000-0000-000000000000"),
830 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
831 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
833 print("Replicating NTDS DN")
834 ctx.repl.replicate(ctx.ntds_dn,
835 misc.GUID("00000000-0000-0000-0000-000000000000"),
837 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
838 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
840 def join_provision(ctx):
841 """Provision the local SAM."""
843 print("Calling bare provision")
845 smbconf = ctx.lp.configfile
847 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
848 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
849 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
850 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
851 serverdn=ctx.server_dn, domain=ctx.domain_name,
852 hostname=ctx.myname, domainsid=ctx.domsid,
853 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
854 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
855 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
856 plaintext_secrets=ctx.plaintext_secrets,
857 backend_store=ctx.backend_store
859 print("Provision OK for domain DN %s" % presult.domaindn)
860 ctx.local_samdb = presult.samdb
862 ctx.paths = presult.paths
863 ctx.names = presult.names
865 # Fix up the forestsid, it may be different if we are joining as a subdomain
866 ctx.names.forestsid = ctx.forestsid
868 def join_provision_own_domain(ctx):
869 """Provision the local SAM."""
871 # we now operate exclusively on the local database, which
872 # we need to reopen in order to get the newly created schema
873 print("Reconnecting to local samdb")
874 ctx.samdb = SamDB(url=ctx.local_samdb.url,
875 session_info=system_session(),
876 lp=ctx.local_samdb.lp,
878 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
879 ctx.local_samdb = ctx.samdb
881 ctx.logger.info("Finding domain GUID from ncName")
882 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
883 controls=["extended_dn:1:1", "reveal_internals:0"])
885 if 'nCName' not in res[0]:
886 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
889 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
891 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
893 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
895 ctx.logger.info("Calling own domain provision")
897 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
899 presult = provision_fill(ctx.local_samdb, secrets_ldb,
900 ctx.logger, ctx.names, ctx.paths,
901 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
902 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
903 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
904 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
905 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
906 print("Provision OK for domain %s" % ctx.names.dnsdomain)
908 def create_replicator(ctx, repl_creds, binding_options):
909 '''Creates a new DRS object for managing replications'''
910 return drs_utils.drs_Replicate(
911 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
912 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
914 def join_replicate(ctx):
915 """Replicate the SAM."""
917 print("Starting replication")
918 ctx.local_samdb.transaction_start()
920 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
921 if ctx.ntds_guid is None:
922 print("Using DS_BIND_GUID_W2K3")
923 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
925 destination_dsa_guid = ctx.ntds_guid
928 repl_creds = Credentials()
929 repl_creds.guess(ctx.lp)
930 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
931 repl_creds.set_username(ctx.samname)
932 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
934 repl_creds = ctx.creds
936 binding_options = "seal"
937 if ctx.lp.log_level() >= 9:
938 binding_options += ",print"
940 repl = ctx.create_replicator(repl_creds, binding_options)
942 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
943 destination_dsa_guid, schema=True, rodc=ctx.RODC,
944 replica_flags=ctx.replica_flags)
945 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
946 destination_dsa_guid, rodc=ctx.RODC,
947 replica_flags=ctx.replica_flags)
948 if not ctx.subdomain:
949 # Replicate first the critical object for the basedn
950 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
951 print("Replicating critical objects from the base DN of the domain")
952 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
953 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
954 destination_dsa_guid, rodc=ctx.RODC,
955 replica_flags=ctx.domain_replica_flags)
956 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 print("Done with always replicated NC (base, config, schema)")
962 # At this point we should already have an entry in the ForestDNS
963 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
964 # indicate that we hold a replica for this NC.
965 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
966 if nc in ctx.nc_list:
967 print("Replicating %s" % (str(nc)))
968 repl.replicate(nc, source_dsa_invocation_id,
969 destination_dsa_guid, rodc=ctx.RODC,
970 replica_flags=ctx.replica_flags)
973 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
974 destination_dsa_guid,
975 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
976 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
977 destination_dsa_guid,
978 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
979 elif ctx.rid_manager_dn is not None:
980 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
982 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
983 destination_dsa_guid,
984 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
985 except samba.DsExtendedError as e1:
986 (enum, estr) = e1.args
987 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
988 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
989 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
994 ctx.source_dsa_invocation_id = source_dsa_invocation_id
995 ctx.destination_dsa_guid = destination_dsa_guid
997 print("Committing SAM database")
999 ctx.local_samdb.transaction_cancel()
1002 ctx.local_samdb.transaction_commit()
1004 def send_DsReplicaUpdateRefs(ctx, dn):
1005 r = drsuapi.DsReplicaUpdateRefsRequest1()
1006 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
1007 r.naming_context.dn = str(dn)
1008 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
1009 r.naming_context.sid = security.dom_sid("S-0-0")
1010 r.dest_dsa_guid = ctx.ntds_guid
1011 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1012 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1014 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1016 if ctx.drsuapi is None:
1017 ctx.drsuapi_connect()
1019 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1021 def join_add_dns_records(ctx):
1022 """Remotely Add a DNS record to the target DC. We assume that if we
1023 replicate DNS that the server holds the DNS roles and can accept
1026 This avoids issues getting replication going after the DC
1027 first starts as the rest of the domain does not have to
1028 wait for samba_dnsupdate to run successfully.
1030 Specifically, we add the records implied by the DsReplicaUpdateRefs
1033 We do not just run samba_dnsupdate as we want to strictly
1034 operate against the DC we just joined:
1035 - We do not want to query another DNS server
1036 - We do not want to obtain a Kerberos ticket
1037 (as the KDC we select may not be the DC we just joined,
1038 and so may not be in sync with the password we just set)
1039 - We do not wish to set the _ldap records until we have started
1040 - We do not wish to use NTLM (the --use-samba-tool mode forces
1045 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1046 record_type = dnsp.DNS_TYPE_A
1047 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1048 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1050 zone = ctx.dnsdomain
1051 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1053 msdcs_cname = str(ctx.ntds_guid)
1054 cname_target = "%s.%s" % (name, zone)
1055 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1057 ctx.logger.info("Adding %d remote DNS records for %s.%s" %
1058 (len(IPs), name, zone))
1060 binding_options = "sign"
1061 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1066 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1068 change_owner_sd = security.descriptor()
1069 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1070 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1072 security.DOMAIN_RID_DCS))
1074 # TODO: Remove any old records from the primary DNS name
1077 = dns_conn.DnssrvEnumRecords2(client_version,
1087 except WERRORError as e:
1088 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1094 for record in rec.records:
1095 if record.wType == dnsp.DNS_TYPE_A or \
1096 record.wType == dnsp.DNS_TYPE_AAAA:
1098 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1099 del_rec_buf.rec = record
1101 dns_conn.DnssrvUpdateRecord2(client_version,
1108 except WERRORError as e:
1109 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1115 if IP.find(':') != -1:
1116 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1118 rec = AAAARecord(IP)
1120 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1125 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1126 add_rec_buf.rec = rec
1127 dns_conn.DnssrvUpdateRecord2(client_version,
1136 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1137 (ctx.dns_a_dn, ldap_record) \
1138 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1139 dns_partition=domaindns_zone_dn)
1141 # Make the DC own the DNS record, not the administrator
1142 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1143 controls=["sd_flags:1:%d"
1144 % (security.SECINFO_OWNER
1145 | security.SECINFO_GROUP)])
1148 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1149 % (msdcs_cname, msdcs_zone, cname_target))
1151 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1152 rec = CNameRecord(cname_target)
1153 add_rec_buf.rec = rec
1154 dns_conn.DnssrvUpdateRecord2(client_version,
1162 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1163 (ctx.dns_cname_dn, ldap_record) \
1164 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1165 dns_partition=forestdns_zone_dn)
1167 # Make the DC own the DNS record, not the administrator
1168 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1169 controls=["sd_flags:1:%d"
1170 % (security.SECINFO_OWNER
1171 | security.SECINFO_GROUP)])
1173 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1174 "will be created samba_dnsupdate on first startup")
1176 def join_replicate_new_dns_records(ctx):
1177 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1178 if nc in ctx.nc_list:
1179 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1180 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1181 ctx.ntds_guid, rodc=ctx.RODC,
1182 replica_flags=ctx.replica_flags,
1185 def join_finalise(ctx):
1186 """Finalise the join, mark us synchronised and setup secrets db."""
1188 # FIXME we shouldn't do this in all cases
1190 # If for some reasons we joined in another site than the one of
1191 # DC we just replicated from then we don't need to send the updatereplicateref
1192 # as replication between sites is time based and on the initiative of the
1194 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1195 for nc in ctx.nc_list:
1196 ctx.send_DsReplicaUpdateRefs(nc)
1199 print("Setting RODC invocationId")
1200 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1201 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1202 ctx.behavior_version)
1204 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1205 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1206 ldb.FLAG_MOD_REPLACE,
1208 ctx.local_samdb.modify(m)
1210 # Note: as RODC the invocationId is only stored
1211 # on the RODC itself, the other DCs never see it.
1213 # Thats is why we fix up the replPropertyMetaData stamp
1214 # for the 'invocationId' attribute, we need to change
1215 # the 'version' to '0', this is what windows 2008r2 does as RODC
1217 # This means if the object on a RWDC ever gets a invocationId
1218 # attribute, it will have version '1' (or higher), which will
1219 # will overwrite the RODC local value.
1220 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1224 ctx.logger.info("Setting isSynchronized and dsServiceName")
1226 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1227 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1229 guid = ctx.ntds_guid
1230 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1231 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1232 ctx.local_samdb.modify(m)
1237 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1239 ctx.logger.info("Setting up secrets database")
1240 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1242 dnsdomain=ctx.dnsdomain,
1243 netbiosname=ctx.myname,
1244 domainsid=ctx.domsid,
1245 machinepass=ctx.acct_pass,
1246 secure_channel_type=ctx.secure_channel_type,
1247 key_version_number=ctx.key_version_number)
1249 if ctx.dns_backend.startswith("BIND9_"):
1250 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1251 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1252 dns_backend=ctx.dns_backend,
1253 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1254 targetdir=ctx.targetdir,
1255 key_version_number=ctx.dns_key_version_number)
1257 def join_setup_trusts(ctx):
1258 """provision the local SAM."""
1260 print("Setup domain trusts with server %s" % ctx.server)
1261 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1262 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1265 objectAttr = lsa.ObjectAttribute()
1266 objectAttr.sec_qos = lsa.QosInfo()
1268 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1269 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1271 info = lsa.TrustDomainInfoInfoEx()
1272 info.domain_name.string = ctx.dnsdomain
1273 info.netbios_name.string = ctx.domain_name
1274 info.sid = ctx.domsid
1275 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1276 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1277 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1280 oldname = lsa.String()
1281 oldname.string = ctx.dnsdomain
1282 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1283 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1284 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1285 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1286 except RuntimeError:
1289 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1291 clear_value = drsblobs.AuthInfoClear()
1292 clear_value.size = len(password_blob)
1293 clear_value.password = password_blob
1295 clear_authentication_information = drsblobs.AuthenticationInformation()
1296 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1297 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1298 clear_authentication_information.AuthInfo = clear_value
1300 authentication_information_array = drsblobs.AuthenticationInformationArray()
1301 authentication_information_array.count = 1
1302 authentication_information_array.array = [clear_authentication_information]
1304 outgoing = drsblobs.trustAuthInOutBlob()
1306 outgoing.current = authentication_information_array
1308 trustpass = drsblobs.trustDomainPasswords()
1309 confounder = [3] * 512
1311 for i in range(512):
1312 confounder[i] = random.randint(0, 255)
1314 trustpass.confounder = confounder
1316 trustpass.outgoing = outgoing
1317 trustpass.incoming = outgoing
1319 trustpass_blob = ndr_pack(trustpass)
1321 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1323 auth_blob = lsa.DATA_BUF2()
1324 auth_blob.size = len(encrypted_trustpass)
1325 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1327 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1328 auth_info.auth_blob = auth_blob
1330 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1333 security.SEC_STD_DELETE)
1336 "dn": "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1337 "objectclass": "trustedDomain",
1338 "trustType": str(info.trust_type),
1339 "trustAttributes": str(info.trust_attributes),
1340 "trustDirection": str(info.trust_direction),
1341 "flatname": ctx.forest_domain_name,
1342 "trustPartner": ctx.dnsforest,
1343 "trustAuthIncoming": ndr_pack(outgoing),
1344 "trustAuthOutgoing": ndr_pack(outgoing),
1345 "securityIdentifier": ndr_pack(ctx.forestsid)
1347 ctx.local_samdb.add(rec)
1350 "dn": "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1351 "objectclass": "user",
1352 "userAccountControl": str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1353 "clearTextPassword": ctx.trustdom_pass.encode('utf-16-le'),
1354 "samAccountName": "%s$" % ctx.forest_domain_name
1356 ctx.local_samdb.add(rec)
1358 def build_nc_lists(ctx):
1359 # nc_list is the list of naming context (NC) for which we will
1360 # replicate in and send a updateRef command to the partner DC
1362 # full_nc_list is the list of naming context (NC) we hold
1363 # read/write copies of. These are not subsets of each other.
1364 ctx.nc_list = [ctx.config_dn, ctx.schema_dn]
1365 ctx.full_nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
1367 if ctx.subdomain and ctx.dns_backend != "NONE":
1368 ctx.full_nc_list += [ctx.domaindns_zone]
1370 elif not ctx.subdomain:
1371 ctx.nc_list += [ctx.base_dn]
1373 if ctx.dns_backend != "NONE":
1374 ctx.nc_list += [ctx.domaindns_zone]
1375 ctx.nc_list += [ctx.forestdns_zone]
1376 ctx.full_nc_list += [ctx.domaindns_zone]
1377 ctx.full_nc_list += [ctx.forestdns_zone]
1380 ctx.build_nc_lists()
1382 if ctx.promote_existing:
1383 ctx.promote_possible()
1385 ctx.cleanup_old_join()
1388 ctx.join_add_objects()
1389 ctx.join_provision()
1390 ctx.join_replicate()
1392 ctx.join_add_objects2()
1393 ctx.join_provision_own_domain()
1394 ctx.join_setup_trusts()
1396 if ctx.dns_backend != "NONE":
1397 ctx.join_add_dns_records()
1398 ctx.join_replicate_new_dns_records()
1403 print("Join failed - cleaning up")
1406 ctx.cleanup_old_join()
1410 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1411 targetdir=None, domain=None, domain_critical_only=False,
1412 machinepass=None, use_ntvfs=False, dns_backend=None,
1413 promote_existing=False, plaintext_secrets=False,
1414 backend_store=None):
1415 """Join as a RODC."""
1417 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1418 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1419 promote_existing, plaintext_secrets,
1420 backend_store=backend_store)
1422 lp.set("workgroup", ctx.domain_name)
1423 logger.info("workgroup is %s" % ctx.domain_name)
1425 lp.set("realm", ctx.realm)
1426 logger.info("realm is %s" % ctx.realm)
1428 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1430 # setup some defaults for accounts that should be replicated to this RODC
1431 ctx.never_reveal_sid = [
1432 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1433 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1434 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1435 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1436 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1437 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1439 mysid = ctx.get_mysid()
1440 admin_dn = "<SID=%s>" % mysid
1441 ctx.managedby = admin_dn
1443 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1444 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1445 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1447 ctx.SPNs.extend(["RestrictedKrbHost/%s" % ctx.myname,
1448 "RestrictedKrbHost/%s" % ctx.dnshostname])
1450 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1451 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1453 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1454 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1455 ctx.domain_replica_flags = ctx.replica_flags
1456 if domain_critical_only:
1457 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1461 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1464 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1465 targetdir=None, domain=None, domain_critical_only=False,
1466 machinepass=None, use_ntvfs=False, dns_backend=None,
1467 promote_existing=False, plaintext_secrets=False,
1468 backend_store=None):
1470 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1471 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1472 promote_existing, plaintext_secrets,
1473 backend_store=backend_store)
1475 lp.set("workgroup", ctx.domain_name)
1476 logger.info("workgroup is %s" % ctx.domain_name)
1478 lp.set("realm", ctx.realm)
1479 logger.info("realm is %s" % ctx.realm)
1481 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1483 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1484 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1486 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1487 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1488 ctx.domain_replica_flags = ctx.replica_flags
1489 if domain_critical_only:
1490 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1493 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1496 def join_clone(logger=None, server=None, creds=None, lp=None,
1497 targetdir=None, domain=None, include_secrets=False,
1498 dns_backend="NONE"):
1499 """Creates a local clone of a remote DC."""
1500 ctx = DCCloneContext(logger, server, creds, lp, targetdir=targetdir,
1501 domain=domain, dns_backend=dns_backend,
1502 include_secrets=include_secrets)
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)
1511 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1515 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1516 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1517 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1518 dns_backend=None, plaintext_secrets=False,
1519 backend_store=None):
1521 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1522 targetdir, parent_domain, machinepass, use_ntvfs,
1523 dns_backend, plaintext_secrets,
1524 backend_store=backend_store)
1525 ctx.subdomain = True
1526 if adminpass is None:
1527 ctx.adminpass = samba.generate_random_password(12, 32)
1529 ctx.adminpass = adminpass
1530 ctx.parent_domain_name = ctx.domain_name
1531 ctx.domain_name = netbios_domain
1532 ctx.realm = dnsdomain
1533 ctx.parent_dnsdomain = ctx.dnsdomain
1534 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1535 ctx.dnsdomain = dnsdomain
1536 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1537 ctx.naming_master = ctx.get_naming_master()
1538 if ctx.naming_master != ctx.server:
1539 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1540 ctx.server = ctx.naming_master
1541 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1542 session_info=system_session(),
1543 credentials=ctx.creds, lp=ctx.lp)
1544 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1546 ctx.server = res[0]["dnsHostName"]
1547 logger.info("DNS name of new naming master is %s" % ctx.server)
1549 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1550 ctx.forestsid = ctx.domsid
1551 ctx.domsid = security.random_sid()
1553 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1554 # Windows uses 240 bytes as UTF16 so we do
1555 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1557 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1559 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1560 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1562 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1563 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1564 ctx.domain_replica_flags = ctx.replica_flags
1567 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1570 class DCCloneContext(DCJoinContext):
1571 """Clones a remote DC."""
1573 def __init__(ctx, logger=None, server=None, creds=None, lp=None,
1574 targetdir=None, domain=None, dns_backend=None,
1575 include_secrets=False):
1576 super(DCCloneContext, ctx).__init__(logger, server, creds, lp,
1577 targetdir=targetdir, domain=domain,
1578 dns_backend=dns_backend)
1580 # As we don't want to create or delete these DNs, we set them to None
1581 ctx.server_dn = None
1584 ctx.myname = ctx.server.split('.')[0]
1585 ctx.ntds_guid = None
1586 ctx.rid_manager_dn = None
1589 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
1591 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1592 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1593 if not include_secrets:
1594 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1595 ctx.domain_replica_flags = ctx.replica_flags
1597 def join_finalise(ctx):
1598 ctx.logger.info("Setting isSynchronized and dsServiceName")
1600 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1601 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE,
1604 # We want to appear to be the server we just cloned
1605 guid = ctx.remote_dc_ntds_guid
1606 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1607 ldb.FLAG_MOD_REPLACE,
1609 ctx.local_samdb.modify(m)
1612 ctx.build_nc_lists()
1614 # When cloning a DC, we just want to provision a DC locally, then
1615 # grab the remote DC's entire DB via DRS replication
1616 ctx.join_provision()
1617 ctx.join_replicate()
1621 # Used to create a renamed backup of a DC. Renaming the domain means that the
1622 # cloned/backup DC can be started without interfering with the production DC.
1623 class DCCloneAndRenameContext(DCCloneContext):
1624 """Clones a remote DC, renaming the domain along the way."""
1626 def __init__(ctx, new_base_dn, new_domain_name, new_realm, logger=None,
1627 server=None, creds=None, lp=None, targetdir=None, domain=None,
1628 dns_backend=None, include_secrets=True):
1629 super(DCCloneAndRenameContext, ctx).__init__(logger, server, creds, lp,
1630 targetdir=targetdir,
1632 dns_backend=dns_backend,
1633 include_secrets=include_secrets)
1634 # store the new DN (etc) that we want the cloned DB to use
1635 ctx.new_base_dn = new_base_dn
1636 ctx.new_domain_name = new_domain_name
1637 ctx.new_realm = new_realm
1639 def create_replicator(ctx, repl_creds, binding_options):
1640 """Creates a new DRS object for managing replications"""
1642 # We want to rename all the domain objects, and the simplest way to do
1643 # this is during replication. This is because the base DN of the top-
1644 # level replicated object will flow through to all the objects below it
1645 binding_str = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
1646 return drs_utils.drs_ReplicateRenamer(binding_str, ctx.lp, repl_creds,
1649 ctx.base_dn, ctx.new_base_dn)
1651 def create_non_global_lp(ctx, global_lp):
1652 '''Creates a non-global LoadParm based on the global LP's settings'''
1654 # the samba code shares a global LoadParm by default. Here we create a
1655 # new LoadParm that retains the global settings, but any changes we
1656 # make to it won't automatically affect the rest of the samba code.
1657 # The easiest way to do this is to dump the global settings to a
1658 # temporary smb.conf file, and then load the temp file into a new
1659 # non-global LoadParm
1660 fd, tmp_file = tempfile.mkstemp()
1661 global_lp.dump(False, tmp_file)
1662 local_lp = samba.param.LoadParm(filename_for_non_global_lp=tmp_file)
1666 def rename_dn(ctx, dn_str):
1667 '''Uses string substitution to replace the base DN'''
1668 old_base_dn = ctx.base_dn
1669 return re.sub('%s$' % old_base_dn, ctx.new_base_dn, dn_str)
1671 # we want to override the normal DCCloneContext's join_provision() so that
1672 # use the new domain DNs during the provision. We do this because:
1673 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1674 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1675 # we couldn't apply the renamed DRS objects during replication)
1676 def join_provision(ctx):
1677 """Provision the local (renamed) SAM."""
1679 print("Provisioning the new (renamed) domain...")
1681 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1682 # to create a new smb.conf. By default, it uses the global LoadParm to
1683 # do this, and so it would overwrite the realm/domain values globally.
1684 # We still need the global LoadParm to retain the old domain's details,
1685 # so we can connect to (and clone) the existing DC.
1686 # So, copy the global settings into a non-global LoadParm, which we can
1687 # then pass into provision(). This generates a new smb.conf correctly,
1688 # without overwriting the global realm/domain values just yet.
1689 non_global_lp = ctx.create_non_global_lp(ctx.lp)
1691 # do the provision with the new/renamed domain DN values
1692 presult = provision(ctx.logger, system_session(),
1693 targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
1694 realm=ctx.new_realm, lp=non_global_lp,
1695 rootdn=ctx.rename_dn(ctx.root_dn), domaindn=ctx.new_base_dn,
1696 schemadn=ctx.rename_dn(ctx.schema_dn),
1697 configdn=ctx.rename_dn(ctx.config_dn),
1698 domain=ctx.new_domain_name, domainsid=ctx.domsid,
1699 serverrole="active directory domain controller",
1700 dns_backend=ctx.dns_backend)
1702 print("Provision OK for renamed domain DN %s" % presult.domaindn)
1703 ctx.local_samdb = presult.samdb
1704 ctx.paths = presult.paths