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 """Joining a domain."""
21 from samba
.auth
import system_session
22 from samba
.samdb
import SamDB
23 from samba
import gensec
, Ldb
, drs_utils
, arcfour_encrypt
27 from samba
.ndr
import ndr_pack
, ndr_unpack
28 from samba
.dcerpc
import security
, drsuapi
, misc
, nbt
, lsa
, drsblobs
, dnsserver
, dnsp
29 from samba
.credentials
import Credentials
, DONT_USE_KERBEROS
30 from samba
.provision
import (secretsdb_self_join
, provision
, provision_fill
,
31 FILL_DRS
, FILL_SUBDOMAIN
, DEFAULTSITE
)
32 from samba
.provision
.common
import setup_path
33 from samba
.schema
import Schema
34 from samba
import descriptor
35 from samba
.net
import Net
36 from samba
.provision
.sambadns
import setup_bind9_dns
37 from samba
import read_and_sub_file
38 from samba
import werror
39 from base64
import b64encode
40 from samba
import WERRORError
, NTSTATUSError
41 from samba
import sd_utils
42 from samba
.dnsserver
import ARecord
, AAAARecord
, CNAMERecord
48 from collections
import OrderedDict
49 from samba
.common
import get_string
50 from samba
.netcmd
import CommandError
51 from samba
import dsdb
, functional_level
54 class DCJoinException(Exception):
56 def __init__(self
, msg
):
57 super().__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,
68 backend_store_size
=None,
69 forced_local_samdb
=None):
75 ctx
.targetdir
= targetdir
76 ctx
.use_ntvfs
= use_ntvfs
77 ctx
.plaintext_secrets
= plaintext_secrets
78 ctx
.backend_store
= backend_store
79 ctx
.backend_store_size
= backend_store_size
81 ctx
.promote_existing
= promote_existing
82 ctx
.promote_from_dn
= None
87 ctx
.creds
.set_gensec_features(creds
.get_gensec_features() | gensec
.FEATURE_SEAL
)
88 ctx
.net
= Net(creds
=ctx
.creds
, lp
=ctx
.lp
)
91 ctx
.forced_local_samdb
= forced_local_samdb
93 if forced_local_samdb
:
94 ctx
.samdb
= forced_local_samdb
95 ctx
.server
= ctx
.samdb
.url
98 # work out the DC's site (if not already specified)
100 ctx
.site
= ctx
.find_dc_site(ctx
.server
)
102 # work out the Primary DC for the domain (as well as an
103 # appropriate site for the new DC)
104 ctx
.logger
.info("Finding a writeable DC for domain '%s'" % domain
)
105 ctx
.server
= ctx
.find_dc(domain
)
106 ctx
.logger
.info("Found DC %s" % ctx
.server
)
107 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
108 session_info
=system_session(),
109 credentials
=ctx
.creds
, lp
=ctx
.lp
)
112 ctx
.site
= DEFAULTSITE
115 ctx
.samdb
.search(scope
=ldb
.SCOPE_BASE
, attrs
=[])
116 except ldb
.LdbError
as e
:
117 (enum
, estr
) = e
.args
118 raise DCJoinException(estr
)
120 ctx
.base_dn
= str(ctx
.samdb
.get_default_basedn())
121 ctx
.root_dn
= str(ctx
.samdb
.get_root_basedn())
122 ctx
.schema_dn
= str(ctx
.samdb
.get_schema_basedn())
123 ctx
.config_dn
= str(ctx
.samdb
.get_config_basedn())
124 ctx
.domsid
= security
.dom_sid(ctx
.samdb
.get_domain_sid())
125 ctx
.forestsid
= ctx
.domsid
126 ctx
.domain_name
= ctx
.get_domain_name()
127 ctx
.forest_domain_name
= ctx
.get_forest_domain_name()
128 ctx
.invocation_id
= misc
.GUID(str(uuid
.uuid4()))
130 ctx
.dc_ntds_dn
= ctx
.samdb
.get_dsServiceName()
131 ctx
.dc_dnsHostName
= ctx
.get_dnsHostName()
132 ctx
.behavior_version
= ctx
.get_behavior_version()
134 if machinepass
is not None:
135 ctx
.acct_pass
= machinepass
137 ctx
.acct_pass
= samba
.generate_random_machine_password(120, 120)
139 ctx
.dnsdomain
= ctx
.samdb
.domain_dns_name()
141 # the following are all dependent on the new DC's netbios_name (which
142 # we expect to always be specified, except when cloning a DC)
144 # work out the DNs of all the objects we will be adding
145 ctx
.myname
= netbios_name
146 ctx
.samname
= "%s$" % ctx
.myname
147 ctx
.server_dn
= "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx
.myname
, ctx
.site
, ctx
.config_dn
)
148 ctx
.ntds_dn
= "CN=NTDS Settings,%s" % ctx
.server_dn
149 ctx
.acct_dn
= "CN=%s,OU=Domain Controllers,%s" % (ctx
.myname
, ctx
.base_dn
)
150 ctx
.dnshostname
= "%s.%s" % (ctx
.myname
.lower(), ctx
.dnsdomain
)
151 ctx
.dnsforest
= ctx
.samdb
.forest_dns_name()
153 topology_base
= "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx
.base_dn
154 if ctx
.dn_exists(topology_base
):
155 ctx
.topology_dn
= "CN=%s,%s" % (ctx
.myname
, topology_base
)
157 ctx
.topology_dn
= None
159 ctx
.SPNs
= ["HOST/%s" % ctx
.myname
,
160 "HOST/%s" % ctx
.dnshostname
,
161 "GC/%s/%s" % (ctx
.dnshostname
, ctx
.dnsforest
)]
163 res_rid_manager
= ctx
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
164 attrs
=["rIDManagerReference"],
167 ctx
.rid_manager_dn
= res_rid_manager
[0]["rIDManagerReference"][0]
169 ctx
.domaindns_zone
= 'DC=DomainDnsZones,%s' % ctx
.base_dn
170 ctx
.forestdns_zone
= 'DC=ForestDnsZones,%s' % ctx
.root_dn
172 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.domaindns_zone
)
173 res_domaindns
= ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
175 base
=ctx
.samdb
.get_partitions_dn(),
177 if dns_backend
is None:
178 ctx
.dns_backend
= "NONE"
180 if len(res_domaindns
) == 0:
181 ctx
.dns_backend
= "NONE"
182 print("NO DNS zone information found in source domain, not replicating DNS")
184 ctx
.dns_backend
= dns_backend
186 ctx
.realm
= ctx
.dnsdomain
190 ctx
.replica_flags
= (drsuapi
.DRSUAPI_DRS_INIT_SYNC |
191 drsuapi
.DRSUAPI_DRS_PER_SYNC |
192 drsuapi
.DRSUAPI_DRS_GET_ANC |
193 drsuapi
.DRSUAPI_DRS_GET_NC_SIZE |
194 drsuapi
.DRSUAPI_DRS_NEVER_SYNCED
)
196 # these elements are optional
197 ctx
.never_reveal_sid
= None
198 ctx
.reveal_sid
= None
199 ctx
.connection_dn
= None
204 ctx
.subdomain
= False
206 ctx
.partition_dn
= None
209 ctx
.dns_cname_dn
= None
211 # Do not normally register 127. addresses but allow override for selftest
212 ctx
.force_all_ips
= False
214 def del_noerror(ctx
, dn
, recursive
=False):
217 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["dn"])
221 ctx
.del_noerror(r
.dn
, recursive
=True)
224 print("Deleted %s" % dn
)
228 def cleanup_old_accounts(ctx
, force
=False):
229 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
230 expression
='sAMAccountName=%s' % ldb
.binary_encode(ctx
.samname
),
231 attrs
=["msDS-krbTgtLink", "objectSID"])
236 creds
= Credentials()
239 creds
.set_machine_account(ctx
.lp
)
240 creds
.set_kerberos_state(ctx
.creds
.get_kerberos_state())
241 machine_samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
242 session_info
=system_session(),
243 credentials
=creds
, lp
=ctx
.lp
)
247 token_res
= machine_samdb
.search(scope
=ldb
.SCOPE_BASE
, base
="", attrs
=["tokenGroups"])
248 if token_res
[0]["tokenGroups"][0] \
249 == res
[0]["objectSID"][0]:
250 raise DCJoinException("Not removing account %s which "
251 "looks like a Samba DC account "
252 "matching the password we already have. "
253 "To override, remove secrets.ldb and secrets.tdb"
256 ctx
.del_noerror(res
[0].dn
, recursive
=True)
258 krbtgt_dn
= res
[0].get('msDS-KrbTgtLink', idx
=0)
259 if krbtgt_dn
is not None:
260 ctx
.new_krbtgt_dn
= krbtgt_dn
261 ctx
.del_noerror(ctx
.new_krbtgt_dn
)
263 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
264 expression
='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
265 (ldb
.binary_encode("dns-%s" % ctx
.myname
),
266 ldb
.binary_encode("dns/%s" % ctx
.dnshostname
)),
269 ctx
.del_noerror(res
[0].dn
, recursive
=True)
271 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
272 expression
='(sAMAccountName=%s)' % ldb
.binary_encode("dns-%s" % ctx
.myname
),
275 raise DCJoinException("Not removing account %s which looks like "
276 "a Samba DNS service account but does not "
277 "have servicePrincipalName=%s" %
278 (ldb
.binary_encode("dns-%s" % ctx
.myname
),
279 ldb
.binary_encode("dns/%s" % ctx
.dnshostname
)))
281 def cleanup_old_join(ctx
, force
=False):
282 """Remove any DNs from a previous join."""
283 # find the krbtgt link
284 if not ctx
.subdomain
:
285 ctx
.cleanup_old_accounts(force
=force
)
287 if ctx
.connection_dn
is not None:
288 ctx
.del_noerror(ctx
.connection_dn
)
289 if ctx
.krbtgt_dn
is not None:
290 ctx
.del_noerror(ctx
.krbtgt_dn
)
291 ctx
.del_noerror(ctx
.ntds_dn
)
292 ctx
.del_noerror(ctx
.server_dn
, recursive
=True)
294 ctx
.del_noerror(ctx
.topology_dn
)
296 ctx
.del_noerror(ctx
.partition_dn
)
299 binding_options
= "sign"
300 lsaconn
= lsa
.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
303 objectAttr
= lsa
.ObjectAttribute()
304 objectAttr
.sec_qos
= lsa
.QosInfo()
306 pol_handle
= lsaconn
.OpenPolicy2('',
308 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
)
402 """get the SID of the connected user. Only works with w2k8 and later,
403 so only used for RODC join"""
404 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["tokenGroups"])
405 binsid
= res
[0]["tokenGroups"][0]
406 return get_string(ctx
.samdb
.schema_format_value("objectSID", binsid
))
408 def dn_exists(ctx
, dn
):
409 """check if a DN exists"""
411 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[])
412 except ldb
.LdbError
as e5
:
413 (enum
, estr
) = e5
.args
414 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
419 def add_krbtgt_account(ctx
):
420 """RODCs need a special krbtgt account"""
421 print("Adding %s" % ctx
.krbtgt_dn
)
424 "objectclass": "user",
425 "useraccountcontrol": str(samba
.dsdb
.UF_NORMAL_ACCOUNT |
426 samba
.dsdb
.UF_ACCOUNTDISABLE
),
427 "showinadvancedviewonly": "TRUE",
428 "description": "krbtgt for %s" % ctx
.samname
}
429 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
431 # now we need to search for the samAccountName attribute on the krbtgt DN,
432 # as this will have been magically set to the krbtgt number
433 res
= ctx
.samdb
.search(base
=ctx
.krbtgt_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["samAccountName"])
434 ctx
.krbtgt_name
= res
[0]["samAccountName"][0]
436 print("Got krbtgt_name=%s" % ctx
.krbtgt_name
)
439 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
440 m
["msDS-krbTgtLink"] = ldb
.MessageElement(ctx
.krbtgt_dn
,
441 ldb
.FLAG_MOD_REPLACE
, "msDS-krbTgtLink")
444 ctx
.new_krbtgt_dn
= "CN=%s,CN=Users,%s" % (ctx
.krbtgt_name
, ctx
.base_dn
)
445 print("Renaming %s to %s" % (ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
))
446 ctx
.samdb
.rename(ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
)
448 def drsuapi_connect(ctx
):
449 """make a DRSUAPI connection to the naming master"""
450 binding_options
= "seal"
451 if ctx
.lp
.log_level() >= 9:
452 binding_options
+= ",print"
453 binding_string
= "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
)
454 ctx
.drsuapi
= drsuapi
.drsuapi(binding_string
, ctx
.lp
, ctx
.creds
)
455 (ctx
.drsuapi_handle
, ctx
.bind_supported_extensions
) = drs_utils
.drs_DsBind(ctx
.drsuapi
)
457 def create_tmp_samdb(ctx
):
458 """create a temporary samdb object for schema queries"""
459 ctx
.tmp_schema
= Schema(ctx
.domsid
,
460 schemadn
=ctx
.schema_dn
)
461 ctx
.tmp_samdb
= SamDB(session_info
=system_session(), url
=None, auto_connect
=False,
462 credentials
=ctx
.creds
, lp
=ctx
.lp
, global_schema
=False,
464 ctx
.tmp_samdb
.set_schema(ctx
.tmp_schema
)
466 def DsAddEntry(ctx
, recs
):
467 """add a record via the DRSUAPI DsAddEntry call"""
468 if ctx
.drsuapi
is None:
469 ctx
.drsuapi_connect()
470 if ctx
.tmp_samdb
is None:
471 ctx
.create_tmp_samdb()
475 id = drsuapi
.DsReplicaObjectIdentifier()
482 if not isinstance(rec
[a
], list):
486 v
= [x
.encode('utf8') if isinstance(x
, str) else x
for x
in v
]
487 rattr
= ctx
.tmp_samdb
.dsdb_DsReplicaAttribute(ctx
.tmp_samdb
, a
, v
)
490 attribute_ctr
= drsuapi
.DsReplicaAttributeCtr()
491 attribute_ctr
.num_attributes
= len(attrs
)
492 attribute_ctr
.attributes
= attrs
494 object = drsuapi
.DsReplicaObject()
495 object.identifier
= id
496 object.attribute_ctr
= attribute_ctr
498 list_object
= drsuapi
.DsReplicaObjectListItem()
499 list_object
.object = object
500 objects
.append(list_object
)
502 req2
= drsuapi
.DsAddEntryRequest2()
503 req2
.first_object
= objects
[0]
504 prev
= req2
.first_object
505 for o
in objects
[1:]:
509 (level
, ctr
) = ctx
.drsuapi
.DsAddEntry(ctx
.drsuapi_handle
, 2, req2
)
511 if ctr
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
512 print("DsAddEntry failed with dir_err %u" % ctr
.dir_err
)
513 raise RuntimeError("DsAddEntry failed")
514 if ctr
.extended_err
[0] != werror
.WERR_SUCCESS
:
515 print("DsAddEntry failed with status %s info %s" % (ctr
.extended_err
))
516 raise RuntimeError("DsAddEntry failed")
519 raise RuntimeError("expected err_ver 1, got %u" % ctr
.err_ver
)
520 if ctr
.err_data
.status
[0] != werror
.WERR_SUCCESS
:
521 if ctr
.err_data
.info
is None:
522 print("DsAddEntry failed with status %s, info omitted" % (ctr
.err_data
.status
[1]))
524 print("DsAddEntry failed with status %s info %s" % (ctr
.err_data
.status
[1],
525 ctr
.err_data
.info
.extended_err
))
526 raise RuntimeError("DsAddEntry failed")
527 if ctr
.err_data
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
528 print("DsAddEntry failed with dir_err %u" % ctr
.err_data
.dir_err
)
529 raise RuntimeError("DsAddEntry failed")
533 def join_ntdsdsa_obj(ctx
):
534 """return the ntdsdsa object to add"""
536 print("Adding %s" % ctx
.ntds_dn
)
538 # When joining Windows, the order of certain attributes (mostly only
539 # msDS-HasMasterNCs and HasMasterNCs) seems to matter
542 ("objectclass", "nTDSDSA"),
543 ("systemFlags", str(samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
)),
544 ("dMDLocation", ctx
.schema_dn
)])
546 nc_list
= [ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
548 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
549 # This allows an override via smb.conf or --option using
550 # "ad dc functional level" to make us seem like 2016 to
551 # join such a domain for (say) a migration, or to test the
552 # partially implemented 2016 support.
553 domainControllerFunctionality
= functional_level
.dc_level_from_lp(ctx
.lp
)
554 rec
["msDS-Behavior-Version"] = str(domainControllerFunctionality
)
556 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
557 rec
["msDS-HasDomainNCs"] = ctx
.base_dn
560 rec
["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx
.schema_dn
561 rec
["msDS-HasFullReplicaNCs"] = ctx
.full_nc_list
562 rec
["options"] = "37"
564 rec
["objectCategory"] = "CN=NTDS-DSA,%s" % ctx
.schema_dn
566 # Note that Windows seems to have an undocumented requirement that
567 # the msDS-HasMasterNCs attribute occurs before HasMasterNCs
568 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
569 rec
["msDS-HasMasterNCs"] = ctx
.full_nc_list
571 rec
["HasMasterNCs"] = []
573 if nc
in ctx
.full_nc_list
:
574 rec
["HasMasterNCs"].append(nc
)
577 rec
["invocationId"] = ndr_pack(ctx
.invocation_id
)
581 def join_add_ntdsdsa(ctx
):
582 """add the ntdsdsa object"""
584 rec
= ctx
.join_ntdsdsa_obj()
585 if ctx
.forced_local_samdb
:
586 ctx
.samdb
.add(rec
, controls
=["relax:0"])
588 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
590 ctx
.DsAddEntry([rec
])
592 # find the GUID of our NTDS DN
593 res
= ctx
.samdb
.search(base
=ctx
.ntds_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["objectGUID"])
594 ctx
.ntds_guid
= misc
.GUID(ctx
.samdb
.schema_format_value("objectGUID", res
[0]["objectGUID"][0]))
596 def join_add_objects(ctx
, specified_sid
=None):
597 """add the various objects needed for the join"""
599 print("Adding %s" % ctx
.acct_dn
)
602 "objectClass": "computer",
603 "displayname": ctx
.samname
,
604 "samaccountname": ctx
.samname
,
605 "userAccountControl": str(ctx
.userAccountControl | samba
.dsdb
.UF_ACCOUNTDISABLE
),
606 "dnshostname": ctx
.dnshostname
}
607 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2008
:
608 rec
['msDS-SupportedEncryptionTypes'] = str(samba
.dsdb
.ENC_ALL_TYPES
)
609 elif ctx
.promote_existing
:
610 rec
['msDS-SupportedEncryptionTypes'] = []
612 rec
["managedby"] = ctx
.managedby
613 elif ctx
.promote_existing
:
614 rec
["managedby"] = []
616 if ctx
.never_reveal_sid
:
617 rec
["msDS-NeverRevealGroup"] = ctx
.never_reveal_sid
618 elif ctx
.promote_existing
:
619 rec
["msDS-NeverRevealGroup"] = []
622 rec
["msDS-RevealOnDemandGroup"] = ctx
.reveal_sid
623 elif ctx
.promote_existing
:
624 rec
["msDS-RevealOnDemandGroup"] = []
627 rec
["objectSid"] = ndr_pack(specified_sid
)
629 if ctx
.promote_existing
:
630 if ctx
.promote_from_dn
!= ctx
.acct_dn
:
631 ctx
.samdb
.rename(ctx
.promote_from_dn
, ctx
.acct_dn
)
632 ctx
.samdb
.modify(ldb
.Message
.from_dict(ctx
.samdb
, rec
, ldb
.FLAG_MOD_REPLACE
))
635 if specified_sid
is not None:
636 controls
= ["relax:0"]
637 ctx
.samdb
.add(rec
, controls
=controls
)
640 ctx
.add_krbtgt_account()
643 print("Adding %s" % ctx
.server_dn
)
646 "objectclass": "server",
647 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
648 "systemFlags": str(samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
649 samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
650 samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
651 # windows seems to add the dnsHostName later
652 "dnsHostName": ctx
.dnshostname
}
655 rec
["serverReference"] = ctx
.acct_dn
660 # the rest is done after replication
665 ctx
.join_add_ntdsdsa()
667 # Add the Replica-Locations or RO-Replica-Locations attributes
668 # TODO Is this supposed to be for the schema partition too?
669 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.domaindns_zone
)
670 domain
= (ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
672 base
=ctx
.samdb
.get_partitions_dn(),
673 expression
=expr
), ctx
.domaindns_zone
)
675 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.forestdns_zone
)
676 forest
= (ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
678 base
=ctx
.samdb
.get_partitions_dn(),
679 expression
=expr
), ctx
.forestdns_zone
)
681 for part
, zone
in (domain
, forest
):
682 if zone
not in ctx
.nc_list
:
688 attr
= "msDS-NC-Replica-Locations"
690 attr
= "msDS-NC-RO-Replica-Locations"
692 m
[attr
] = ldb
.MessageElement(ctx
.ntds_dn
,
693 ldb
.FLAG_MOD_ADD
, attr
)
696 if ctx
.connection_dn
is not None:
697 print("Adding %s" % ctx
.connection_dn
)
699 "dn": ctx
.connection_dn
,
700 "objectclass": "nTDSConnection",
701 "enabledconnection": "TRUE",
703 "fromServer": ctx
.dc_ntds_dn
}
707 print("Adding SPNs to %s" % ctx
.acct_dn
)
709 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
710 for i
in range(len(ctx
.SPNs
)):
711 ctx
.SPNs
[i
] = ctx
.SPNs
[i
].replace("$NTDSGUID", str(ctx
.ntds_guid
))
712 m
["servicePrincipalName"] = ldb
.MessageElement(ctx
.SPNs
,
713 ldb
.FLAG_MOD_REPLACE
,
714 "servicePrincipalName")
717 # The account password set operation should normally be done over
718 # LDAP. Windows 2000 DCs however allow this only with SSL
719 # connections which are hard to set up and otherwise refuse with
720 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
722 print("Setting account password for %s" % ctx
.samname
)
724 ctx
.samdb
.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
725 % ldb
.binary_encode(ctx
.samname
),
727 force_change_at_next_login
=False,
728 username
=ctx
.samname
)
729 except ldb
.LdbError
as e2
:
731 if num
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
733 ctx
.net
.set_password(account_name
=ctx
.samname
,
734 domain_name
=ctx
.domain_name
,
735 newpassword
=ctx
.acct_pass
)
737 res
= ctx
.samdb
.search(base
=ctx
.acct_dn
, scope
=ldb
.SCOPE_BASE
,
738 attrs
=["msDS-KeyVersionNumber",
740 if "msDS-KeyVersionNumber" in res
[0]:
741 ctx
.key_version_number
= int(res
[0]["msDS-KeyVersionNumber"][0])
743 ctx
.key_version_number
= None
745 ctx
.new_dc_account_sid
= ndr_unpack(security
.dom_sid
,
746 res
[0]["objectSid"][0])
748 print("Enabling account")
750 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
751 m
["userAccountControl"] = ldb
.MessageElement(str(ctx
.userAccountControl
),
752 ldb
.FLAG_MOD_REPLACE
,
753 "userAccountControl")
756 if ctx
.dns_backend
.startswith("BIND9_"):
757 ctx
.dnspass
= samba
.generate_random_password(128, 255)
759 recs
= ctx
.samdb
.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
760 {"DNSDOMAIN": ctx
.dnsdomain
,
761 "DOMAINDN": ctx
.base_dn
,
762 "HOSTNAME": ctx
.myname
,
763 "DNSPASS_B64": b64encode(ctx
.dnspass
.encode('utf-16-le')).decode('utf8'),
764 "DNSNAME": ctx
.dnshostname
}))
765 for changetype
, msg
in recs
:
766 assert changetype
== ldb
.CHANGETYPE_NONE
767 dns_acct_dn
= msg
["dn"]
768 print("Adding DNS account %s with dns/ SPN" % msg
["dn"])
770 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
771 del msg
["clearTextPassword"]
772 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
773 del msg
["isCriticalSystemObject"]
774 # Disable account until password is set
775 msg
["userAccountControl"] = str(samba
.dsdb
.UF_NORMAL_ACCOUNT |
776 samba
.dsdb
.UF_ACCOUNTDISABLE
)
779 except ldb
.LdbError
as e
:
781 if num
!= ldb
.ERR_ENTRY_ALREADY_EXISTS
:
784 # The account password set operation should normally be done over
785 # LDAP. Windows 2000 DCs however allow this only with SSL
786 # connections which are hard to set up and otherwise refuse with
787 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
789 print("Setting account password for dns-%s" % ctx
.myname
)
791 ctx
.samdb
.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
792 % ldb
.binary_encode(ctx
.myname
),
794 force_change_at_next_login
=False,
795 username
=ctx
.samname
)
796 except ldb
.LdbError
as e3
:
798 if num
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
800 ctx
.net
.set_password(account_name
="dns-%s" % ctx
.myname
,
801 domain_name
=ctx
.domain_name
,
802 newpassword
=ctx
.dnspass
)
804 res
= ctx
.samdb
.search(base
=dns_acct_dn
, scope
=ldb
.SCOPE_BASE
,
805 attrs
=["msDS-KeyVersionNumber"])
806 if "msDS-KeyVersionNumber" in res
[0]:
807 ctx
.dns_key_version_number
= int(res
[0]["msDS-KeyVersionNumber"][0])
809 ctx
.dns_key_version_number
= None
811 def join_add_objects2(ctx
):
812 """add the various objects needed for the join, for subdomains post replication"""
814 print("Adding %s" % ctx
.partition_dn
)
815 name_map
= {'SubdomainAdmins': "%s-%s" % (str(ctx
.domsid
), security
.DOMAIN_RID_ADMINS
)}
816 sd_binary
= descriptor
.get_paritions_crossref_subdomain_descriptor(ctx
.forestsid
, name_map
=name_map
)
818 "dn": ctx
.partition_dn
,
819 "objectclass": "crossRef",
820 "objectCategory": "CN=Cross-Ref,%s" % ctx
.schema_dn
,
821 "nCName": ctx
.base_dn
,
822 "nETBIOSName": ctx
.domain_name
,
823 "dnsRoot": ctx
.dnsdomain
,
824 "trustParent": ctx
.parent_partition_dn
,
825 "systemFlags": str(samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_NC |samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_DOMAIN
),
826 "ntSecurityDescriptor": sd_binary
,
829 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
830 rec
["msDS-Behavior-Version"] = str(ctx
.behavior_version
)
832 rec2
= ctx
.join_ntdsdsa_obj()
834 objects
= ctx
.DsAddEntry([rec
, rec2
])
835 if len(objects
) != 2:
836 raise DCJoinException("Expected 2 objects from DsAddEntry")
838 ctx
.ntds_guid
= objects
[1].guid
840 print("Replicating partition DN")
841 ctx
.repl
.replicate(ctx
.partition_dn
,
842 misc
.GUID("00000000-0000-0000-0000-000000000000"),
844 exop
=drsuapi
.DRSUAPI_EXOP_REPL_OBJ
,
845 replica_flags
=drsuapi
.DRSUAPI_DRS_WRIT_REP
)
847 print("Replicating NTDS DN")
848 ctx
.repl
.replicate(ctx
.ntds_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 def join_provision(ctx
):
855 """Provision the local SAM."""
857 print("Calling bare provision")
859 smbconf
= ctx
.lp
.configfile
861 presult
= provision(ctx
.logger
, system_session(), smbconf
=smbconf
,
862 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_DRS
, realm
=ctx
.realm
,
863 rootdn
=ctx
.root_dn
, domaindn
=ctx
.base_dn
,
864 schemadn
=ctx
.schema_dn
, configdn
=ctx
.config_dn
,
865 serverdn
=ctx
.server_dn
, domain
=ctx
.domain_name
,
866 hostname
=ctx
.myname
, domainsid
=ctx
.domsid
,
867 machinepass
=ctx
.acct_pass
, serverrole
="active directory domain controller",
868 sitename
=ctx
.site
, lp
=ctx
.lp
, ntdsguid
=ctx
.ntds_guid
,
869 use_ntvfs
=ctx
.use_ntvfs
, dns_backend
=ctx
.dns_backend
,
870 plaintext_secrets
=ctx
.plaintext_secrets
,
871 backend_store
=ctx
.backend_store
,
872 backend_store_size
=ctx
.backend_store_size
,
874 print("Provision OK for domain DN %s" % presult
.domaindn
)
875 ctx
.local_samdb
= presult
.samdb
877 ctx
.paths
= presult
.paths
878 ctx
.names
= presult
.names
880 # Fix up the forestsid, it may be different if we are joining as a subdomain
881 ctx
.names
.forestsid
= ctx
.forestsid
883 def join_provision_own_domain(ctx
):
884 """Provision the local SAM."""
886 # we now operate exclusively on the local database, which
887 # we need to reopen in order to get the newly created schema
888 # we set the transaction_index_cache_size to 200,000 to ensure it is
889 # not too small, if it's too small the performance of the join will
890 # be negatively impacted.
891 print("Reconnecting to local samdb")
892 ctx
.samdb
= SamDB(url
=ctx
.local_samdb
.url
,
894 "transaction_index_cache_size:200000"],
895 session_info
=system_session(),
896 lp
=ctx
.local_samdb
.lp
,
898 ctx
.samdb
.set_invocation_id(str(ctx
.invocation_id
))
899 ctx
.local_samdb
= ctx
.samdb
901 ctx
.logger
.info("Finding domain GUID from ncName")
902 res
= ctx
.local_samdb
.search(base
=ctx
.partition_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=['ncName'],
903 controls
=["extended_dn:1:1", "reveal_internals:0"])
905 if 'nCName' not in res
[0]:
906 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx
.partition_dn
, ctx
.samdb
.url
))
909 ctx
.names
.domainguid
= str(misc
.GUID(ldb
.Dn(ctx
.samdb
, res
[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
911 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res
[0]['ncName'][0])
913 ctx
.logger
.info("Got domain GUID %s" % ctx
.names
.domainguid
)
915 ctx
.logger
.info("Calling own domain provision")
917 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
919 provision_fill(ctx
.local_samdb
, secrets_ldb
,
920 ctx
.logger
, ctx
.names
, ctx
.paths
,
921 dom_for_fun_level
=ctx
.behavior_version
,
922 samdb_fill
=FILL_SUBDOMAIN
,
923 machinepass
=ctx
.acct_pass
, serverrole
="active directory domain controller",
924 lp
=ctx
.lp
, hostip
=ctx
.names
.hostip
, hostip6
=ctx
.names
.hostip6
,
925 dns_backend
=ctx
.dns_backend
, adminpass
=ctx
.adminpass
)
927 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2012
:
928 adprep_level
= ctx
.behavior_version
930 updates_allowed_overridden
= False
931 if ctx
.lp
.get("dsdb:schema update allowed") is None:
932 ctx
.lp
.set("dsdb:schema update allowed", "yes")
933 print("Temporarily overriding 'dsdb:schema update allowed' setting")
934 updates_allowed_overridden
= True
936 ctx
.samdb
.transaction_start()
938 from samba
.domain_update
import DomainUpdate
940 domain
= DomainUpdate(ctx
.local_samdb
, fix
=True)
941 domain
.check_updates_functional_level(adprep_level
,
942 samba
.dsdb
.DS_DOMAIN_FUNCTION_2008
,
943 update_revision
=True)
945 ctx
.samdb
.transaction_commit()
946 except Exception as e
:
947 ctx
.samdb
.transaction_cancel()
948 raise DCJoinException("DomainUpdate() failed: %s" % e
)
950 if updates_allowed_overridden
:
951 ctx
.lp
.set("dsdb:schema update allowed", "no")
953 print("Provision OK for domain %s" % ctx
.names
.dnsdomain
)
955 def create_replicator(ctx
, repl_creds
, binding_options
):
956 """Creates a new DRS object for managing replications"""
957 return drs_utils
.drs_Replicate(
958 "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
959 ctx
.lp
, repl_creds
, ctx
.local_samdb
, ctx
.invocation_id
)
961 def join_replicate(ctx
):
962 """Replicate the SAM."""
964 ctx
.logger
.info("Starting replication")
966 # A global transaction is started so that linked attributes
967 # are applied at the very end, once all partitions are
968 # replicated. This helps get all cross-partition links.
969 ctx
.local_samdb
.transaction_start()
971 source_dsa_invocation_id
= misc
.GUID(ctx
.samdb
.get_invocation_id())
972 if ctx
.ntds_guid
is None:
973 print("Using DS_BIND_GUID_W2K3")
974 destination_dsa_guid
= misc
.GUID(drsuapi
.DRSUAPI_DS_BIND_GUID_W2K3
)
976 destination_dsa_guid
= ctx
.ntds_guid
979 repl_creds
= Credentials()
980 repl_creds
.guess(ctx
.lp
)
981 repl_creds
.set_kerberos_state(DONT_USE_KERBEROS
)
982 repl_creds
.set_username(ctx
.samname
)
983 repl_creds
.set_password(ctx
.acct_pass
)
985 repl_creds
= ctx
.creds
987 binding_options
= "seal"
988 if ctx
.lp
.log_level() >= 9:
989 binding_options
+= ",print"
991 repl
= ctx
.create_replicator(repl_creds
, binding_options
)
993 repl
.replicate(ctx
.schema_dn
, source_dsa_invocation_id
,
994 destination_dsa_guid
, schema
=True, rodc
=ctx
.RODC
,
995 replica_flags
=ctx
.replica_flags
)
996 repl
.replicate(ctx
.config_dn
, source_dsa_invocation_id
,
997 destination_dsa_guid
, rodc
=ctx
.RODC
,
998 replica_flags
=ctx
.replica_flags
)
999 if not ctx
.subdomain
:
1000 # Replicate first the critical objects for the basedn
1002 # We do this to match Windows. The default case is to
1003 # do a critical objects replication, then a second
1006 print("Replicating critical objects from the base DN of the domain")
1008 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
1009 destination_dsa_guid
, rodc
=ctx
.RODC
,
1010 replica_flags
=ctx
.domain_replica_flags | drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
)
1011 except WERRORError
as e
:
1013 if e
.args
[0] == werror
.WERR_DS_DRA_MISSING_PARENT
:
1014 ctx
.logger
.warning("First pass of replication with "
1015 "DRSUAPI_DRS_CRITICAL_ONLY "
1016 "not possible due to a missing parent object. "
1017 "This is typical of a Samba "
1018 "4.5 or earlier server. "
1019 "We will replicate all the objects instead.")
1023 # Now replicate all the objects in the domain (unless
1024 # we were run with --critical-only).
1026 # Doing the replication of users as a second pass
1027 # matches more closely the Windows behaviour, which is
1028 # actually to do this on first startup.
1030 # Use --critical-only if you want that (but you don't
1031 # really, it is better to see any errors here).
1032 if not ctx
.domain_replica_flags
& drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
:
1034 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
1035 destination_dsa_guid
, rodc
=ctx
.RODC
,
1036 replica_flags
=ctx
.domain_replica_flags
)
1037 except WERRORError
as e
:
1039 if e
.args
[0] == werror
.WERR_DS_DRA_MISSING_PARENT
and \
1040 ctx
.domain_replica_flags
& drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
:
1041 ctx
.logger
.warning("Replication with DRSUAPI_DRS_CRITICAL_ONLY "
1042 "failed due to a missing parent object. "
1043 "This may be a Samba 4.5 or earlier server "
1044 "and is not compatible with --critical-only")
1047 print("Done with always replicated NC (base, config, schema)")
1049 # At this point we should already have an entry in the ForestDNS
1050 # and DomainDNS NC (those under CN=Partitions,DC=...) in order to
1051 # indicate that we hold a replica for this NC.
1052 for nc
in (ctx
.domaindns_zone
, ctx
.forestdns_zone
):
1053 if nc
in ctx
.nc_list
:
1054 print("Replicating %s" % (str(nc
)))
1055 repl
.replicate(nc
, source_dsa_invocation_id
,
1056 destination_dsa_guid
, rodc
=ctx
.RODC
,
1057 replica_flags
=ctx
.replica_flags
)
1060 repl
.replicate(ctx
.acct_dn
, source_dsa_invocation_id
,
1061 destination_dsa_guid
,
1062 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
1063 repl
.replicate(ctx
.new_krbtgt_dn
, source_dsa_invocation_id
,
1064 destination_dsa_guid
,
1065 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
1066 elif ctx
.rid_manager_dn
is not None:
1067 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
1069 repl
.replicate(ctx
.rid_manager_dn
, source_dsa_invocation_id
,
1070 destination_dsa_guid
,
1071 exop
=drsuapi
.DRSUAPI_EXOP_FSMO_RID_ALLOC
)
1072 except samba
.DsExtendedError
as e1
:
1073 (enum
, estr
) = e1
.args
1074 if enum
== drsuapi
.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER
:
1075 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx
.server
)
1076 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
1081 ctx
.source_dsa_invocation_id
= source_dsa_invocation_id
1082 ctx
.destination_dsa_guid
= destination_dsa_guid
1084 ctx
.logger
.info("Committing SAM database - this may take some time")
1086 ctx
.local_samdb
.transaction_cancel()
1090 # This is a special case, we have completed a full
1091 # replication so if a link comes to us that points to a
1092 # deleted object, and we asked for all objects already, we
1093 # just have to ignore it, the chance to re-try the
1094 # replication with GET_TGT has long gone. This can happen
1095 # if the object is deleted and sent to us after the link
1096 # was sent, as we are processing all links in the
1097 # transaction_commit().
1098 if not ctx
.domain_replica_flags
& drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
:
1099 ctx
.local_samdb
.set_opaque(dsdb
.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME
,
1101 ctx
.local_samdb
.transaction_commit()
1102 ctx
.local_samdb
.set_opaque(dsdb
.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME
,
1104 ctx
.logger
.info("Committed SAM database")
1106 # A large replication may have caused our LDB connection to the
1107 # remote DC to timeout, so check the connection is still alive
1108 ctx
.refresh_ldb_connection()
1110 def refresh_ldb_connection(ctx
):
1112 # query the rootDSE to check the connection
1113 ctx
.samdb
.search(scope
=ldb
.SCOPE_BASE
, attrs
=[])
1114 except ldb
.LdbError
as e
:
1115 (enum
, estr
) = e
.args
1117 # if the connection was disconnected, then reconnect
1118 if (enum
== ldb
.ERR_OPERATIONS_ERROR
and
1119 ('NT_STATUS_CONNECTION_DISCONNECTED' in estr
or
1120 'NT_STATUS_CONNECTION_RESET' in estr
)):
1121 ctx
.logger
.warning("LDB connection disconnected. Reconnecting")
1122 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
1123 session_info
=system_session(),
1124 credentials
=ctx
.creds
, lp
=ctx
.lp
)
1126 raise DCJoinException(estr
)
1128 def send_DsReplicaUpdateRefs(ctx
, dn
):
1129 r
= drsuapi
.DsReplicaUpdateRefsRequest1()
1130 r
.naming_context
= drsuapi
.DsReplicaObjectIdentifier()
1131 r
.naming_context
.dn
= str(dn
)
1132 r
.naming_context
.guid
= misc
.GUID("00000000-0000-0000-0000-000000000000")
1133 r
.naming_context
.sid
= security
.dom_sid("S-0-0")
1134 r
.dest_dsa_guid
= ctx
.ntds_guid
1135 r
.dest_dsa_dns_name
= "%s._msdcs.%s" % (str(ctx
.ntds_guid
), ctx
.dnsforest
)
1136 r
.options
= drsuapi
.DRSUAPI_DRS_ADD_REF | drsuapi
.DRSUAPI_DRS_DEL_REF
1138 r
.options |
= drsuapi
.DRSUAPI_DRS_WRIT_REP
1140 if ctx
.drsuapi
is None:
1141 ctx
.drsuapi_connect()
1143 ctx
.drsuapi
.DsReplicaUpdateRefs(ctx
.drsuapi_handle
, 1, r
)
1145 def join_add_dns_records(ctx
):
1146 """Remotely Add a DNS record to the target DC. We assume that if we
1147 replicate DNS that the server holds the DNS roles and can accept
1150 This avoids issues getting replication going after the DC
1151 first starts as the rest of the domain does not have to
1152 wait for samba_dnsupdate to run successfully.
1154 Specifically, we add the records implied by the DsReplicaUpdateRefs
1157 We do not just run samba_dnsupdate as we want to strictly
1158 operate against the DC we just joined:
1159 - We do not want to query another DNS server
1160 - We do not want to obtain a Kerberos ticket
1161 (as the KDC we select may not be the DC we just joined,
1162 and so may not be in sync with the password we just set)
1163 - We do not wish to set the _ldap records until we have started
1164 - We do not wish to use NTLM (the --use-samba-tool mode forces
1169 client_version
= dnsserver
.DNS_CLIENT_VERSION_LONGHORN
1170 select_flags
= dnsserver
.DNS_RPC_VIEW_AUTHORITY_DATA |\
1171 dnsserver
.DNS_RPC_VIEW_NO_CHILDREN
1173 zone
= ctx
.dnsdomain
1174 msdcs_zone
= "_msdcs.%s" % ctx
.dnsforest
1176 msdcs_cname
= str(ctx
.ntds_guid
)
1177 cname_target
= "%s.%s" % (name
, zone
)
1178 IPs
= samba
.interface_ips(ctx
.lp
, ctx
.force_all_ips
)
1180 ctx
.logger
.info("Adding %d remote DNS records for %s.%s" %
1181 (len(IPs
), name
, zone
))
1183 binding_options
= "sign"
1184 dns_conn
= dnsserver
.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
1189 sd_helper
= sd_utils
.SDUtils(ctx
.samdb
)
1191 change_owner_sd
= security
.descriptor()
1192 change_owner_sd
.owner_sid
= ctx
.new_dc_account_sid
1193 change_owner_sd
.group_sid
= security
.dom_sid("%s-%d" %
1195 security
.DOMAIN_RID_DCS
))
1197 # TODO: Remove any old records from the primary DNS name
1200 = dns_conn
.DnssrvEnumRecords2(client_version
,
1210 except WERRORError
as e
:
1211 if e
.args
[0] == werror
.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST
:
1216 for record
in rec
.records
:
1217 if record
.wType
== dnsp
.DNS_TYPE_A
or \
1218 record
.wType
== dnsp
.DNS_TYPE_AAAA
:
1220 del_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1221 del_rec_buf
.rec
= record
1223 dns_conn
.DnssrvUpdateRecord2(client_version
,
1230 except WERRORError
as e
:
1231 if e
.args
[0] == werror
.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST
:
1237 if IP
.find(':') != -1:
1238 ctx
.logger
.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1240 rec
= AAAARecord(IP
)
1242 ctx
.logger
.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1247 add_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1248 add_rec_buf
.rec
= rec
1249 dns_conn
.DnssrvUpdateRecord2(client_version
,
1258 domaindns_zone_dn
= ldb
.Dn(ctx
.samdb
, ctx
.domaindns_zone
)
1259 (ctx
.dns_a_dn
, ldap_record
) \
1260 = ctx
.samdb
.dns_lookup("%s.%s" % (name
, zone
),
1261 dns_partition
=domaindns_zone_dn
)
1263 # Make the DC own the DNS record, not the administrator
1264 sd_helper
.modify_sd_on_dn(ctx
.dns_a_dn
, change_owner_sd
,
1265 controls
=["sd_flags:1:%d"
1266 % (security
.SECINFO_OWNER
1267 | security
.SECINFO_GROUP
)])
1270 ctx
.logger
.info("Adding DNS CNAME record %s.%s for %s"
1271 % (msdcs_cname
, msdcs_zone
, cname_target
))
1273 add_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1274 rec
= CNAMERecord(cname_target
)
1275 add_rec_buf
.rec
= rec
1276 dns_conn
.DnssrvUpdateRecord2(client_version
,
1284 forestdns_zone_dn
= ldb
.Dn(ctx
.samdb
, ctx
.forestdns_zone
)
1285 (ctx
.dns_cname_dn
, ldap_record
) \
1286 = ctx
.samdb
.dns_lookup("%s.%s" % (msdcs_cname
, msdcs_zone
),
1287 dns_partition
=forestdns_zone_dn
)
1289 # Make the DC own the DNS record, not the administrator
1290 sd_helper
.modify_sd_on_dn(ctx
.dns_cname_dn
, change_owner_sd
,
1291 controls
=["sd_flags:1:%d"
1292 % (security
.SECINFO_OWNER
1293 | security
.SECINFO_GROUP
)])
1295 ctx
.logger
.info("All other DNS records (like _ldap SRV records) " +
1296 "will be created samba_dnsupdate on first startup")
1298 def join_replicate_new_dns_records(ctx
):
1299 for nc
in (ctx
.domaindns_zone
, ctx
.forestdns_zone
):
1300 if nc
in ctx
.nc_list
:
1301 ctx
.logger
.info("Replicating new DNS records in %s" % (str(nc
)))
1302 ctx
.repl
.replicate(nc
, ctx
.source_dsa_invocation_id
,
1303 ctx
.ntds_guid
, rodc
=ctx
.RODC
,
1304 replica_flags
=ctx
.replica_flags
,
1307 def join_finalise(ctx
):
1308 """Finalise the join, mark us synchronised and setup secrets db."""
1310 # FIXME we shouldn't do this in all cases
1312 # If for some reasons we joined in another site than the one of
1313 # DC we just replicated from then we don't need to send the updatereplicateref
1314 # as replication between sites is time based and on the initiative of the
1316 ctx
.logger
.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1317 for nc
in ctx
.nc_list
:
1318 ctx
.send_DsReplicaUpdateRefs(nc
)
1321 print("Setting RODC invocationId")
1322 ctx
.local_samdb
.set_invocation_id(str(ctx
.invocation_id
))
1323 ctx
.local_samdb
.set_opaque("domainFunctionality",
1324 ctx
.behavior_version
)
1326 m
.dn
= ldb
.Dn(ctx
.local_samdb
, "%s" % ctx
.ntds_dn
)
1327 m
["invocationId"] = ldb
.MessageElement(ndr_pack(ctx
.invocation_id
),
1328 ldb
.FLAG_MOD_REPLACE
,
1330 ctx
.local_samdb
.modify(m
)
1332 # Note: as RODC the invocationId is only stored
1333 # on the RODC itself, the other DCs never see it.
1335 # That's is why we fix up the replPropertyMetaData stamp
1336 # for the 'invocationId' attribute, we need to change
1337 # the 'version' to '0', this is what windows 2008r2 does as RODC
1339 # This means if the object on a RWDC ever gets a invocationId
1340 # attribute, it will have version '1' (or higher), which will
1341 # will overwrite the RODC local value.
1342 ctx
.local_samdb
.set_attribute_replmetadata_version(m
.dn
,
1346 ctx
.logger
.info("Setting isSynchronized and dsServiceName")
1348 m
.dn
= ldb
.Dn(ctx
.local_samdb
, '@ROOTDSE')
1349 m
["isSynchronized"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isSynchronized")
1351 guid
= ctx
.ntds_guid
1352 m
["dsServiceName"] = ldb
.MessageElement("<GUID=%s>" % str(guid
),
1353 ldb
.FLAG_MOD_REPLACE
, "dsServiceName")
1354 ctx
.local_samdb
.modify(m
)
1359 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
1361 ctx
.logger
.info("Setting up secrets database")
1362 secretsdb_self_join(secrets_ldb
, domain
=ctx
.domain_name
,
1364 dnsdomain
=ctx
.dnsdomain
,
1365 netbiosname
=ctx
.myname
,
1366 domainsid
=ctx
.domsid
,
1367 machinepass
=ctx
.acct_pass
,
1368 secure_channel_type
=ctx
.secure_channel_type
,
1369 key_version_number
=ctx
.key_version_number
)
1371 if ctx
.dns_backend
.startswith("BIND9_"):
1372 setup_bind9_dns(ctx
.local_samdb
, secrets_ldb
,
1373 ctx
.names
, ctx
.paths
, ctx
.logger
,
1374 dns_backend
=ctx
.dns_backend
,
1375 dnspass
=ctx
.dnspass
, os_level
=ctx
.behavior_version
,
1376 key_version_number
=ctx
.dns_key_version_number
)
1378 def join_setup_trusts(ctx
):
1379 """provision the local SAM."""
1381 print("Setup domain trusts with server %s" % ctx
.server
)
1382 binding_options
= "" # why doesn't signing work here? w2k8r2 claims no session key
1383 lsaconn
= lsa
.lsarpc("ncacn_np:%s[%s]" % (ctx
.server
, binding_options
),
1386 objectAttr
= lsa
.ObjectAttribute()
1387 objectAttr
.sec_qos
= lsa
.QosInfo()
1389 pol_handle
= lsaconn
.OpenPolicy2(''.decode('utf-8'),
1390 objectAttr
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
1392 info
= lsa
.TrustDomainInfoInfoEx()
1393 info
.domain_name
.string
= ctx
.dnsdomain
1394 info
.netbios_name
.string
= ctx
.domain_name
1395 info
.sid
= ctx
.domsid
1396 info
.trust_direction
= lsa
.LSA_TRUST_DIRECTION_INBOUND | lsa
.LSA_TRUST_DIRECTION_OUTBOUND
1397 info
.trust_type
= lsa
.LSA_TRUST_TYPE_UPLEVEL
1398 info
.trust_attributes
= lsa
.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1401 oldname
= lsa
.String()
1402 oldname
.string
= ctx
.dnsdomain
1403 oldinfo
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, oldname
,
1404 lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
1405 print("Removing old trust record for %s (SID %s)" % (ctx
.dnsdomain
, oldinfo
.info_ex
.sid
))
1406 lsaconn
.DeleteTrustedDomain(pol_handle
, oldinfo
.info_ex
.sid
)
1407 except RuntimeError:
1410 password_blob
= list(ctx
.trustdom_pass
.encode('utf-16-le'))
1412 clear_value
= drsblobs
.AuthInfoClear()
1413 clear_value
.size
= len(password_blob
)
1414 clear_value
.password
= password_blob
1416 clear_authentication_information
= drsblobs
.AuthenticationInformation()
1417 clear_authentication_information
.LastUpdateTime
= samba
.unix2nttime(int(time
.time()))
1418 clear_authentication_information
.AuthType
= lsa
.TRUST_AUTH_TYPE_CLEAR
1419 clear_authentication_information
.AuthInfo
= clear_value
1421 authentication_information_array
= drsblobs
.AuthenticationInformationArray()
1422 authentication_information_array
.count
= 1
1423 authentication_information_array
.array
= [clear_authentication_information
]
1425 outgoing
= drsblobs
.trustAuthInOutBlob()
1427 outgoing
.current
= authentication_information_array
1429 trustpass
= drsblobs
.trustDomainPasswords()
1430 confounder
= [3] * 512
1432 for i
in range(512):
1433 confounder
[i
] = random
.randint(0, 255)
1435 trustpass
.confounder
= confounder
1437 trustpass
.outgoing
= outgoing
1438 trustpass
.incoming
= outgoing
1440 trustpass_blob
= ndr_pack(trustpass
)
1442 encrypted_trustpass
= arcfour_encrypt(lsaconn
.session_key
, trustpass_blob
)
1444 auth_blob
= lsa
.DATA_BUF2()
1445 auth_blob
.size
= len(encrypted_trustpass
)
1446 auth_blob
.data
= list(encrypted_trustpass
)
1448 auth_info
= lsa
.TrustDomainInfoAuthInfoInternal()
1449 auth_info
.auth_blob
= auth_blob
1451 trustdom_handle
= lsaconn
.CreateTrustedDomainEx2(pol_handle
,
1454 security
.SEC_STD_DELETE
)
1457 "dn": "cn=%s,cn=system,%s" % (ctx
.dnsforest
, ctx
.base_dn
),
1458 "objectclass": "trustedDomain",
1459 "trustType": str(info
.trust_type
),
1460 "trustAttributes": str(info
.trust_attributes
),
1461 "trustDirection": str(info
.trust_direction
),
1462 "flatname": ctx
.forest_domain_name
,
1463 "trustPartner": ctx
.dnsforest
,
1464 "trustAuthIncoming": ndr_pack(outgoing
),
1465 "trustAuthOutgoing": ndr_pack(outgoing
),
1466 "securityIdentifier": ndr_pack(ctx
.forestsid
)
1468 ctx
.local_samdb
.add(rec
)
1471 "dn": "cn=%s$,cn=users,%s" % (ctx
.forest_domain_name
, ctx
.base_dn
),
1472 "objectclass": "user",
1473 "userAccountControl": str(samba
.dsdb
.UF_INTERDOMAIN_TRUST_ACCOUNT
),
1474 "clearTextPassword": ctx
.trustdom_pass
.encode('utf-16-le'),
1475 "samAccountName": "%s$" % ctx
.forest_domain_name
1477 ctx
.local_samdb
.add(rec
)
1479 def build_nc_lists(ctx
):
1480 # nc_list is the list of naming context (NC) for which we will
1481 # replicate in and send a updateRef command to the partner DC
1483 # full_nc_list is the list of naming context (NC) we hold
1484 # read/write copies of. These are not subsets of each other.
1485 ctx
.nc_list
= [ctx
.config_dn
, ctx
.schema_dn
]
1486 ctx
.full_nc_list
= [ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
1488 if ctx
.subdomain
and ctx
.dns_backend
!= "NONE":
1489 ctx
.full_nc_list
+= [ctx
.domaindns_zone
]
1491 elif not ctx
.subdomain
:
1492 ctx
.nc_list
+= [ctx
.base_dn
]
1494 if ctx
.dns_backend
!= "NONE":
1495 ctx
.nc_list
+= [ctx
.domaindns_zone
]
1496 ctx
.nc_list
+= [ctx
.forestdns_zone
]
1497 ctx
.full_nc_list
+= [ctx
.domaindns_zone
]
1498 ctx
.full_nc_list
+= [ctx
.forestdns_zone
]
1501 ctx
.build_nc_lists()
1503 if ctx
.promote_existing
:
1504 ctx
.promote_possible()
1506 ctx
.cleanup_old_join()
1509 ctx
.join_add_objects()
1510 ctx
.join_provision()
1511 ctx
.join_replicate()
1513 ctx
.join_add_objects2()
1514 ctx
.join_provision_own_domain()
1515 ctx
.join_setup_trusts()
1517 if ctx
.dns_backend
!= "NONE":
1518 ctx
.join_add_dns_records()
1519 ctx
.join_replicate_new_dns_records()
1524 print("Join failed - cleaning up")
1528 # cleanup the failed join (checking we still have a live LDB
1529 # connection to the remote DC first)
1530 ctx
.refresh_ldb_connection()
1531 ctx
.cleanup_old_join()
1535 def join_RODC(logger
=None, server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
1536 targetdir
=None, domain
=None, domain_critical_only
=False,
1537 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
1538 promote_existing
=False, plaintext_secrets
=False,
1540 backend_store_size
=None):
1541 """Join as a RODC."""
1543 ctx
= DCJoinContext(logger
, server
, creds
, lp
, site
, netbios_name
,
1544 targetdir
, domain
, machinepass
, use_ntvfs
, dns_backend
,
1545 promote_existing
, plaintext_secrets
,
1546 backend_store
=backend_store
,
1547 backend_store_size
=backend_store_size
)
1549 lp
.set("workgroup", ctx
.domain_name
)
1550 logger
.info("workgroup is %s" % ctx
.domain_name
)
1552 lp
.set("realm", ctx
.realm
)
1553 logger
.info("realm is %s" % ctx
.realm
)
1555 ctx
.krbtgt_dn
= "CN=krbtgt_%s,CN=Users,%s" % (ctx
.myname
, ctx
.base_dn
)
1557 # setup some defaults for accounts that should be replicated to this RODC
1558 ctx
.never_reveal_sid
= [
1559 "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_DENY
),
1560 "<SID=%s>" % security
.SID_BUILTIN_ADMINISTRATORS
,
1561 "<SID=%s>" % security
.SID_BUILTIN_SERVER_OPERATORS
,
1562 "<SID=%s>" % security
.SID_BUILTIN_BACKUP_OPERATORS
,
1563 "<SID=%s>" % security
.SID_BUILTIN_ACCOUNT_OPERATORS
]
1564 ctx
.reveal_sid
= "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_ALLOW
)
1566 mysid
= ctx
.get_mysid()
1567 admin_dn
= "<SID=%s>" % mysid
1568 ctx
.managedby
= admin_dn
1570 ctx
.userAccountControl
= (samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT |
1571 samba
.dsdb
.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1572 samba
.dsdb
.UF_PARTIAL_SECRETS_ACCOUNT
)
1574 ctx
.SPNs
.extend(["RestrictedKrbHost/%s" % ctx
.myname
,
1575 "RestrictedKrbHost/%s" % ctx
.dnshostname
])
1577 ctx
.connection_dn
= "CN=RODC Connection (FRS),%s" % ctx
.ntds_dn
1578 ctx
.secure_channel_type
= misc
.SEC_CHAN_RODC
1580 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1581 drsuapi
.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP
)
1582 ctx
.domain_replica_flags
= ctx
.replica_flags
1583 if domain_critical_only
:
1584 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
1588 logger
.info("Joined domain %s (SID %s) as an RODC" % (ctx
.domain_name
, ctx
.domsid
))
1591 def join_DC(logger
=None, server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
1592 targetdir
=None, domain
=None, domain_critical_only
=False,
1593 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
1594 promote_existing
=False, plaintext_secrets
=False,
1596 backend_store_size
=None):
1598 ctx
= DCJoinContext(logger
, server
, creds
, lp
, site
, netbios_name
,
1599 targetdir
, domain
, machinepass
, use_ntvfs
, dns_backend
,
1600 promote_existing
, plaintext_secrets
,
1601 backend_store
=backend_store
,
1602 backend_store_size
=backend_store_size
)
1604 lp
.set("workgroup", ctx
.domain_name
)
1605 logger
.info("workgroup is %s" % ctx
.domain_name
)
1607 lp
.set("realm", ctx
.realm
)
1608 logger
.info("realm is %s" % ctx
.realm
)
1610 ctx
.userAccountControl
= samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT | samba
.dsdb
.UF_TRUSTED_FOR_DELEGATION
1612 ctx
.SPNs
.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx
.dnsdomain
)
1613 ctx
.secure_channel_type
= misc
.SEC_CHAN_BDC
1615 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1616 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1617 ctx
.domain_replica_flags
= ctx
.replica_flags
1618 if domain_critical_only
:
1619 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
1622 logger
.info("Joined domain %s (SID %s) as a DC" % (ctx
.domain_name
, ctx
.domsid
))
1625 def join_clone(logger
=None, server
=None, creds
=None, lp
=None,
1626 targetdir
=None, domain
=None, include_secrets
=False,
1627 dns_backend
="NONE", backend_store
=None,
1628 backend_store_size
=None):
1629 """Creates a local clone of a remote DC."""
1630 ctx
= DCCloneContext(logger
, server
, creds
, lp
, targetdir
=targetdir
,
1631 domain
=domain
, dns_backend
=dns_backend
,
1632 include_secrets
=include_secrets
,
1633 backend_store
=backend_store
,
1634 backend_store_size
=backend_store_size
)
1636 lp
.set("workgroup", ctx
.domain_name
)
1637 logger
.info("workgroup is %s" % ctx
.domain_name
)
1639 lp
.set("realm", ctx
.realm
)
1640 logger
.info("realm is %s" % ctx
.realm
)
1643 logger
.info("Cloned domain %s (SID %s)" % (ctx
.domain_name
, ctx
.domsid
))
1647 class DCCloneContext(DCJoinContext
):
1648 """Clones a remote DC."""
1650 def __init__(ctx
, logger
=None, server
=None, creds
=None, lp
=None,
1651 targetdir
=None, domain
=None, dns_backend
=None,
1652 include_secrets
=False, backend_store
=None,
1653 backend_store_size
=None):
1654 super().__init
__(logger
, server
, creds
, lp
,
1655 targetdir
=targetdir
, domain
=domain
,
1656 dns_backend
=dns_backend
,
1657 backend_store
=backend_store
,
1658 backend_store_size
=backend_store_size
)
1660 # As we don't want to create or delete these DNs, we set them to None
1661 ctx
.server_dn
= None
1664 ctx
.myname
= ctx
.server
.split('.')[0]
1665 ctx
.ntds_guid
= None
1666 ctx
.rid_manager_dn
= None
1669 ctx
.remote_dc_ntds_guid
= ctx
.samdb
.get_ntds_GUID()
1671 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1672 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1673 if not include_secrets
:
1674 ctx
.replica_flags |
= drsuapi
.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1675 ctx
.domain_replica_flags
= ctx
.replica_flags
1677 def join_finalise(ctx
):
1678 ctx
.logger
.info("Setting isSynchronized and dsServiceName")
1680 m
.dn
= ldb
.Dn(ctx
.local_samdb
, '@ROOTDSE')
1681 m
["isSynchronized"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
,
1684 # We want to appear to be the server we just cloned
1685 guid
= ctx
.remote_dc_ntds_guid
1686 m
["dsServiceName"] = ldb
.MessageElement("<GUID=%s>" % str(guid
),
1687 ldb
.FLAG_MOD_REPLACE
,
1689 ctx
.local_samdb
.modify(m
)
1692 ctx
.build_nc_lists()
1694 # When cloning a DC, we just want to provision a DC locally, then
1695 # grab the remote DC's entire DB via DRS replication
1696 ctx
.join_provision()
1697 ctx
.join_replicate()
1701 # Used to create a renamed backup of a DC. Renaming the domain means that the
1702 # cloned/backup DC can be started without interfering with the production DC.
1703 class DCCloneAndRenameContext(DCCloneContext
):
1704 """Clones a remote DC, renaming the domain along the way."""
1706 def __init__(ctx
, new_base_dn
, new_domain_name
, new_realm
, logger
=None,
1707 server
=None, creds
=None, lp
=None, targetdir
=None, domain
=None,
1708 dns_backend
=None, include_secrets
=True, backend_store
=None):
1709 super().__init
__(logger
, server
, creds
, lp
,
1710 targetdir
=targetdir
,
1712 dns_backend
=dns_backend
,
1713 include_secrets
=include_secrets
,
1714 backend_store
=backend_store
)
1715 # store the new DN (etc) that we want the cloned DB to use
1716 ctx
.new_base_dn
= new_base_dn
1717 ctx
.new_domain_name
= new_domain_name
1718 ctx
.new_realm
= new_realm
1720 def create_replicator(ctx
, repl_creds
, binding_options
):
1721 """Creates a new DRS object for managing replications"""
1723 # We want to rename all the domain objects, and the simplest way to do
1724 # this is during replication. This is because the base DN of the top-
1725 # level replicated object will flow through to all the objects below it
1726 binding_str
= "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
)
1727 return drs_utils
.drs_ReplicateRenamer(binding_str
, ctx
.lp
, repl_creds
,
1730 ctx
.base_dn
, ctx
.new_base_dn
)
1732 def create_non_global_lp(ctx
, global_lp
):
1733 """Creates a non-global LoadParm based on the global LP's settings"""
1735 # the samba code shares a global LoadParm by default. Here we create a
1736 # new LoadParm that retains the global settings, but any changes we
1737 # make to it won't automatically affect the rest of the samba code.
1738 # The easiest way to do this is to dump the global settings to a
1739 # temporary smb.conf file, and then load the temp file into a new
1740 # non-global LoadParm
1741 fd
, tmp_file
= tempfile
.mkstemp()
1742 global_lp
.dump(False, tmp_file
)
1743 local_lp
= samba
.param
.LoadParm(filename_for_non_global_lp
=tmp_file
)
1747 def rename_dn(ctx
, dn_str
):
1748 """Uses string substitution to replace the base DN"""
1749 old_base_dn
= ctx
.base_dn
1750 return re
.sub('%s$' % old_base_dn
, ctx
.new_base_dn
, dn_str
)
1752 # we want to override the normal DCCloneContext's join_provision() so that
1753 # use the new domain DNs during the provision. We do this because:
1754 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1755 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1756 # we couldn't apply the renamed DRS objects during replication)
1757 def join_provision(ctx
):
1758 """Provision the local (renamed) SAM."""
1760 print("Provisioning the new (renamed) domain...")
1762 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1763 # to create a new smb.conf. By default, it uses the global LoadParm to
1764 # do this, and so it would overwrite the realm/domain values globally.
1765 # We still need the global LoadParm to retain the old domain's details,
1766 # so we can connect to (and clone) the existing DC.
1767 # So, copy the global settings into a non-global LoadParm, which we can
1768 # then pass into provision(). This generates a new smb.conf correctly,
1769 # without overwriting the global realm/domain values just yet.
1770 non_global_lp
= ctx
.create_non_global_lp(ctx
.lp
)
1772 # do the provision with the new/renamed domain DN values
1773 presult
= provision(ctx
.logger
, system_session(),
1774 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_DRS
,
1775 realm
=ctx
.new_realm
, lp
=non_global_lp
,
1776 rootdn
=ctx
.rename_dn(ctx
.root_dn
), domaindn
=ctx
.new_base_dn
,
1777 schemadn
=ctx
.rename_dn(ctx
.schema_dn
),
1778 configdn
=ctx
.rename_dn(ctx
.config_dn
),
1779 domain
=ctx
.new_domain_name
, domainsid
=ctx
.domsid
,
1780 serverrole
="active directory domain controller",
1781 dns_backend
=ctx
.dns_backend
,
1782 backend_store
=ctx
.backend_store
)
1784 print("Provision OK for renamed domain DN %s" % presult
.domaindn
)
1785 ctx
.local_samdb
= presult
.samdb
1786 ctx
.paths
= presult
.paths