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
25 import ldb
, samba
, sys
, uuid
26 from samba
.ndr
import ndr_pack
, ndr_unpack
27 from samba
.dcerpc
import security
, drsuapi
, misc
, nbt
, lsa
, drsblobs
, dnsserver
, dnsp
28 from samba
.dsdb
import DS_DOMAIN_FUNCTION_2003
29 from samba
.credentials
import Credentials
, DONT_USE_KERBEROS
30 from samba
.provision
import secretsdb_self_join
, provision
, provision_fill
, FILL_DRS
, FILL_SUBDOMAIN
31 from samba
.provision
.common
import setup_path
32 from samba
.schema
import Schema
33 from samba
import descriptor
34 from samba
.net
import Net
35 from samba
.provision
.sambadns
import setup_bind9_dns
36 from samba
import read_and_sub_file
37 from samba
import werror
38 from base64
import b64encode
39 from samba
import WERRORError
, NTSTATUSError
40 from samba
.dnsserver
import ARecord
, AAAARecord
, PTRRecord
, CNameRecord
, NSRecord
, MXRecord
, SOARecord
, SRVRecord
, TXTRecord
41 from samba
import sd_utils
50 class DCJoinException(Exception):
52 def __init__(self
, msg
):
53 super(DCJoinException
, self
).__init
__("Can't join, error: %s" % msg
)
56 class DCJoinContext(object):
57 """Perform a DC join."""
59 def __init__(ctx
, logger
=None, server
=None, creds
=None, lp
=None, site
=None,
60 netbios_name
=None, targetdir
=None, domain
=None,
61 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
62 promote_existing
=False, plaintext_secrets
=False,
63 backend_store
=None, forced_local_samdb
=None):
65 site
= "Default-First-Site-Name"
71 ctx
.targetdir
= targetdir
72 ctx
.use_ntvfs
= use_ntvfs
73 ctx
.plaintext_secrets
= plaintext_secrets
74 ctx
.backend_store
= backend_store
76 ctx
.promote_existing
= promote_existing
77 ctx
.promote_from_dn
= None
82 ctx
.creds
.set_gensec_features(creds
.get_gensec_features() | gensec
.FEATURE_SEAL
)
83 ctx
.net
= Net(creds
=ctx
.creds
, lp
=ctx
.lp
)
86 ctx
.forced_local_samdb
= forced_local_samdb
88 if forced_local_samdb
:
89 ctx
.samdb
= forced_local_samdb
90 ctx
.server
= ctx
.samdb
.url
93 ctx
.logger
.info("Finding a writeable DC for domain '%s'" % domain
)
94 ctx
.server
= ctx
.find_dc(domain
)
95 ctx
.logger
.info("Found DC %s" % ctx
.server
)
96 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
97 session_info
=system_session(),
98 credentials
=ctx
.creds
, lp
=ctx
.lp
)
101 ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["dn"])
102 except ldb
.LdbError
as e4
:
103 (enum
, estr
) = e4
.args
104 raise DCJoinException(estr
)
107 ctx
.base_dn
= str(ctx
.samdb
.get_default_basedn())
108 ctx
.root_dn
= str(ctx
.samdb
.get_root_basedn())
109 ctx
.schema_dn
= str(ctx
.samdb
.get_schema_basedn())
110 ctx
.config_dn
= str(ctx
.samdb
.get_config_basedn())
111 ctx
.domsid
= security
.dom_sid(ctx
.samdb
.get_domain_sid())
112 ctx
.forestsid
= ctx
.domsid
113 ctx
.domain_name
= ctx
.get_domain_name()
114 ctx
.forest_domain_name
= ctx
.get_forest_domain_name()
115 ctx
.invocation_id
= misc
.GUID(str(uuid
.uuid4()))
117 ctx
.dc_ntds_dn
= ctx
.samdb
.get_dsServiceName()
118 ctx
.dc_dnsHostName
= ctx
.get_dnsHostName()
119 ctx
.behavior_version
= ctx
.get_behavior_version()
121 if machinepass
is not None:
122 ctx
.acct_pass
= machinepass
124 ctx
.acct_pass
= samba
.generate_random_machine_password(128, 255)
126 ctx
.dnsdomain
= ctx
.samdb
.domain_dns_name()
128 # the following are all dependent on the new DC's netbios_name (which
129 # we expect to always be specified, except when cloning a DC)
131 # work out the DNs of all the objects we will be adding
132 ctx
.myname
= netbios_name
133 ctx
.samname
= "%s$" % ctx
.myname
134 ctx
.server_dn
= "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx
.myname
, ctx
.site
, ctx
.config_dn
)
135 ctx
.ntds_dn
= "CN=NTDS Settings,%s" % ctx
.server_dn
136 ctx
.acct_dn
= "CN=%s,OU=Domain Controllers,%s" % (ctx
.myname
, ctx
.base_dn
)
137 ctx
.dnshostname
= "%s.%s" % (ctx
.myname
.lower(), ctx
.dnsdomain
)
138 ctx
.dnsforest
= ctx
.samdb
.forest_dns_name()
140 topology_base
= "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx
.base_dn
141 if ctx
.dn_exists(topology_base
):
142 ctx
.topology_dn
= "CN=%s,%s" % (ctx
.myname
, topology_base
)
144 ctx
.topology_dn
= None
146 ctx
.SPNs
= [ "HOST/%s" % ctx
.myname
,
147 "HOST/%s" % ctx
.dnshostname
,
148 "GC/%s/%s" % (ctx
.dnshostname
, ctx
.dnsforest
) ]
150 res_rid_manager
= ctx
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
151 attrs
=["rIDManagerReference"],
154 ctx
.rid_manager_dn
= res_rid_manager
[0]["rIDManagerReference"][0]
156 ctx
.domaindns_zone
= 'DC=DomainDnsZones,%s' % ctx
.base_dn
157 ctx
.forestdns_zone
= 'DC=ForestDnsZones,%s' % ctx
.root_dn
159 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.domaindns_zone
)
160 res_domaindns
= ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
162 base
=ctx
.samdb
.get_partitions_dn(),
164 if dns_backend
is None:
165 ctx
.dns_backend
= "NONE"
167 if len(res_domaindns
) == 0:
168 ctx
.dns_backend
= "NONE"
169 print("NO DNS zone information found in source domain, not replicating DNS")
171 ctx
.dns_backend
= dns_backend
173 ctx
.realm
= ctx
.dnsdomain
177 ctx
.replica_flags
= (drsuapi
.DRSUAPI_DRS_INIT_SYNC |
178 drsuapi
.DRSUAPI_DRS_PER_SYNC |
179 drsuapi
.DRSUAPI_DRS_GET_ANC |
180 drsuapi
.DRSUAPI_DRS_GET_NC_SIZE |
181 drsuapi
.DRSUAPI_DRS_NEVER_SYNCED
)
183 # these elements are optional
184 ctx
.never_reveal_sid
= None
185 ctx
.reveal_sid
= None
186 ctx
.connection_dn
= None
191 ctx
.subdomain
= False
193 ctx
.partition_dn
= None
196 ctx
.dns_cname_dn
= None
198 # Do not normally register 127. addresses but allow override for selftest
199 ctx
.force_all_ips
= False
201 def del_noerror(ctx
, dn
, recursive
=False):
204 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["dn"])
208 ctx
.del_noerror(r
.dn
, recursive
=True)
211 print("Deleted %s" % dn
)
215 def cleanup_old_accounts(ctx
, force
=False):
216 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
217 expression
='sAMAccountName=%s' % ldb
.binary_encode(ctx
.samname
),
218 attrs
=["msDS-krbTgtLink", "objectSID"])
223 creds
= Credentials()
226 creds
.set_machine_account(ctx
.lp
)
227 creds
.set_kerberos_state(ctx
.creds
.get_kerberos_state())
228 machine_samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
229 session_info
=system_session(),
230 credentials
=creds
, lp
=ctx
.lp
)
234 token_res
= machine_samdb
.search(scope
=ldb
.SCOPE_BASE
, base
="", attrs
=["tokenGroups"])
235 if token_res
[0]["tokenGroups"][0] \
236 == res
[0]["objectSID"][0]:
237 raise DCJoinException("Not removing account %s which "
238 "looks like a Samba DC account "
239 "matching the password we already have. "
240 "To override, remove secrets.ldb and secrets.tdb"
243 ctx
.del_noerror(res
[0].dn
, recursive
=True)
245 if "msDS-Krbtgtlink" in res
[0]:
246 new_krbtgt_dn
= res
[0]["msDS-Krbtgtlink"][0]
247 ctx
.del_noerror(ctx
.new_krbtgt_dn
)
249 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
250 expression
='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
251 (ldb
.binary_encode("dns-%s" % ctx
.myname
),
252 ldb
.binary_encode("dns/%s" % ctx
.dnshostname
)),
255 ctx
.del_noerror(res
[0].dn
, recursive
=True)
257 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
258 expression
='(sAMAccountName=%s)' % ldb
.binary_encode("dns-%s" % ctx
.myname
),
261 raise DCJoinException("Not removing account %s which looks like "
262 "a Samba DNS service account but does not "
263 "have servicePrincipalName=%s" %
264 (ldb
.binary_encode("dns-%s" % ctx
.myname
),
265 ldb
.binary_encode("dns/%s" % ctx
.dnshostname
)))
268 def cleanup_old_join(ctx
, force
=False):
269 """Remove any DNs from a previous join."""
270 # find the krbtgt link
271 if not ctx
.subdomain
:
272 ctx
.cleanup_old_accounts(force
=force
)
274 if ctx
.connection_dn
is not None:
275 ctx
.del_noerror(ctx
.connection_dn
)
276 if ctx
.krbtgt_dn
is not None:
277 ctx
.del_noerror(ctx
.krbtgt_dn
)
278 ctx
.del_noerror(ctx
.ntds_dn
)
279 ctx
.del_noerror(ctx
.server_dn
, recursive
=True)
281 ctx
.del_noerror(ctx
.topology_dn
)
283 ctx
.del_noerror(ctx
.partition_dn
)
286 binding_options
= "sign"
287 lsaconn
= lsa
.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
290 objectAttr
= lsa
.ObjectAttribute()
291 objectAttr
.sec_qos
= lsa
.QosInfo()
293 pol_handle
= lsaconn
.OpenPolicy2(''.decode('utf-8'),
294 objectAttr
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
297 name
.string
= ctx
.realm
298 info
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, name
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
300 lsaconn
.DeleteTrustedDomain(pol_handle
, info
.info_ex
.sid
)
303 name
.string
= ctx
.forest_domain_name
304 info
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, name
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
306 lsaconn
.DeleteTrustedDomain(pol_handle
, info
.info_ex
.sid
)
309 ctx
.del_noerror(ctx
.dns_a_dn
)
312 ctx
.del_noerror(ctx
.dns_cname_dn
)
316 def promote_possible(ctx
):
317 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
319 # This shouldn't happen
320 raise Exception("Can not promote into a subdomain")
322 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
323 expression
='sAMAccountName=%s' % ldb
.binary_encode(ctx
.samname
),
324 attrs
=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
326 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx
.samname
)
327 if "msDS-krbTgtLink" in res
[0] or "serverReferenceBL" in res
[0] or "rIDSetReferences" in res
[0]:
328 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx
.samname
)
329 if (int(res
[0]["userAccountControl"][0]) & (samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT|samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT
) == 0):
330 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx
.samname
)
332 ctx
.promote_from_dn
= res
[0].dn
335 def find_dc(ctx
, domain
):
336 """find a writeable DC for the given domain"""
338 ctx
.cldap_ret
= ctx
.net
.finddc(domain
=domain
, flags
=nbt
.NBT_SERVER_LDAP | nbt
.NBT_SERVER_DS | nbt
.NBT_SERVER_WRITABLE
)
339 except NTSTATUSError
as error
:
340 raise Exception("Failed to find a writeable DC for domain '%s': %s" %
343 raise Exception("Failed to find a writeable DC for domain '%s'" % domain
)
344 if ctx
.cldap_ret
.client_site
is not None and ctx
.cldap_ret
.client_site
!= "":
345 ctx
.site
= ctx
.cldap_ret
.client_site
346 return ctx
.cldap_ret
.pdc_dns_name
349 def get_behavior_version(ctx
):
350 res
= ctx
.samdb
.search(base
=ctx
.base_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["msDS-Behavior-Version"])
351 if "msDS-Behavior-Version" in res
[0]:
352 return int(res
[0]["msDS-Behavior-Version"][0])
354 return samba
.dsdb
.DS_DOMAIN_FUNCTION_2000
356 def get_dnsHostName(ctx
):
357 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["dnsHostName"])
358 return res
[0]["dnsHostName"][0]
360 def get_domain_name(ctx
):
361 '''get netbios name of the domain from the partitions record'''
362 partitions_dn
= ctx
.samdb
.get_partitions_dn()
363 res
= ctx
.samdb
.search(base
=partitions_dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["nETBIOSName"],
364 expression
='ncName=%s' % ldb
.binary_encode(str(ctx
.samdb
.get_default_basedn())))
365 return res
[0]["nETBIOSName"][0]
367 def get_forest_domain_name(ctx
):
368 '''get netbios name of the domain from the partitions record'''
369 partitions_dn
= ctx
.samdb
.get_partitions_dn()
370 res
= ctx
.samdb
.search(base
=partitions_dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["nETBIOSName"],
371 expression
='ncName=%s' % ldb
.binary_encode(str(ctx
.samdb
.get_root_basedn())))
372 return res
[0]["nETBIOSName"][0]
374 def get_parent_partition_dn(ctx
):
375 '''get the parent domain partition DN from parent DNS name'''
376 res
= ctx
.samdb
.search(base
=ctx
.config_dn
, attrs
=[],
377 expression
='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
378 (ldb
.binary_encode(ctx
.parent_dnsdomain
),
379 ldb
.OID_COMPARATOR_AND
, samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_DOMAIN
))
380 return str(res
[0].dn
)
382 def get_naming_master(ctx
):
383 '''get the parent domain partition DN from parent DNS name'''
384 res
= ctx
.samdb
.search(base
='CN=Partitions,%s' % ctx
.config_dn
, attrs
=['fSMORoleOwner'],
385 scope
=ldb
.SCOPE_BASE
, controls
=["extended_dn:1:1"])
386 if not 'fSMORoleOwner' in res
[0]:
387 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx
.partition_dn
, ctx
.samdb
.url
))
389 master_guid
= str(misc
.GUID(ldb
.Dn(ctx
.samdb
, res
[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
391 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res
[0]['fSMORoleOwner'][0])
393 master_host
= '%s._msdcs.%s' % (master_guid
, ctx
.dnsforest
)
397 '''get the SID of the connected user. Only works with w2k8 and later,
398 so only used for RODC join'''
399 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["tokenGroups"])
400 binsid
= res
[0]["tokenGroups"][0]
401 return ctx
.samdb
.schema_format_value("objectSID", binsid
)
403 def dn_exists(ctx
, dn
):
404 '''check if a DN exists'''
406 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[])
407 except ldb
.LdbError
as e5
:
408 (enum
, estr
) = e5
.args
409 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
414 def add_krbtgt_account(ctx
):
415 '''RODCs need a special krbtgt account'''
416 print("Adding %s" % ctx
.krbtgt_dn
)
418 "dn" : ctx
.krbtgt_dn
,
419 "objectclass" : "user",
420 "useraccountcontrol" : str(samba
.dsdb
.UF_NORMAL_ACCOUNT |
421 samba
.dsdb
.UF_ACCOUNTDISABLE
),
422 "showinadvancedviewonly" : "TRUE",
423 "description" : "krbtgt for %s" % ctx
.samname
}
424 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
426 # now we need to search for the samAccountName attribute on the krbtgt DN,
427 # as this will have been magically set to the krbtgt number
428 res
= ctx
.samdb
.search(base
=ctx
.krbtgt_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["samAccountName"])
429 ctx
.krbtgt_name
= res
[0]["samAccountName"][0]
431 print("Got krbtgt_name=%s" % ctx
.krbtgt_name
)
434 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
435 m
["msDS-krbTgtLink"] = ldb
.MessageElement(ctx
.krbtgt_dn
,
436 ldb
.FLAG_MOD_REPLACE
, "msDS-krbTgtLink")
439 ctx
.new_krbtgt_dn
= "CN=%s,CN=Users,%s" % (ctx
.krbtgt_name
, ctx
.base_dn
)
440 print("Renaming %s to %s" % (ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
))
441 ctx
.samdb
.rename(ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
)
443 def drsuapi_connect(ctx
):
444 '''make a DRSUAPI connection to the naming master'''
445 binding_options
= "seal"
446 if ctx
.lp
.log_level() >= 9:
447 binding_options
+= ",print"
448 binding_string
= "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
)
449 ctx
.drsuapi
= drsuapi
.drsuapi(binding_string
, ctx
.lp
, ctx
.creds
)
450 (ctx
.drsuapi_handle
, ctx
.bind_supported_extensions
) = drs_utils
.drs_DsBind(ctx
.drsuapi
)
452 def create_tmp_samdb(ctx
):
453 '''create a temporary samdb object for schema queries'''
454 ctx
.tmp_schema
= Schema(ctx
.domsid
,
455 schemadn
=ctx
.schema_dn
)
456 ctx
.tmp_samdb
= SamDB(session_info
=system_session(), url
=None, auto_connect
=False,
457 credentials
=ctx
.creds
, lp
=ctx
.lp
, global_schema
=False,
459 ctx
.tmp_samdb
.set_schema(ctx
.tmp_schema
)
461 def build_DsReplicaAttribute(ctx
, attrname
, attrvalue
):
462 '''build a DsReplicaAttributeCtr object'''
463 r
= drsuapi
.DsReplicaAttribute()
464 r
.attid
= ctx
.tmp_samdb
.get_attid_from_lDAPDisplayName(attrname
)
468 def DsAddEntry(ctx
, recs
):
469 '''add a record via the DRSUAPI DsAddEntry call'''
470 if ctx
.drsuapi
is None:
471 ctx
.drsuapi_connect()
472 if ctx
.tmp_samdb
is None:
473 ctx
.create_tmp_samdb()
477 id = drsuapi
.DsReplicaObjectIdentifier()
484 if not isinstance(rec
[a
], list):
488 rattr
= ctx
.tmp_samdb
.dsdb_DsReplicaAttribute(ctx
.tmp_samdb
, a
, v
)
491 attribute_ctr
= drsuapi
.DsReplicaAttributeCtr()
492 attribute_ctr
.num_attributes
= len(attrs
)
493 attribute_ctr
.attributes
= attrs
495 object = drsuapi
.DsReplicaObject()
496 object.identifier
= id
497 object.attribute_ctr
= attribute_ctr
499 list_object
= drsuapi
.DsReplicaObjectListItem()
500 list_object
.object = object
501 objects
.append(list_object
)
503 req2
= drsuapi
.DsAddEntryRequest2()
504 req2
.first_object
= objects
[0]
505 prev
= req2
.first_object
506 for o
in objects
[1:]:
510 (level
, ctr
) = ctx
.drsuapi
.DsAddEntry(ctx
.drsuapi_handle
, 2, req2
)
512 if ctr
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
513 print("DsAddEntry failed with dir_err %u" % ctr
.dir_err
)
514 raise RuntimeError("DsAddEntry failed")
515 if ctr
.extended_err
[0] != werror
.WERR_SUCCESS
:
516 print("DsAddEntry failed with status %s info %s" % (ctr
.extended_err
))
517 raise RuntimeError("DsAddEntry failed")
520 raise RuntimeError("expected err_ver 1, got %u" % ctr
.err_ver
)
521 if ctr
.err_data
.status
[0] != werror
.WERR_SUCCESS
:
522 if ctr
.err_data
.info
is None:
523 print("DsAddEntry failed with status %s, info omitted" % (ctr
.err_data
.status
[1]))
525 print("DsAddEntry failed with status %s info %s" % (ctr
.err_data
.status
[1],
526 ctr
.err_data
.info
.extended_err
))
527 raise RuntimeError("DsAddEntry failed")
528 if ctr
.err_data
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
529 print("DsAddEntry failed with dir_err %u" % ctr
.err_data
.dir_err
)
530 raise RuntimeError("DsAddEntry failed")
534 def join_ntdsdsa_obj(ctx
):
535 '''return the ntdsdsa object to add'''
537 print("Adding %s" % ctx
.ntds_dn
)
540 "objectclass" : "nTDSDSA",
541 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
542 "dMDLocation" : ctx
.schema_dn
}
544 nc_list
= [ ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
546 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
547 rec
["msDS-Behavior-Version"] = str(samba
.dsdb
.DS_DOMAIN_FUNCTION_2008_R2
)
549 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
550 rec
["msDS-HasDomainNCs"] = ctx
.base_dn
553 rec
["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx
.schema_dn
554 rec
["msDS-HasFullReplicaNCs"] = ctx
.full_nc_list
555 rec
["options"] = "37"
557 rec
["objectCategory"] = "CN=NTDS-DSA,%s" % ctx
.schema_dn
558 rec
["HasMasterNCs"] = []
560 if nc
in ctx
.full_nc_list
:
561 rec
["HasMasterNCs"].append(nc
)
562 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
563 rec
["msDS-HasMasterNCs"] = ctx
.full_nc_list
565 rec
["invocationId"] = ndr_pack(ctx
.invocation_id
)
569 def join_add_ntdsdsa(ctx
):
570 '''add the ntdsdsa object'''
572 rec
= ctx
.join_ntdsdsa_obj()
573 if ctx
.forced_local_samdb
:
574 ctx
.samdb
.add(rec
, controls
=["relax:0"])
576 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
578 ctx
.DsAddEntry([rec
])
580 # find the GUID of our NTDS DN
581 res
= ctx
.samdb
.search(base
=ctx
.ntds_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["objectGUID"])
582 ctx
.ntds_guid
= misc
.GUID(ctx
.samdb
.schema_format_value("objectGUID", res
[0]["objectGUID"][0]))
584 def join_add_objects(ctx
, specified_sid
=None):
585 '''add the various objects needed for the join'''
587 print("Adding %s" % ctx
.acct_dn
)
590 "objectClass": "computer",
591 "displayname": ctx
.samname
,
592 "samaccountname" : ctx
.samname
,
593 "userAccountControl" : str(ctx
.userAccountControl | samba
.dsdb
.UF_ACCOUNTDISABLE
),
594 "dnshostname" : ctx
.dnshostname
}
595 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2008
:
596 rec
['msDS-SupportedEncryptionTypes'] = str(samba
.dsdb
.ENC_ALL_TYPES
)
597 elif ctx
.promote_existing
:
598 rec
['msDS-SupportedEncryptionTypes'] = []
600 rec
["managedby"] = ctx
.managedby
601 elif ctx
.promote_existing
:
602 rec
["managedby"] = []
604 if ctx
.never_reveal_sid
:
605 rec
["msDS-NeverRevealGroup"] = ctx
.never_reveal_sid
606 elif ctx
.promote_existing
:
607 rec
["msDS-NeverRevealGroup"] = []
610 rec
["msDS-RevealOnDemandGroup"] = ctx
.reveal_sid
611 elif ctx
.promote_existing
:
612 rec
["msDS-RevealOnDemandGroup"] = []
615 rec
["objectSid"] = ndr_pack(specified_sid
)
617 if ctx
.promote_existing
:
618 if ctx
.promote_from_dn
!= ctx
.acct_dn
:
619 ctx
.samdb
.rename(ctx
.promote_from_dn
, ctx
.acct_dn
)
620 ctx
.samdb
.modify(ldb
.Message
.from_dict(ctx
.samdb
, rec
, ldb
.FLAG_MOD_REPLACE
))
623 if specified_sid
is not None:
624 controls
= ["relax:0"]
625 ctx
.samdb
.add(rec
, controls
=controls
)
628 ctx
.add_krbtgt_account()
631 print("Adding %s" % ctx
.server_dn
)
634 "objectclass" : "server",
635 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
636 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
637 samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
638 samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
639 # windows seems to add the dnsHostName later
640 "dnsHostName" : ctx
.dnshostname
}
643 rec
["serverReference"] = ctx
.acct_dn
648 # the rest is done after replication
653 ctx
.join_add_ntdsdsa()
655 # Add the Replica-Locations or RO-Replica-Locations attributes
656 # TODO Is this supposed to be for the schema partition too?
657 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.domaindns_zone
)
658 domain
= (ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
660 base
=ctx
.samdb
.get_partitions_dn(),
661 expression
=expr
), ctx
.domaindns_zone
)
663 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.forestdns_zone
)
664 forest
= (ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
666 base
=ctx
.samdb
.get_partitions_dn(),
667 expression
=expr
), ctx
.forestdns_zone
)
669 for part
, zone
in (domain
, forest
):
670 if zone
not in ctx
.nc_list
:
676 attr
= "msDS-NC-Replica-Locations"
678 attr
= "msDS-NC-RO-Replica-Locations"
680 m
[attr
] = ldb
.MessageElement(ctx
.ntds_dn
,
681 ldb
.FLAG_MOD_ADD
, attr
)
684 if ctx
.connection_dn
is not None:
685 print("Adding %s" % ctx
.connection_dn
)
687 "dn" : ctx
.connection_dn
,
688 "objectclass" : "nTDSConnection",
689 "enabledconnection" : "TRUE",
691 "fromServer" : ctx
.dc_ntds_dn
}
695 print("Adding SPNs to %s" % ctx
.acct_dn
)
697 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
698 for i
in range(len(ctx
.SPNs
)):
699 ctx
.SPNs
[i
] = ctx
.SPNs
[i
].replace("$NTDSGUID", str(ctx
.ntds_guid
))
700 m
["servicePrincipalName"] = ldb
.MessageElement(ctx
.SPNs
,
701 ldb
.FLAG_MOD_REPLACE
,
702 "servicePrincipalName")
705 # The account password set operation should normally be done over
706 # LDAP. Windows 2000 DCs however allow this only with SSL
707 # connections which are hard to set up and otherwise refuse with
708 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
710 print("Setting account password for %s" % ctx
.samname
)
712 ctx
.samdb
.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
713 % ldb
.binary_encode(ctx
.samname
),
715 force_change_at_next_login
=False,
716 username
=ctx
.samname
)
717 except ldb
.LdbError
as e2
:
719 if num
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
721 ctx
.net
.set_password(account_name
=ctx
.samname
,
722 domain_name
=ctx
.domain_name
,
723 newpassword
=ctx
.acct_pass
.encode('utf-8'))
725 res
= ctx
.samdb
.search(base
=ctx
.acct_dn
, scope
=ldb
.SCOPE_BASE
,
726 attrs
=["msDS-KeyVersionNumber",
728 if "msDS-KeyVersionNumber" in res
[0]:
729 ctx
.key_version_number
= int(res
[0]["msDS-KeyVersionNumber"][0])
731 ctx
.key_version_number
= None
733 ctx
.new_dc_account_sid
= ndr_unpack(security
.dom_sid
,
734 res
[0]["objectSid"][0])
736 print("Enabling account")
738 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
739 m
["userAccountControl"] = ldb
.MessageElement(str(ctx
.userAccountControl
),
740 ldb
.FLAG_MOD_REPLACE
,
741 "userAccountControl")
744 if ctx
.dns_backend
.startswith("BIND9_"):
745 ctx
.dnspass
= samba
.generate_random_password(128, 255)
747 recs
= ctx
.samdb
.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
748 {"DNSDOMAIN": ctx
.dnsdomain
,
749 "DOMAINDN": ctx
.base_dn
,
750 "HOSTNAME" : ctx
.myname
,
751 "DNSPASS_B64": b64encode(ctx
.dnspass
.encode('utf-16-le')).decode('utf8'),
752 "DNSNAME" : ctx
.dnshostname
}))
753 for changetype
, msg
in recs
:
754 assert changetype
== ldb
.CHANGETYPE_NONE
755 dns_acct_dn
= msg
["dn"]
756 print("Adding DNS account %s with dns/ SPN" % msg
["dn"])
758 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
759 del msg
["clearTextPassword"]
760 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
761 del msg
["isCriticalSystemObject"]
762 # Disable account until password is set
763 msg
["userAccountControl"] = str(samba
.dsdb
.UF_NORMAL_ACCOUNT |
764 samba
.dsdb
.UF_ACCOUNTDISABLE
)
767 except ldb
.LdbError
as e
:
769 if num
!= ldb
.ERR_ENTRY_ALREADY_EXISTS
:
772 # The account password set operation should normally be done over
773 # LDAP. Windows 2000 DCs however allow this only with SSL
774 # connections which are hard to set up and otherwise refuse with
775 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
777 print("Setting account password for dns-%s" % ctx
.myname
)
779 ctx
.samdb
.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
780 % ldb
.binary_encode(ctx
.myname
),
782 force_change_at_next_login
=False,
783 username
=ctx
.samname
)
784 except ldb
.LdbError
as e3
:
786 if num
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
788 ctx
.net
.set_password(account_name
="dns-%s" % ctx
.myname
,
789 domain_name
=ctx
.domain_name
,
790 newpassword
=ctx
.dnspass
)
792 res
= ctx
.samdb
.search(base
=dns_acct_dn
, scope
=ldb
.SCOPE_BASE
,
793 attrs
=["msDS-KeyVersionNumber"])
794 if "msDS-KeyVersionNumber" in res
[0]:
795 ctx
.dns_key_version_number
= int(res
[0]["msDS-KeyVersionNumber"][0])
797 ctx
.dns_key_version_number
= None
799 def join_add_objects2(ctx
):
800 """add the various objects needed for the join, for subdomains post replication"""
802 print("Adding %s" % ctx
.partition_dn
)
803 name_map
= {'SubdomainAdmins': "%s-%s" % (str(ctx
.domsid
), security
.DOMAIN_RID_ADMINS
)}
804 sd_binary
= descriptor
.get_paritions_crossref_subdomain_descriptor(ctx
.forestsid
, name_map
=name_map
)
806 "dn" : ctx
.partition_dn
,
807 "objectclass" : "crossRef",
808 "objectCategory" : "CN=Cross-Ref,%s" % ctx
.schema_dn
,
809 "nCName" : ctx
.base_dn
,
810 "nETBIOSName" : ctx
.domain_name
,
811 "dnsRoot": ctx
.dnsdomain
,
812 "trustParent" : ctx
.parent_partition_dn
,
813 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_NC|samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_DOMAIN
),
814 "ntSecurityDescriptor" : sd_binary
,
817 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
818 rec
["msDS-Behavior-Version"] = str(ctx
.behavior_version
)
820 rec2
= ctx
.join_ntdsdsa_obj()
822 objects
= ctx
.DsAddEntry([rec
, rec2
])
823 if len(objects
) != 2:
824 raise DCJoinException("Expected 2 objects from DsAddEntry")
826 ctx
.ntds_guid
= objects
[1].guid
828 print("Replicating partition DN")
829 ctx
.repl
.replicate(ctx
.partition_dn
,
830 misc
.GUID("00000000-0000-0000-0000-000000000000"),
832 exop
=drsuapi
.DRSUAPI_EXOP_REPL_OBJ
,
833 replica_flags
=drsuapi
.DRSUAPI_DRS_WRIT_REP
)
835 print("Replicating NTDS DN")
836 ctx
.repl
.replicate(ctx
.ntds_dn
,
837 misc
.GUID("00000000-0000-0000-0000-000000000000"),
839 exop
=drsuapi
.DRSUAPI_EXOP_REPL_OBJ
,
840 replica_flags
=drsuapi
.DRSUAPI_DRS_WRIT_REP
)
842 def join_provision(ctx
):
843 """Provision the local SAM."""
845 print("Calling bare provision")
847 smbconf
= ctx
.lp
.configfile
849 presult
= provision(ctx
.logger
, system_session(), smbconf
=smbconf
,
850 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_DRS
, realm
=ctx
.realm
,
851 rootdn
=ctx
.root_dn
, domaindn
=ctx
.base_dn
,
852 schemadn
=ctx
.schema_dn
, configdn
=ctx
.config_dn
,
853 serverdn
=ctx
.server_dn
, domain
=ctx
.domain_name
,
854 hostname
=ctx
.myname
, domainsid
=ctx
.domsid
,
855 machinepass
=ctx
.acct_pass
, serverrole
="active directory domain controller",
856 sitename
=ctx
.site
, lp
=ctx
.lp
, ntdsguid
=ctx
.ntds_guid
,
857 use_ntvfs
=ctx
.use_ntvfs
, dns_backend
=ctx
.dns_backend
,
858 plaintext_secrets
=ctx
.plaintext_secrets
,
859 backend_store
=ctx
.backend_store
861 print("Provision OK for domain DN %s" % presult
.domaindn
)
862 ctx
.local_samdb
= presult
.samdb
864 ctx
.paths
= presult
.paths
865 ctx
.names
= presult
.names
867 # Fix up the forestsid, it may be different if we are joining as a subdomain
868 ctx
.names
.forestsid
= ctx
.forestsid
870 def join_provision_own_domain(ctx
):
871 """Provision the local SAM."""
873 # we now operate exclusively on the local database, which
874 # we need to reopen in order to get the newly created schema
875 print("Reconnecting to local samdb")
876 ctx
.samdb
= SamDB(url
=ctx
.local_samdb
.url
,
877 session_info
=system_session(),
878 lp
=ctx
.local_samdb
.lp
,
880 ctx
.samdb
.set_invocation_id(str(ctx
.invocation_id
))
881 ctx
.local_samdb
= ctx
.samdb
883 ctx
.logger
.info("Finding domain GUID from ncName")
884 res
= ctx
.local_samdb
.search(base
=ctx
.partition_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=['ncName'],
885 controls
=["extended_dn:1:1", "reveal_internals:0"])
887 if 'nCName' not in res
[0]:
888 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx
.partition_dn
, ctx
.samdb
.url
))
891 ctx
.names
.domainguid
= str(misc
.GUID(ldb
.Dn(ctx
.samdb
, res
[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
893 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res
[0]['ncName'][0])
895 ctx
.logger
.info("Got domain GUID %s" % ctx
.names
.domainguid
)
897 ctx
.logger
.info("Calling own domain provision")
899 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
901 presult
= provision_fill(ctx
.local_samdb
, secrets_ldb
,
902 ctx
.logger
, ctx
.names
, ctx
.paths
,
903 dom_for_fun_level
=DS_DOMAIN_FUNCTION_2003
,
904 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_SUBDOMAIN
,
905 machinepass
=ctx
.acct_pass
, serverrole
="active directory domain controller",
906 lp
=ctx
.lp
, hostip
=ctx
.names
.hostip
, hostip6
=ctx
.names
.hostip6
,
907 dns_backend
=ctx
.dns_backend
, adminpass
=ctx
.adminpass
)
908 print("Provision OK for domain %s" % ctx
.names
.dnsdomain
)
910 def create_replicator(ctx
, repl_creds
, binding_options
):
911 '''Creates a new DRS object for managing replications'''
912 return drs_utils
.drs_Replicate(
913 "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
914 ctx
.lp
, repl_creds
, ctx
.local_samdb
, ctx
.invocation_id
)
916 def join_replicate(ctx
):
917 """Replicate the SAM."""
919 print("Starting replication")
920 ctx
.local_samdb
.transaction_start()
922 source_dsa_invocation_id
= misc
.GUID(ctx
.samdb
.get_invocation_id())
923 if ctx
.ntds_guid
is None:
924 print("Using DS_BIND_GUID_W2K3")
925 destination_dsa_guid
= misc
.GUID(drsuapi
.DRSUAPI_DS_BIND_GUID_W2K3
)
927 destination_dsa_guid
= ctx
.ntds_guid
930 repl_creds
= Credentials()
931 repl_creds
.guess(ctx
.lp
)
932 repl_creds
.set_kerberos_state(DONT_USE_KERBEROS
)
933 repl_creds
.set_username(ctx
.samname
)
934 repl_creds
.set_password(ctx
.acct_pass
.encode('utf-8'))
936 repl_creds
= ctx
.creds
938 binding_options
= "seal"
939 if ctx
.lp
.log_level() >= 9:
940 binding_options
+= ",print"
942 repl
= ctx
.create_replicator(repl_creds
, binding_options
)
944 repl
.replicate(ctx
.schema_dn
, source_dsa_invocation_id
,
945 destination_dsa_guid
, schema
=True, rodc
=ctx
.RODC
,
946 replica_flags
=ctx
.replica_flags
)
947 repl
.replicate(ctx
.config_dn
, source_dsa_invocation_id
,
948 destination_dsa_guid
, rodc
=ctx
.RODC
,
949 replica_flags
=ctx
.replica_flags
)
950 if not ctx
.subdomain
:
951 # Replicate first the critical object for the basedn
952 if not ctx
.domain_replica_flags
& drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
:
953 print("Replicating critical objects from the base DN of the domain")
954 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
955 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
956 destination_dsa_guid
, rodc
=ctx
.RODC
,
957 replica_flags
=ctx
.domain_replica_flags
)
958 ctx
.domain_replica_flags ^
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
959 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
960 destination_dsa_guid
, rodc
=ctx
.RODC
,
961 replica_flags
=ctx
.domain_replica_flags
)
962 print("Done with always replicated NC (base, config, schema)")
964 # At this point we should already have an entry in the ForestDNS
965 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
966 # indicate that we hold a replica for this NC.
967 for nc
in (ctx
.domaindns_zone
, ctx
.forestdns_zone
):
968 if nc
in ctx
.nc_list
:
969 print("Replicating %s" % (str(nc
)))
970 repl
.replicate(nc
, source_dsa_invocation_id
,
971 destination_dsa_guid
, rodc
=ctx
.RODC
,
972 replica_flags
=ctx
.replica_flags
)
975 repl
.replicate(ctx
.acct_dn
, source_dsa_invocation_id
,
976 destination_dsa_guid
,
977 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
978 repl
.replicate(ctx
.new_krbtgt_dn
, source_dsa_invocation_id
,
979 destination_dsa_guid
,
980 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
981 elif ctx
.rid_manager_dn
!= None:
982 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
984 repl
.replicate(ctx
.rid_manager_dn
, source_dsa_invocation_id
,
985 destination_dsa_guid
,
986 exop
=drsuapi
.DRSUAPI_EXOP_FSMO_RID_ALLOC
)
987 except samba
.DsExtendedError
as e1
:
988 (enum
, estr
) = e1
.args
989 if enum
== drsuapi
.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER
:
990 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx
.server
)
991 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
996 ctx
.source_dsa_invocation_id
= source_dsa_invocation_id
997 ctx
.destination_dsa_guid
= destination_dsa_guid
999 print("Committing SAM database")
1001 ctx
.local_samdb
.transaction_cancel()
1004 ctx
.local_samdb
.transaction_commit()
1006 def send_DsReplicaUpdateRefs(ctx
, dn
):
1007 r
= drsuapi
.DsReplicaUpdateRefsRequest1()
1008 r
.naming_context
= drsuapi
.DsReplicaObjectIdentifier()
1009 r
.naming_context
.dn
= str(dn
)
1010 r
.naming_context
.guid
= misc
.GUID("00000000-0000-0000-0000-000000000000")
1011 r
.naming_context
.sid
= security
.dom_sid("S-0-0")
1012 r
.dest_dsa_guid
= ctx
.ntds_guid
1013 r
.dest_dsa_dns_name
= "%s._msdcs.%s" % (str(ctx
.ntds_guid
), ctx
.dnsforest
)
1014 r
.options
= drsuapi
.DRSUAPI_DRS_ADD_REF | drsuapi
.DRSUAPI_DRS_DEL_REF
1016 r
.options |
= drsuapi
.DRSUAPI_DRS_WRIT_REP
1018 if ctx
.drsuapi
is None:
1019 ctx
.drsuapi_connect()
1021 ctx
.drsuapi
.DsReplicaUpdateRefs(ctx
.drsuapi_handle
, 1, r
)
1023 def join_add_dns_records(ctx
):
1024 """Remotely Add a DNS record to the target DC. We assume that if we
1025 replicate DNS that the server holds the DNS roles and can accept
1028 This avoids issues getting replication going after the DC
1029 first starts as the rest of the domain does not have to
1030 wait for samba_dnsupdate to run successfully.
1032 Specifically, we add the records implied by the DsReplicaUpdateRefs
1035 We do not just run samba_dnsupdate as we want to strictly
1036 operate against the DC we just joined:
1037 - We do not want to query another DNS server
1038 - We do not want to obtain a Kerberos ticket
1039 (as the KDC we select may not be the DC we just joined,
1040 and so may not be in sync with the password we just set)
1041 - We do not wish to set the _ldap records until we have started
1042 - We do not wish to use NTLM (the --use-samba-tool mode forces
1047 client_version
= dnsserver
.DNS_CLIENT_VERSION_LONGHORN
1048 record_type
= dnsp
.DNS_TYPE_A
1049 select_flags
= dnsserver
.DNS_RPC_VIEW_AUTHORITY_DATA |\
1050 dnsserver
.DNS_RPC_VIEW_NO_CHILDREN
1052 zone
= ctx
.dnsdomain
1053 msdcs_zone
= "_msdcs.%s" % ctx
.dnsforest
1055 msdcs_cname
= str(ctx
.ntds_guid
)
1056 cname_target
= "%s.%s" % (name
, zone
)
1057 IPs
= samba
.interface_ips(ctx
.lp
, ctx
.force_all_ips
)
1059 ctx
.logger
.info("Adding %d remote DNS records for %s.%s" % \
1060 (len(IPs
), name
, zone
))
1062 binding_options
= "sign"
1063 dns_conn
= dnsserver
.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
1069 sd_helper
= samba
.sd_utils
.SDUtils(ctx
.samdb
)
1071 change_owner_sd
= security
.descriptor()
1072 change_owner_sd
.owner_sid
= ctx
.new_dc_account_sid
1073 change_owner_sd
.group_sid
= security
.dom_sid("%s-%d" %
1075 security
.DOMAIN_RID_DCS
))
1077 # TODO: Remove any old records from the primary DNS name
1080 = dns_conn
.DnssrvEnumRecords2(client_version
,
1090 except WERRORError
as e
:
1091 if e
.args
[0] == werror
.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST
:
1097 for record
in rec
.records
:
1098 if record
.wType
== dnsp
.DNS_TYPE_A
or \
1099 record
.wType
== dnsp
.DNS_TYPE_AAAA
:
1101 del_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1102 del_rec_buf
.rec
= record
1104 dns_conn
.DnssrvUpdateRecord2(client_version
,
1111 except WERRORError
as e
:
1112 if e
.args
[0] == werror
.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST
:
1118 if IP
.find(':') != -1:
1119 ctx
.logger
.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1121 rec
= AAAARecord(IP
)
1123 ctx
.logger
.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1128 add_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1129 add_rec_buf
.rec
= rec
1130 dns_conn
.DnssrvUpdateRecord2(client_version
,
1139 domaindns_zone_dn
= ldb
.Dn(ctx
.samdb
, ctx
.domaindns_zone
)
1140 (ctx
.dns_a_dn
, ldap_record
) \
1141 = ctx
.samdb
.dns_lookup("%s.%s" % (name
, zone
),
1142 dns_partition
=domaindns_zone_dn
)
1144 # Make the DC own the DNS record, not the administrator
1145 sd_helper
.modify_sd_on_dn(ctx
.dns_a_dn
, change_owner_sd
,
1146 controls
=["sd_flags:1:%d"
1147 % (security
.SECINFO_OWNER
1148 | security
.SECINFO_GROUP
)])
1152 ctx
.logger
.info("Adding DNS CNAME record %s.%s for %s"
1153 % (msdcs_cname
, msdcs_zone
, cname_target
))
1155 add_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1156 rec
= CNameRecord(cname_target
)
1157 add_rec_buf
.rec
= rec
1158 dns_conn
.DnssrvUpdateRecord2(client_version
,
1166 forestdns_zone_dn
= ldb
.Dn(ctx
.samdb
, ctx
.forestdns_zone
)
1167 (ctx
.dns_cname_dn
, ldap_record
) \
1168 = ctx
.samdb
.dns_lookup("%s.%s" % (msdcs_cname
, msdcs_zone
),
1169 dns_partition
=forestdns_zone_dn
)
1171 # Make the DC own the DNS record, not the administrator
1172 sd_helper
.modify_sd_on_dn(ctx
.dns_cname_dn
, change_owner_sd
,
1173 controls
=["sd_flags:1:%d"
1174 % (security
.SECINFO_OWNER
1175 | security
.SECINFO_GROUP
)])
1177 ctx
.logger
.info("All other DNS records (like _ldap SRV records) " +
1178 "will be created samba_dnsupdate on first startup")
1181 def join_replicate_new_dns_records(ctx
):
1182 for nc
in (ctx
.domaindns_zone
, ctx
.forestdns_zone
):
1183 if nc
in ctx
.nc_list
:
1184 ctx
.logger
.info("Replicating new DNS records in %s" % (str(nc
)))
1185 ctx
.repl
.replicate(nc
, ctx
.source_dsa_invocation_id
,
1186 ctx
.ntds_guid
, rodc
=ctx
.RODC
,
1187 replica_flags
=ctx
.replica_flags
,
1192 def join_finalise(ctx
):
1193 """Finalise the join, mark us synchronised and setup secrets db."""
1195 # FIXME we shouldn't do this in all cases
1197 # If for some reasons we joined in another site than the one of
1198 # DC we just replicated from then we don't need to send the updatereplicateref
1199 # as replication between sites is time based and on the initiative of the
1201 ctx
.logger
.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1202 for nc
in ctx
.nc_list
:
1203 ctx
.send_DsReplicaUpdateRefs(nc
)
1206 print("Setting RODC invocationId")
1207 ctx
.local_samdb
.set_invocation_id(str(ctx
.invocation_id
))
1208 ctx
.local_samdb
.set_opaque_integer("domainFunctionality",
1209 ctx
.behavior_version
)
1211 m
.dn
= ldb
.Dn(ctx
.local_samdb
, "%s" % ctx
.ntds_dn
)
1212 m
["invocationId"] = ldb
.MessageElement(ndr_pack(ctx
.invocation_id
),
1213 ldb
.FLAG_MOD_REPLACE
,
1215 ctx
.local_samdb
.modify(m
)
1217 # Note: as RODC the invocationId is only stored
1218 # on the RODC itself, the other DCs never see it.
1220 # Thats is why we fix up the replPropertyMetaData stamp
1221 # for the 'invocationId' attribute, we need to change
1222 # the 'version' to '0', this is what windows 2008r2 does as RODC
1224 # This means if the object on a RWDC ever gets a invocationId
1225 # attribute, it will have version '1' (or higher), which will
1226 # will overwrite the RODC local value.
1227 ctx
.local_samdb
.set_attribute_replmetadata_version(m
.dn
,
1231 ctx
.logger
.info("Setting isSynchronized and dsServiceName")
1233 m
.dn
= ldb
.Dn(ctx
.local_samdb
, '@ROOTDSE')
1234 m
["isSynchronized"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isSynchronized")
1236 guid
= ctx
.ntds_guid
1237 m
["dsServiceName"] = ldb
.MessageElement("<GUID=%s>" % str(guid
),
1238 ldb
.FLAG_MOD_REPLACE
, "dsServiceName")
1239 ctx
.local_samdb
.modify(m
)
1244 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
1246 ctx
.logger
.info("Setting up secrets database")
1247 secretsdb_self_join(secrets_ldb
, domain
=ctx
.domain_name
,
1249 dnsdomain
=ctx
.dnsdomain
,
1250 netbiosname
=ctx
.myname
,
1251 domainsid
=ctx
.domsid
,
1252 machinepass
=ctx
.acct_pass
,
1253 secure_channel_type
=ctx
.secure_channel_type
,
1254 key_version_number
=ctx
.key_version_number
)
1256 if ctx
.dns_backend
.startswith("BIND9_"):
1257 setup_bind9_dns(ctx
.local_samdb
, secrets_ldb
,
1258 ctx
.names
, ctx
.paths
, ctx
.lp
, ctx
.logger
,
1259 dns_backend
=ctx
.dns_backend
,
1260 dnspass
=ctx
.dnspass
, os_level
=ctx
.behavior_version
,
1261 targetdir
=ctx
.targetdir
,
1262 key_version_number
=ctx
.dns_key_version_number
)
1264 def join_setup_trusts(ctx
):
1265 """provision the local SAM."""
1267 print("Setup domain trusts with server %s" % ctx
.server
)
1268 binding_options
= "" # why doesn't signing work here? w2k8r2 claims no session key
1269 lsaconn
= lsa
.lsarpc("ncacn_np:%s[%s]" % (ctx
.server
, binding_options
),
1272 objectAttr
= lsa
.ObjectAttribute()
1273 objectAttr
.sec_qos
= lsa
.QosInfo()
1275 pol_handle
= lsaconn
.OpenPolicy2(''.decode('utf-8'),
1276 objectAttr
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
1278 info
= lsa
.TrustDomainInfoInfoEx()
1279 info
.domain_name
.string
= ctx
.dnsdomain
1280 info
.netbios_name
.string
= ctx
.domain_name
1281 info
.sid
= ctx
.domsid
1282 info
.trust_direction
= lsa
.LSA_TRUST_DIRECTION_INBOUND | lsa
.LSA_TRUST_DIRECTION_OUTBOUND
1283 info
.trust_type
= lsa
.LSA_TRUST_TYPE_UPLEVEL
1284 info
.trust_attributes
= lsa
.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1287 oldname
= lsa
.String()
1288 oldname
.string
= ctx
.dnsdomain
1289 oldinfo
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, oldname
,
1290 lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
1291 print("Removing old trust record for %s (SID %s)" % (ctx
.dnsdomain
, oldinfo
.info_ex
.sid
))
1292 lsaconn
.DeleteTrustedDomain(pol_handle
, oldinfo
.info_ex
.sid
)
1293 except RuntimeError:
1296 password_blob
= string_to_byte_array(ctx
.trustdom_pass
.encode('utf-16-le'))
1298 clear_value
= drsblobs
.AuthInfoClear()
1299 clear_value
.size
= len(password_blob
)
1300 clear_value
.password
= password_blob
1302 clear_authentication_information
= drsblobs
.AuthenticationInformation()
1303 clear_authentication_information
.LastUpdateTime
= samba
.unix2nttime(int(time
.time()))
1304 clear_authentication_information
.AuthType
= lsa
.TRUST_AUTH_TYPE_CLEAR
1305 clear_authentication_information
.AuthInfo
= clear_value
1307 authentication_information_array
= drsblobs
.AuthenticationInformationArray()
1308 authentication_information_array
.count
= 1
1309 authentication_information_array
.array
= [clear_authentication_information
]
1311 outgoing
= drsblobs
.trustAuthInOutBlob()
1313 outgoing
.current
= authentication_information_array
1315 trustpass
= drsblobs
.trustDomainPasswords()
1316 confounder
= [3] * 512
1318 for i
in range(512):
1319 confounder
[i
] = random
.randint(0, 255)
1321 trustpass
.confounder
= confounder
1323 trustpass
.outgoing
= outgoing
1324 trustpass
.incoming
= outgoing
1326 trustpass_blob
= ndr_pack(trustpass
)
1328 encrypted_trustpass
= arcfour_encrypt(lsaconn
.session_key
, trustpass_blob
)
1330 auth_blob
= lsa
.DATA_BUF2()
1331 auth_blob
.size
= len(encrypted_trustpass
)
1332 auth_blob
.data
= string_to_byte_array(encrypted_trustpass
)
1334 auth_info
= lsa
.TrustDomainInfoAuthInfoInternal()
1335 auth_info
.auth_blob
= auth_blob
1337 trustdom_handle
= lsaconn
.CreateTrustedDomainEx2(pol_handle
,
1340 security
.SEC_STD_DELETE
)
1343 "dn" : "cn=%s,cn=system,%s" % (ctx
.dnsforest
, ctx
.base_dn
),
1344 "objectclass" : "trustedDomain",
1345 "trustType" : str(info
.trust_type
),
1346 "trustAttributes" : str(info
.trust_attributes
),
1347 "trustDirection" : str(info
.trust_direction
),
1348 "flatname" : ctx
.forest_domain_name
,
1349 "trustPartner" : ctx
.dnsforest
,
1350 "trustAuthIncoming" : ndr_pack(outgoing
),
1351 "trustAuthOutgoing" : ndr_pack(outgoing
),
1352 "securityIdentifier" : ndr_pack(ctx
.forestsid
)
1354 ctx
.local_samdb
.add(rec
)
1357 "dn" : "cn=%s$,cn=users,%s" % (ctx
.forest_domain_name
, ctx
.base_dn
),
1358 "objectclass" : "user",
1359 "userAccountControl" : str(samba
.dsdb
.UF_INTERDOMAIN_TRUST_ACCOUNT
),
1360 "clearTextPassword" : ctx
.trustdom_pass
.encode('utf-16-le'),
1361 "samAccountName" : "%s$" % ctx
.forest_domain_name
1363 ctx
.local_samdb
.add(rec
)
1366 def build_nc_lists(ctx
):
1367 # nc_list is the list of naming context (NC) for which we will
1368 # replicate in and send a updateRef command to the partner DC
1370 # full_nc_list is the list of naming context (NC) we hold
1371 # read/write copies of. These are not subsets of each other.
1372 ctx
.nc_list
= [ ctx
.config_dn
, ctx
.schema_dn
]
1373 ctx
.full_nc_list
= [ ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
1375 if ctx
.subdomain
and ctx
.dns_backend
!= "NONE":
1376 ctx
.full_nc_list
+= [ctx
.domaindns_zone
]
1378 elif not ctx
.subdomain
:
1379 ctx
.nc_list
+= [ctx
.base_dn
]
1381 if ctx
.dns_backend
!= "NONE":
1382 ctx
.nc_list
+= [ctx
.domaindns_zone
]
1383 ctx
.nc_list
+= [ctx
.forestdns_zone
]
1384 ctx
.full_nc_list
+= [ctx
.domaindns_zone
]
1385 ctx
.full_nc_list
+= [ctx
.forestdns_zone
]
1388 ctx
.build_nc_lists()
1390 if ctx
.promote_existing
:
1391 ctx
.promote_possible()
1393 ctx
.cleanup_old_join()
1396 ctx
.join_add_objects()
1397 ctx
.join_provision()
1398 ctx
.join_replicate()
1400 ctx
.join_add_objects2()
1401 ctx
.join_provision_own_domain()
1402 ctx
.join_setup_trusts()
1404 if ctx
.dns_backend
!= "NONE":
1405 ctx
.join_add_dns_records()
1406 ctx
.join_replicate_new_dns_records()
1411 print("Join failed - cleaning up")
1414 ctx
.cleanup_old_join()
1418 def join_RODC(logger
=None, server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
1419 targetdir
=None, domain
=None, domain_critical_only
=False,
1420 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
1421 promote_existing
=False, plaintext_secrets
=False,
1422 backend_store
=None):
1423 """Join as a RODC."""
1425 ctx
= DCJoinContext(logger
, server
, creds
, lp
, site
, netbios_name
,
1426 targetdir
, domain
, machinepass
, use_ntvfs
, dns_backend
,
1427 promote_existing
, plaintext_secrets
,
1428 backend_store
=backend_store
)
1430 lp
.set("workgroup", ctx
.domain_name
)
1431 logger
.info("workgroup is %s" % ctx
.domain_name
)
1433 lp
.set("realm", ctx
.realm
)
1434 logger
.info("realm is %s" % ctx
.realm
)
1436 ctx
.krbtgt_dn
= "CN=krbtgt_%s,CN=Users,%s" % (ctx
.myname
, ctx
.base_dn
)
1438 # setup some defaults for accounts that should be replicated to this RODC
1439 ctx
.never_reveal_sid
= [
1440 "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_DENY
),
1441 "<SID=%s>" % security
.SID_BUILTIN_ADMINISTRATORS
,
1442 "<SID=%s>" % security
.SID_BUILTIN_SERVER_OPERATORS
,
1443 "<SID=%s>" % security
.SID_BUILTIN_BACKUP_OPERATORS
,
1444 "<SID=%s>" % security
.SID_BUILTIN_ACCOUNT_OPERATORS
]
1445 ctx
.reveal_sid
= "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_ALLOW
)
1447 mysid
= ctx
.get_mysid()
1448 admin_dn
= "<SID=%s>" % mysid
1449 ctx
.managedby
= admin_dn
1451 ctx
.userAccountControl
= (samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT |
1452 samba
.dsdb
.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1453 samba
.dsdb
.UF_PARTIAL_SECRETS_ACCOUNT
)
1455 ctx
.SPNs
.extend([ "RestrictedKrbHost/%s" % ctx
.myname
,
1456 "RestrictedKrbHost/%s" % ctx
.dnshostname
])
1458 ctx
.connection_dn
= "CN=RODC Connection (FRS),%s" % ctx
.ntds_dn
1459 ctx
.secure_channel_type
= misc
.SEC_CHAN_RODC
1461 ctx
.replica_flags |
= ( drsuapi
.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1462 drsuapi
.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP
)
1463 ctx
.domain_replica_flags
= ctx
.replica_flags
1464 if domain_critical_only
:
1465 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
1469 logger
.info("Joined domain %s (SID %s) as an RODC" % (ctx
.domain_name
, ctx
.domsid
))
1472 def join_DC(logger
=None, server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
1473 targetdir
=None, domain
=None, domain_critical_only
=False,
1474 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
1475 promote_existing
=False, plaintext_secrets
=False,
1476 backend_store
=None):
1478 ctx
= DCJoinContext(logger
, server
, creds
, lp
, site
, netbios_name
,
1479 targetdir
, domain
, machinepass
, use_ntvfs
, dns_backend
,
1480 promote_existing
, plaintext_secrets
,
1481 backend_store
=backend_store
)
1483 lp
.set("workgroup", ctx
.domain_name
)
1484 logger
.info("workgroup is %s" % ctx
.domain_name
)
1486 lp
.set("realm", ctx
.realm
)
1487 logger
.info("realm is %s" % ctx
.realm
)
1489 ctx
.userAccountControl
= samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT | samba
.dsdb
.UF_TRUSTED_FOR_DELEGATION
1491 ctx
.SPNs
.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx
.dnsdomain
)
1492 ctx
.secure_channel_type
= misc
.SEC_CHAN_BDC
1494 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1495 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1496 ctx
.domain_replica_flags
= ctx
.replica_flags
1497 if domain_critical_only
:
1498 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
1501 logger
.info("Joined domain %s (SID %s) as a DC" % (ctx
.domain_name
, ctx
.domsid
))
1503 def join_clone(logger
=None, server
=None, creds
=None, lp
=None,
1504 targetdir
=None, domain
=None, include_secrets
=False,
1505 dns_backend
="NONE"):
1506 """Creates a local clone of a remote DC."""
1507 ctx
= DCCloneContext(logger
, server
, creds
, lp
, targetdir
=targetdir
,
1508 domain
=domain
, dns_backend
=dns_backend
,
1509 include_secrets
=include_secrets
)
1511 lp
.set("workgroup", ctx
.domain_name
)
1512 logger
.info("workgroup is %s" % ctx
.domain_name
)
1514 lp
.set("realm", ctx
.realm
)
1515 logger
.info("realm is %s" % ctx
.realm
)
1518 logger
.info("Cloned domain %s (SID %s)" % (ctx
.domain_name
, ctx
.domsid
))
1521 def join_subdomain(logger
=None, server
=None, creds
=None, lp
=None, site
=None,
1522 netbios_name
=None, targetdir
=None, parent_domain
=None, dnsdomain
=None,
1523 netbios_domain
=None, machinepass
=None, adminpass
=None, use_ntvfs
=False,
1524 dns_backend
=None, plaintext_secrets
=False,
1525 backend_store
=None):
1527 ctx
= DCJoinContext(logger
, server
, creds
, lp
, site
, netbios_name
,
1528 targetdir
, parent_domain
, machinepass
, use_ntvfs
,
1529 dns_backend
, plaintext_secrets
,
1530 backend_store
=backend_store
)
1531 ctx
.subdomain
= True
1532 if adminpass
is None:
1533 ctx
.adminpass
= samba
.generate_random_password(12, 32)
1535 ctx
.adminpass
= adminpass
1536 ctx
.parent_domain_name
= ctx
.domain_name
1537 ctx
.domain_name
= netbios_domain
1538 ctx
.realm
= dnsdomain
1539 ctx
.parent_dnsdomain
= ctx
.dnsdomain
1540 ctx
.parent_partition_dn
= ctx
.get_parent_partition_dn()
1541 ctx
.dnsdomain
= dnsdomain
1542 ctx
.partition_dn
= "CN=%s,CN=Partitions,%s" % (ctx
.domain_name
, ctx
.config_dn
)
1543 ctx
.naming_master
= ctx
.get_naming_master()
1544 if ctx
.naming_master
!= ctx
.server
:
1545 logger
.info("Reconnecting to naming master %s" % ctx
.naming_master
)
1546 ctx
.server
= ctx
.naming_master
1547 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
1548 session_info
=system_session(),
1549 credentials
=ctx
.creds
, lp
=ctx
.lp
)
1550 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['dnsHostName'],
1552 ctx
.server
= res
[0]["dnsHostName"]
1553 logger
.info("DNS name of new naming master is %s" % ctx
.server
)
1555 ctx
.base_dn
= samba
.dn_from_dns_name(dnsdomain
)
1556 ctx
.forestsid
= ctx
.domsid
1557 ctx
.domsid
= security
.random_sid()
1559 ctx
.dnshostname
= "%s.%s" % (ctx
.myname
.lower(), ctx
.dnsdomain
)
1560 # Windows uses 240 bytes as UTF16 so we do
1561 ctx
.trustdom_pass
= samba
.generate_random_machine_password(120, 120)
1563 ctx
.userAccountControl
= samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT | samba
.dsdb
.UF_TRUSTED_FOR_DELEGATION
1565 ctx
.SPNs
.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx
.dnsdomain
)
1566 ctx
.secure_channel_type
= misc
.SEC_CHAN_BDC
1568 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1569 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1570 ctx
.domain_replica_flags
= ctx
.replica_flags
1573 ctx
.logger
.info("Created domain %s (SID %s) as a DC" % (ctx
.domain_name
, ctx
.domsid
))
1576 class DCCloneContext(DCJoinContext
):
1577 """Clones a remote DC."""
1579 def __init__(ctx
, logger
=None, server
=None, creds
=None, lp
=None,
1580 targetdir
=None, domain
=None, dns_backend
=None,
1581 include_secrets
=False):
1582 super(DCCloneContext
, ctx
).__init
__(logger
, server
, creds
, lp
,
1583 targetdir
=targetdir
, domain
=domain
,
1584 dns_backend
=dns_backend
)
1586 # As we don't want to create or delete these DNs, we set them to None
1587 ctx
.server_dn
= None
1590 ctx
.myname
= ctx
.server
.split('.')[0]
1591 ctx
.ntds_guid
= None
1592 ctx
.rid_manager_dn
= None
1595 ctx
.remote_dc_ntds_guid
= ctx
.samdb
.get_ntds_GUID()
1597 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1598 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1599 if not include_secrets
:
1600 ctx
.replica_flags |
= drsuapi
.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1601 ctx
.domain_replica_flags
= ctx
.replica_flags
1603 def join_finalise(ctx
):
1604 ctx
.logger
.info("Setting isSynchronized and dsServiceName")
1606 m
.dn
= ldb
.Dn(ctx
.local_samdb
, '@ROOTDSE')
1607 m
["isSynchronized"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
,
1610 # We want to appear to be the server we just cloned
1611 guid
= ctx
.remote_dc_ntds_guid
1612 m
["dsServiceName"] = ldb
.MessageElement("<GUID=%s>" % str(guid
),
1613 ldb
.FLAG_MOD_REPLACE
,
1615 ctx
.local_samdb
.modify(m
)
1618 ctx
.build_nc_lists()
1620 # When cloning a DC, we just want to provision a DC locally, then
1621 # grab the remote DC's entire DB via DRS replication
1622 ctx
.join_provision()
1623 ctx
.join_replicate()
1627 # Used to create a renamed backup of a DC. Renaming the domain means that the
1628 # cloned/backup DC can be started without interfering with the production DC.
1629 class DCCloneAndRenameContext(DCCloneContext
):
1630 """Clones a remote DC, renaming the domain along the way."""
1632 def __init__(ctx
, new_base_dn
, new_domain_name
, new_realm
, logger
=None,
1633 server
=None, creds
=None, lp
=None, targetdir
=None, domain
=None,
1634 dns_backend
=None, include_secrets
=True):
1635 super(DCCloneAndRenameContext
, ctx
).__init
__(logger
, server
, creds
, lp
,
1636 targetdir
=targetdir
,
1638 dns_backend
=dns_backend
,
1639 include_secrets
=include_secrets
)
1640 # store the new DN (etc) that we want the cloned DB to use
1641 ctx
.new_base_dn
= new_base_dn
1642 ctx
.new_domain_name
= new_domain_name
1643 ctx
.new_realm
= new_realm
1645 def create_replicator(ctx
, repl_creds
, binding_options
):
1646 """Creates a new DRS object for managing replications"""
1648 # We want to rename all the domain objects, and the simplest way to do
1649 # this is during replication. This is because the base DN of the top-
1650 # level replicated object will flow through to all the objects below it
1651 binding_str
= "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
)
1652 return drs_utils
.drs_ReplicateRenamer(binding_str
, ctx
.lp
, repl_creds
,
1655 ctx
.base_dn
, ctx
.new_base_dn
)
1657 def create_non_global_lp(ctx
, global_lp
):
1658 '''Creates a non-global LoadParm based on the global LP's settings'''
1660 # the samba code shares a global LoadParm by default. Here we create a
1661 # new LoadParm that retains the global settings, but any changes we
1662 # make to it won't automatically affect the rest of the samba code.
1663 # The easiest way to do this is to dump the global settings to a
1664 # temporary smb.conf file, and then load the temp file into a new
1665 # non-global LoadParm
1666 fd
, tmp_file
= tempfile
.mkstemp()
1667 global_lp
.dump(False, tmp_file
)
1668 local_lp
= samba
.param
.LoadParm(filename_for_non_global_lp
=tmp_file
)
1672 def rename_dn(ctx
, dn_str
):
1673 '''Uses string substitution to replace the base DN'''
1674 old_base_dn
= ctx
.base_dn
1675 return re
.sub('%s$' % old_base_dn
, ctx
.new_base_dn
, dn_str
)
1677 # we want to override the normal DCCloneContext's join_provision() so that
1678 # use the new domain DNs during the provision. We do this because:
1679 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1680 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1681 # we couldn't apply the renamed DRS objects during replication)
1682 def join_provision(ctx
):
1683 """Provision the local (renamed) SAM."""
1685 print("Provisioning the new (renamed) domain...")
1687 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1688 # to create a new smb.conf. By default, it uses the global LoadParm to
1689 # do this, and so it would overwrite the realm/domain values globally.
1690 # We still need the global LoadParm to retain the old domain's details,
1691 # so we can connect to (and clone) the existing DC.
1692 # So, copy the global settings into a non-global LoadParm, which we can
1693 # then pass into provision(). This generates a new smb.conf correctly,
1694 # without overwriting the global realm/domain values just yet.
1695 non_global_lp
= ctx
.create_non_global_lp(ctx
.lp
)
1697 # do the provision with the new/renamed domain DN values
1698 presult
= provision(ctx
.logger
, system_session(),
1699 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_DRS
,
1700 realm
=ctx
.new_realm
, lp
=non_global_lp
,
1701 rootdn
=ctx
.rename_dn(ctx
.root_dn
), domaindn
=ctx
.new_base_dn
,
1702 schemadn
=ctx
.rename_dn(ctx
.schema_dn
),
1703 configdn
=ctx
.rename_dn(ctx
.config_dn
),
1704 domain
=ctx
.new_domain_name
, domainsid
=ctx
.domsid
,
1705 serverrole
="active directory domain controller",
1706 dns_backend
=ctx
.dns_backend
)
1708 print("Provision OK for renamed domain DN %s" % presult
.domaindn
)
1709 ctx
.local_samdb
= presult
.samdb
1710 ctx
.paths
= presult
.paths