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
28 from samba
.ndr
import ndr_pack
, ndr_unpack
29 from samba
.dcerpc
import security
, drsuapi
, misc
, nbt
, lsa
, drsblobs
, dnsserver
, dnsp
30 from samba
.dsdb
import DS_DOMAIN_FUNCTION_2003
31 from samba
.credentials
import Credentials
, DONT_USE_KERBEROS
32 from samba
.provision
import (secretsdb_self_join
, provision
, provision_fill
,
33 FILL_DRS
, FILL_SUBDOMAIN
, DEFAULTSITE
)
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
import sd_utils
44 from samba
.dnsserver
import ARecord
, AAAARecord
, CNameRecord
51 from samba
.compat
import text_type
52 from samba
.compat
import get_string
53 from samba
.netcmd
import CommandError
56 class DCJoinException(Exception):
58 def __init__(self
, msg
):
59 super(DCJoinException
, self
).__init
__("Can't join, error: %s" % msg
)
62 class DCJoinContext(object):
63 """Perform a DC join."""
65 def __init__(ctx
, logger
=None, server
=None, creds
=None, lp
=None, site
=None,
66 netbios_name
=None, targetdir
=None, domain
=None,
67 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
68 promote_existing
=False, plaintext_secrets
=False,
70 backend_store_size
=None,
71 forced_local_samdb
=None):
77 ctx
.targetdir
= targetdir
78 ctx
.use_ntvfs
= use_ntvfs
79 ctx
.plaintext_secrets
= plaintext_secrets
80 ctx
.backend_store
= backend_store
81 ctx
.backend_store_size
= backend_store_size
83 ctx
.promote_existing
= promote_existing
84 ctx
.promote_from_dn
= None
89 ctx
.creds
.set_gensec_features(creds
.get_gensec_features() | gensec
.FEATURE_SEAL
)
90 ctx
.net
= Net(creds
=ctx
.creds
, lp
=ctx
.lp
)
93 ctx
.forced_local_samdb
= forced_local_samdb
95 if forced_local_samdb
:
96 ctx
.samdb
= forced_local_samdb
97 ctx
.server
= ctx
.samdb
.url
100 # work out the DC's site (if not already specified)
102 ctx
.site
= ctx
.find_dc_site(ctx
.server
)
104 # work out the Primary DC for the domain (as well as an
105 # appropriate site for the new DC)
106 ctx
.logger
.info("Finding a writeable DC for domain '%s'" % domain
)
107 ctx
.server
= ctx
.find_dc(domain
)
108 ctx
.logger
.info("Found DC %s" % ctx
.server
)
109 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
110 session_info
=system_session(),
111 credentials
=ctx
.creds
, lp
=ctx
.lp
)
114 ctx
.site
= DEFAULTSITE
117 ctx
.samdb
.search(scope
=ldb
.SCOPE_BASE
, attrs
=[])
118 except ldb
.LdbError
as e
:
119 (enum
, estr
) = e
.args
120 raise DCJoinException(estr
)
122 ctx
.base_dn
= str(ctx
.samdb
.get_default_basedn())
123 ctx
.root_dn
= str(ctx
.samdb
.get_root_basedn())
124 ctx
.schema_dn
= str(ctx
.samdb
.get_schema_basedn())
125 ctx
.config_dn
= str(ctx
.samdb
.get_config_basedn())
126 ctx
.domsid
= security
.dom_sid(ctx
.samdb
.get_domain_sid())
127 ctx
.forestsid
= ctx
.domsid
128 ctx
.domain_name
= ctx
.get_domain_name()
129 ctx
.forest_domain_name
= ctx
.get_forest_domain_name()
130 ctx
.invocation_id
= misc
.GUID(str(uuid
.uuid4()))
132 ctx
.dc_ntds_dn
= ctx
.samdb
.get_dsServiceName()
133 ctx
.dc_dnsHostName
= ctx
.get_dnsHostName()
134 ctx
.behavior_version
= ctx
.get_behavior_version()
136 if machinepass
is not None:
137 ctx
.acct_pass
= machinepass
139 ctx
.acct_pass
= samba
.generate_random_machine_password(128, 255)
141 ctx
.dnsdomain
= ctx
.samdb
.domain_dns_name()
143 # the following are all dependent on the new DC's netbios_name (which
144 # we expect to always be specified, except when cloning a DC)
146 # work out the DNs of all the objects we will be adding
147 ctx
.myname
= netbios_name
148 ctx
.samname
= "%s$" % ctx
.myname
149 ctx
.server_dn
= "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx
.myname
, ctx
.site
, ctx
.config_dn
)
150 ctx
.ntds_dn
= "CN=NTDS Settings,%s" % ctx
.server_dn
151 ctx
.acct_dn
= "CN=%s,OU=Domain Controllers,%s" % (ctx
.myname
, ctx
.base_dn
)
152 ctx
.dnshostname
= "%s.%s" % (ctx
.myname
.lower(), ctx
.dnsdomain
)
153 ctx
.dnsforest
= ctx
.samdb
.forest_dns_name()
155 topology_base
= "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx
.base_dn
156 if ctx
.dn_exists(topology_base
):
157 ctx
.topology_dn
= "CN=%s,%s" % (ctx
.myname
, topology_base
)
159 ctx
.topology_dn
= None
161 ctx
.SPNs
= ["HOST/%s" % ctx
.myname
,
162 "HOST/%s" % ctx
.dnshostname
,
163 "GC/%s/%s" % (ctx
.dnshostname
, ctx
.dnsforest
)]
165 res_rid_manager
= ctx
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
166 attrs
=["rIDManagerReference"],
169 ctx
.rid_manager_dn
= res_rid_manager
[0]["rIDManagerReference"][0]
171 ctx
.domaindns_zone
= 'DC=DomainDnsZones,%s' % ctx
.base_dn
172 ctx
.forestdns_zone
= 'DC=ForestDnsZones,%s' % ctx
.root_dn
174 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.domaindns_zone
)
175 res_domaindns
= ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
177 base
=ctx
.samdb
.get_partitions_dn(),
179 if dns_backend
is None:
180 ctx
.dns_backend
= "NONE"
182 if len(res_domaindns
) == 0:
183 ctx
.dns_backend
= "NONE"
184 print("NO DNS zone information found in source domain, not replicating DNS")
186 ctx
.dns_backend
= dns_backend
188 ctx
.realm
= ctx
.dnsdomain
192 ctx
.replica_flags
= (drsuapi
.DRSUAPI_DRS_INIT_SYNC |
193 drsuapi
.DRSUAPI_DRS_PER_SYNC |
194 drsuapi
.DRSUAPI_DRS_GET_ANC |
195 drsuapi
.DRSUAPI_DRS_GET_NC_SIZE |
196 drsuapi
.DRSUAPI_DRS_NEVER_SYNCED
)
198 # these elements are optional
199 ctx
.never_reveal_sid
= None
200 ctx
.reveal_sid
= None
201 ctx
.connection_dn
= None
206 ctx
.subdomain
= False
208 ctx
.partition_dn
= None
211 ctx
.dns_cname_dn
= None
213 # Do not normally register 127. addresses but allow override for selftest
214 ctx
.force_all_ips
= False
216 def del_noerror(ctx
, dn
, recursive
=False):
219 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["dn"])
223 ctx
.del_noerror(r
.dn
, recursive
=True)
226 print("Deleted %s" % dn
)
230 def cleanup_old_accounts(ctx
, force
=False):
231 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
232 expression
='sAMAccountName=%s' % ldb
.binary_encode(ctx
.samname
),
233 attrs
=["msDS-krbTgtLink", "objectSID"])
238 creds
= Credentials()
241 creds
.set_machine_account(ctx
.lp
)
242 creds
.set_kerberos_state(ctx
.creds
.get_kerberos_state())
243 machine_samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
244 session_info
=system_session(),
245 credentials
=creds
, lp
=ctx
.lp
)
249 token_res
= machine_samdb
.search(scope
=ldb
.SCOPE_BASE
, base
="", attrs
=["tokenGroups"])
250 if token_res
[0]["tokenGroups"][0] \
251 == res
[0]["objectSID"][0]:
252 raise DCJoinException("Not removing account %s which "
253 "looks like a Samba DC account "
254 "matching the password we already have. "
255 "To override, remove secrets.ldb and secrets.tdb"
258 ctx
.del_noerror(res
[0].dn
, recursive
=True)
260 if "msDS-Krbtgtlink" in res
[0]:
261 new_krbtgt_dn
= res
[0]["msDS-Krbtgtlink"][0]
262 ctx
.del_noerror(ctx
.new_krbtgt_dn
)
264 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
265 expression
='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
266 (ldb
.binary_encode("dns-%s" % ctx
.myname
),
267 ldb
.binary_encode("dns/%s" % ctx
.dnshostname
)),
270 ctx
.del_noerror(res
[0].dn
, recursive
=True)
272 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
273 expression
='(sAMAccountName=%s)' % ldb
.binary_encode("dns-%s" % ctx
.myname
),
276 raise DCJoinException("Not removing account %s which looks like "
277 "a Samba DNS service account but does not "
278 "have servicePrincipalName=%s" %
279 (ldb
.binary_encode("dns-%s" % ctx
.myname
),
280 ldb
.binary_encode("dns/%s" % ctx
.dnshostname
)))
282 def cleanup_old_join(ctx
, force
=False):
283 """Remove any DNs from a previous join."""
284 # find the krbtgt link
285 if not ctx
.subdomain
:
286 ctx
.cleanup_old_accounts(force
=force
)
288 if ctx
.connection_dn
is not None:
289 ctx
.del_noerror(ctx
.connection_dn
)
290 if ctx
.krbtgt_dn
is not None:
291 ctx
.del_noerror(ctx
.krbtgt_dn
)
292 ctx
.del_noerror(ctx
.ntds_dn
)
293 ctx
.del_noerror(ctx
.server_dn
, recursive
=True)
295 ctx
.del_noerror(ctx
.topology_dn
)
297 ctx
.del_noerror(ctx
.partition_dn
)
300 binding_options
= "sign"
301 lsaconn
= lsa
.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
304 objectAttr
= lsa
.ObjectAttribute()
305 objectAttr
.sec_qos
= lsa
.QosInfo()
307 pol_handle
= lsaconn
.OpenPolicy2(''.decode('utf-8'),
308 objectAttr
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
311 name
.string
= ctx
.realm
312 info
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, name
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
314 lsaconn
.DeleteTrustedDomain(pol_handle
, info
.info_ex
.sid
)
317 name
.string
= ctx
.forest_domain_name
318 info
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, name
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
320 lsaconn
.DeleteTrustedDomain(pol_handle
, info
.info_ex
.sid
)
323 ctx
.del_noerror(ctx
.dns_a_dn
)
326 ctx
.del_noerror(ctx
.dns_cname_dn
)
328 def promote_possible(ctx
):
329 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
331 # This shouldn't happen
332 raise Exception("Can not promote into a subdomain")
334 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
335 expression
='sAMAccountName=%s' % ldb
.binary_encode(ctx
.samname
),
336 attrs
=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
338 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx
.samname
)
339 if "msDS-krbTgtLink" in res
[0] or "serverReferenceBL" in res
[0] or "rIDSetReferences" in res
[0]:
340 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx
.samname
)
341 if (int(res
[0]["userAccountControl"][0]) & (samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT |
342 samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT
) == 0):
343 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx
.samname
)
345 ctx
.promote_from_dn
= res
[0].dn
347 def find_dc(ctx
, domain
):
348 """find a writeable DC for the given domain"""
350 ctx
.cldap_ret
= ctx
.net
.finddc(domain
=domain
, flags
=nbt
.NBT_SERVER_LDAP | nbt
.NBT_SERVER_DS | nbt
.NBT_SERVER_WRITABLE
)
351 except NTSTATUSError
as error
:
352 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
353 (domain
, error
.args
[1]))
355 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain
)
356 if ctx
.cldap_ret
.client_site
is not None and ctx
.cldap_ret
.client_site
!= "":
357 ctx
.site
= ctx
.cldap_ret
.client_site
358 return ctx
.cldap_ret
.pdc_dns_name
360 def find_dc_site(ctx
, server
):
362 cldap_ret
= ctx
.net
.finddc(address
=server
,
363 flags
=nbt
.NBT_SERVER_LDAP | nbt
.NBT_SERVER_DS
)
364 if cldap_ret
.client_site
is not None and cldap_ret
.client_site
!= "":
365 site
= cldap_ret
.client_site
368 def get_behavior_version(ctx
):
369 res
= ctx
.samdb
.search(base
=ctx
.base_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["msDS-Behavior-Version"])
370 if "msDS-Behavior-Version" in res
[0]:
371 return int(res
[0]["msDS-Behavior-Version"][0])
373 return samba
.dsdb
.DS_DOMAIN_FUNCTION_2000
375 def get_dnsHostName(ctx
):
376 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["dnsHostName"])
377 return str(res
[0]["dnsHostName"][0])
379 def get_domain_name(ctx
):
380 '''get netbios name of the domain from the partitions record'''
381 partitions_dn
= ctx
.samdb
.get_partitions_dn()
382 res
= ctx
.samdb
.search(base
=partitions_dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["nETBIOSName"],
383 expression
='ncName=%s' % ldb
.binary_encode(str(ctx
.samdb
.get_default_basedn())))
384 return str(res
[0]["nETBIOSName"][0])
386 def get_forest_domain_name(ctx
):
387 '''get netbios name of the domain from the partitions record'''
388 partitions_dn
= ctx
.samdb
.get_partitions_dn()
389 res
= ctx
.samdb
.search(base
=partitions_dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["nETBIOSName"],
390 expression
='ncName=%s' % ldb
.binary_encode(str(ctx
.samdb
.get_root_basedn())))
391 return str(res
[0]["nETBIOSName"][0])
393 def get_parent_partition_dn(ctx
):
394 '''get the parent domain partition DN from parent DNS name'''
395 res
= ctx
.samdb
.search(base
=ctx
.config_dn
, attrs
=[],
396 expression
='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
397 (ldb
.binary_encode(ctx
.parent_dnsdomain
),
398 ldb
.OID_COMPARATOR_AND
, samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_DOMAIN
))
399 return str(res
[0].dn
)
401 def get_naming_master(ctx
):
402 '''get the parent domain partition DN from parent DNS name'''
403 res
= ctx
.samdb
.search(base
='CN=Partitions,%s' % ctx
.config_dn
, attrs
=['fSMORoleOwner'],
404 scope
=ldb
.SCOPE_BASE
, controls
=["extended_dn:1:1"])
405 if 'fSMORoleOwner' not in res
[0]:
406 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx
.partition_dn
, ctx
.samdb
.url
))
408 master_guid
= str(misc
.GUID(ldb
.Dn(ctx
.samdb
, res
[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
410 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res
[0]['fSMORoleOwner'][0])
412 master_host
= '%s._msdcs.%s' % (master_guid
, ctx
.dnsforest
)
416 '''get the SID of the connected user. Only works with w2k8 and later,
417 so only used for RODC join'''
418 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["tokenGroups"])
419 binsid
= res
[0]["tokenGroups"][0]
420 return get_string(ctx
.samdb
.schema_format_value("objectSID", binsid
))
422 def dn_exists(ctx
, dn
):
423 '''check if a DN exists'''
425 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[])
426 except ldb
.LdbError
as e5
:
427 (enum
, estr
) = e5
.args
428 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
433 def add_krbtgt_account(ctx
):
434 '''RODCs need a special krbtgt account'''
435 print("Adding %s" % ctx
.krbtgt_dn
)
438 "objectclass": "user",
439 "useraccountcontrol": str(samba
.dsdb
.UF_NORMAL_ACCOUNT |
440 samba
.dsdb
.UF_ACCOUNTDISABLE
),
441 "showinadvancedviewonly": "TRUE",
442 "description": "krbtgt for %s" % ctx
.samname
}
443 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
445 # now we need to search for the samAccountName attribute on the krbtgt DN,
446 # as this will have been magically set to the krbtgt number
447 res
= ctx
.samdb
.search(base
=ctx
.krbtgt_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["samAccountName"])
448 ctx
.krbtgt_name
= res
[0]["samAccountName"][0]
450 print("Got krbtgt_name=%s" % ctx
.krbtgt_name
)
453 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
454 m
["msDS-krbTgtLink"] = ldb
.MessageElement(ctx
.krbtgt_dn
,
455 ldb
.FLAG_MOD_REPLACE
, "msDS-krbTgtLink")
458 ctx
.new_krbtgt_dn
= "CN=%s,CN=Users,%s" % (ctx
.krbtgt_name
, ctx
.base_dn
)
459 print("Renaming %s to %s" % (ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
))
460 ctx
.samdb
.rename(ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
)
462 def drsuapi_connect(ctx
):
463 '''make a DRSUAPI connection to the naming master'''
464 binding_options
= "seal"
465 if ctx
.lp
.log_level() >= 9:
466 binding_options
+= ",print"
467 binding_string
= "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
)
468 ctx
.drsuapi
= drsuapi
.drsuapi(binding_string
, ctx
.lp
, ctx
.creds
)
469 (ctx
.drsuapi_handle
, ctx
.bind_supported_extensions
) = drs_utils
.drs_DsBind(ctx
.drsuapi
)
471 def create_tmp_samdb(ctx
):
472 '''create a temporary samdb object for schema queries'''
473 ctx
.tmp_schema
= Schema(ctx
.domsid
,
474 schemadn
=ctx
.schema_dn
)
475 ctx
.tmp_samdb
= SamDB(session_info
=system_session(), url
=None, auto_connect
=False,
476 credentials
=ctx
.creds
, lp
=ctx
.lp
, global_schema
=False,
478 ctx
.tmp_samdb
.set_schema(ctx
.tmp_schema
)
480 def build_DsReplicaAttribute(ctx
, attrname
, attrvalue
):
481 '''build a DsReplicaAttributeCtr object'''
482 r
= drsuapi
.DsReplicaAttribute()
483 r
.attid
= ctx
.tmp_samdb
.get_attid_from_lDAPDisplayName(attrname
)
486 def DsAddEntry(ctx
, recs
):
487 '''add a record via the DRSUAPI DsAddEntry call'''
488 if ctx
.drsuapi
is None:
489 ctx
.drsuapi_connect()
490 if ctx
.tmp_samdb
is None:
491 ctx
.create_tmp_samdb()
495 id = drsuapi
.DsReplicaObjectIdentifier()
502 if not isinstance(rec
[a
], list):
506 v
= [x
.encode('utf8') if isinstance(x
, text_type
) else x
for x
in v
]
507 rattr
= ctx
.tmp_samdb
.dsdb_DsReplicaAttribute(ctx
.tmp_samdb
, a
, v
)
510 attribute_ctr
= drsuapi
.DsReplicaAttributeCtr()
511 attribute_ctr
.num_attributes
= len(attrs
)
512 attribute_ctr
.attributes
= attrs
514 object = drsuapi
.DsReplicaObject()
515 object.identifier
= id
516 object.attribute_ctr
= attribute_ctr
518 list_object
= drsuapi
.DsReplicaObjectListItem()
519 list_object
.object = object
520 objects
.append(list_object
)
522 req2
= drsuapi
.DsAddEntryRequest2()
523 req2
.first_object
= objects
[0]
524 prev
= req2
.first_object
525 for o
in objects
[1:]:
529 (level
, ctr
) = ctx
.drsuapi
.DsAddEntry(ctx
.drsuapi_handle
, 2, req2
)
531 if ctr
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
532 print("DsAddEntry failed with dir_err %u" % ctr
.dir_err
)
533 raise RuntimeError("DsAddEntry failed")
534 if ctr
.extended_err
[0] != werror
.WERR_SUCCESS
:
535 print("DsAddEntry failed with status %s info %s" % (ctr
.extended_err
))
536 raise RuntimeError("DsAddEntry failed")
539 raise RuntimeError("expected err_ver 1, got %u" % ctr
.err_ver
)
540 if ctr
.err_data
.status
[0] != werror
.WERR_SUCCESS
:
541 if ctr
.err_data
.info
is None:
542 print("DsAddEntry failed with status %s, info omitted" % (ctr
.err_data
.status
[1]))
544 print("DsAddEntry failed with status %s info %s" % (ctr
.err_data
.status
[1],
545 ctr
.err_data
.info
.extended_err
))
546 raise RuntimeError("DsAddEntry failed")
547 if ctr
.err_data
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
548 print("DsAddEntry failed with dir_err %u" % ctr
.err_data
.dir_err
)
549 raise RuntimeError("DsAddEntry failed")
553 def join_ntdsdsa_obj(ctx
):
554 '''return the ntdsdsa object to add'''
556 print("Adding %s" % ctx
.ntds_dn
)
559 "objectclass": "nTDSDSA",
560 "systemFlags": str(samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
561 "dMDLocation": ctx
.schema_dn
}
563 nc_list
= [ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
565 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
566 rec
["msDS-Behavior-Version"] = str(samba
.dsdb
.DS_DOMAIN_FUNCTION_2008_R2
)
568 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
569 rec
["msDS-HasDomainNCs"] = ctx
.base_dn
572 rec
["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx
.schema_dn
573 rec
["msDS-HasFullReplicaNCs"] = ctx
.full_nc_list
574 rec
["options"] = "37"
576 rec
["objectCategory"] = "CN=NTDS-DSA,%s" % ctx
.schema_dn
577 rec
["HasMasterNCs"] = []
579 if nc
in ctx
.full_nc_list
:
580 rec
["HasMasterNCs"].append(nc
)
581 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
582 rec
["msDS-HasMasterNCs"] = ctx
.full_nc_list
584 rec
["invocationId"] = ndr_pack(ctx
.invocation_id
)
588 def join_add_ntdsdsa(ctx
):
589 '''add the ntdsdsa object'''
591 rec
= ctx
.join_ntdsdsa_obj()
592 if ctx
.forced_local_samdb
:
593 ctx
.samdb
.add(rec
, controls
=["relax:0"])
595 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
597 ctx
.DsAddEntry([rec
])
599 # find the GUID of our NTDS DN
600 res
= ctx
.samdb
.search(base
=ctx
.ntds_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["objectGUID"])
601 ctx
.ntds_guid
= misc
.GUID(ctx
.samdb
.schema_format_value("objectGUID", res
[0]["objectGUID"][0]))
603 def join_add_objects(ctx
, specified_sid
=None):
604 '''add the various objects needed for the join'''
606 print("Adding %s" % ctx
.acct_dn
)
609 "objectClass": "computer",
610 "displayname": ctx
.samname
,
611 "samaccountname": ctx
.samname
,
612 "userAccountControl": str(ctx
.userAccountControl | samba
.dsdb
.UF_ACCOUNTDISABLE
),
613 "dnshostname": ctx
.dnshostname
}
614 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2008
:
615 rec
['msDS-SupportedEncryptionTypes'] = str(samba
.dsdb
.ENC_ALL_TYPES
)
616 elif ctx
.promote_existing
:
617 rec
['msDS-SupportedEncryptionTypes'] = []
619 rec
["managedby"] = ctx
.managedby
620 elif ctx
.promote_existing
:
621 rec
["managedby"] = []
623 if ctx
.never_reveal_sid
:
624 rec
["msDS-NeverRevealGroup"] = ctx
.never_reveal_sid
625 elif ctx
.promote_existing
:
626 rec
["msDS-NeverRevealGroup"] = []
629 rec
["msDS-RevealOnDemandGroup"] = ctx
.reveal_sid
630 elif ctx
.promote_existing
:
631 rec
["msDS-RevealOnDemandGroup"] = []
634 rec
["objectSid"] = ndr_pack(specified_sid
)
636 if ctx
.promote_existing
:
637 if ctx
.promote_from_dn
!= ctx
.acct_dn
:
638 ctx
.samdb
.rename(ctx
.promote_from_dn
, ctx
.acct_dn
)
639 ctx
.samdb
.modify(ldb
.Message
.from_dict(ctx
.samdb
, rec
, ldb
.FLAG_MOD_REPLACE
))
642 if specified_sid
is not None:
643 controls
= ["relax:0"]
644 ctx
.samdb
.add(rec
, controls
=controls
)
647 ctx
.add_krbtgt_account()
650 print("Adding %s" % ctx
.server_dn
)
653 "objectclass": "server",
654 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
655 "systemFlags": str(samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
656 samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
657 samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
658 # windows seems to add the dnsHostName later
659 "dnsHostName": ctx
.dnshostname
}
662 rec
["serverReference"] = ctx
.acct_dn
667 # the rest is done after replication
672 ctx
.join_add_ntdsdsa()
674 # Add the Replica-Locations or RO-Replica-Locations attributes
675 # TODO Is this supposed to be for the schema partition too?
676 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.domaindns_zone
)
677 domain
= (ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
679 base
=ctx
.samdb
.get_partitions_dn(),
680 expression
=expr
), ctx
.domaindns_zone
)
682 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.forestdns_zone
)
683 forest
= (ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
685 base
=ctx
.samdb
.get_partitions_dn(),
686 expression
=expr
), ctx
.forestdns_zone
)
688 for part
, zone
in (domain
, forest
):
689 if zone
not in ctx
.nc_list
:
695 attr
= "msDS-NC-Replica-Locations"
697 attr
= "msDS-NC-RO-Replica-Locations"
699 m
[attr
] = ldb
.MessageElement(ctx
.ntds_dn
,
700 ldb
.FLAG_MOD_ADD
, attr
)
703 if ctx
.connection_dn
is not None:
704 print("Adding %s" % ctx
.connection_dn
)
706 "dn": ctx
.connection_dn
,
707 "objectclass": "nTDSConnection",
708 "enabledconnection": "TRUE",
710 "fromServer": ctx
.dc_ntds_dn
}
714 print("Adding SPNs to %s" % ctx
.acct_dn
)
716 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
717 for i
in range(len(ctx
.SPNs
)):
718 ctx
.SPNs
[i
] = ctx
.SPNs
[i
].replace("$NTDSGUID", str(ctx
.ntds_guid
))
719 m
["servicePrincipalName"] = ldb
.MessageElement(ctx
.SPNs
,
720 ldb
.FLAG_MOD_REPLACE
,
721 "servicePrincipalName")
724 # The account password set operation should normally be done over
725 # LDAP. Windows 2000 DCs however allow this only with SSL
726 # connections which are hard to set up and otherwise refuse with
727 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
729 print("Setting account password for %s" % ctx
.samname
)
731 ctx
.samdb
.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
732 % ldb
.binary_encode(ctx
.samname
),
734 force_change_at_next_login
=False,
735 username
=ctx
.samname
)
736 except ldb
.LdbError
as e2
:
738 if num
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
740 ctx
.net
.set_password(account_name
=ctx
.samname
,
741 domain_name
=ctx
.domain_name
,
742 newpassword
=ctx
.acct_pass
)
744 res
= ctx
.samdb
.search(base
=ctx
.acct_dn
, scope
=ldb
.SCOPE_BASE
,
745 attrs
=["msDS-KeyVersionNumber",
747 if "msDS-KeyVersionNumber" in res
[0]:
748 ctx
.key_version_number
= int(res
[0]["msDS-KeyVersionNumber"][0])
750 ctx
.key_version_number
= None
752 ctx
.new_dc_account_sid
= ndr_unpack(security
.dom_sid
,
753 res
[0]["objectSid"][0])
755 print("Enabling account")
757 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
758 m
["userAccountControl"] = ldb
.MessageElement(str(ctx
.userAccountControl
),
759 ldb
.FLAG_MOD_REPLACE
,
760 "userAccountControl")
763 if ctx
.dns_backend
.startswith("BIND9_"):
764 ctx
.dnspass
= samba
.generate_random_password(128, 255)
766 recs
= ctx
.samdb
.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
767 {"DNSDOMAIN": ctx
.dnsdomain
,
768 "DOMAINDN": ctx
.base_dn
,
769 "HOSTNAME": ctx
.myname
,
770 "DNSPASS_B64": b64encode(ctx
.dnspass
.encode('utf-16-le')).decode('utf8'),
771 "DNSNAME": ctx
.dnshostname
}))
772 for changetype
, msg
in recs
:
773 assert changetype
== ldb
.CHANGETYPE_NONE
774 dns_acct_dn
= msg
["dn"]
775 print("Adding DNS account %s with dns/ SPN" % msg
["dn"])
777 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
778 del msg
["clearTextPassword"]
779 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
780 del msg
["isCriticalSystemObject"]
781 # Disable account until password is set
782 msg
["userAccountControl"] = str(samba
.dsdb
.UF_NORMAL_ACCOUNT |
783 samba
.dsdb
.UF_ACCOUNTDISABLE
)
786 except ldb
.LdbError
as e
:
788 if num
!= ldb
.ERR_ENTRY_ALREADY_EXISTS
:
791 # The account password set operation should normally be done over
792 # LDAP. Windows 2000 DCs however allow this only with SSL
793 # connections which are hard to set up and otherwise refuse with
794 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
796 print("Setting account password for dns-%s" % ctx
.myname
)
798 ctx
.samdb
.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
799 % ldb
.binary_encode(ctx
.myname
),
801 force_change_at_next_login
=False,
802 username
=ctx
.samname
)
803 except ldb
.LdbError
as e3
:
805 if num
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
807 ctx
.net
.set_password(account_name
="dns-%s" % ctx
.myname
,
808 domain_name
=ctx
.domain_name
,
809 newpassword
=ctx
.dnspass
)
811 res
= ctx
.samdb
.search(base
=dns_acct_dn
, scope
=ldb
.SCOPE_BASE
,
812 attrs
=["msDS-KeyVersionNumber"])
813 if "msDS-KeyVersionNumber" in res
[0]:
814 ctx
.dns_key_version_number
= int(res
[0]["msDS-KeyVersionNumber"][0])
816 ctx
.dns_key_version_number
= None
818 def join_add_objects2(ctx
):
819 """add the various objects needed for the join, for subdomains post replication"""
821 print("Adding %s" % ctx
.partition_dn
)
822 name_map
= {'SubdomainAdmins': "%s-%s" % (str(ctx
.domsid
), security
.DOMAIN_RID_ADMINS
)}
823 sd_binary
= descriptor
.get_paritions_crossref_subdomain_descriptor(ctx
.forestsid
, name_map
=name_map
)
825 "dn": ctx
.partition_dn
,
826 "objectclass": "crossRef",
827 "objectCategory": "CN=Cross-Ref,%s" % ctx
.schema_dn
,
828 "nCName": ctx
.base_dn
,
829 "nETBIOSName": ctx
.domain_name
,
830 "dnsRoot": ctx
.dnsdomain
,
831 "trustParent": ctx
.parent_partition_dn
,
832 "systemFlags": str(samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_NC |samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_DOMAIN
),
833 "ntSecurityDescriptor": sd_binary
,
836 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
837 rec
["msDS-Behavior-Version"] = str(ctx
.behavior_version
)
839 rec2
= ctx
.join_ntdsdsa_obj()
841 objects
= ctx
.DsAddEntry([rec
, rec2
])
842 if len(objects
) != 2:
843 raise DCJoinException("Expected 2 objects from DsAddEntry")
845 ctx
.ntds_guid
= objects
[1].guid
847 print("Replicating partition DN")
848 ctx
.repl
.replicate(ctx
.partition_dn
,
849 misc
.GUID("00000000-0000-0000-0000-000000000000"),
851 exop
=drsuapi
.DRSUAPI_EXOP_REPL_OBJ
,
852 replica_flags
=drsuapi
.DRSUAPI_DRS_WRIT_REP
)
854 print("Replicating NTDS DN")
855 ctx
.repl
.replicate(ctx
.ntds_dn
,
856 misc
.GUID("00000000-0000-0000-0000-000000000000"),
858 exop
=drsuapi
.DRSUAPI_EXOP_REPL_OBJ
,
859 replica_flags
=drsuapi
.DRSUAPI_DRS_WRIT_REP
)
861 def join_provision(ctx
):
862 """Provision the local SAM."""
864 print("Calling bare provision")
866 smbconf
= ctx
.lp
.configfile
868 presult
= provision(ctx
.logger
, system_session(), smbconf
=smbconf
,
869 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_DRS
, realm
=ctx
.realm
,
870 rootdn
=ctx
.root_dn
, domaindn
=ctx
.base_dn
,
871 schemadn
=ctx
.schema_dn
, configdn
=ctx
.config_dn
,
872 serverdn
=ctx
.server_dn
, domain
=ctx
.domain_name
,
873 hostname
=ctx
.myname
, domainsid
=ctx
.domsid
,
874 machinepass
=ctx
.acct_pass
, serverrole
="active directory domain controller",
875 sitename
=ctx
.site
, lp
=ctx
.lp
, ntdsguid
=ctx
.ntds_guid
,
876 use_ntvfs
=ctx
.use_ntvfs
, dns_backend
=ctx
.dns_backend
,
877 plaintext_secrets
=ctx
.plaintext_secrets
,
878 backend_store
=ctx
.backend_store
,
879 backend_store_size
=ctx
.backend_store_size
881 print("Provision OK for domain DN %s" % presult
.domaindn
)
882 ctx
.local_samdb
= presult
.samdb
884 ctx
.paths
= presult
.paths
885 ctx
.names
= presult
.names
887 # Fix up the forestsid, it may be different if we are joining as a subdomain
888 ctx
.names
.forestsid
= ctx
.forestsid
890 def join_provision_own_domain(ctx
):
891 """Provision the local SAM."""
893 # we now operate exclusively on the local database, which
894 # we need to reopen in order to get the newly created schema
895 # we set the transaction_index_cache_size to 200,000 to ensure it is
896 # not too small, if it's too small the performance of the join will
897 # be negatively impacted.
898 print("Reconnecting to local samdb")
899 ctx
.samdb
= SamDB(url
=ctx
.local_samdb
.url
,
901 "transaction_index_cache_size:200000"],
902 session_info
=system_session(),
903 lp
=ctx
.local_samdb
.lp
,
905 ctx
.samdb
.set_invocation_id(str(ctx
.invocation_id
))
906 ctx
.local_samdb
= ctx
.samdb
908 ctx
.logger
.info("Finding domain GUID from ncName")
909 res
= ctx
.local_samdb
.search(base
=ctx
.partition_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=['ncName'],
910 controls
=["extended_dn:1:1", "reveal_internals:0"])
912 if 'nCName' not in res
[0]:
913 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx
.partition_dn
, ctx
.samdb
.url
))
916 ctx
.names
.domainguid
= str(misc
.GUID(ldb
.Dn(ctx
.samdb
, res
[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
918 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res
[0]['ncName'][0])
920 ctx
.logger
.info("Got domain GUID %s" % ctx
.names
.domainguid
)
922 ctx
.logger
.info("Calling own domain provision")
924 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
926 presult
= provision_fill(ctx
.local_samdb
, secrets_ldb
,
927 ctx
.logger
, ctx
.names
, ctx
.paths
,
928 dom_for_fun_level
=DS_DOMAIN_FUNCTION_2003
,
929 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_SUBDOMAIN
,
930 machinepass
=ctx
.acct_pass
, serverrole
="active directory domain controller",
931 lp
=ctx
.lp
, hostip
=ctx
.names
.hostip
, hostip6
=ctx
.names
.hostip6
,
932 dns_backend
=ctx
.dns_backend
, adminpass
=ctx
.adminpass
)
933 print("Provision OK for domain %s" % ctx
.names
.dnsdomain
)
935 def create_replicator(ctx
, repl_creds
, binding_options
):
936 '''Creates a new DRS object for managing replications'''
937 return drs_utils
.drs_Replicate(
938 "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
939 ctx
.lp
, repl_creds
, ctx
.local_samdb
, ctx
.invocation_id
)
941 def join_replicate(ctx
):
942 """Replicate the SAM."""
944 print("Starting replication")
945 ctx
.local_samdb
.transaction_start()
947 source_dsa_invocation_id
= misc
.GUID(ctx
.samdb
.get_invocation_id())
948 if ctx
.ntds_guid
is None:
949 print("Using DS_BIND_GUID_W2K3")
950 destination_dsa_guid
= misc
.GUID(drsuapi
.DRSUAPI_DS_BIND_GUID_W2K3
)
952 destination_dsa_guid
= ctx
.ntds_guid
955 repl_creds
= Credentials()
956 repl_creds
.guess(ctx
.lp
)
957 repl_creds
.set_kerberos_state(DONT_USE_KERBEROS
)
958 repl_creds
.set_username(ctx
.samname
)
959 repl_creds
.set_password(ctx
.acct_pass
)
961 repl_creds
= ctx
.creds
963 binding_options
= "seal"
964 if ctx
.lp
.log_level() >= 9:
965 binding_options
+= ",print"
967 repl
= ctx
.create_replicator(repl_creds
, binding_options
)
969 repl
.replicate(ctx
.schema_dn
, source_dsa_invocation_id
,
970 destination_dsa_guid
, schema
=True, rodc
=ctx
.RODC
,
971 replica_flags
=ctx
.replica_flags
)
972 repl
.replicate(ctx
.config_dn
, source_dsa_invocation_id
,
973 destination_dsa_guid
, rodc
=ctx
.RODC
,
974 replica_flags
=ctx
.replica_flags
)
975 if not ctx
.subdomain
:
976 # Replicate first the critical object for the basedn
977 if not ctx
.domain_replica_flags
& drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
:
978 print("Replicating critical objects from the base DN of the domain")
979 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
980 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
981 destination_dsa_guid
, rodc
=ctx
.RODC
,
982 replica_flags
=ctx
.domain_replica_flags
)
983 ctx
.domain_replica_flags ^
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
984 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
985 destination_dsa_guid
, rodc
=ctx
.RODC
,
986 replica_flags
=ctx
.domain_replica_flags
)
987 print("Done with always replicated NC (base, config, schema)")
989 # At this point we should already have an entry in the ForestDNS
990 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
991 # indicate that we hold a replica for this NC.
992 for nc
in (ctx
.domaindns_zone
, ctx
.forestdns_zone
):
993 if nc
in ctx
.nc_list
:
994 print("Replicating %s" % (str(nc
)))
995 repl
.replicate(nc
, source_dsa_invocation_id
,
996 destination_dsa_guid
, rodc
=ctx
.RODC
,
997 replica_flags
=ctx
.replica_flags
)
1000 repl
.replicate(ctx
.acct_dn
, source_dsa_invocation_id
,
1001 destination_dsa_guid
,
1002 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
1003 repl
.replicate(ctx
.new_krbtgt_dn
, source_dsa_invocation_id
,
1004 destination_dsa_guid
,
1005 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
1006 elif ctx
.rid_manager_dn
is not None:
1007 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
1009 repl
.replicate(ctx
.rid_manager_dn
, source_dsa_invocation_id
,
1010 destination_dsa_guid
,
1011 exop
=drsuapi
.DRSUAPI_EXOP_FSMO_RID_ALLOC
)
1012 except samba
.DsExtendedError
as e1
:
1013 (enum
, estr
) = e1
.args
1014 if enum
== drsuapi
.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER
:
1015 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx
.server
)
1016 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
1021 ctx
.source_dsa_invocation_id
= source_dsa_invocation_id
1022 ctx
.destination_dsa_guid
= destination_dsa_guid
1024 print("Committing SAM database")
1026 ctx
.local_samdb
.transaction_cancel()
1029 ctx
.local_samdb
.transaction_commit()
1031 # A large replication may have caused our LDB connection to the
1032 # remote DC to timeout, so check the connection is still alive
1033 ctx
.refresh_ldb_connection()
1035 def refresh_ldb_connection(ctx
):
1037 # query the rootDSE to check the connection
1038 ctx
.samdb
.search(scope
=ldb
.SCOPE_BASE
, attrs
=[])
1039 except ldb
.LdbError
as e
:
1040 (enum
, estr
) = e
.args
1042 # if the connection was disconnected, then reconnect
1043 if (enum
== ldb
.ERR_OPERATIONS_ERROR
and
1044 ('NT_STATUS_CONNECTION_DISCONNECTED' in estr
or
1045 'NT_STATUS_CONNECTION_RESET' in estr
)):
1046 ctx
.logger
.warning("LDB connection disconnected. Reconnecting")
1047 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
1048 session_info
=system_session(),
1049 credentials
=ctx
.creds
, lp
=ctx
.lp
)
1051 raise DCJoinException(estr
)
1053 def send_DsReplicaUpdateRefs(ctx
, dn
):
1054 r
= drsuapi
.DsReplicaUpdateRefsRequest1()
1055 r
.naming_context
= drsuapi
.DsReplicaObjectIdentifier()
1056 r
.naming_context
.dn
= str(dn
)
1057 r
.naming_context
.guid
= misc
.GUID("00000000-0000-0000-0000-000000000000")
1058 r
.naming_context
.sid
= security
.dom_sid("S-0-0")
1059 r
.dest_dsa_guid
= ctx
.ntds_guid
1060 r
.dest_dsa_dns_name
= "%s._msdcs.%s" % (str(ctx
.ntds_guid
), ctx
.dnsforest
)
1061 r
.options
= drsuapi
.DRSUAPI_DRS_ADD_REF | drsuapi
.DRSUAPI_DRS_DEL_REF
1063 r
.options |
= drsuapi
.DRSUAPI_DRS_WRIT_REP
1065 if ctx
.drsuapi
is None:
1066 ctx
.drsuapi_connect()
1068 ctx
.drsuapi
.DsReplicaUpdateRefs(ctx
.drsuapi_handle
, 1, r
)
1070 def join_add_dns_records(ctx
):
1071 """Remotely Add a DNS record to the target DC. We assume that if we
1072 replicate DNS that the server holds the DNS roles and can accept
1075 This avoids issues getting replication going after the DC
1076 first starts as the rest of the domain does not have to
1077 wait for samba_dnsupdate to run successfully.
1079 Specifically, we add the records implied by the DsReplicaUpdateRefs
1082 We do not just run samba_dnsupdate as we want to strictly
1083 operate against the DC we just joined:
1084 - We do not want to query another DNS server
1085 - We do not want to obtain a Kerberos ticket
1086 (as the KDC we select may not be the DC we just joined,
1087 and so may not be in sync with the password we just set)
1088 - We do not wish to set the _ldap records until we have started
1089 - We do not wish to use NTLM (the --use-samba-tool mode forces
1094 client_version
= dnsserver
.DNS_CLIENT_VERSION_LONGHORN
1095 select_flags
= dnsserver
.DNS_RPC_VIEW_AUTHORITY_DATA |\
1096 dnsserver
.DNS_RPC_VIEW_NO_CHILDREN
1098 zone
= ctx
.dnsdomain
1099 msdcs_zone
= "_msdcs.%s" % ctx
.dnsforest
1101 msdcs_cname
= str(ctx
.ntds_guid
)
1102 cname_target
= "%s.%s" % (name
, zone
)
1103 IPs
= samba
.interface_ips(ctx
.lp
, ctx
.force_all_ips
)
1105 ctx
.logger
.info("Adding %d remote DNS records for %s.%s" %
1106 (len(IPs
), name
, zone
))
1108 binding_options
= "sign"
1109 dns_conn
= dnsserver
.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
1114 sd_helper
= sd_utils
.SDUtils(ctx
.samdb
)
1116 change_owner_sd
= security
.descriptor()
1117 change_owner_sd
.owner_sid
= ctx
.new_dc_account_sid
1118 change_owner_sd
.group_sid
= security
.dom_sid("%s-%d" %
1120 security
.DOMAIN_RID_DCS
))
1122 # TODO: Remove any old records from the primary DNS name
1125 = dns_conn
.DnssrvEnumRecords2(client_version
,
1135 except WERRORError
as e
:
1136 if e
.args
[0] == werror
.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST
:
1142 for record
in rec
.records
:
1143 if record
.wType
== dnsp
.DNS_TYPE_A
or \
1144 record
.wType
== dnsp
.DNS_TYPE_AAAA
:
1146 del_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1147 del_rec_buf
.rec
= record
1149 dns_conn
.DnssrvUpdateRecord2(client_version
,
1156 except WERRORError
as e
:
1157 if e
.args
[0] == werror
.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST
:
1163 if IP
.find(':') != -1:
1164 ctx
.logger
.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1166 rec
= AAAARecord(IP
)
1168 ctx
.logger
.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1173 add_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1174 add_rec_buf
.rec
= rec
1175 dns_conn
.DnssrvUpdateRecord2(client_version
,
1184 domaindns_zone_dn
= ldb
.Dn(ctx
.samdb
, ctx
.domaindns_zone
)
1185 (ctx
.dns_a_dn
, ldap_record
) \
1186 = ctx
.samdb
.dns_lookup("%s.%s" % (name
, zone
),
1187 dns_partition
=domaindns_zone_dn
)
1189 # Make the DC own the DNS record, not the administrator
1190 sd_helper
.modify_sd_on_dn(ctx
.dns_a_dn
, change_owner_sd
,
1191 controls
=["sd_flags:1:%d"
1192 % (security
.SECINFO_OWNER
1193 | security
.SECINFO_GROUP
)])
1196 ctx
.logger
.info("Adding DNS CNAME record %s.%s for %s"
1197 % (msdcs_cname
, msdcs_zone
, cname_target
))
1199 add_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1200 rec
= CNameRecord(cname_target
)
1201 add_rec_buf
.rec
= rec
1202 dns_conn
.DnssrvUpdateRecord2(client_version
,
1210 forestdns_zone_dn
= ldb
.Dn(ctx
.samdb
, ctx
.forestdns_zone
)
1211 (ctx
.dns_cname_dn
, ldap_record
) \
1212 = ctx
.samdb
.dns_lookup("%s.%s" % (msdcs_cname
, msdcs_zone
),
1213 dns_partition
=forestdns_zone_dn
)
1215 # Make the DC own the DNS record, not the administrator
1216 sd_helper
.modify_sd_on_dn(ctx
.dns_cname_dn
, change_owner_sd
,
1217 controls
=["sd_flags:1:%d"
1218 % (security
.SECINFO_OWNER
1219 | security
.SECINFO_GROUP
)])
1221 ctx
.logger
.info("All other DNS records (like _ldap SRV records) " +
1222 "will be created samba_dnsupdate on first startup")
1224 def join_replicate_new_dns_records(ctx
):
1225 for nc
in (ctx
.domaindns_zone
, ctx
.forestdns_zone
):
1226 if nc
in ctx
.nc_list
:
1227 ctx
.logger
.info("Replicating new DNS records in %s" % (str(nc
)))
1228 ctx
.repl
.replicate(nc
, ctx
.source_dsa_invocation_id
,
1229 ctx
.ntds_guid
, rodc
=ctx
.RODC
,
1230 replica_flags
=ctx
.replica_flags
,
1233 def join_finalise(ctx
):
1234 """Finalise the join, mark us synchronised and setup secrets db."""
1236 # FIXME we shouldn't do this in all cases
1238 # If for some reasons we joined in another site than the one of
1239 # DC we just replicated from then we don't need to send the updatereplicateref
1240 # as replication between sites is time based and on the initiative of the
1242 ctx
.logger
.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1243 for nc
in ctx
.nc_list
:
1244 ctx
.send_DsReplicaUpdateRefs(nc
)
1247 print("Setting RODC invocationId")
1248 ctx
.local_samdb
.set_invocation_id(str(ctx
.invocation_id
))
1249 ctx
.local_samdb
.set_opaque_integer("domainFunctionality",
1250 ctx
.behavior_version
)
1252 m
.dn
= ldb
.Dn(ctx
.local_samdb
, "%s" % ctx
.ntds_dn
)
1253 m
["invocationId"] = ldb
.MessageElement(ndr_pack(ctx
.invocation_id
),
1254 ldb
.FLAG_MOD_REPLACE
,
1256 ctx
.local_samdb
.modify(m
)
1258 # Note: as RODC the invocationId is only stored
1259 # on the RODC itself, the other DCs never see it.
1261 # Thats is why we fix up the replPropertyMetaData stamp
1262 # for the 'invocationId' attribute, we need to change
1263 # the 'version' to '0', this is what windows 2008r2 does as RODC
1265 # This means if the object on a RWDC ever gets a invocationId
1266 # attribute, it will have version '1' (or higher), which will
1267 # will overwrite the RODC local value.
1268 ctx
.local_samdb
.set_attribute_replmetadata_version(m
.dn
,
1272 ctx
.logger
.info("Setting isSynchronized and dsServiceName")
1274 m
.dn
= ldb
.Dn(ctx
.local_samdb
, '@ROOTDSE')
1275 m
["isSynchronized"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isSynchronized")
1277 guid
= ctx
.ntds_guid
1278 m
["dsServiceName"] = ldb
.MessageElement("<GUID=%s>" % str(guid
),
1279 ldb
.FLAG_MOD_REPLACE
, "dsServiceName")
1280 ctx
.local_samdb
.modify(m
)
1285 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
1287 ctx
.logger
.info("Setting up secrets database")
1288 secretsdb_self_join(secrets_ldb
, domain
=ctx
.domain_name
,
1290 dnsdomain
=ctx
.dnsdomain
,
1291 netbiosname
=ctx
.myname
,
1292 domainsid
=ctx
.domsid
,
1293 machinepass
=ctx
.acct_pass
,
1294 secure_channel_type
=ctx
.secure_channel_type
,
1295 key_version_number
=ctx
.key_version_number
)
1297 if ctx
.dns_backend
.startswith("BIND9_"):
1298 setup_bind9_dns(ctx
.local_samdb
, secrets_ldb
,
1299 ctx
.names
, ctx
.paths
, ctx
.lp
, ctx
.logger
,
1300 dns_backend
=ctx
.dns_backend
,
1301 dnspass
=ctx
.dnspass
, os_level
=ctx
.behavior_version
,
1302 targetdir
=ctx
.targetdir
,
1303 key_version_number
=ctx
.dns_key_version_number
)
1305 def join_setup_trusts(ctx
):
1306 """provision the local SAM."""
1308 print("Setup domain trusts with server %s" % ctx
.server
)
1309 binding_options
= "" # why doesn't signing work here? w2k8r2 claims no session key
1310 lsaconn
= lsa
.lsarpc("ncacn_np:%s[%s]" % (ctx
.server
, binding_options
),
1313 objectAttr
= lsa
.ObjectAttribute()
1314 objectAttr
.sec_qos
= lsa
.QosInfo()
1316 pol_handle
= lsaconn
.OpenPolicy2(''.decode('utf-8'),
1317 objectAttr
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
1319 info
= lsa
.TrustDomainInfoInfoEx()
1320 info
.domain_name
.string
= ctx
.dnsdomain
1321 info
.netbios_name
.string
= ctx
.domain_name
1322 info
.sid
= ctx
.domsid
1323 info
.trust_direction
= lsa
.LSA_TRUST_DIRECTION_INBOUND | lsa
.LSA_TRUST_DIRECTION_OUTBOUND
1324 info
.trust_type
= lsa
.LSA_TRUST_TYPE_UPLEVEL
1325 info
.trust_attributes
= lsa
.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1328 oldname
= lsa
.String()
1329 oldname
.string
= ctx
.dnsdomain
1330 oldinfo
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, oldname
,
1331 lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
1332 print("Removing old trust record for %s (SID %s)" % (ctx
.dnsdomain
, oldinfo
.info_ex
.sid
))
1333 lsaconn
.DeleteTrustedDomain(pol_handle
, oldinfo
.info_ex
.sid
)
1334 except RuntimeError:
1337 password_blob
= string_to_byte_array(ctx
.trustdom_pass
.encode('utf-16-le'))
1339 clear_value
= drsblobs
.AuthInfoClear()
1340 clear_value
.size
= len(password_blob
)
1341 clear_value
.password
= password_blob
1343 clear_authentication_information
= drsblobs
.AuthenticationInformation()
1344 clear_authentication_information
.LastUpdateTime
= samba
.unix2nttime(int(time
.time()))
1345 clear_authentication_information
.AuthType
= lsa
.TRUST_AUTH_TYPE_CLEAR
1346 clear_authentication_information
.AuthInfo
= clear_value
1348 authentication_information_array
= drsblobs
.AuthenticationInformationArray()
1349 authentication_information_array
.count
= 1
1350 authentication_information_array
.array
= [clear_authentication_information
]
1352 outgoing
= drsblobs
.trustAuthInOutBlob()
1354 outgoing
.current
= authentication_information_array
1356 trustpass
= drsblobs
.trustDomainPasswords()
1357 confounder
= [3] * 512
1359 for i
in range(512):
1360 confounder
[i
] = random
.randint(0, 255)
1362 trustpass
.confounder
= confounder
1364 trustpass
.outgoing
= outgoing
1365 trustpass
.incoming
= outgoing
1367 trustpass_blob
= ndr_pack(trustpass
)
1369 encrypted_trustpass
= arcfour_encrypt(lsaconn
.session_key
, trustpass_blob
)
1371 auth_blob
= lsa
.DATA_BUF2()
1372 auth_blob
.size
= len(encrypted_trustpass
)
1373 auth_blob
.data
= string_to_byte_array(encrypted_trustpass
)
1375 auth_info
= lsa
.TrustDomainInfoAuthInfoInternal()
1376 auth_info
.auth_blob
= auth_blob
1378 trustdom_handle
= lsaconn
.CreateTrustedDomainEx2(pol_handle
,
1381 security
.SEC_STD_DELETE
)
1384 "dn": "cn=%s,cn=system,%s" % (ctx
.dnsforest
, ctx
.base_dn
),
1385 "objectclass": "trustedDomain",
1386 "trustType": str(info
.trust_type
),
1387 "trustAttributes": str(info
.trust_attributes
),
1388 "trustDirection": str(info
.trust_direction
),
1389 "flatname": ctx
.forest_domain_name
,
1390 "trustPartner": ctx
.dnsforest
,
1391 "trustAuthIncoming": ndr_pack(outgoing
),
1392 "trustAuthOutgoing": ndr_pack(outgoing
),
1393 "securityIdentifier": ndr_pack(ctx
.forestsid
)
1395 ctx
.local_samdb
.add(rec
)
1398 "dn": "cn=%s$,cn=users,%s" % (ctx
.forest_domain_name
, ctx
.base_dn
),
1399 "objectclass": "user",
1400 "userAccountControl": str(samba
.dsdb
.UF_INTERDOMAIN_TRUST_ACCOUNT
),
1401 "clearTextPassword": ctx
.trustdom_pass
.encode('utf-16-le'),
1402 "samAccountName": "%s$" % ctx
.forest_domain_name
1404 ctx
.local_samdb
.add(rec
)
1406 def build_nc_lists(ctx
):
1407 # nc_list is the list of naming context (NC) for which we will
1408 # replicate in and send a updateRef command to the partner DC
1410 # full_nc_list is the list of naming context (NC) we hold
1411 # read/write copies of. These are not subsets of each other.
1412 ctx
.nc_list
= [ctx
.config_dn
, ctx
.schema_dn
]
1413 ctx
.full_nc_list
= [ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
1415 if ctx
.subdomain
and ctx
.dns_backend
!= "NONE":
1416 ctx
.full_nc_list
+= [ctx
.domaindns_zone
]
1418 elif not ctx
.subdomain
:
1419 ctx
.nc_list
+= [ctx
.base_dn
]
1421 if ctx
.dns_backend
!= "NONE":
1422 ctx
.nc_list
+= [ctx
.domaindns_zone
]
1423 ctx
.nc_list
+= [ctx
.forestdns_zone
]
1424 ctx
.full_nc_list
+= [ctx
.domaindns_zone
]
1425 ctx
.full_nc_list
+= [ctx
.forestdns_zone
]
1428 ctx
.build_nc_lists()
1430 if ctx
.promote_existing
:
1431 ctx
.promote_possible()
1433 ctx
.cleanup_old_join()
1436 ctx
.join_add_objects()
1437 ctx
.join_provision()
1438 ctx
.join_replicate()
1440 ctx
.join_add_objects2()
1441 ctx
.join_provision_own_domain()
1442 ctx
.join_setup_trusts()
1444 if ctx
.dns_backend
!= "NONE":
1445 ctx
.join_add_dns_records()
1446 ctx
.join_replicate_new_dns_records()
1451 print("Join failed - cleaning up")
1455 # cleanup the failed join (checking we still have a live LDB
1456 # connection to the remote DC first)
1457 ctx
.refresh_ldb_connection()
1458 ctx
.cleanup_old_join()
1462 def join_RODC(logger
=None, server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
1463 targetdir
=None, domain
=None, domain_critical_only
=False,
1464 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
1465 promote_existing
=False, plaintext_secrets
=False,
1467 backend_store_size
=None):
1468 """Join as a RODC."""
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
,
1474 backend_store_size
=backend_store_size
)
1476 lp
.set("workgroup", ctx
.domain_name
)
1477 logger
.info("workgroup is %s" % ctx
.domain_name
)
1479 lp
.set("realm", ctx
.realm
)
1480 logger
.info("realm is %s" % ctx
.realm
)
1482 ctx
.krbtgt_dn
= "CN=krbtgt_%s,CN=Users,%s" % (ctx
.myname
, ctx
.base_dn
)
1484 # setup some defaults for accounts that should be replicated to this RODC
1485 ctx
.never_reveal_sid
= [
1486 "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_DENY
),
1487 "<SID=%s>" % security
.SID_BUILTIN_ADMINISTRATORS
,
1488 "<SID=%s>" % security
.SID_BUILTIN_SERVER_OPERATORS
,
1489 "<SID=%s>" % security
.SID_BUILTIN_BACKUP_OPERATORS
,
1490 "<SID=%s>" % security
.SID_BUILTIN_ACCOUNT_OPERATORS
]
1491 ctx
.reveal_sid
= "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_ALLOW
)
1493 mysid
= ctx
.get_mysid()
1494 admin_dn
= "<SID=%s>" % mysid
1495 ctx
.managedby
= admin_dn
1497 ctx
.userAccountControl
= (samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT |
1498 samba
.dsdb
.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1499 samba
.dsdb
.UF_PARTIAL_SECRETS_ACCOUNT
)
1501 ctx
.SPNs
.extend(["RestrictedKrbHost/%s" % ctx
.myname
,
1502 "RestrictedKrbHost/%s" % ctx
.dnshostname
])
1504 ctx
.connection_dn
= "CN=RODC Connection (FRS),%s" % ctx
.ntds_dn
1505 ctx
.secure_channel_type
= misc
.SEC_CHAN_RODC
1507 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1508 drsuapi
.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP
)
1509 ctx
.domain_replica_flags
= ctx
.replica_flags
1510 if domain_critical_only
:
1511 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
1515 logger
.info("Joined domain %s (SID %s) as an RODC" % (ctx
.domain_name
, ctx
.domsid
))
1518 def join_DC(logger
=None, server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
1519 targetdir
=None, domain
=None, domain_critical_only
=False,
1520 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
1521 promote_existing
=False, plaintext_secrets
=False,
1523 backend_store_size
=None):
1525 ctx
= DCJoinContext(logger
, server
, creds
, lp
, site
, netbios_name
,
1526 targetdir
, domain
, machinepass
, use_ntvfs
, dns_backend
,
1527 promote_existing
, plaintext_secrets
,
1528 backend_store
=backend_store
,
1529 backend_store_size
=backend_store_size
)
1531 lp
.set("workgroup", ctx
.domain_name
)
1532 logger
.info("workgroup is %s" % ctx
.domain_name
)
1534 lp
.set("realm", ctx
.realm
)
1535 logger
.info("realm is %s" % ctx
.realm
)
1537 ctx
.userAccountControl
= samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT | samba
.dsdb
.UF_TRUSTED_FOR_DELEGATION
1539 ctx
.SPNs
.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx
.dnsdomain
)
1540 ctx
.secure_channel_type
= misc
.SEC_CHAN_BDC
1542 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1543 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1544 ctx
.domain_replica_flags
= ctx
.replica_flags
1545 if domain_critical_only
:
1546 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
1549 logger
.info("Joined domain %s (SID %s) as a DC" % (ctx
.domain_name
, ctx
.domsid
))
1552 def join_clone(logger
=None, server
=None, creds
=None, lp
=None,
1553 targetdir
=None, domain
=None, include_secrets
=False,
1554 dns_backend
="NONE", backend_store
=None,
1555 backend_store_size
=None):
1556 """Creates a local clone of a remote DC."""
1557 ctx
= DCCloneContext(logger
, server
, creds
, lp
, targetdir
=targetdir
,
1558 domain
=domain
, dns_backend
=dns_backend
,
1559 include_secrets
=include_secrets
,
1560 backend_store
=backend_store
,
1561 backend_store_size
=backend_store_size
)
1563 lp
.set("workgroup", ctx
.domain_name
)
1564 logger
.info("workgroup is %s" % ctx
.domain_name
)
1566 lp
.set("realm", ctx
.realm
)
1567 logger
.info("realm is %s" % ctx
.realm
)
1570 logger
.info("Cloned domain %s (SID %s)" % (ctx
.domain_name
, ctx
.domsid
))
1574 def join_subdomain(logger
=None, server
=None, creds
=None, lp
=None, site
=None,
1575 netbios_name
=None, targetdir
=None, parent_domain
=None, dnsdomain
=None,
1576 netbios_domain
=None, machinepass
=None, adminpass
=None, use_ntvfs
=False,
1577 dns_backend
=None, plaintext_secrets
=False,
1578 backend_store
=None):
1580 ctx
= DCJoinContext(logger
, server
, creds
, lp
, site
, netbios_name
,
1581 targetdir
, parent_domain
, machinepass
, use_ntvfs
,
1582 dns_backend
, plaintext_secrets
,
1583 backend_store
=backend_store
)
1584 ctx
.subdomain
= True
1585 if adminpass
is None:
1586 ctx
.adminpass
= samba
.generate_random_password(12, 32)
1588 ctx
.adminpass
= adminpass
1589 ctx
.parent_domain_name
= ctx
.domain_name
1590 ctx
.domain_name
= netbios_domain
1591 ctx
.realm
= dnsdomain
1592 ctx
.parent_dnsdomain
= ctx
.dnsdomain
1593 ctx
.parent_partition_dn
= ctx
.get_parent_partition_dn()
1594 ctx
.dnsdomain
= dnsdomain
1595 ctx
.partition_dn
= "CN=%s,CN=Partitions,%s" % (ctx
.domain_name
, ctx
.config_dn
)
1596 ctx
.naming_master
= ctx
.get_naming_master()
1597 if ctx
.naming_master
!= ctx
.server
:
1598 logger
.info("Reconnecting to naming master %s" % ctx
.naming_master
)
1599 ctx
.server
= ctx
.naming_master
1600 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
1601 session_info
=system_session(),
1602 credentials
=ctx
.creds
, lp
=ctx
.lp
)
1603 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['dnsHostName'],
1605 ctx
.server
= res
[0]["dnsHostName"]
1606 logger
.info("DNS name of new naming master is %s" % ctx
.server
)
1608 ctx
.base_dn
= samba
.dn_from_dns_name(dnsdomain
)
1609 ctx
.forestsid
= ctx
.domsid
1610 ctx
.domsid
= security
.random_sid()
1612 ctx
.dnshostname
= "%s.%s" % (ctx
.myname
.lower(), ctx
.dnsdomain
)
1613 # Windows uses 240 bytes as UTF16 so we do
1614 ctx
.trustdom_pass
= samba
.generate_random_machine_password(120, 120)
1616 ctx
.userAccountControl
= samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT | samba
.dsdb
.UF_TRUSTED_FOR_DELEGATION
1618 ctx
.SPNs
.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx
.dnsdomain
)
1619 ctx
.secure_channel_type
= misc
.SEC_CHAN_BDC
1621 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1622 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1623 ctx
.domain_replica_flags
= ctx
.replica_flags
1626 ctx
.logger
.info("Created domain %s (SID %s) as a DC" % (ctx
.domain_name
, ctx
.domsid
))
1629 class DCCloneContext(DCJoinContext
):
1630 """Clones a remote DC."""
1632 def __init__(ctx
, logger
=None, server
=None, creds
=None, lp
=None,
1633 targetdir
=None, domain
=None, dns_backend
=None,
1634 include_secrets
=False, backend_store
=None,
1635 backend_store_size
=None):
1636 super(DCCloneContext
, ctx
).__init
__(logger
, server
, creds
, lp
,
1637 targetdir
=targetdir
, domain
=domain
,
1638 dns_backend
=dns_backend
,
1639 backend_store
=backend_store
,
1640 backend_store_size
=backend_store_size
)
1642 # As we don't want to create or delete these DNs, we set them to None
1643 ctx
.server_dn
= None
1646 ctx
.myname
= ctx
.server
.split('.')[0]
1647 ctx
.ntds_guid
= None
1648 ctx
.rid_manager_dn
= None
1651 ctx
.remote_dc_ntds_guid
= ctx
.samdb
.get_ntds_GUID()
1653 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1654 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1655 if not include_secrets
:
1656 ctx
.replica_flags |
= drsuapi
.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1657 ctx
.domain_replica_flags
= ctx
.replica_flags
1659 def join_finalise(ctx
):
1660 ctx
.logger
.info("Setting isSynchronized and dsServiceName")
1662 m
.dn
= ldb
.Dn(ctx
.local_samdb
, '@ROOTDSE')
1663 m
["isSynchronized"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
,
1666 # We want to appear to be the server we just cloned
1667 guid
= ctx
.remote_dc_ntds_guid
1668 m
["dsServiceName"] = ldb
.MessageElement("<GUID=%s>" % str(guid
),
1669 ldb
.FLAG_MOD_REPLACE
,
1671 ctx
.local_samdb
.modify(m
)
1674 ctx
.build_nc_lists()
1676 # When cloning a DC, we just want to provision a DC locally, then
1677 # grab the remote DC's entire DB via DRS replication
1678 ctx
.join_provision()
1679 ctx
.join_replicate()
1683 # Used to create a renamed backup of a DC. Renaming the domain means that the
1684 # cloned/backup DC can be started without interfering with the production DC.
1685 class DCCloneAndRenameContext(DCCloneContext
):
1686 """Clones a remote DC, renaming the domain along the way."""
1688 def __init__(ctx
, new_base_dn
, new_domain_name
, new_realm
, logger
=None,
1689 server
=None, creds
=None, lp
=None, targetdir
=None, domain
=None,
1690 dns_backend
=None, include_secrets
=True, backend_store
=None):
1691 super(DCCloneAndRenameContext
, ctx
).__init
__(logger
, server
, creds
, lp
,
1692 targetdir
=targetdir
,
1694 dns_backend
=dns_backend
,
1695 include_secrets
=include_secrets
,
1696 backend_store
=backend_store
)
1697 # store the new DN (etc) that we want the cloned DB to use
1698 ctx
.new_base_dn
= new_base_dn
1699 ctx
.new_domain_name
= new_domain_name
1700 ctx
.new_realm
= new_realm
1702 def create_replicator(ctx
, repl_creds
, binding_options
):
1703 """Creates a new DRS object for managing replications"""
1705 # We want to rename all the domain objects, and the simplest way to do
1706 # this is during replication. This is because the base DN of the top-
1707 # level replicated object will flow through to all the objects below it
1708 binding_str
= "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
)
1709 return drs_utils
.drs_ReplicateRenamer(binding_str
, ctx
.lp
, repl_creds
,
1712 ctx
.base_dn
, ctx
.new_base_dn
)
1714 def create_non_global_lp(ctx
, global_lp
):
1715 '''Creates a non-global LoadParm based on the global LP's settings'''
1717 # the samba code shares a global LoadParm by default. Here we create a
1718 # new LoadParm that retains the global settings, but any changes we
1719 # make to it won't automatically affect the rest of the samba code.
1720 # The easiest way to do this is to dump the global settings to a
1721 # temporary smb.conf file, and then load the temp file into a new
1722 # non-global LoadParm
1723 fd
, tmp_file
= tempfile
.mkstemp()
1724 global_lp
.dump(False, tmp_file
)
1725 local_lp
= samba
.param
.LoadParm(filename_for_non_global_lp
=tmp_file
)
1729 def rename_dn(ctx
, dn_str
):
1730 '''Uses string substitution to replace the base DN'''
1731 old_base_dn
= ctx
.base_dn
1732 return re
.sub('%s$' % old_base_dn
, ctx
.new_base_dn
, dn_str
)
1734 # we want to override the normal DCCloneContext's join_provision() so that
1735 # use the new domain DNs during the provision. We do this because:
1736 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1737 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1738 # we couldn't apply the renamed DRS objects during replication)
1739 def join_provision(ctx
):
1740 """Provision the local (renamed) SAM."""
1742 print("Provisioning the new (renamed) domain...")
1744 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1745 # to create a new smb.conf. By default, it uses the global LoadParm to
1746 # do this, and so it would overwrite the realm/domain values globally.
1747 # We still need the global LoadParm to retain the old domain's details,
1748 # so we can connect to (and clone) the existing DC.
1749 # So, copy the global settings into a non-global LoadParm, which we can
1750 # then pass into provision(). This generates a new smb.conf correctly,
1751 # without overwriting the global realm/domain values just yet.
1752 non_global_lp
= ctx
.create_non_global_lp(ctx
.lp
)
1754 # do the provision with the new/renamed domain DN values
1755 presult
= provision(ctx
.logger
, system_session(),
1756 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_DRS
,
1757 realm
=ctx
.new_realm
, lp
=non_global_lp
,
1758 rootdn
=ctx
.rename_dn(ctx
.root_dn
), domaindn
=ctx
.new_base_dn
,
1759 schemadn
=ctx
.rename_dn(ctx
.schema_dn
),
1760 configdn
=ctx
.rename_dn(ctx
.config_dn
),
1761 domain
=ctx
.new_domain_name
, domainsid
=ctx
.domsid
,
1762 serverrole
="active directory domain controller",
1763 dns_backend
=ctx
.dns_backend
,
1764 backend_store
=ctx
.backend_store
)
1766 print("Provision OK for renamed domain DN %s" % presult
.domaindn
)
1767 ctx
.local_samdb
= presult
.samdb
1768 ctx
.paths
= presult
.paths