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 |samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
330 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
332 ctx.promote_from_dn = res[0].dn
334 def find_dc(ctx, domain):
335 """find a writeable DC for the given domain"""
337 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
338 except NTSTATUSError as error:
339 raise Exception("Failed to find a writeable DC for domain '%s': %s" %
342 raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
343 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
344 ctx.site = ctx.cldap_ret.client_site
345 return ctx.cldap_ret.pdc_dns_name
347 def get_behavior_version(ctx):
348 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
349 if "msDS-Behavior-Version" in res[0]:
350 return int(res[0]["msDS-Behavior-Version"][0])
352 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
354 def get_dnsHostName(ctx):
355 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
356 return res[0]["dnsHostName"][0]
358 def get_domain_name(ctx):
359 '''get netbios name of the domain from the partitions record'''
360 partitions_dn = ctx.samdb.get_partitions_dn()
361 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
362 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
363 return res[0]["nETBIOSName"][0]
365 def get_forest_domain_name(ctx):
366 '''get netbios name of the domain from the partitions record'''
367 partitions_dn = ctx.samdb.get_partitions_dn()
368 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
369 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
370 return res[0]["nETBIOSName"][0]
372 def get_parent_partition_dn(ctx):
373 '''get the parent domain partition DN from parent DNS name'''
374 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
375 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
376 (ldb.binary_encode(ctx.parent_dnsdomain),
377 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
378 return str(res[0].dn)
380 def get_naming_master(ctx):
381 '''get the parent domain partition DN from parent DNS name'''
382 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
383 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
384 if not 'fSMORoleOwner' in res[0]:
385 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
387 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
389 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
391 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
395 '''get the SID of the connected user. Only works with w2k8 and later,
396 so only used for RODC join'''
397 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
398 binsid = res[0]["tokenGroups"][0]
399 return ctx.samdb.schema_format_value("objectSID", binsid)
401 def dn_exists(ctx, dn):
402 '''check if a DN exists'''
404 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
405 except ldb.LdbError as e5:
406 (enum, estr) = e5.args
407 if enum == ldb.ERR_NO_SUCH_OBJECT:
412 def add_krbtgt_account(ctx):
413 '''RODCs need a special krbtgt account'''
414 print("Adding %s" % ctx.krbtgt_dn)
417 "objectclass": "user",
418 "useraccountcontrol": str(samba.dsdb.UF_NORMAL_ACCOUNT |
419 samba.dsdb.UF_ACCOUNTDISABLE),
420 "showinadvancedviewonly": "TRUE",
421 "description": "krbtgt for %s" % ctx.samname}
422 ctx.samdb.add(rec, ["rodc_join:1:1"])
424 # now we need to search for the samAccountName attribute on the krbtgt DN,
425 # as this will have been magically set to the krbtgt number
426 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
427 ctx.krbtgt_name = res[0]["samAccountName"][0]
429 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
432 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
433 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
434 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
437 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
438 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
439 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
441 def drsuapi_connect(ctx):
442 '''make a DRSUAPI connection to the naming master'''
443 binding_options = "seal"
444 if ctx.lp.log_level() >= 9:
445 binding_options += ",print"
446 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
447 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
448 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
450 def create_tmp_samdb(ctx):
451 '''create a temporary samdb object for schema queries'''
452 ctx.tmp_schema = Schema(ctx.domsid,
453 schemadn=ctx.schema_dn)
454 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
455 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
457 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
459 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
460 '''build a DsReplicaAttributeCtr object'''
461 r = drsuapi.DsReplicaAttribute()
462 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
465 def DsAddEntry(ctx, recs):
466 '''add a record via the DRSUAPI DsAddEntry call'''
467 if ctx.drsuapi is None:
468 ctx.drsuapi_connect()
469 if ctx.tmp_samdb is None:
470 ctx.create_tmp_samdb()
474 id = drsuapi.DsReplicaObjectIdentifier()
481 if not isinstance(rec[a], list):
485 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
488 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
489 attribute_ctr.num_attributes = len(attrs)
490 attribute_ctr.attributes = attrs
492 object = drsuapi.DsReplicaObject()
493 object.identifier = id
494 object.attribute_ctr = attribute_ctr
496 list_object = drsuapi.DsReplicaObjectListItem()
497 list_object.object = object
498 objects.append(list_object)
500 req2 = drsuapi.DsAddEntryRequest2()
501 req2.first_object = objects[0]
502 prev = req2.first_object
503 for o in objects[1:]:
507 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
509 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
510 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
511 raise RuntimeError("DsAddEntry failed")
512 if ctr.extended_err[0] != werror.WERR_SUCCESS:
513 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
514 raise RuntimeError("DsAddEntry failed")
517 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
518 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
519 if ctr.err_data.info is None:
520 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
522 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
523 ctr.err_data.info.extended_err))
524 raise RuntimeError("DsAddEntry failed")
525 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
526 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
527 raise RuntimeError("DsAddEntry failed")
531 def join_ntdsdsa_obj(ctx):
532 '''return the ntdsdsa object to add'''
534 print("Adding %s" % ctx.ntds_dn)
537 "objectclass": "nTDSDSA",
538 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
539 "dMDLocation": ctx.schema_dn}
541 nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
543 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
544 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
546 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
547 rec["msDS-HasDomainNCs"] = ctx.base_dn
550 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
551 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
552 rec["options"] = "37"
554 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
555 rec["HasMasterNCs"] = []
557 if nc in ctx.full_nc_list:
558 rec["HasMasterNCs"].append(nc)
559 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
560 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
562 rec["invocationId"] = ndr_pack(ctx.invocation_id)
566 def join_add_ntdsdsa(ctx):
567 '''add the ntdsdsa object'''
569 rec = ctx.join_ntdsdsa_obj()
570 if ctx.forced_local_samdb:
571 ctx.samdb.add(rec, controls=["relax:0"])
573 ctx.samdb.add(rec, ["rodc_join:1:1"])
575 ctx.DsAddEntry([rec])
577 # find the GUID of our NTDS DN
578 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
579 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
581 def join_add_objects(ctx, specified_sid=None):
582 '''add the various objects needed for the join'''
584 print("Adding %s" % ctx.acct_dn)
587 "objectClass": "computer",
588 "displayname": ctx.samname,
589 "samaccountname": ctx.samname,
590 "userAccountControl": str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
591 "dnshostname": ctx.dnshostname}
592 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
593 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
594 elif ctx.promote_existing:
595 rec['msDS-SupportedEncryptionTypes'] = []
597 rec["managedby"] = ctx.managedby
598 elif ctx.promote_existing:
599 rec["managedby"] = []
601 if ctx.never_reveal_sid:
602 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
603 elif ctx.promote_existing:
604 rec["msDS-NeverRevealGroup"] = []
607 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
608 elif ctx.promote_existing:
609 rec["msDS-RevealOnDemandGroup"] = []
612 rec["objectSid"] = ndr_pack(specified_sid)
614 if ctx.promote_existing:
615 if ctx.promote_from_dn != ctx.acct_dn:
616 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
617 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
620 if specified_sid is not None:
621 controls = ["relax:0"]
622 ctx.samdb.add(rec, controls=controls)
625 ctx.add_krbtgt_account()
628 print("Adding %s" % ctx.server_dn)
631 "objectclass": "server",
632 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
633 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
634 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
635 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
636 # windows seems to add the dnsHostName later
637 "dnsHostName": ctx.dnshostname}
640 rec["serverReference"] = ctx.acct_dn
645 # the rest is done after replication
650 ctx.join_add_ntdsdsa()
652 # Add the Replica-Locations or RO-Replica-Locations attributes
653 # TODO Is this supposed to be for the schema partition too?
654 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
655 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
657 base=ctx.samdb.get_partitions_dn(),
658 expression=expr), ctx.domaindns_zone)
660 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
661 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
663 base=ctx.samdb.get_partitions_dn(),
664 expression=expr), ctx.forestdns_zone)
666 for part, zone in (domain, forest):
667 if zone not in ctx.nc_list:
673 attr = "msDS-NC-Replica-Locations"
675 attr = "msDS-NC-RO-Replica-Locations"
677 m[attr] = ldb.MessageElement(ctx.ntds_dn,
678 ldb.FLAG_MOD_ADD, attr)
681 if ctx.connection_dn is not None:
682 print("Adding %s" % ctx.connection_dn)
684 "dn": ctx.connection_dn,
685 "objectclass": "nTDSConnection",
686 "enabledconnection": "TRUE",
688 "fromServer": ctx.dc_ntds_dn}
692 print("Adding SPNs to %s" % ctx.acct_dn)
694 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
695 for i in range(len(ctx.SPNs)):
696 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
697 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
698 ldb.FLAG_MOD_REPLACE,
699 "servicePrincipalName")
702 # The account password set operation should normally be done over
703 # LDAP. Windows 2000 DCs however allow this only with SSL
704 # connections which are hard to set up and otherwise refuse with
705 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
707 print("Setting account password for %s" % ctx.samname)
709 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
710 % ldb.binary_encode(ctx.samname),
712 force_change_at_next_login=False,
713 username=ctx.samname)
714 except ldb.LdbError as e2:
716 if num != ldb.ERR_UNWILLING_TO_PERFORM:
718 ctx.net.set_password(account_name=ctx.samname,
719 domain_name=ctx.domain_name,
720 newpassword=ctx.acct_pass.encode('utf-8'))
722 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
723 attrs=["msDS-KeyVersionNumber",
725 if "msDS-KeyVersionNumber" in res[0]:
726 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
728 ctx.key_version_number = None
730 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
731 res[0]["objectSid"][0])
733 print("Enabling account")
735 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
736 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
737 ldb.FLAG_MOD_REPLACE,
738 "userAccountControl")
741 if ctx.dns_backend.startswith("BIND9_"):
742 ctx.dnspass = samba.generate_random_password(128, 255)
744 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
745 {"DNSDOMAIN": ctx.dnsdomain,
746 "DOMAINDN": ctx.base_dn,
747 "HOSTNAME": ctx.myname,
748 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
749 "DNSNAME": ctx.dnshostname}))
750 for changetype, msg in recs:
751 assert changetype == ldb.CHANGETYPE_NONE
752 dns_acct_dn = msg["dn"]
753 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
755 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
756 del msg["clearTextPassword"]
757 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
758 del msg["isCriticalSystemObject"]
759 # Disable account until password is set
760 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
761 samba.dsdb.UF_ACCOUNTDISABLE)
764 except ldb.LdbError as e:
766 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
769 # The account password set operation should normally be done over
770 # LDAP. Windows 2000 DCs however allow this only with SSL
771 # connections which are hard to set up and otherwise refuse with
772 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
774 print("Setting account password for dns-%s" % ctx.myname)
776 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
777 % ldb.binary_encode(ctx.myname),
779 force_change_at_next_login=False,
780 username=ctx.samname)
781 except ldb.LdbError as e3:
783 if num != ldb.ERR_UNWILLING_TO_PERFORM:
785 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
786 domain_name=ctx.domain_name,
787 newpassword=ctx.dnspass)
789 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
790 attrs=["msDS-KeyVersionNumber"])
791 if "msDS-KeyVersionNumber" in res[0]:
792 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
794 ctx.dns_key_version_number = None
796 def join_add_objects2(ctx):
797 """add the various objects needed for the join, for subdomains post replication"""
799 print("Adding %s" % ctx.partition_dn)
800 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
801 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
803 "dn": ctx.partition_dn,
804 "objectclass": "crossRef",
805 "objectCategory": "CN=Cross-Ref,%s" % ctx.schema_dn,
806 "nCName": ctx.base_dn,
807 "nETBIOSName": ctx.domain_name,
808 "dnsRoot": ctx.dnsdomain,
809 "trustParent": ctx.parent_partition_dn,
810 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC |samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
811 "ntSecurityDescriptor": sd_binary,
814 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
815 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
817 rec2 = ctx.join_ntdsdsa_obj()
819 objects = ctx.DsAddEntry([rec, rec2])
820 if len(objects) != 2:
821 raise DCJoinException("Expected 2 objects from DsAddEntry")
823 ctx.ntds_guid = objects[1].guid
825 print("Replicating partition DN")
826 ctx.repl.replicate(ctx.partition_dn,
827 misc.GUID("00000000-0000-0000-0000-000000000000"),
829 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
830 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
832 print("Replicating NTDS DN")
833 ctx.repl.replicate(ctx.ntds_dn,
834 misc.GUID("00000000-0000-0000-0000-000000000000"),
836 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
837 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
839 def join_provision(ctx):
840 """Provision the local SAM."""
842 print("Calling bare provision")
844 smbconf = ctx.lp.configfile
846 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
847 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
848 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
849 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
850 serverdn=ctx.server_dn, domain=ctx.domain_name,
851 hostname=ctx.myname, domainsid=ctx.domsid,
852 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
853 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
854 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
855 plaintext_secrets=ctx.plaintext_secrets,
856 backend_store=ctx.backend_store
858 print("Provision OK for domain DN %s" % presult.domaindn)
859 ctx.local_samdb = presult.samdb
861 ctx.paths = presult.paths
862 ctx.names = presult.names
864 # Fix up the forestsid, it may be different if we are joining as a subdomain
865 ctx.names.forestsid = ctx.forestsid
867 def join_provision_own_domain(ctx):
868 """Provision the local SAM."""
870 # we now operate exclusively on the local database, which
871 # we need to reopen in order to get the newly created schema
872 print("Reconnecting to local samdb")
873 ctx.samdb = SamDB(url=ctx.local_samdb.url,
874 session_info=system_session(),
875 lp=ctx.local_samdb.lp,
877 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
878 ctx.local_samdb = ctx.samdb
880 ctx.logger.info("Finding domain GUID from ncName")
881 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
882 controls=["extended_dn:1:1", "reveal_internals:0"])
884 if 'nCName' not in res[0]:
885 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
888 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
890 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
892 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
894 ctx.logger.info("Calling own domain provision")
896 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
898 presult = provision_fill(ctx.local_samdb, secrets_ldb,
899 ctx.logger, ctx.names, ctx.paths,
900 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
901 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
902 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
903 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
904 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
905 print("Provision OK for domain %s" % ctx.names.dnsdomain)
907 def create_replicator(ctx, repl_creds, binding_options):
908 '''Creates a new DRS object for managing replications'''
909 return drs_utils.drs_Replicate(
910 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
911 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
913 def join_replicate(ctx):
914 """Replicate the SAM."""
916 print("Starting replication")
917 ctx.local_samdb.transaction_start()
919 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
920 if ctx.ntds_guid is None:
921 print("Using DS_BIND_GUID_W2K3")
922 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
924 destination_dsa_guid = ctx.ntds_guid
927 repl_creds = Credentials()
928 repl_creds.guess(ctx.lp)
929 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
930 repl_creds.set_username(ctx.samname)
931 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
933 repl_creds = ctx.creds
935 binding_options = "seal"
936 if ctx.lp.log_level() >= 9:
937 binding_options += ",print"
939 repl = ctx.create_replicator(repl_creds, binding_options)
941 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
942 destination_dsa_guid, schema=True, rodc=ctx.RODC,
943 replica_flags=ctx.replica_flags)
944 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
945 destination_dsa_guid, rodc=ctx.RODC,
946 replica_flags=ctx.replica_flags)
947 if not ctx.subdomain:
948 # Replicate first the critical object for the basedn
949 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
950 print("Replicating critical objects from the base DN of the domain")
951 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
952 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
953 destination_dsa_guid, rodc=ctx.RODC,
954 replica_flags=ctx.domain_replica_flags)
955 ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
956 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
957 destination_dsa_guid, rodc=ctx.RODC,
958 replica_flags=ctx.domain_replica_flags)
959 print("Done with always replicated NC (base, config, schema)")
961 # At this point we should already have an entry in the ForestDNS
962 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
963 # indicate that we hold a replica for this NC.
964 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
965 if nc in ctx.nc_list:
966 print("Replicating %s" % (str(nc)))
967 repl.replicate(nc, source_dsa_invocation_id,
968 destination_dsa_guid, rodc=ctx.RODC,
969 replica_flags=ctx.replica_flags)
972 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
973 destination_dsa_guid,
974 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
975 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
976 destination_dsa_guid,
977 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
978 elif ctx.rid_manager_dn != None:
979 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
981 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
982 destination_dsa_guid,
983 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
984 except samba.DsExtendedError as e1:
985 (enum, estr) = e1.args
986 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
987 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
988 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
993 ctx.source_dsa_invocation_id = source_dsa_invocation_id
994 ctx.destination_dsa_guid = destination_dsa_guid
996 print("Committing SAM database")
998 ctx.local_samdb.transaction_cancel()
1001 ctx.local_samdb.transaction_commit()
1003 def send_DsReplicaUpdateRefs(ctx, dn):
1004 r = drsuapi.DsReplicaUpdateRefsRequest1()
1005 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
1006 r.naming_context.dn = str(dn)
1007 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
1008 r.naming_context.sid = security.dom_sid("S-0-0")
1009 r.dest_dsa_guid = ctx.ntds_guid
1010 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1011 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1013 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1015 if ctx.drsuapi is None:
1016 ctx.drsuapi_connect()
1018 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1020 def join_add_dns_records(ctx):
1021 """Remotely Add a DNS record to the target DC. We assume that if we
1022 replicate DNS that the server holds the DNS roles and can accept
1025 This avoids issues getting replication going after the DC
1026 first starts as the rest of the domain does not have to
1027 wait for samba_dnsupdate to run successfully.
1029 Specifically, we add the records implied by the DsReplicaUpdateRefs
1032 We do not just run samba_dnsupdate as we want to strictly
1033 operate against the DC we just joined:
1034 - We do not want to query another DNS server
1035 - We do not want to obtain a Kerberos ticket
1036 (as the KDC we select may not be the DC we just joined,
1037 and so may not be in sync with the password we just set)
1038 - We do not wish to set the _ldap records until we have started
1039 - We do not wish to use NTLM (the --use-samba-tool mode forces
1044 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1045 record_type = dnsp.DNS_TYPE_A
1046 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1047 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1049 zone = ctx.dnsdomain
1050 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1052 msdcs_cname = str(ctx.ntds_guid)
1053 cname_target = "%s.%s" % (name, zone)
1054 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1056 ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
1057 (len(IPs), name, zone))
1059 binding_options = "sign"
1060 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1065 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1067 change_owner_sd = security.descriptor()
1068 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1069 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1071 security.DOMAIN_RID_DCS))
1073 # TODO: Remove any old records from the primary DNS name
1076 = dns_conn.DnssrvEnumRecords2(client_version,
1086 except WERRORError as e:
1087 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1093 for record in rec.records:
1094 if record.wType == dnsp.DNS_TYPE_A or \
1095 record.wType == dnsp.DNS_TYPE_AAAA:
1097 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1098 del_rec_buf.rec = record
1100 dns_conn.DnssrvUpdateRecord2(client_version,
1107 except WERRORError as e:
1108 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1114 if IP.find(':') != -1:
1115 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1117 rec = AAAARecord(IP)
1119 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1124 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1125 add_rec_buf.rec = rec
1126 dns_conn.DnssrvUpdateRecord2(client_version,
1135 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1136 (ctx.dns_a_dn, ldap_record) \
1137 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1138 dns_partition=domaindns_zone_dn)
1140 # Make the DC own the DNS record, not the administrator
1141 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1142 controls=["sd_flags:1:%d"
1143 % (security.SECINFO_OWNER
1144 | security.SECINFO_GROUP)])
1147 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1148 % (msdcs_cname, msdcs_zone, cname_target))
1150 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1151 rec = CNameRecord(cname_target)
1152 add_rec_buf.rec = rec
1153 dns_conn.DnssrvUpdateRecord2(client_version,
1161 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1162 (ctx.dns_cname_dn, ldap_record) \
1163 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1164 dns_partition=forestdns_zone_dn)
1166 # Make the DC own the DNS record, not the administrator
1167 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1168 controls=["sd_flags:1:%d"
1169 % (security.SECINFO_OWNER
1170 | security.SECINFO_GROUP)])
1172 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1173 "will be created samba_dnsupdate on first startup")
1175 def join_replicate_new_dns_records(ctx):
1176 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1177 if nc in ctx.nc_list:
1178 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1179 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1180 ctx.ntds_guid, rodc=ctx.RODC,
1181 replica_flags=ctx.replica_flags,
1184 def join_finalise(ctx):
1185 """Finalise the join, mark us synchronised and setup secrets db."""
1187 # FIXME we shouldn't do this in all cases
1189 # If for some reasons we joined in another site than the one of
1190 # DC we just replicated from then we don't need to send the updatereplicateref
1191 # as replication between sites is time based and on the initiative of the
1193 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1194 for nc in ctx.nc_list:
1195 ctx.send_DsReplicaUpdateRefs(nc)
1198 print("Setting RODC invocationId")
1199 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1200 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1201 ctx.behavior_version)
1203 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1204 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1205 ldb.FLAG_MOD_REPLACE,
1207 ctx.local_samdb.modify(m)
1209 # Note: as RODC the invocationId is only stored
1210 # on the RODC itself, the other DCs never see it.
1212 # Thats is why we fix up the replPropertyMetaData stamp
1213 # for the 'invocationId' attribute, we need to change
1214 # the 'version' to '0', this is what windows 2008r2 does as RODC
1216 # This means if the object on a RWDC ever gets a invocationId
1217 # attribute, it will have version '1' (or higher), which will
1218 # will overwrite the RODC local value.
1219 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1223 ctx.logger.info("Setting isSynchronized and dsServiceName")
1225 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1226 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1228 guid = ctx.ntds_guid
1229 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1230 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1231 ctx.local_samdb.modify(m)
1236 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1238 ctx.logger.info("Setting up secrets database")
1239 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1241 dnsdomain=ctx.dnsdomain,
1242 netbiosname=ctx.myname,
1243 domainsid=ctx.domsid,
1244 machinepass=ctx.acct_pass,
1245 secure_channel_type=ctx.secure_channel_type,
1246 key_version_number=ctx.key_version_number)
1248 if ctx.dns_backend.startswith("BIND9_"):
1249 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1250 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1251 dns_backend=ctx.dns_backend,
1252 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1253 targetdir=ctx.targetdir,
1254 key_version_number=ctx.dns_key_version_number)
1256 def join_setup_trusts(ctx):
1257 """provision the local SAM."""
1259 print("Setup domain trusts with server %s" % ctx.server)
1260 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1261 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1264 objectAttr = lsa.ObjectAttribute()
1265 objectAttr.sec_qos = lsa.QosInfo()
1267 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1268 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1270 info = lsa.TrustDomainInfoInfoEx()
1271 info.domain_name.string = ctx.dnsdomain
1272 info.netbios_name.string = ctx.domain_name
1273 info.sid = ctx.domsid
1274 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1275 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1276 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1279 oldname = lsa.String()
1280 oldname.string = ctx.dnsdomain
1281 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1282 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1283 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1284 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1285 except RuntimeError:
1288 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1290 clear_value = drsblobs.AuthInfoClear()
1291 clear_value.size = len(password_blob)
1292 clear_value.password = password_blob
1294 clear_authentication_information = drsblobs.AuthenticationInformation()
1295 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1296 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1297 clear_authentication_information.AuthInfo = clear_value
1299 authentication_information_array = drsblobs.AuthenticationInformationArray()
1300 authentication_information_array.count = 1
1301 authentication_information_array.array = [clear_authentication_information]
1303 outgoing = drsblobs.trustAuthInOutBlob()
1305 outgoing.current = authentication_information_array
1307 trustpass = drsblobs.trustDomainPasswords()
1308 confounder = [3] * 512
1310 for i in range(512):
1311 confounder[i] = random.randint(0, 255)
1313 trustpass.confounder = confounder
1315 trustpass.outgoing = outgoing
1316 trustpass.incoming = outgoing
1318 trustpass_blob = ndr_pack(trustpass)
1320 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1322 auth_blob = lsa.DATA_BUF2()
1323 auth_blob.size = len(encrypted_trustpass)
1324 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1326 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1327 auth_info.auth_blob = auth_blob
1329 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1332 security.SEC_STD_DELETE)
1335 "dn": "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1336 "objectclass": "trustedDomain",
1337 "trustType": str(info.trust_type),
1338 "trustAttributes": str(info.trust_attributes),
1339 "trustDirection": str(info.trust_direction),
1340 "flatname": ctx.forest_domain_name,
1341 "trustPartner": ctx.dnsforest,
1342 "trustAuthIncoming": ndr_pack(outgoing),
1343 "trustAuthOutgoing": ndr_pack(outgoing),
1344 "securityIdentifier": ndr_pack(ctx.forestsid)
1346 ctx.local_samdb.add(rec)
1349 "dn": "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1350 "objectclass": "user",
1351 "userAccountControl": str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1352 "clearTextPassword": ctx.trustdom_pass.encode('utf-16-le'),
1353 "samAccountName": "%s$" % ctx.forest_domain_name
1355 ctx.local_samdb.add(rec)
1357 def build_nc_lists(ctx):
1358 # nc_list is the list of naming context (NC) for which we will
1359 # replicate in and send a updateRef command to the partner DC
1361 # full_nc_list is the list of naming context (NC) we hold
1362 # read/write copies of. These are not subsets of each other.
1363 ctx.nc_list = [ctx.config_dn, ctx.schema_dn]
1364 ctx.full_nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
1366 if ctx.subdomain and ctx.dns_backend != "NONE":
1367 ctx.full_nc_list += [ctx.domaindns_zone]
1369 elif not ctx.subdomain:
1370 ctx.nc_list += [ctx.base_dn]
1372 if ctx.dns_backend != "NONE":
1373 ctx.nc_list += [ctx.domaindns_zone]
1374 ctx.nc_list += [ctx.forestdns_zone]
1375 ctx.full_nc_list += [ctx.domaindns_zone]
1376 ctx.full_nc_list += [ctx.forestdns_zone]
1379 ctx.build_nc_lists()
1381 if ctx.promote_existing:
1382 ctx.promote_possible()
1384 ctx.cleanup_old_join()
1387 ctx.join_add_objects()
1388 ctx.join_provision()
1389 ctx.join_replicate()
1391 ctx.join_add_objects2()
1392 ctx.join_provision_own_domain()
1393 ctx.join_setup_trusts()
1395 if ctx.dns_backend != "NONE":
1396 ctx.join_add_dns_records()
1397 ctx.join_replicate_new_dns_records()
1402 print("Join failed - cleaning up")
1405 ctx.cleanup_old_join()
1409 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1410 targetdir=None, domain=None, domain_critical_only=False,
1411 machinepass=None, use_ntvfs=False, dns_backend=None,
1412 promote_existing=False, plaintext_secrets=False,
1413 backend_store=None):
1414 """Join as a RODC."""
1416 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1417 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1418 promote_existing, plaintext_secrets,
1419 backend_store=backend_store)
1421 lp.set("workgroup", ctx.domain_name)
1422 logger.info("workgroup is %s" % ctx.domain_name)
1424 lp.set("realm", ctx.realm)
1425 logger.info("realm is %s" % ctx.realm)
1427 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1429 # setup some defaults for accounts that should be replicated to this RODC
1430 ctx.never_reveal_sid = [
1431 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1432 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1433 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1434 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1435 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1436 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1438 mysid = ctx.get_mysid()
1439 admin_dn = "<SID=%s>" % mysid
1440 ctx.managedby = admin_dn
1442 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1443 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1444 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1446 ctx.SPNs.extend(["RestrictedKrbHost/%s" % ctx.myname,
1447 "RestrictedKrbHost/%s" % ctx.dnshostname])
1449 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1450 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1452 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1453 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1454 ctx.domain_replica_flags = ctx.replica_flags
1455 if domain_critical_only:
1456 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1460 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1463 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1464 targetdir=None, domain=None, domain_critical_only=False,
1465 machinepass=None, use_ntvfs=False, dns_backend=None,
1466 promote_existing=False, plaintext_secrets=False,
1467 backend_store=None):
1469 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1470 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1471 promote_existing, plaintext_secrets,
1472 backend_store=backend_store)
1474 lp.set("workgroup", ctx.domain_name)
1475 logger.info("workgroup is %s" % ctx.domain_name)
1477 lp.set("realm", ctx.realm)
1478 logger.info("realm is %s" % ctx.realm)
1480 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1482 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1483 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1485 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1486 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1487 ctx.domain_replica_flags = ctx.replica_flags
1488 if domain_critical_only:
1489 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1492 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1495 def join_clone(logger=None, server=None, creds=None, lp=None,
1496 targetdir=None, domain=None, include_secrets=False,
1497 dns_backend="NONE"):
1498 """Creates a local clone of a remote DC."""
1499 ctx = DCCloneContext(logger, server, creds, lp, targetdir=targetdir,
1500 domain=domain, dns_backend=dns_backend,
1501 include_secrets=include_secrets)
1503 lp.set("workgroup", ctx.domain_name)
1504 logger.info("workgroup is %s" % ctx.domain_name)
1506 lp.set("realm", ctx.realm)
1507 logger.info("realm is %s" % ctx.realm)
1510 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1514 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1515 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1516 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1517 dns_backend=None, plaintext_secrets=False,
1518 backend_store=None):
1520 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1521 targetdir, parent_domain, machinepass, use_ntvfs,
1522 dns_backend, plaintext_secrets,
1523 backend_store=backend_store)
1524 ctx.subdomain = True
1525 if adminpass is None:
1526 ctx.adminpass = samba.generate_random_password(12, 32)
1528 ctx.adminpass = adminpass
1529 ctx.parent_domain_name = ctx.domain_name
1530 ctx.domain_name = netbios_domain
1531 ctx.realm = dnsdomain
1532 ctx.parent_dnsdomain = ctx.dnsdomain
1533 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1534 ctx.dnsdomain = dnsdomain
1535 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1536 ctx.naming_master = ctx.get_naming_master()
1537 if ctx.naming_master != ctx.server:
1538 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1539 ctx.server = ctx.naming_master
1540 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1541 session_info=system_session(),
1542 credentials=ctx.creds, lp=ctx.lp)
1543 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1545 ctx.server = res[0]["dnsHostName"]
1546 logger.info("DNS name of new naming master is %s" % ctx.server)
1548 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1549 ctx.forestsid = ctx.domsid
1550 ctx.domsid = security.random_sid()
1552 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1553 # Windows uses 240 bytes as UTF16 so we do
1554 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1556 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1558 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1559 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1561 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1562 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1563 ctx.domain_replica_flags = ctx.replica_flags
1566 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1569 class DCCloneContext(DCJoinContext):
1570 """Clones a remote DC."""
1572 def __init__(ctx, logger=None, server=None, creds=None, lp=None,
1573 targetdir=None, domain=None, dns_backend=None,
1574 include_secrets=False):
1575 super(DCCloneContext, ctx).__init__(logger, server, creds, lp,
1576 targetdir=targetdir, domain=domain,
1577 dns_backend=dns_backend)
1579 # As we don't want to create or delete these DNs, we set them to None
1580 ctx.server_dn = None
1583 ctx.myname = ctx.server.split('.')[0]
1584 ctx.ntds_guid = None
1585 ctx.rid_manager_dn = None
1588 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
1590 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1591 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1592 if not include_secrets:
1593 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1594 ctx.domain_replica_flags = ctx.replica_flags
1596 def join_finalise(ctx):
1597 ctx.logger.info("Setting isSynchronized and dsServiceName")
1599 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1600 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE,
1603 # We want to appear to be the server we just cloned
1604 guid = ctx.remote_dc_ntds_guid
1605 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1606 ldb.FLAG_MOD_REPLACE,
1608 ctx.local_samdb.modify(m)
1611 ctx.build_nc_lists()
1613 # When cloning a DC, we just want to provision a DC locally, then
1614 # grab the remote DC's entire DB via DRS replication
1615 ctx.join_provision()
1616 ctx.join_replicate()
1620 # Used to create a renamed backup of a DC. Renaming the domain means that the
1621 # cloned/backup DC can be started without interfering with the production DC.
1622 class DCCloneAndRenameContext(DCCloneContext):
1623 """Clones a remote DC, renaming the domain along the way."""
1625 def __init__(ctx, new_base_dn, new_domain_name, new_realm, logger=None,
1626 server=None, creds=None, lp=None, targetdir=None, domain=None,
1627 dns_backend=None, include_secrets=True):
1628 super(DCCloneAndRenameContext, ctx).__init__(logger, server, creds, lp,
1629 targetdir=targetdir,
1631 dns_backend=dns_backend,
1632 include_secrets=include_secrets)
1633 # store the new DN (etc) that we want the cloned DB to use
1634 ctx.new_base_dn = new_base_dn
1635 ctx.new_domain_name = new_domain_name
1636 ctx.new_realm = new_realm
1638 def create_replicator(ctx, repl_creds, binding_options):
1639 """Creates a new DRS object for managing replications"""
1641 # We want to rename all the domain objects, and the simplest way to do
1642 # this is during replication. This is because the base DN of the top-
1643 # level replicated object will flow through to all the objects below it
1644 binding_str = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
1645 return drs_utils.drs_ReplicateRenamer(binding_str, ctx.lp, repl_creds,
1648 ctx.base_dn, ctx.new_base_dn)
1650 def create_non_global_lp(ctx, global_lp):
1651 '''Creates a non-global LoadParm based on the global LP's settings'''
1653 # the samba code shares a global LoadParm by default. Here we create a
1654 # new LoadParm that retains the global settings, but any changes we
1655 # make to it won't automatically affect the rest of the samba code.
1656 # The easiest way to do this is to dump the global settings to a
1657 # temporary smb.conf file, and then load the temp file into a new
1658 # non-global LoadParm
1659 fd, tmp_file = tempfile.mkstemp()
1660 global_lp.dump(False, tmp_file)
1661 local_lp = samba.param.LoadParm(filename_for_non_global_lp=tmp_file)
1665 def rename_dn(ctx, dn_str):
1666 '''Uses string substitution to replace the base DN'''
1667 old_base_dn = ctx.base_dn
1668 return re.sub('%s$' % old_base_dn, ctx.new_base_dn, dn_str)
1670 # we want to override the normal DCCloneContext's join_provision() so that
1671 # use the new domain DNs during the provision. We do this because:
1672 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1673 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1674 # we couldn't apply the renamed DRS objects during replication)
1675 def join_provision(ctx):
1676 """Provision the local (renamed) SAM."""
1678 print("Provisioning the new (renamed) domain...")
1680 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1681 # to create a new smb.conf. By default, it uses the global LoadParm to
1682 # do this, and so it would overwrite the realm/domain values globally.
1683 # We still need the global LoadParm to retain the old domain's details,
1684 # so we can connect to (and clone) the existing DC.
1685 # So, copy the global settings into a non-global LoadParm, which we can
1686 # then pass into provision(). This generates a new smb.conf correctly,
1687 # without overwriting the global realm/domain values just yet.
1688 non_global_lp = ctx.create_non_global_lp(ctx.lp)
1690 # do the provision with the new/renamed domain DN values
1691 presult = provision(ctx.logger, system_session(),
1692 targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
1693 realm=ctx.new_realm, lp=non_global_lp,
1694 rootdn=ctx.rename_dn(ctx.root_dn), domaindn=ctx.new_base_dn,
1695 schemadn=ctx.rename_dn(ctx.schema_dn),
1696 configdn=ctx.rename_dn(ctx.config_dn),
1697 domain=ctx.new_domain_name, domainsid=ctx.domsid,
1698 serverrole="active directory domain controller",
1699 dns_backend=ctx.dns_backend)
1701 print("Provision OK for renamed domain DN %s" % presult.domaindn)
1702 ctx.local_samdb = presult.samdb
1703 ctx.paths = presult.paths