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
, string_to_byte_array
24 import ldb
, samba
, sys
, uuid
25 from samba
.ndr
import ndr_pack
, ndr_unpack
26 from samba
.dcerpc
import security
, drsuapi
, misc
, nbt
, lsa
, drsblobs
, dnsserver
, dnsp
27 from samba
.dsdb
import DS_DOMAIN_FUNCTION_2003
28 from samba
.credentials
import Credentials
, DONT_USE_KERBEROS
29 from samba
.provision
import secretsdb_self_join
, provision
, provision_fill
, FILL_DRS
, FILL_SUBDOMAIN
30 from samba
.provision
.common
import setup_path
31 from samba
.schema
import Schema
32 from samba
import descriptor
33 from samba
.net
import Net
34 from samba
.provision
.sambadns
import setup_bind9_dns
35 from samba
import read_and_sub_file
36 from samba
import werror
37 from base64
import b64encode
38 from samba
import WERRORError
39 from samba
.dnsserver
import ARecord
, AAAARecord
, PTRRecord
, CNameRecord
, NSRecord
, MXRecord
, SOARecord
, SRVRecord
, TXTRecord
40 from samba
import sd_utils
46 class DCJoinException(Exception):
48 def __init__(self
, msg
):
49 super(DCJoinException
, self
).__init
__("Can't join, error: %s" % msg
)
52 class dc_join(object):
53 """Perform a DC join."""
55 def __init__(ctx
, logger
=None, server
=None, creds
=None, lp
=None, site
=None,
56 netbios_name
=None, targetdir
=None, domain
=None,
57 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
58 promote_existing
=False, clone_only
=False,
59 plaintext_secrets
=False):
61 site
= "Default-First-Site-Name"
63 ctx
.clone_only
=clone_only
69 ctx
.targetdir
= targetdir
70 ctx
.use_ntvfs
= use_ntvfs
71 ctx
.plaintext_secrets
= plaintext_secrets
73 ctx
.promote_existing
= promote_existing
74 ctx
.promote_from_dn
= None
79 ctx
.creds
.set_gensec_features(creds
.get_gensec_features() | gensec
.FEATURE_SEAL
)
80 ctx
.net
= Net(creds
=ctx
.creds
, lp
=ctx
.lp
)
82 if server
is not None:
85 ctx
.logger
.info("Finding a writeable DC for domain '%s'" % domain
)
86 ctx
.server
= ctx
.find_dc(domain
)
87 ctx
.logger
.info("Found DC %s" % ctx
.server
)
89 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
90 session_info
=system_session(),
91 credentials
=ctx
.creds
, lp
=ctx
.lp
)
94 ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["dn"])
95 except ldb
.LdbError
as e4
:
96 (enum
, estr
) = e4
.args
97 raise DCJoinException(estr
)
100 ctx
.base_dn
= str(ctx
.samdb
.get_default_basedn())
101 ctx
.root_dn
= str(ctx
.samdb
.get_root_basedn())
102 ctx
.schema_dn
= str(ctx
.samdb
.get_schema_basedn())
103 ctx
.config_dn
= str(ctx
.samdb
.get_config_basedn())
104 ctx
.domsid
= security
.dom_sid(ctx
.samdb
.get_domain_sid())
105 ctx
.forestsid
= ctx
.domsid
106 ctx
.domain_name
= ctx
.get_domain_name()
107 ctx
.forest_domain_name
= ctx
.get_forest_domain_name()
108 ctx
.invocation_id
= misc
.GUID(str(uuid
.uuid4()))
110 ctx
.dc_ntds_dn
= ctx
.samdb
.get_dsServiceName()
111 ctx
.dc_dnsHostName
= ctx
.get_dnsHostName()
112 ctx
.behavior_version
= ctx
.get_behavior_version()
114 if machinepass
is not None:
115 ctx
.acct_pass
= machinepass
117 ctx
.acct_pass
= samba
.generate_random_machine_password(128, 255)
119 ctx
.dnsdomain
= ctx
.samdb
.domain_dns_name()
121 # As we don't want to create or delete these DNs, we set them to None
125 ctx
.myname
= ctx
.server
.split('.')[0]
127 ctx
.rid_manager_dn
= None
130 ctx
.remote_dc_ntds_guid
= ctx
.samdb
.get_ntds_GUID()
132 # work out the DNs of all the objects we will be adding
133 ctx
.myname
= netbios_name
134 ctx
.samname
= "%s$" % ctx
.myname
135 ctx
.server_dn
= "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx
.myname
, ctx
.site
, ctx
.config_dn
)
136 ctx
.ntds_dn
= "CN=NTDS Settings,%s" % ctx
.server_dn
137 ctx
.acct_dn
= "CN=%s,OU=Domain Controllers,%s" % (ctx
.myname
, ctx
.base_dn
)
138 ctx
.dnshostname
= "%s.%s" % (ctx
.myname
.lower(), ctx
.dnsdomain
)
139 ctx
.dnsforest
= ctx
.samdb
.forest_dns_name()
141 topology_base
= "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx
.base_dn
142 if ctx
.dn_exists(topology_base
):
143 ctx
.topology_dn
= "CN=%s,%s" % (ctx
.myname
, topology_base
)
145 ctx
.topology_dn
= None
147 ctx
.SPNs
= [ "HOST/%s" % ctx
.myname
,
148 "HOST/%s" % ctx
.dnshostname
,
149 "GC/%s/%s" % (ctx
.dnshostname
, ctx
.dnsforest
) ]
151 res_rid_manager
= ctx
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
152 attrs
=["rIDManagerReference"],
155 ctx
.rid_manager_dn
= res_rid_manager
[0]["rIDManagerReference"][0]
157 ctx
.domaindns_zone
= 'DC=DomainDnsZones,%s' % ctx
.base_dn
158 ctx
.forestdns_zone
= 'DC=ForestDnsZones,%s' % ctx
.root_dn
160 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.domaindns_zone
)
161 res_domaindns
= ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
163 base
=ctx
.samdb
.get_partitions_dn(),
165 if dns_backend
is None:
166 ctx
.dns_backend
= "NONE"
168 if len(res_domaindns
) == 0:
169 ctx
.dns_backend
= "NONE"
170 print "NO DNS zone information found in source domain, not replicating DNS"
172 ctx
.dns_backend
= dns_backend
174 ctx
.realm
= ctx
.dnsdomain
178 ctx
.replica_flags
= (drsuapi
.DRSUAPI_DRS_INIT_SYNC |
179 drsuapi
.DRSUAPI_DRS_PER_SYNC |
180 drsuapi
.DRSUAPI_DRS_GET_ANC |
181 drsuapi
.DRSUAPI_DRS_GET_NC_SIZE |
182 drsuapi
.DRSUAPI_DRS_NEVER_SYNCED
)
184 # these elements are optional
185 ctx
.never_reveal_sid
= None
186 ctx
.reveal_sid
= None
187 ctx
.connection_dn
= None
192 ctx
.subdomain
= False
194 ctx
.partition_dn
= None
197 ctx
.dns_cname_dn
= None
199 # Do not normally register 127. addresses but allow override for selftest
200 ctx
.force_all_ips
= False
202 def del_noerror(ctx
, dn
, recursive
=False):
205 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["dn"])
209 ctx
.del_noerror(r
.dn
, recursive
=True)
212 print "Deleted %s" % dn
216 def cleanup_old_accounts(ctx
, force
=False):
217 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
218 expression
='sAMAccountName=%s' % ldb
.binary_encode(ctx
.samname
),
219 attrs
=["msDS-krbTgtLink", "objectSID"])
224 creds
= Credentials()
227 creds
.set_machine_account(ctx
.lp
)
228 creds
.set_kerberos_state(ctx
.creds
.get_kerberos_state())
229 machine_samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
230 session_info
=system_session(),
231 credentials
=creds
, lp
=ctx
.lp
)
235 token_res
= machine_samdb
.search(scope
=ldb
.SCOPE_BASE
, base
="", attrs
=["tokenGroups"])
236 if token_res
[0]["tokenGroups"][0] \
237 == res
[0]["objectSID"][0]:
238 raise DCJoinException("Not removing account %s which "
239 "looks like a Samba DC account "
240 "maching the password we already have. "
241 "To override, remove secrets.ldb and secrets.tdb"
244 ctx
.del_noerror(res
[0].dn
, recursive
=True)
246 if "msDS-Krbtgtlink" in res
[0]:
247 new_krbtgt_dn
= res
[0]["msDS-Krbtgtlink"][0]
248 ctx
.del_noerror(ctx
.new_krbtgt_dn
)
250 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
251 expression
='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
252 (ldb
.binary_encode("dns-%s" % ctx
.myname
),
253 ldb
.binary_encode("dns/%s" % ctx
.dnshostname
)),
256 ctx
.del_noerror(res
[0].dn
, recursive
=True)
258 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
259 expression
='(sAMAccountName=%s)' % ldb
.binary_encode("dns-%s" % ctx
.myname
),
262 raise DCJoinException("Not removing account %s which looks like "
263 "a Samba DNS service account but does not "
264 "have servicePrincipalName=%s" %
265 (ldb
.binary_encode("dns-%s" % ctx
.myname
),
266 ldb
.binary_encode("dns/%s" % ctx
.dnshostname
)))
269 def cleanup_old_join(ctx
, force
=False):
270 """Remove any DNs from a previous join."""
271 # find the krbtgt link
272 if not ctx
.subdomain
:
273 ctx
.cleanup_old_accounts(force
=force
)
275 if ctx
.connection_dn
is not None:
276 ctx
.del_noerror(ctx
.connection_dn
)
277 if ctx
.krbtgt_dn
is not None:
278 ctx
.del_noerror(ctx
.krbtgt_dn
)
279 ctx
.del_noerror(ctx
.ntds_dn
)
280 ctx
.del_noerror(ctx
.server_dn
, recursive
=True)
282 ctx
.del_noerror(ctx
.topology_dn
)
284 ctx
.del_noerror(ctx
.partition_dn
)
287 binding_options
= "sign"
288 lsaconn
= lsa
.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
291 objectAttr
= lsa
.ObjectAttribute()
292 objectAttr
.sec_qos
= lsa
.QosInfo()
294 pol_handle
= lsaconn
.OpenPolicy2(''.decode('utf-8'),
295 objectAttr
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
298 name
.string
= ctx
.realm
299 info
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, name
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
301 lsaconn
.DeleteTrustedDomain(pol_handle
, info
.info_ex
.sid
)
304 name
.string
= ctx
.forest_domain_name
305 info
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, name
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
307 lsaconn
.DeleteTrustedDomain(pol_handle
, info
.info_ex
.sid
)
310 ctx
.del_noerror(ctx
.dns_a_dn
)
313 ctx
.del_noerror(ctx
.dns_cname_dn
)
317 def promote_possible(ctx
):
318 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
320 # This shouldn't happen
321 raise Exception("Can not promote into a subdomain")
323 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
324 expression
='sAMAccountName=%s' % ldb
.binary_encode(ctx
.samname
),
325 attrs
=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
327 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx
.samname
)
328 if "msDS-krbTgtLink" in res
[0] or "serverReferenceBL" in res
[0] or "rIDSetReferences" in res
[0]:
329 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx
.samname
)
330 if (int(res
[0]["userAccountControl"][0]) & (samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT|samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT
) == 0):
331 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx
.samname
)
333 ctx
.promote_from_dn
= res
[0].dn
336 def find_dc(ctx
, domain
):
337 """find a writeable DC for the given domain"""
339 ctx
.cldap_ret
= ctx
.net
.finddc(domain
=domain
, flags
=nbt
.NBT_SERVER_LDAP | nbt
.NBT_SERVER_DS | nbt
.NBT_SERVER_WRITABLE
)
340 except NTSTATUSError
as error
:
341 raise Exception("Failed to find a writeable DC for domain '%s': %s" %
344 raise Exception("Failed to find a writeable DC for domain '%s'" % domain
)
345 if ctx
.cldap_ret
.client_site
is not None and ctx
.cldap_ret
.client_site
!= "":
346 ctx
.site
= ctx
.cldap_ret
.client_site
347 return ctx
.cldap_ret
.pdc_dns_name
350 def get_behavior_version(ctx
):
351 res
= ctx
.samdb
.search(base
=ctx
.base_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["msDS-Behavior-Version"])
352 if "msDS-Behavior-Version" in res
[0]:
353 return int(res
[0]["msDS-Behavior-Version"][0])
355 return samba
.dsdb
.DS_DOMAIN_FUNCTION_2000
357 def get_dnsHostName(ctx
):
358 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["dnsHostName"])
359 return res
[0]["dnsHostName"][0]
361 def get_domain_name(ctx
):
362 '''get netbios name of the domain from the partitions record'''
363 partitions_dn
= ctx
.samdb
.get_partitions_dn()
364 res
= ctx
.samdb
.search(base
=partitions_dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["nETBIOSName"],
365 expression
='ncName=%s' % ldb
.binary_encode(str(ctx
.samdb
.get_default_basedn())))
366 return res
[0]["nETBIOSName"][0]
368 def get_forest_domain_name(ctx
):
369 '''get netbios name of the domain from the partitions record'''
370 partitions_dn
= ctx
.samdb
.get_partitions_dn()
371 res
= ctx
.samdb
.search(base
=partitions_dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["nETBIOSName"],
372 expression
='ncName=%s' % ldb
.binary_encode(str(ctx
.samdb
.get_root_basedn())))
373 return res
[0]["nETBIOSName"][0]
375 def get_parent_partition_dn(ctx
):
376 '''get the parent domain partition DN from parent DNS name'''
377 res
= ctx
.samdb
.search(base
=ctx
.config_dn
, attrs
=[],
378 expression
='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
379 (ldb
.binary_encode(ctx
.parent_dnsdomain
),
380 ldb
.OID_COMPARATOR_AND
, samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_DOMAIN
))
381 return str(res
[0].dn
)
383 def get_naming_master(ctx
):
384 '''get the parent domain partition DN from parent DNS name'''
385 res
= ctx
.samdb
.search(base
='CN=Partitions,%s' % ctx
.config_dn
, attrs
=['fSMORoleOwner'],
386 scope
=ldb
.SCOPE_BASE
, controls
=["extended_dn:1:1"])
387 if not 'fSMORoleOwner' in res
[0]:
388 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx
.partition_dn
, ctx
.samdb
.url
))
390 master_guid
= str(misc
.GUID(ldb
.Dn(ctx
.samdb
, res
[0]['fSMORoleOwner'][0]).get_extended_component('GUID')))
392 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res
[0]['fSMORoleOwner'][0])
394 master_host
= '%s._msdcs.%s' % (master_guid
, ctx
.dnsforest
)
398 '''get the SID of the connected user. Only works with w2k8 and later,
399 so only used for RODC join'''
400 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["tokenGroups"])
401 binsid
= res
[0]["tokenGroups"][0]
402 return ctx
.samdb
.schema_format_value("objectSID", binsid
)
404 def dn_exists(ctx
, dn
):
405 '''check if a DN exists'''
407 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[])
408 except ldb
.LdbError
as e5
:
409 (enum
, estr
) = e5
.args
410 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
415 def add_krbtgt_account(ctx
):
416 '''RODCs need a special krbtgt account'''
417 print "Adding %s" % ctx
.krbtgt_dn
419 "dn" : ctx
.krbtgt_dn
,
420 "objectclass" : "user",
421 "useraccountcontrol" : str(samba
.dsdb
.UF_NORMAL_ACCOUNT |
422 samba
.dsdb
.UF_ACCOUNTDISABLE
),
423 "showinadvancedviewonly" : "TRUE",
424 "description" : "krbtgt for %s" % ctx
.samname
}
425 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
427 # now we need to search for the samAccountName attribute on the krbtgt DN,
428 # as this will have been magically set to the krbtgt number
429 res
= ctx
.samdb
.search(base
=ctx
.krbtgt_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["samAccountName"])
430 ctx
.krbtgt_name
= res
[0]["samAccountName"][0]
432 print "Got krbtgt_name=%s" % ctx
.krbtgt_name
435 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
436 m
["msDS-krbTgtLink"] = ldb
.MessageElement(ctx
.krbtgt_dn
,
437 ldb
.FLAG_MOD_REPLACE
, "msDS-krbTgtLink")
440 ctx
.new_krbtgt_dn
= "CN=%s,CN=Users,%s" % (ctx
.krbtgt_name
, ctx
.base_dn
)
441 print "Renaming %s to %s" % (ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
)
442 ctx
.samdb
.rename(ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
)
444 def drsuapi_connect(ctx
):
445 '''make a DRSUAPI connection to the naming master'''
446 binding_options
= "seal"
447 if ctx
.lp
.log_level() >= 9:
448 binding_options
+= ",print"
449 binding_string
= "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
)
450 ctx
.drsuapi
= drsuapi
.drsuapi(binding_string
, ctx
.lp
, ctx
.creds
)
451 (ctx
.drsuapi_handle
, ctx
.bind_supported_extensions
) = drs_utils
.drs_DsBind(ctx
.drsuapi
)
453 def create_tmp_samdb(ctx
):
454 '''create a temporary samdb object for schema queries'''
455 ctx
.tmp_schema
= Schema(ctx
.domsid
,
456 schemadn
=ctx
.schema_dn
)
457 ctx
.tmp_samdb
= SamDB(session_info
=system_session(), url
=None, auto_connect
=False,
458 credentials
=ctx
.creds
, lp
=ctx
.lp
, global_schema
=False,
460 ctx
.tmp_samdb
.set_schema(ctx
.tmp_schema
)
462 def build_DsReplicaAttribute(ctx
, attrname
, attrvalue
):
463 '''build a DsReplicaAttributeCtr object'''
464 r
= drsuapi
.DsReplicaAttribute()
465 r
.attid
= ctx
.tmp_samdb
.get_attid_from_lDAPDisplayName(attrname
)
469 def DsAddEntry(ctx
, recs
):
470 '''add a record via the DRSUAPI DsAddEntry call'''
471 if ctx
.drsuapi
is None:
472 ctx
.drsuapi_connect()
473 if ctx
.tmp_samdb
is None:
474 ctx
.create_tmp_samdb()
478 id = drsuapi
.DsReplicaObjectIdentifier()
485 if not isinstance(rec
[a
], list):
489 rattr
= ctx
.tmp_samdb
.dsdb_DsReplicaAttribute(ctx
.tmp_samdb
, a
, v
)
492 attribute_ctr
= drsuapi
.DsReplicaAttributeCtr()
493 attribute_ctr
.num_attributes
= len(attrs
)
494 attribute_ctr
.attributes
= attrs
496 object = drsuapi
.DsReplicaObject()
497 object.identifier
= id
498 object.attribute_ctr
= attribute_ctr
500 list_object
= drsuapi
.DsReplicaObjectListItem()
501 list_object
.object = object
502 objects
.append(list_object
)
504 req2
= drsuapi
.DsAddEntryRequest2()
505 req2
.first_object
= objects
[0]
506 prev
= req2
.first_object
507 for o
in objects
[1:]:
511 (level
, ctr
) = ctx
.drsuapi
.DsAddEntry(ctx
.drsuapi_handle
, 2, req2
)
513 if ctr
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
514 print("DsAddEntry failed with dir_err %u" % ctr
.dir_err
)
515 raise RuntimeError("DsAddEntry failed")
516 if ctr
.extended_err
[0] != werror
.WERR_SUCCESS
:
517 print("DsAddEntry failed with status %s info %s" % (ctr
.extended_err
))
518 raise RuntimeError("DsAddEntry failed")
521 raise RuntimeError("expected err_ver 1, got %u" % ctr
.err_ver
)
522 if ctr
.err_data
.status
[0] != werror
.WERR_SUCCESS
:
523 if ctr
.err_data
.info
is None:
524 print("DsAddEntry failed with status %s, info omitted" % (ctr
.err_data
.status
[1]))
526 print("DsAddEntry failed with status %s info %s" % (ctr
.err_data
.status
[1],
527 ctr
.err_data
.info
.extended_err
))
528 raise RuntimeError("DsAddEntry failed")
529 if ctr
.err_data
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
530 print("DsAddEntry failed with dir_err %u" % ctr
.err_data
.dir_err
)
531 raise RuntimeError("DsAddEntry failed")
535 def join_ntdsdsa_obj(ctx
):
536 '''return the ntdsdsa object to add'''
538 print "Adding %s" % ctx
.ntds_dn
541 "objectclass" : "nTDSDSA",
542 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
543 "dMDLocation" : ctx
.schema_dn
}
545 nc_list
= [ ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
547 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
548 rec
["msDS-Behavior-Version"] = str(samba
.dsdb
.DS_DOMAIN_FUNCTION_2008_R2
)
550 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
551 rec
["msDS-HasDomainNCs"] = ctx
.base_dn
554 rec
["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx
.schema_dn
555 rec
["msDS-HasFullReplicaNCs"] = ctx
.full_nc_list
556 rec
["options"] = "37"
558 rec
["objectCategory"] = "CN=NTDS-DSA,%s" % ctx
.schema_dn
559 rec
["HasMasterNCs"] = []
561 if nc
in ctx
.full_nc_list
:
562 rec
["HasMasterNCs"].append(nc
)
563 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
564 rec
["msDS-HasMasterNCs"] = ctx
.full_nc_list
566 rec
["invocationId"] = ndr_pack(ctx
.invocation_id
)
570 def join_add_ntdsdsa(ctx
):
571 '''add the ntdsdsa object'''
573 rec
= ctx
.join_ntdsdsa_obj()
575 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
577 ctx
.DsAddEntry([rec
])
579 # find the GUID of our NTDS DN
580 res
= ctx
.samdb
.search(base
=ctx
.ntds_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["objectGUID"])
581 ctx
.ntds_guid
= misc
.GUID(ctx
.samdb
.schema_format_value("objectGUID", res
[0]["objectGUID"][0]))
583 def join_add_objects(ctx
):
584 '''add the various objects needed for the join'''
586 print "Adding %s" % ctx
.acct_dn
589 "objectClass": "computer",
590 "displayname": ctx
.samname
,
591 "samaccountname" : ctx
.samname
,
592 "userAccountControl" : str(ctx
.userAccountControl | samba
.dsdb
.UF_ACCOUNTDISABLE
),
593 "dnshostname" : ctx
.dnshostname
}
594 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2008
:
595 rec
['msDS-SupportedEncryptionTypes'] = str(samba
.dsdb
.ENC_ALL_TYPES
)
596 elif ctx
.promote_existing
:
597 rec
['msDS-SupportedEncryptionTypes'] = []
599 rec
["managedby"] = ctx
.managedby
600 elif ctx
.promote_existing
:
601 rec
["managedby"] = []
603 if ctx
.never_reveal_sid
:
604 rec
["msDS-NeverRevealGroup"] = ctx
.never_reveal_sid
605 elif ctx
.promote_existing
:
606 rec
["msDS-NeverRevealGroup"] = []
609 rec
["msDS-RevealOnDemandGroup"] = ctx
.reveal_sid
610 elif ctx
.promote_existing
:
611 rec
["msDS-RevealOnDemandGroup"] = []
613 if ctx
.promote_existing
:
614 if ctx
.promote_from_dn
!= ctx
.acct_dn
:
615 ctx
.samdb
.rename(ctx
.promote_from_dn
, ctx
.acct_dn
)
616 ctx
.samdb
.modify(ldb
.Message
.from_dict(ctx
.samdb
, rec
, ldb
.FLAG_MOD_REPLACE
))
621 ctx
.add_krbtgt_account()
624 print "Adding %s" % ctx
.server_dn
627 "objectclass" : "server",
628 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
629 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
630 samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
631 samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
632 # windows seems to add the dnsHostName later
633 "dnsHostName" : ctx
.dnshostname
}
636 rec
["serverReference"] = ctx
.acct_dn
641 # the rest is done after replication
646 ctx
.join_add_ntdsdsa()
648 # Add the Replica-Locations or RO-Replica-Locations attributes
649 # TODO Is this supposed to be for the schema partition too?
650 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.domaindns_zone
)
651 domain
= (ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
653 base
=ctx
.samdb
.get_partitions_dn(),
654 expression
=expr
), ctx
.domaindns_zone
)
656 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.forestdns_zone
)
657 forest
= (ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
659 base
=ctx
.samdb
.get_partitions_dn(),
660 expression
=expr
), ctx
.forestdns_zone
)
662 for part
, zone
in (domain
, forest
):
663 if zone
not in ctx
.nc_list
:
669 attr
= "msDS-NC-Replica-Locations"
671 attr
= "msDS-NC-RO-Replica-Locations"
673 m
[attr
] = ldb
.MessageElement(ctx
.ntds_dn
,
674 ldb
.FLAG_MOD_ADD
, attr
)
677 if ctx
.connection_dn
is not None:
678 print "Adding %s" % ctx
.connection_dn
680 "dn" : ctx
.connection_dn
,
681 "objectclass" : "nTDSConnection",
682 "enabledconnection" : "TRUE",
684 "fromServer" : ctx
.dc_ntds_dn
}
688 print "Adding SPNs to %s" % ctx
.acct_dn
690 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
691 for i
in range(len(ctx
.SPNs
)):
692 ctx
.SPNs
[i
] = ctx
.SPNs
[i
].replace("$NTDSGUID", str(ctx
.ntds_guid
))
693 m
["servicePrincipalName"] = ldb
.MessageElement(ctx
.SPNs
,
694 ldb
.FLAG_MOD_REPLACE
,
695 "servicePrincipalName")
698 # The account password set operation should normally be done over
699 # LDAP. Windows 2000 DCs however allow this only with SSL
700 # connections which are hard to set up and otherwise refuse with
701 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
703 print "Setting account password for %s" % ctx
.samname
705 ctx
.samdb
.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
706 % ldb
.binary_encode(ctx
.samname
),
708 force_change_at_next_login
=False,
709 username
=ctx
.samname
)
710 except ldb
.LdbError
as e2
:
712 if num
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
714 ctx
.net
.set_password(account_name
=ctx
.samname
,
715 domain_name
=ctx
.domain_name
,
716 newpassword
=ctx
.acct_pass
.encode('utf-8'))
718 res
= ctx
.samdb
.search(base
=ctx
.acct_dn
, scope
=ldb
.SCOPE_BASE
,
719 attrs
=["msDS-KeyVersionNumber",
721 if "msDS-KeyVersionNumber" in res
[0]:
722 ctx
.key_version_number
= int(res
[0]["msDS-KeyVersionNumber"][0])
724 ctx
.key_version_number
= None
726 ctx
.new_dc_account_sid
= ndr_unpack(security
.dom_sid
,
727 res
[0]["objectSid"][0])
729 print("Enabling account")
731 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
732 m
["userAccountControl"] = ldb
.MessageElement(str(ctx
.userAccountControl
),
733 ldb
.FLAG_MOD_REPLACE
,
734 "userAccountControl")
737 if ctx
.dns_backend
.startswith("BIND9_"):
738 ctx
.dnspass
= samba
.generate_random_password(128, 255)
740 recs
= ctx
.samdb
.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
741 {"DNSDOMAIN": ctx
.dnsdomain
,
742 "DOMAINDN": ctx
.base_dn
,
743 "HOSTNAME" : ctx
.myname
,
744 "DNSPASS_B64": b64encode(ctx
.dnspass
.encode('utf-16-le')),
745 "DNSNAME" : ctx
.dnshostname
}))
746 for changetype
, msg
in recs
:
747 assert changetype
== ldb
.CHANGETYPE_NONE
748 dns_acct_dn
= msg
["dn"]
749 print "Adding DNS account %s with dns/ SPN" % msg
["dn"]
751 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
752 del msg
["clearTextPassword"]
753 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
754 del msg
["isCriticalSystemObject"]
755 # Disable account until password is set
756 msg
["userAccountControl"] = str(samba
.dsdb
.UF_NORMAL_ACCOUNT |
757 samba
.dsdb
.UF_ACCOUNTDISABLE
)
760 except ldb
.LdbError
as e
:
762 if num
!= ldb
.ERR_ENTRY_ALREADY_EXISTS
:
765 # The account password set operation should normally be done over
766 # LDAP. Windows 2000 DCs however allow this only with SSL
767 # connections which are hard to set up and otherwise refuse with
768 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
770 print "Setting account password for dns-%s" % ctx
.myname
772 ctx
.samdb
.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
773 % ldb
.binary_encode(ctx
.myname
),
775 force_change_at_next_login
=False,
776 username
=ctx
.samname
)
777 except ldb
.LdbError
as e3
:
779 if num
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
781 ctx
.net
.set_password(account_name
="dns-%s" % ctx
.myname
,
782 domain_name
=ctx
.domain_name
,
783 newpassword
=ctx
.dnspass
)
785 res
= ctx
.samdb
.search(base
=dns_acct_dn
, scope
=ldb
.SCOPE_BASE
,
786 attrs
=["msDS-KeyVersionNumber"])
787 if "msDS-KeyVersionNumber" in res
[0]:
788 ctx
.dns_key_version_number
= int(res
[0]["msDS-KeyVersionNumber"][0])
790 ctx
.dns_key_version_number
= None
792 def join_add_objects2(ctx
):
793 """add the various objects needed for the join, for subdomains post replication"""
795 print "Adding %s" % ctx
.partition_dn
796 name_map
= {'SubdomainAdmins': "%s-%s" % (str(ctx
.domsid
), security
.DOMAIN_RID_ADMINS
)}
797 sd_binary
= descriptor
.get_paritions_crossref_subdomain_descriptor(ctx
.forestsid
, name_map
=name_map
)
799 "dn" : ctx
.partition_dn
,
800 "objectclass" : "crossRef",
801 "objectCategory" : "CN=Cross-Ref,%s" % ctx
.schema_dn
,
802 "nCName" : ctx
.base_dn
,
803 "nETBIOSName" : ctx
.domain_name
,
804 "dnsRoot": ctx
.dnsdomain
,
805 "trustParent" : ctx
.parent_partition_dn
,
806 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_NC|samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_DOMAIN
),
807 "ntSecurityDescriptor" : sd_binary
,
810 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
811 rec
["msDS-Behavior-Version"] = str(ctx
.behavior_version
)
813 rec2
= ctx
.join_ntdsdsa_obj()
815 objects
= ctx
.DsAddEntry([rec
, rec2
])
816 if len(objects
) != 2:
817 raise DCJoinException("Expected 2 objects from DsAddEntry")
819 ctx
.ntds_guid
= objects
[1].guid
821 print("Replicating partition DN")
822 ctx
.repl
.replicate(ctx
.partition_dn
,
823 misc
.GUID("00000000-0000-0000-0000-000000000000"),
825 exop
=drsuapi
.DRSUAPI_EXOP_REPL_OBJ
,
826 replica_flags
=drsuapi
.DRSUAPI_DRS_WRIT_REP
)
828 print("Replicating NTDS DN")
829 ctx
.repl
.replicate(ctx
.ntds_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 def join_provision(ctx
):
836 """Provision the local SAM."""
838 print "Calling bare provision"
840 smbconf
= ctx
.lp
.configfile
842 presult
= provision(ctx
.logger
, system_session(), smbconf
=smbconf
,
843 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_DRS
, realm
=ctx
.realm
,
844 rootdn
=ctx
.root_dn
, domaindn
=ctx
.base_dn
,
845 schemadn
=ctx
.schema_dn
, configdn
=ctx
.config_dn
,
846 serverdn
=ctx
.server_dn
, domain
=ctx
.domain_name
,
847 hostname
=ctx
.myname
, domainsid
=ctx
.domsid
,
848 machinepass
=ctx
.acct_pass
, serverrole
="active directory domain controller",
849 sitename
=ctx
.site
, lp
=ctx
.lp
, ntdsguid
=ctx
.ntds_guid
,
850 use_ntvfs
=ctx
.use_ntvfs
, dns_backend
=ctx
.dns_backend
,
851 plaintext_secrets
=ctx
.plaintext_secrets
)
852 print "Provision OK for domain DN %s" % presult
.domaindn
853 ctx
.local_samdb
= presult
.samdb
855 ctx
.paths
= presult
.paths
856 ctx
.names
= presult
.names
858 # Fix up the forestsid, it may be different if we are joining as a subdomain
859 ctx
.names
.forestsid
= ctx
.forestsid
861 def join_provision_own_domain(ctx
):
862 """Provision the local SAM."""
864 # we now operate exclusively on the local database, which
865 # we need to reopen in order to get the newly created schema
866 print("Reconnecting to local samdb")
867 ctx
.samdb
= SamDB(url
=ctx
.local_samdb
.url
,
868 session_info
=system_session(),
869 lp
=ctx
.local_samdb
.lp
,
871 ctx
.samdb
.set_invocation_id(str(ctx
.invocation_id
))
872 ctx
.local_samdb
= ctx
.samdb
874 ctx
.logger
.info("Finding domain GUID from ncName")
875 res
= ctx
.local_samdb
.search(base
=ctx
.partition_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=['ncName'],
876 controls
=["extended_dn:1:1", "reveal_internals:0"])
878 if 'nCName' not in res
[0]:
879 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx
.partition_dn
, ctx
.samdb
.url
))
882 ctx
.names
.domainguid
= str(misc
.GUID(ldb
.Dn(ctx
.samdb
, res
[0]['ncName'][0]).get_extended_component('GUID')))
884 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res
[0]['ncName'][0])
886 ctx
.logger
.info("Got domain GUID %s" % ctx
.names
.domainguid
)
888 ctx
.logger
.info("Calling own domain provision")
890 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
892 presult
= provision_fill(ctx
.local_samdb
, secrets_ldb
,
893 ctx
.logger
, ctx
.names
, ctx
.paths
,
894 dom_for_fun_level
=DS_DOMAIN_FUNCTION_2003
,
895 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_SUBDOMAIN
,
896 machinepass
=ctx
.acct_pass
, serverrole
="active directory domain controller",
897 lp
=ctx
.lp
, hostip
=ctx
.names
.hostip
, hostip6
=ctx
.names
.hostip6
,
898 dns_backend
=ctx
.dns_backend
, adminpass
=ctx
.adminpass
)
899 print("Provision OK for domain %s" % ctx
.names
.dnsdomain
)
901 def join_replicate(ctx
):
902 """Replicate the SAM."""
904 print "Starting replication"
905 ctx
.local_samdb
.transaction_start()
907 source_dsa_invocation_id
= misc
.GUID(ctx
.samdb
.get_invocation_id())
908 if ctx
.ntds_guid
is None:
909 print("Using DS_BIND_GUID_W2K3")
910 destination_dsa_guid
= misc
.GUID(drsuapi
.DRSUAPI_DS_BIND_GUID_W2K3
)
912 destination_dsa_guid
= ctx
.ntds_guid
915 repl_creds
= Credentials()
916 repl_creds
.guess(ctx
.lp
)
917 repl_creds
.set_kerberos_state(DONT_USE_KERBEROS
)
918 repl_creds
.set_username(ctx
.samname
)
919 repl_creds
.set_password(ctx
.acct_pass
.encode('utf-8'))
921 repl_creds
= ctx
.creds
923 binding_options
= "seal"
924 if ctx
.lp
.log_level() >= 9:
925 binding_options
+= ",print"
926 repl
= drs_utils
.drs_Replicate(
927 "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
928 ctx
.lp
, repl_creds
, ctx
.local_samdb
, ctx
.invocation_id
)
930 repl
.replicate(ctx
.schema_dn
, source_dsa_invocation_id
,
931 destination_dsa_guid
, schema
=True, rodc
=ctx
.RODC
,
932 replica_flags
=ctx
.replica_flags
)
933 repl
.replicate(ctx
.config_dn
, source_dsa_invocation_id
,
934 destination_dsa_guid
, rodc
=ctx
.RODC
,
935 replica_flags
=ctx
.replica_flags
)
936 if not ctx
.subdomain
:
937 # Replicate first the critical object for the basedn
938 if not ctx
.domain_replica_flags
& drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
:
939 print "Replicating critical objects from the base DN of the domain"
940 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
941 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
942 destination_dsa_guid
, rodc
=ctx
.RODC
,
943 replica_flags
=ctx
.domain_replica_flags
)
944 ctx
.domain_replica_flags ^
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
945 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
946 destination_dsa_guid
, rodc
=ctx
.RODC
,
947 replica_flags
=ctx
.domain_replica_flags
)
948 print "Done with always replicated NC (base, config, schema)"
950 # At this point we should already have an entry in the ForestDNS
951 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
952 # indicate that we hold a replica for this NC.
953 for nc
in (ctx
.domaindns_zone
, ctx
.forestdns_zone
):
954 if nc
in ctx
.nc_list
:
955 print "Replicating %s" % (str(nc
))
956 repl
.replicate(nc
, source_dsa_invocation_id
,
957 destination_dsa_guid
, rodc
=ctx
.RODC
,
958 replica_flags
=ctx
.replica_flags
)
961 repl
.replicate(ctx
.acct_dn
, source_dsa_invocation_id
,
962 destination_dsa_guid
,
963 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
964 repl
.replicate(ctx
.new_krbtgt_dn
, source_dsa_invocation_id
,
965 destination_dsa_guid
,
966 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
967 elif ctx
.rid_manager_dn
!= None:
968 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
970 repl
.replicate(ctx
.rid_manager_dn
, source_dsa_invocation_id
,
971 destination_dsa_guid
,
972 exop
=drsuapi
.DRSUAPI_EXOP_FSMO_RID_ALLOC
)
973 except samba
.DsExtendedError
as e1
:
974 (enum
, estr
) = e1
.args
975 if enum
== drsuapi
.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER
:
976 print "WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx
.server
977 print "NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup."
982 ctx
.source_dsa_invocation_id
= source_dsa_invocation_id
983 ctx
.destination_dsa_guid
= destination_dsa_guid
985 print "Committing SAM database"
987 ctx
.local_samdb
.transaction_cancel()
990 ctx
.local_samdb
.transaction_commit()
992 def send_DsReplicaUpdateRefs(ctx
, dn
):
993 r
= drsuapi
.DsReplicaUpdateRefsRequest1()
994 r
.naming_context
= drsuapi
.DsReplicaObjectIdentifier()
995 r
.naming_context
.dn
= str(dn
)
996 r
.naming_context
.guid
= misc
.GUID("00000000-0000-0000-0000-000000000000")
997 r
.naming_context
.sid
= security
.dom_sid("S-0-0")
998 r
.dest_dsa_guid
= ctx
.ntds_guid
999 r
.dest_dsa_dns_name
= "%s._msdcs.%s" % (str(ctx
.ntds_guid
), ctx
.dnsforest
)
1000 r
.options
= drsuapi
.DRSUAPI_DRS_ADD_REF | drsuapi
.DRSUAPI_DRS_DEL_REF
1002 r
.options |
= drsuapi
.DRSUAPI_DRS_WRIT_REP
1004 if ctx
.drsuapi
is None:
1005 ctx
.drsuapi_connect()
1007 ctx
.drsuapi
.DsReplicaUpdateRefs(ctx
.drsuapi_handle
, 1, r
)
1009 def join_add_dns_records(ctx
):
1010 """Remotely Add a DNS record to the target DC. We assume that if we
1011 replicate DNS that the server holds the DNS roles and can accept
1014 This avoids issues getting replication going after the DC
1015 first starts as the rest of the domain does not have to
1016 wait for samba_dnsupdate to run successfully.
1018 Specifically, we add the records implied by the DsReplicaUpdateRefs
1021 We do not just run samba_dnsupdate as we want to strictly
1022 operate against the DC we just joined:
1023 - We do not want to query another DNS server
1024 - We do not want to obtain a Kerberos ticket
1025 (as the KDC we select may not be the DC we just joined,
1026 and so may not be in sync with the password we just set)
1027 - We do not wish to set the _ldap records until we have started
1028 - We do not wish to use NTLM (the --use-samba-tool mode forces
1033 client_version
= dnsserver
.DNS_CLIENT_VERSION_LONGHORN
1034 record_type
= dnsp
.DNS_TYPE_A
1035 select_flags
= dnsserver
.DNS_RPC_VIEW_AUTHORITY_DATA |\
1036 dnsserver
.DNS_RPC_VIEW_NO_CHILDREN
1038 zone
= ctx
.dnsdomain
1039 msdcs_zone
= "_msdcs.%s" % ctx
.dnsforest
1041 msdcs_cname
= str(ctx
.ntds_guid
)
1042 cname_target
= "%s.%s" % (name
, zone
)
1043 IPs
= samba
.interface_ips(ctx
.lp
, ctx
.force_all_ips
)
1045 ctx
.logger
.info("Adding %d remote DNS records for %s.%s" % \
1046 (len(IPs
), name
, zone
))
1048 binding_options
= "sign"
1049 dns_conn
= dnsserver
.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
1055 sd_helper
= samba
.sd_utils
.SDUtils(ctx
.samdb
)
1057 change_owner_sd
= security
.descriptor()
1058 change_owner_sd
.owner_sid
= ctx
.new_dc_account_sid
1059 change_owner_sd
.group_sid
= security
.dom_sid("%s-%d" %
1061 security
.DOMAIN_RID_DCS
))
1063 # TODO: Remove any old records from the primary DNS name
1066 = dns_conn
.DnssrvEnumRecords2(client_version
,
1076 except WERRORError
as e
:
1077 if e
.args
[0] == werror
.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST
:
1083 for record
in rec
.records
:
1084 if record
.wType
== dnsp
.DNS_TYPE_A
or \
1085 record
.wType
== dnsp
.DNS_TYPE_AAAA
:
1087 del_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1088 del_rec_buf
.rec
= record
1090 dns_conn
.DnssrvUpdateRecord2(client_version
,
1097 except WERRORError
as e
:
1098 if e
.args
[0] == werror
.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST
:
1104 if IP
.find(':') != -1:
1105 ctx
.logger
.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1107 rec
= AAAARecord(IP
)
1109 ctx
.logger
.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1114 add_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1115 add_rec_buf
.rec
= rec
1116 dns_conn
.DnssrvUpdateRecord2(client_version
,
1125 domaindns_zone_dn
= ldb
.Dn(ctx
.samdb
, ctx
.domaindns_zone
)
1126 (ctx
.dns_a_dn
, ldap_record
) \
1127 = ctx
.samdb
.dns_lookup("%s.%s" % (name
, zone
),
1128 dns_partition
=domaindns_zone_dn
)
1130 # Make the DC own the DNS record, not the administrator
1131 sd_helper
.modify_sd_on_dn(ctx
.dns_a_dn
, change_owner_sd
,
1132 controls
=["sd_flags:1:%d"
1133 % (security
.SECINFO_OWNER
1134 | security
.SECINFO_GROUP
)])
1138 ctx
.logger
.info("Adding DNS CNAME record %s.%s for %s"
1139 % (msdcs_cname
, msdcs_zone
, cname_target
))
1141 add_rec_buf
= dnsserver
.DNS_RPC_RECORD_BUF()
1142 rec
= CNameRecord(cname_target
)
1143 add_rec_buf
.rec
= rec
1144 dns_conn
.DnssrvUpdateRecord2(client_version
,
1152 forestdns_zone_dn
= ldb
.Dn(ctx
.samdb
, ctx
.forestdns_zone
)
1153 (ctx
.dns_cname_dn
, ldap_record
) \
1154 = ctx
.samdb
.dns_lookup("%s.%s" % (msdcs_cname
, msdcs_zone
),
1155 dns_partition
=forestdns_zone_dn
)
1157 # Make the DC own the DNS record, not the administrator
1158 sd_helper
.modify_sd_on_dn(ctx
.dns_cname_dn
, change_owner_sd
,
1159 controls
=["sd_flags:1:%d"
1160 % (security
.SECINFO_OWNER
1161 | security
.SECINFO_GROUP
)])
1163 ctx
.logger
.info("All other DNS records (like _ldap SRV records) " +
1164 "will be created samba_dnsupdate on first startup")
1167 def join_replicate_new_dns_records(ctx
):
1168 for nc
in (ctx
.domaindns_zone
, ctx
.forestdns_zone
):
1169 if nc
in ctx
.nc_list
:
1170 ctx
.logger
.info("Replicating new DNS records in %s" % (str(nc
)))
1171 ctx
.repl
.replicate(nc
, ctx
.source_dsa_invocation_id
,
1172 ctx
.ntds_guid
, rodc
=ctx
.RODC
,
1173 replica_flags
=ctx
.replica_flags
,
1178 def join_finalise(ctx
):
1179 """Finalise the join, mark us synchronised and setup secrets db."""
1181 # FIXME we shouldn't do this in all cases
1183 # If for some reasons we joined in another site than the one of
1184 # DC we just replicated from then we don't need to send the updatereplicateref
1185 # as replication between sites is time based and on the initiative of the
1187 if not ctx
.clone_only
:
1188 ctx
.logger
.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1189 for nc
in ctx
.nc_list
:
1190 ctx
.send_DsReplicaUpdateRefs(nc
)
1192 if not ctx
.clone_only
and ctx
.RODC
:
1193 print "Setting RODC invocationId"
1194 ctx
.local_samdb
.set_invocation_id(str(ctx
.invocation_id
))
1195 ctx
.local_samdb
.set_opaque_integer("domainFunctionality",
1196 ctx
.behavior_version
)
1198 m
.dn
= ldb
.Dn(ctx
.local_samdb
, "%s" % ctx
.ntds_dn
)
1199 m
["invocationId"] = ldb
.MessageElement(ndr_pack(ctx
.invocation_id
),
1200 ldb
.FLAG_MOD_REPLACE
,
1202 ctx
.local_samdb
.modify(m
)
1204 # Note: as RODC the invocationId is only stored
1205 # on the RODC itself, the other DCs never see it.
1207 # Thats is why we fix up the replPropertyMetaData stamp
1208 # for the 'invocationId' attribute, we need to change
1209 # the 'version' to '0', this is what windows 2008r2 does as RODC
1211 # This means if the object on a RWDC ever gets a invocationId
1212 # attribute, it will have version '1' (or higher), which will
1213 # will overwrite the RODC local value.
1214 ctx
.local_samdb
.set_attribute_replmetadata_version(m
.dn
,
1218 ctx
.logger
.info("Setting isSynchronized and dsServiceName")
1220 m
.dn
= ldb
.Dn(ctx
.local_samdb
, '@ROOTDSE')
1221 m
["isSynchronized"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isSynchronized")
1223 # We want to appear to be the server we just cloned
1225 guid
= ctx
.remote_dc_ntds_guid
1227 guid
= ctx
.ntds_guid
1229 m
["dsServiceName"] = ldb
.MessageElement("<GUID=%s>" % str(guid
),
1230 ldb
.FLAG_MOD_REPLACE
, "dsServiceName")
1231 ctx
.local_samdb
.modify(m
)
1233 if ctx
.clone_only
or ctx
.subdomain
:
1236 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
1238 ctx
.logger
.info("Setting up secrets database")
1239 secretsdb_self_join(secrets_ldb
, domain
=ctx
.domain_name
,
1241 dnsdomain
=ctx
.dnsdomain
,
1242 netbiosname
=ctx
.myname
,
1243 domainsid
=ctx
.domsid
,
1244 machinepass
=ctx
.acct_pass
,
1245 secure_channel_type
=ctx
.secure_channel_type
,
1246 key_version_number
=ctx
.key_version_number
)
1248 if ctx
.dns_backend
.startswith("BIND9_"):
1249 setup_bind9_dns(ctx
.local_samdb
, secrets_ldb
,
1250 ctx
.names
, ctx
.paths
, ctx
.lp
, ctx
.logger
,
1251 dns_backend
=ctx
.dns_backend
,
1252 dnspass
=ctx
.dnspass
, os_level
=ctx
.behavior_version
,
1253 targetdir
=ctx
.targetdir
,
1254 key_version_number
=ctx
.dns_key_version_number
)
1256 def join_setup_trusts(ctx
):
1257 """provision the local SAM."""
1259 print "Setup domain trusts with server %s" % ctx
.server
1260 binding_options
= "" # why doesn't signing work here? w2k8r2 claims no session key
1261 lsaconn
= lsa
.lsarpc("ncacn_np:%s[%s]" % (ctx
.server
, binding_options
),
1264 objectAttr
= lsa
.ObjectAttribute()
1265 objectAttr
.sec_qos
= lsa
.QosInfo()
1267 pol_handle
= lsaconn
.OpenPolicy2(''.decode('utf-8'),
1268 objectAttr
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
1270 info
= lsa
.TrustDomainInfoInfoEx()
1271 info
.domain_name
.string
= ctx
.dnsdomain
1272 info
.netbios_name
.string
= ctx
.domain_name
1273 info
.sid
= ctx
.domsid
1274 info
.trust_direction
= lsa
.LSA_TRUST_DIRECTION_INBOUND | lsa
.LSA_TRUST_DIRECTION_OUTBOUND
1275 info
.trust_type
= lsa
.LSA_TRUST_TYPE_UPLEVEL
1276 info
.trust_attributes
= lsa
.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1279 oldname
= lsa
.String()
1280 oldname
.string
= ctx
.dnsdomain
1281 oldinfo
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, oldname
,
1282 lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
1283 print("Removing old trust record for %s (SID %s)" % (ctx
.dnsdomain
, oldinfo
.info_ex
.sid
))
1284 lsaconn
.DeleteTrustedDomain(pol_handle
, oldinfo
.info_ex
.sid
)
1285 except RuntimeError:
1288 password_blob
= string_to_byte_array(ctx
.trustdom_pass
.encode('utf-16-le'))
1290 clear_value
= drsblobs
.AuthInfoClear()
1291 clear_value
.size
= len(password_blob
)
1292 clear_value
.password
= password_blob
1294 clear_authentication_information
= drsblobs
.AuthenticationInformation()
1295 clear_authentication_information
.LastUpdateTime
= samba
.unix2nttime(int(time
.time()))
1296 clear_authentication_information
.AuthType
= lsa
.TRUST_AUTH_TYPE_CLEAR
1297 clear_authentication_information
.AuthInfo
= clear_value
1299 authentication_information_array
= drsblobs
.AuthenticationInformationArray()
1300 authentication_information_array
.count
= 1
1301 authentication_information_array
.array
= [clear_authentication_information
]
1303 outgoing
= drsblobs
.trustAuthInOutBlob()
1305 outgoing
.current
= authentication_information_array
1307 trustpass
= drsblobs
.trustDomainPasswords()
1308 confounder
= [3] * 512
1310 for i
in range(512):
1311 confounder
[i
] = random
.randint(0, 255)
1313 trustpass
.confounder
= confounder
1315 trustpass
.outgoing
= outgoing
1316 trustpass
.incoming
= outgoing
1318 trustpass_blob
= ndr_pack(trustpass
)
1320 encrypted_trustpass
= arcfour_encrypt(lsaconn
.session_key
, trustpass_blob
)
1322 auth_blob
= lsa
.DATA_BUF2()
1323 auth_blob
.size
= len(encrypted_trustpass
)
1324 auth_blob
.data
= string_to_byte_array(encrypted_trustpass
)
1326 auth_info
= lsa
.TrustDomainInfoAuthInfoInternal()
1327 auth_info
.auth_blob
= auth_blob
1329 trustdom_handle
= lsaconn
.CreateTrustedDomainEx2(pol_handle
,
1332 security
.SEC_STD_DELETE
)
1335 "dn" : "cn=%s,cn=system,%s" % (ctx
.dnsforest
, ctx
.base_dn
),
1336 "objectclass" : "trustedDomain",
1337 "trustType" : str(info
.trust_type
),
1338 "trustAttributes" : str(info
.trust_attributes
),
1339 "trustDirection" : str(info
.trust_direction
),
1340 "flatname" : ctx
.forest_domain_name
,
1341 "trustPartner" : ctx
.dnsforest
,
1342 "trustAuthIncoming" : ndr_pack(outgoing
),
1343 "trustAuthOutgoing" : ndr_pack(outgoing
),
1344 "securityIdentifier" : ndr_pack(ctx
.forestsid
)
1346 ctx
.local_samdb
.add(rec
)
1349 "dn" : "cn=%s$,cn=users,%s" % (ctx
.forest_domain_name
, ctx
.base_dn
),
1350 "objectclass" : "user",
1351 "userAccountControl" : str(samba
.dsdb
.UF_INTERDOMAIN_TRUST_ACCOUNT
),
1352 "clearTextPassword" : ctx
.trustdom_pass
.encode('utf-16-le'),
1353 "samAccountName" : "%s$" % ctx
.forest_domain_name
1355 ctx
.local_samdb
.add(rec
)
1359 # nc_list is the list of naming context (NC) for which we will
1360 # replicate in and send a updateRef command to the partner DC
1362 # full_nc_list is the list of naming context (NC) we hold
1363 # read/write copies of. These are not subsets of each other.
1364 ctx
.nc_list
= [ ctx
.config_dn
, ctx
.schema_dn
]
1365 ctx
.full_nc_list
= [ ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
1367 if ctx
.subdomain
and ctx
.dns_backend
!= "NONE":
1368 ctx
.full_nc_list
+= [ctx
.domaindns_zone
]
1370 elif not ctx
.subdomain
:
1371 ctx
.nc_list
+= [ctx
.base_dn
]
1373 if ctx
.dns_backend
!= "NONE":
1374 ctx
.nc_list
+= [ctx
.domaindns_zone
]
1375 ctx
.nc_list
+= [ctx
.forestdns_zone
]
1376 ctx
.full_nc_list
+= [ctx
.domaindns_zone
]
1377 ctx
.full_nc_list
+= [ctx
.forestdns_zone
]
1379 if not ctx
.clone_only
:
1380 if ctx
.promote_existing
:
1381 ctx
.promote_possible()
1383 ctx
.cleanup_old_join()
1386 if not ctx
.clone_only
:
1387 ctx
.join_add_objects()
1388 ctx
.join_provision()
1389 ctx
.join_replicate()
1390 if (not ctx
.clone_only
and ctx
.subdomain
):
1391 ctx
.join_add_objects2()
1392 ctx
.join_provision_own_domain()
1393 ctx
.join_setup_trusts()
1395 if not ctx
.clone_only
and ctx
.dns_backend
!= "NONE":
1396 ctx
.join_add_dns_records()
1397 ctx
.join_replicate_new_dns_records()
1402 print "Join failed - cleaning up"
1405 if not ctx
.clone_only
:
1406 ctx
.cleanup_old_join()
1410 def join_RODC(logger
=None, server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
1411 targetdir
=None, domain
=None, domain_critical_only
=False,
1412 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
1413 promote_existing
=False, plaintext_secrets
=False):
1414 """Join as a RODC."""
1416 ctx
= dc_join(logger
, server
, creds
, lp
, site
, netbios_name
, targetdir
, domain
,
1417 machinepass
, use_ntvfs
, dns_backend
, promote_existing
,
1420 lp
.set("workgroup", ctx
.domain_name
)
1421 logger
.info("workgroup is %s" % ctx
.domain_name
)
1423 lp
.set("realm", ctx
.realm
)
1424 logger
.info("realm is %s" % ctx
.realm
)
1426 ctx
.krbtgt_dn
= "CN=krbtgt_%s,CN=Users,%s" % (ctx
.myname
, ctx
.base_dn
)
1428 # setup some defaults for accounts that should be replicated to this RODC
1429 ctx
.never_reveal_sid
= [
1430 "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_DENY
),
1431 "<SID=%s>" % security
.SID_BUILTIN_ADMINISTRATORS
,
1432 "<SID=%s>" % security
.SID_BUILTIN_SERVER_OPERATORS
,
1433 "<SID=%s>" % security
.SID_BUILTIN_BACKUP_OPERATORS
,
1434 "<SID=%s>" % security
.SID_BUILTIN_ACCOUNT_OPERATORS
]
1435 ctx
.reveal_sid
= "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_ALLOW
)
1437 mysid
= ctx
.get_mysid()
1438 admin_dn
= "<SID=%s>" % mysid
1439 ctx
.managedby
= admin_dn
1441 ctx
.userAccountControl
= (samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT |
1442 samba
.dsdb
.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1443 samba
.dsdb
.UF_PARTIAL_SECRETS_ACCOUNT
)
1445 ctx
.SPNs
.extend([ "RestrictedKrbHost/%s" % ctx
.myname
,
1446 "RestrictedKrbHost/%s" % ctx
.dnshostname
])
1448 ctx
.connection_dn
= "CN=RODC Connection (FRS),%s" % ctx
.ntds_dn
1449 ctx
.secure_channel_type
= misc
.SEC_CHAN_RODC
1451 ctx
.replica_flags |
= ( drsuapi
.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1452 drsuapi
.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP
)
1453 ctx
.domain_replica_flags
= ctx
.replica_flags
1454 if domain_critical_only
:
1455 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
1459 logger
.info("Joined domain %s (SID %s) as an RODC" % (ctx
.domain_name
, ctx
.domsid
))
1462 def join_DC(logger
=None, server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
1463 targetdir
=None, domain
=None, domain_critical_only
=False,
1464 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
1465 promote_existing
=False, plaintext_secrets
=False):
1467 ctx
= dc_join(logger
, server
, creds
, lp
, site
, netbios_name
, targetdir
, domain
,
1468 machinepass
, use_ntvfs
, dns_backend
, promote_existing
,
1471 lp
.set("workgroup", ctx
.domain_name
)
1472 logger
.info("workgroup is %s" % ctx
.domain_name
)
1474 lp
.set("realm", ctx
.realm
)
1475 logger
.info("realm is %s" % ctx
.realm
)
1477 ctx
.userAccountControl
= samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT | samba
.dsdb
.UF_TRUSTED_FOR_DELEGATION
1479 ctx
.SPNs
.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx
.dnsdomain
)
1480 ctx
.secure_channel_type
= misc
.SEC_CHAN_BDC
1482 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1483 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1484 ctx
.domain_replica_flags
= ctx
.replica_flags
1485 if domain_critical_only
:
1486 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
1489 logger
.info("Joined domain %s (SID %s) as a DC" % (ctx
.domain_name
, ctx
.domsid
))
1491 def join_clone(logger
=None, server
=None, creds
=None, lp
=None,
1492 targetdir
=None, domain
=None, include_secrets
=False):
1494 ctx
= dc_join(logger
, server
, creds
, lp
, site
=None, netbios_name
=None, targetdir
=targetdir
, domain
=domain
,
1495 machinepass
=None, use_ntvfs
=False, dns_backend
="NONE", promote_existing
=False, clone_only
=True)
1497 lp
.set("workgroup", ctx
.domain_name
)
1498 logger
.info("workgroup is %s" % ctx
.domain_name
)
1500 lp
.set("realm", ctx
.realm
)
1501 logger
.info("realm is %s" % ctx
.realm
)
1503 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1504 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1505 if not include_secrets
:
1506 ctx
.replica_flags |
= drsuapi
.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1507 ctx
.domain_replica_flags
= ctx
.replica_flags
1510 logger
.info("Cloned domain %s (SID %s)" % (ctx
.domain_name
, ctx
.domsid
))
1512 def join_subdomain(logger
=None, server
=None, creds
=None, lp
=None, site
=None,
1513 netbios_name
=None, targetdir
=None, parent_domain
=None, dnsdomain
=None,
1514 netbios_domain
=None, machinepass
=None, adminpass
=None, use_ntvfs
=False,
1515 dns_backend
=None, plaintext_secrets
=False):
1517 ctx
= dc_join(logger
, server
, creds
, lp
, site
, netbios_name
, targetdir
, parent_domain
,
1518 machinepass
, use_ntvfs
, dns_backend
, plaintext_secrets
)
1519 ctx
.subdomain
= True
1520 if adminpass
is None:
1521 ctx
.adminpass
= samba
.generate_random_password(12, 32)
1523 ctx
.adminpass
= adminpass
1524 ctx
.parent_domain_name
= ctx
.domain_name
1525 ctx
.domain_name
= netbios_domain
1526 ctx
.realm
= dnsdomain
1527 ctx
.parent_dnsdomain
= ctx
.dnsdomain
1528 ctx
.parent_partition_dn
= ctx
.get_parent_partition_dn()
1529 ctx
.dnsdomain
= dnsdomain
1530 ctx
.partition_dn
= "CN=%s,CN=Partitions,%s" % (ctx
.domain_name
, ctx
.config_dn
)
1531 ctx
.naming_master
= ctx
.get_naming_master()
1532 if ctx
.naming_master
!= ctx
.server
:
1533 logger
.info("Reconnecting to naming master %s" % ctx
.naming_master
)
1534 ctx
.server
= ctx
.naming_master
1535 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
1536 session_info
=system_session(),
1537 credentials
=ctx
.creds
, lp
=ctx
.lp
)
1538 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['dnsHostName'],
1540 ctx
.server
= res
[0]["dnsHostName"]
1541 logger
.info("DNS name of new naming master is %s" % ctx
.server
)
1543 ctx
.base_dn
= samba
.dn_from_dns_name(dnsdomain
)
1544 ctx
.forestsid
= ctx
.domsid
1545 ctx
.domsid
= security
.random_sid()
1547 ctx
.dnshostname
= "%s.%s" % (ctx
.myname
.lower(), ctx
.dnsdomain
)
1548 # Windows uses 240 bytes as UTF16 so we do
1549 ctx
.trustdom_pass
= samba
.generate_random_machine_password(120, 120)
1551 ctx
.userAccountControl
= samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT | samba
.dsdb
.UF_TRUSTED_FOR_DELEGATION
1553 ctx
.SPNs
.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx
.dnsdomain
)
1554 ctx
.secure_channel_type
= misc
.SEC_CHAN_BDC
1556 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1557 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1558 ctx
.domain_replica_flags
= ctx
.replica_flags
1561 ctx
.logger
.info("Created domain %s (SID %s) as a DC" % (ctx
.domain_name
, ctx
.domsid
))