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
26 from samba
.dcerpc
import security
, drsuapi
, misc
, nbt
, lsa
, drsblobs
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
43 class DCJoinException(Exception):
45 def __init__(self
, msg
):
46 super(DCJoinException
, self
).__init
__("Can't join, error: %s" % msg
)
49 class dc_join(object):
50 """Perform a DC join."""
52 def __init__(ctx
, logger
=None, server
=None, creds
=None, lp
=None, site
=None,
53 netbios_name
=None, targetdir
=None, domain
=None,
54 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
55 promote_existing
=False, clone_only
=False):
56 ctx
.clone_only
=clone_only
62 ctx
.targetdir
= targetdir
63 ctx
.use_ntvfs
= use_ntvfs
65 ctx
.promote_existing
= promote_existing
66 ctx
.promote_from_dn
= None
71 ctx
.creds
.set_gensec_features(creds
.get_gensec_features() | gensec
.FEATURE_SEAL
)
72 ctx
.net
= Net(creds
=ctx
.creds
, lp
=ctx
.lp
)
74 if server
is not None:
77 ctx
.logger
.info("Finding a writeable DC for domain '%s'" % domain
)
78 ctx
.server
= ctx
.find_dc(domain
)
79 ctx
.logger
.info("Found DC %s" % ctx
.server
)
81 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
82 session_info
=system_session(),
83 credentials
=ctx
.creds
, lp
=ctx
.lp
)
86 ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["dn"])
87 except ldb
.LdbError
, (enum
, estr
):
88 raise DCJoinException(estr
)
91 ctx
.base_dn
= str(ctx
.samdb
.get_default_basedn())
92 ctx
.root_dn
= str(ctx
.samdb
.get_root_basedn())
93 ctx
.schema_dn
= str(ctx
.samdb
.get_schema_basedn())
94 ctx
.config_dn
= str(ctx
.samdb
.get_config_basedn())
95 ctx
.domsid
= security
.dom_sid(ctx
.samdb
.get_domain_sid())
96 ctx
.forestsid
= ctx
.domsid
97 ctx
.domain_name
= ctx
.get_domain_name()
98 ctx
.forest_domain_name
= ctx
.get_forest_domain_name()
99 ctx
.invocation_id
= misc
.GUID(str(uuid
.uuid4()))
101 ctx
.dc_ntds_dn
= ctx
.samdb
.get_dsServiceName()
102 ctx
.dc_dnsHostName
= ctx
.get_dnsHostName()
103 ctx
.behavior_version
= ctx
.get_behavior_version()
105 if machinepass
is not None:
106 ctx
.acct_pass
= machinepass
108 ctx
.acct_pass
= samba
.generate_random_machine_password(128, 255)
110 ctx
.dnsdomain
= ctx
.samdb
.domain_dns_name()
112 # As we don't want to create or delete these DNs, we set them to None
116 ctx
.myname
= ctx
.server
.split('.')[0]
118 ctx
.rid_manager_dn
= None
121 ctx
.remote_dc_ntds_guid
= ctx
.samdb
.get_ntds_GUID()
123 # work out the DNs of all the objects we will be adding
124 ctx
.myname
= netbios_name
125 ctx
.samname
= "%s$" % ctx
.myname
126 ctx
.server_dn
= "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx
.myname
, ctx
.site
, ctx
.config_dn
)
127 ctx
.ntds_dn
= "CN=NTDS Settings,%s" % ctx
.server_dn
128 ctx
.acct_dn
= "CN=%s,OU=Domain Controllers,%s" % (ctx
.myname
, ctx
.base_dn
)
129 ctx
.dnshostname
= "%s.%s" % (ctx
.myname
.lower(), ctx
.dnsdomain
)
130 ctx
.dnsforest
= ctx
.samdb
.forest_dns_name()
132 topology_base
= "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx
.base_dn
133 if ctx
.dn_exists(topology_base
):
134 ctx
.topology_dn
= "CN=%s,%s" % (ctx
.myname
, topology_base
)
136 ctx
.topology_dn
= None
138 ctx
.SPNs
= [ "HOST/%s" % ctx
.myname
,
139 "HOST/%s" % ctx
.dnshostname
,
140 "GC/%s/%s" % (ctx
.dnshostname
, ctx
.dnsforest
) ]
142 res_rid_manager
= ctx
.samdb
.search(scope
=ldb
.SCOPE_BASE
,
143 attrs
=["rIDManagerReference"],
146 ctx
.rid_manager_dn
= res_rid_manager
[0]["rIDManagerReference"][0]
148 ctx
.domaindns_zone
= 'DC=DomainDnsZones,%s' % ctx
.base_dn
149 ctx
.forestdns_zone
= 'DC=ForestDnsZones,%s' % ctx
.root_dn
151 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.domaindns_zone
)
152 res_domaindns
= ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
154 base
=ctx
.samdb
.get_partitions_dn(),
156 if dns_backend
is None:
157 ctx
.dns_backend
= "NONE"
159 if len(res_domaindns
) == 0:
160 ctx
.dns_backend
= "NONE"
161 print "NO DNS zone information found in source domain, not replicating DNS"
163 ctx
.dns_backend
= dns_backend
165 ctx
.realm
= ctx
.dnsdomain
169 ctx
.replica_flags
= (drsuapi
.DRSUAPI_DRS_INIT_SYNC |
170 drsuapi
.DRSUAPI_DRS_PER_SYNC |
171 drsuapi
.DRSUAPI_DRS_GET_ANC |
172 drsuapi
.DRSUAPI_DRS_GET_NC_SIZE |
173 drsuapi
.DRSUAPI_DRS_NEVER_SYNCED
)
175 # these elements are optional
176 ctx
.never_reveal_sid
= None
177 ctx
.reveal_sid
= None
178 ctx
.connection_dn
= None
183 ctx
.subdomain
= False
185 ctx
.partition_dn
= None
187 def del_noerror(ctx
, dn
, recursive
=False):
190 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["dn"])
194 ctx
.del_noerror(r
.dn
, recursive
=True)
197 print "Deleted %s" % dn
201 def cleanup_old_accounts(ctx
):
202 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
203 expression
='sAMAccountName=%s' % ldb
.binary_encode(ctx
.samname
),
204 attrs
=["msDS-krbTgtLink", "objectSID"])
208 creds
= Credentials()
211 creds
.set_machine_account(ctx
.lp
)
212 machine_samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
213 session_info
=system_session(),
214 credentials
=creds
, lp
=ctx
.lp
)
218 token_res
= machine_samdb
.search(scope
=ldb
.SCOPE_BASE
, base
="", attrs
=["tokenGroups"])
219 if token_res
[0]["tokenGroups"][0] \
220 == res
[0]["objectSID"][0]:
221 raise DCJoinException("Not removing account %s which "
222 "looks like a Samba DC account "
223 "maching the password we already have. "
224 "To override, remove secrets.ldb and secrets.tdb"
227 ctx
.del_noerror(res
[0].dn
, recursive
=True)
229 if "msDS-Krbtgtlink" in res
[0]:
230 new_krbtgt_dn
= res
[0]["msDS-Krbtgtlink"][0]
231 ctx
.del_noerror(ctx
.new_krbtgt_dn
)
233 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
234 expression
='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
235 (ldb
.binary_encode("dns-%s" % ctx
.myname
),
236 ldb
.binary_encode("dns/%s" % ctx
.dnshostname
)),
239 ctx
.del_noerror(res
[0].dn
, recursive
=True)
241 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
242 expression
='(sAMAccountName=%s)' % ldb
.binary_encode("dns-%s" % ctx
.myname
),
245 raise DCJoinException("Not removing account %s which looks like "
246 "a Samba DNS service account but does not "
247 "have servicePrincipalName=%s" %
248 (ldb
.binary_encode("dns-%s" % ctx
.myname
),
249 ldb
.binary_encode("dns/%s" % ctx
.dnshostname
)))
252 def cleanup_old_join(ctx
):
253 """Remove any DNs from a previous join."""
254 # find the krbtgt link
255 if not ctx
.subdomain
:
256 ctx
.cleanup_old_accounts()
258 if ctx
.connection_dn
is not None:
259 ctx
.del_noerror(ctx
.connection_dn
)
260 if ctx
.krbtgt_dn
is not None:
261 ctx
.del_noerror(ctx
.krbtgt_dn
)
262 ctx
.del_noerror(ctx
.ntds_dn
)
263 ctx
.del_noerror(ctx
.server_dn
, recursive
=True)
265 ctx
.del_noerror(ctx
.topology_dn
)
267 ctx
.del_noerror(ctx
.partition_dn
)
270 binding_options
= "sign"
271 lsaconn
= lsa
.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
274 objectAttr
= lsa
.ObjectAttribute()
275 objectAttr
.sec_qos
= lsa
.QosInfo()
277 pol_handle
= lsaconn
.OpenPolicy2(''.decode('utf-8'),
278 objectAttr
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
281 name
.string
= ctx
.realm
282 info
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, name
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
284 lsaconn
.DeleteTrustedDomain(pol_handle
, info
.info_ex
.sid
)
287 name
.string
= ctx
.forest_domain_name
288 info
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, name
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
290 lsaconn
.DeleteTrustedDomain(pol_handle
, info
.info_ex
.sid
)
293 def promote_possible(ctx
):
294 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
296 # This shouldn't happen
297 raise Exception("Can not promote into a subdomain")
299 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
300 expression
='sAMAccountName=%s' % ldb
.binary_encode(ctx
.samname
),
301 attrs
=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
303 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx
.samname
)
304 if "msDS-krbTgtLink" in res
[0] or "serverReferenceBL" in res
[0] or "rIDSetReferences" in res
[0]:
305 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx
.samname
)
306 if (int(res
[0]["userAccountControl"][0]) & (samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT|samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT
) == 0):
307 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx
.samname
)
309 ctx
.promote_from_dn
= res
[0].dn
312 def find_dc(ctx
, domain
):
313 """find a writeable DC for the given domain"""
315 ctx
.cldap_ret
= ctx
.net
.finddc(domain
=domain
, flags
=nbt
.NBT_SERVER_LDAP | nbt
.NBT_SERVER_DS | nbt
.NBT_SERVER_WRITABLE
)
317 raise Exception("Failed to find a writeable DC for domain '%s'" % domain
)
318 if ctx
.cldap_ret
.client_site
is not None and ctx
.cldap_ret
.client_site
!= "":
319 ctx
.site
= ctx
.cldap_ret
.client_site
320 return ctx
.cldap_ret
.pdc_dns_name
323 def get_behavior_version(ctx
):
324 res
= ctx
.samdb
.search(base
=ctx
.base_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["msDS-Behavior-Version"])
325 if "msDS-Behavior-Version" in res
[0]:
326 return int(res
[0]["msDS-Behavior-Version"][0])
328 return samba
.dsdb
.DS_DOMAIN_FUNCTION_2000
330 def get_dnsHostName(ctx
):
331 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["dnsHostName"])
332 return res
[0]["dnsHostName"][0]
334 def get_domain_name(ctx
):
335 '''get netbios name of the domain from the partitions record'''
336 partitions_dn
= ctx
.samdb
.get_partitions_dn()
337 res
= ctx
.samdb
.search(base
=partitions_dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["nETBIOSName"],
338 expression
='ncName=%s' % ldb
.binary_encode(str(ctx
.samdb
.get_default_basedn())))
339 return res
[0]["nETBIOSName"][0]
341 def get_forest_domain_name(ctx
):
342 '''get netbios name of the domain from the partitions record'''
343 partitions_dn
= ctx
.samdb
.get_partitions_dn()
344 res
= ctx
.samdb
.search(base
=partitions_dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["nETBIOSName"],
345 expression
='ncName=%s' % ldb
.binary_encode(str(ctx
.samdb
.get_root_basedn())))
346 return res
[0]["nETBIOSName"][0]
348 def get_parent_partition_dn(ctx
):
349 '''get the parent domain partition DN from parent DNS name'''
350 res
= ctx
.samdb
.search(base
=ctx
.config_dn
, attrs
=[],
351 expression
='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
352 (ldb
.binary_encode(ctx
.parent_dnsdomain
),
353 ldb
.OID_COMPARATOR_AND
, samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_DOMAIN
))
354 return str(res
[0].dn
)
356 def get_naming_master(ctx
):
357 '''get the parent domain partition DN from parent DNS name'''
358 res
= ctx
.samdb
.search(base
='CN=Partitions,%s' % ctx
.config_dn
, attrs
=['fSMORoleOwner'],
359 scope
=ldb
.SCOPE_BASE
, controls
=["extended_dn:1:1"])
360 if not 'fSMORoleOwner' in res
[0]:
361 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx
.partition_dn
, ctx
.samdb
.url
))
363 master_guid
= str(misc
.GUID(ldb
.Dn(ctx
.samdb
, res
[0]['fSMORoleOwner'][0]).get_extended_component('GUID')))
365 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res
[0]['fSMORoleOwner'][0])
367 master_host
= '%s._msdcs.%s' % (master_guid
, ctx
.dnsforest
)
371 '''get the SID of the connected user. Only works with w2k8 and later,
372 so only used for RODC join'''
373 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["tokenGroups"])
374 binsid
= res
[0]["tokenGroups"][0]
375 return ctx
.samdb
.schema_format_value("objectSID", binsid
)
377 def dn_exists(ctx
, dn
):
378 '''check if a DN exists'''
380 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[])
381 except ldb
.LdbError
, (enum
, estr
):
382 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
387 def add_krbtgt_account(ctx
):
388 '''RODCs need a special krbtgt account'''
389 print "Adding %s" % ctx
.krbtgt_dn
391 "dn" : ctx
.krbtgt_dn
,
392 "objectclass" : "user",
393 "useraccountcontrol" : str(samba
.dsdb
.UF_NORMAL_ACCOUNT |
394 samba
.dsdb
.UF_ACCOUNTDISABLE
),
395 "showinadvancedviewonly" : "TRUE",
396 "description" : "krbtgt for %s" % ctx
.samname
}
397 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
399 # now we need to search for the samAccountName attribute on the krbtgt DN,
400 # as this will have been magically set to the krbtgt number
401 res
= ctx
.samdb
.search(base
=ctx
.krbtgt_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["samAccountName"])
402 ctx
.krbtgt_name
= res
[0]["samAccountName"][0]
404 print "Got krbtgt_name=%s" % ctx
.krbtgt_name
407 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
408 m
["msDS-krbTgtLink"] = ldb
.MessageElement(ctx
.krbtgt_dn
,
409 ldb
.FLAG_MOD_REPLACE
, "msDS-krbTgtLink")
412 ctx
.new_krbtgt_dn
= "CN=%s,CN=Users,%s" % (ctx
.krbtgt_name
, ctx
.base_dn
)
413 print "Renaming %s to %s" % (ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
)
414 ctx
.samdb
.rename(ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
)
416 def drsuapi_connect(ctx
):
417 '''make a DRSUAPI connection to the naming master'''
418 binding_options
= "seal"
419 if ctx
.lp
.log_level() >= 4:
420 binding_options
+= ",print"
421 binding_string
= "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
)
422 ctx
.drsuapi
= drsuapi
.drsuapi(binding_string
, ctx
.lp
, ctx
.creds
)
423 (ctx
.drsuapi_handle
, ctx
.bind_supported_extensions
) = drs_utils
.drs_DsBind(ctx
.drsuapi
)
425 def create_tmp_samdb(ctx
):
426 '''create a temporary samdb object for schema queries'''
427 ctx
.tmp_schema
= Schema(ctx
.domsid
,
428 schemadn
=ctx
.schema_dn
)
429 ctx
.tmp_samdb
= SamDB(session_info
=system_session(), url
=None, auto_connect
=False,
430 credentials
=ctx
.creds
, lp
=ctx
.lp
, global_schema
=False,
432 ctx
.tmp_samdb
.set_schema(ctx
.tmp_schema
)
434 def build_DsReplicaAttribute(ctx
, attrname
, attrvalue
):
435 '''build a DsReplicaAttributeCtr object'''
436 r
= drsuapi
.DsReplicaAttribute()
437 r
.attid
= ctx
.tmp_samdb
.get_attid_from_lDAPDisplayName(attrname
)
441 def DsAddEntry(ctx
, recs
):
442 '''add a record via the DRSUAPI DsAddEntry call'''
443 if ctx
.drsuapi
is None:
444 ctx
.drsuapi_connect()
445 if ctx
.tmp_samdb
is None:
446 ctx
.create_tmp_samdb()
450 id = drsuapi
.DsReplicaObjectIdentifier()
457 if not isinstance(rec
[a
], list):
461 rattr
= ctx
.tmp_samdb
.dsdb_DsReplicaAttribute(ctx
.tmp_samdb
, a
, v
)
464 attribute_ctr
= drsuapi
.DsReplicaAttributeCtr()
465 attribute_ctr
.num_attributes
= len(attrs
)
466 attribute_ctr
.attributes
= attrs
468 object = drsuapi
.DsReplicaObject()
469 object.identifier
= id
470 object.attribute_ctr
= attribute_ctr
472 list_object
= drsuapi
.DsReplicaObjectListItem()
473 list_object
.object = object
474 objects
.append(list_object
)
476 req2
= drsuapi
.DsAddEntryRequest2()
477 req2
.first_object
= objects
[0]
478 prev
= req2
.first_object
479 for o
in objects
[1:]:
483 (level
, ctr
) = ctx
.drsuapi
.DsAddEntry(ctx
.drsuapi_handle
, 2, req2
)
485 if ctr
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
486 print("DsAddEntry failed with dir_err %u" % ctr
.dir_err
)
487 raise RuntimeError("DsAddEntry failed")
488 if ctr
.extended_err
[0] != werror
.WERR_SUCCESS
:
489 print("DsAddEntry failed with status %s info %s" % (ctr
.extended_err
))
490 raise RuntimeError("DsAddEntry failed")
493 raise RuntimeError("expected err_ver 1, got %u" % ctr
.err_ver
)
494 if ctr
.err_data
.status
[0] != werror
.WERR_SUCCESS
:
495 if ctr
.err_data
.info
is None:
496 print("DsAddEntry failed with status %s, info omitted" % (ctr
.err_data
.status
[1]))
498 print("DsAddEntry failed with status %s info %s" % (ctr
.err_data
.status
[1],
499 ctr
.err_data
.info
.extended_err
))
500 raise RuntimeError("DsAddEntry failed")
501 if ctr
.err_data
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
502 print("DsAddEntry failed with dir_err %u" % ctr
.err_data
.dir_err
)
503 raise RuntimeError("DsAddEntry failed")
507 def join_ntdsdsa_obj(ctx
):
508 '''return the ntdsdsa object to add'''
510 print "Adding %s" % ctx
.ntds_dn
513 "objectclass" : "nTDSDSA",
514 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
515 "dMDLocation" : ctx
.schema_dn
}
517 nc_list
= [ ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
519 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
520 rec
["msDS-Behavior-Version"] = str(samba
.dsdb
.DS_DOMAIN_FUNCTION_2008_R2
)
522 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
523 rec
["msDS-HasDomainNCs"] = ctx
.base_dn
526 rec
["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx
.schema_dn
527 rec
["msDS-HasFullReplicaNCs"] = ctx
.full_nc_list
528 rec
["options"] = "37"
530 rec
["objectCategory"] = "CN=NTDS-DSA,%s" % ctx
.schema_dn
531 rec
["HasMasterNCs"] = []
533 if nc
in ctx
.full_nc_list
:
534 rec
["HasMasterNCs"].append(nc
)
535 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
536 rec
["msDS-HasMasterNCs"] = ctx
.full_nc_list
538 rec
["invocationId"] = ndr_pack(ctx
.invocation_id
)
542 def join_add_ntdsdsa(ctx
):
543 '''add the ntdsdsa object'''
545 rec
= ctx
.join_ntdsdsa_obj()
547 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
549 ctx
.DsAddEntry([rec
])
551 # find the GUID of our NTDS DN
552 res
= ctx
.samdb
.search(base
=ctx
.ntds_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["objectGUID"])
553 ctx
.ntds_guid
= misc
.GUID(ctx
.samdb
.schema_format_value("objectGUID", res
[0]["objectGUID"][0]))
555 def join_add_objects(ctx
):
556 '''add the various objects needed for the join'''
558 print "Adding %s" % ctx
.acct_dn
561 "objectClass": "computer",
562 "displayname": ctx
.samname
,
563 "samaccountname" : ctx
.samname
,
564 "userAccountControl" : str(ctx
.userAccountControl | samba
.dsdb
.UF_ACCOUNTDISABLE
),
565 "dnshostname" : ctx
.dnshostname
}
566 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2008
:
567 rec
['msDS-SupportedEncryptionTypes'] = str(samba
.dsdb
.ENC_ALL_TYPES
)
568 elif ctx
.promote_existing
:
569 rec
['msDS-SupportedEncryptionTypes'] = []
571 rec
["managedby"] = ctx
.managedby
572 elif ctx
.promote_existing
:
573 rec
["managedby"] = []
575 if ctx
.never_reveal_sid
:
576 rec
["msDS-NeverRevealGroup"] = ctx
.never_reveal_sid
577 elif ctx
.promote_existing
:
578 rec
["msDS-NeverRevealGroup"] = []
581 rec
["msDS-RevealOnDemandGroup"] = ctx
.reveal_sid
582 elif ctx
.promote_existing
:
583 rec
["msDS-RevealOnDemandGroup"] = []
585 if ctx
.promote_existing
:
586 if ctx
.promote_from_dn
!= ctx
.acct_dn
:
587 ctx
.samdb
.rename(ctx
.promote_from_dn
, ctx
.acct_dn
)
588 ctx
.samdb
.modify(ldb
.Message
.from_dict(ctx
.samdb
, rec
, ldb
.FLAG_MOD_REPLACE
))
593 ctx
.add_krbtgt_account()
596 print "Adding %s" % ctx
.server_dn
599 "objectclass" : "server",
600 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
601 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
602 samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
603 samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
604 # windows seems to add the dnsHostName later
605 "dnsHostName" : ctx
.dnshostname
}
608 rec
["serverReference"] = ctx
.acct_dn
613 # the rest is done after replication
618 ctx
.join_add_ntdsdsa()
620 # Add the Replica-Locations or RO-Replica-Locations attributes
621 # TODO Is this supposed to be for the schema partition too?
622 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.domaindns_zone
)
623 domain
= (ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
625 base
=ctx
.samdb
.get_partitions_dn(),
626 expression
=expr
), ctx
.domaindns_zone
)
628 expr
= "(&(objectClass=crossRef)(ncName=%s))" % ldb
.binary_encode(ctx
.forestdns_zone
)
629 forest
= (ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
,
631 base
=ctx
.samdb
.get_partitions_dn(),
632 expression
=expr
), ctx
.forestdns_zone
)
634 for part
, zone
in (domain
, forest
):
635 if zone
not in ctx
.nc_list
:
641 attr
= "msDS-NC-Replica-Locations"
643 attr
= "msDS-NC-RO-Replica-Locations"
645 m
[attr
] = ldb
.MessageElement(ctx
.ntds_dn
,
646 ldb
.FLAG_MOD_ADD
, attr
)
649 if ctx
.connection_dn
is not None:
650 print "Adding %s" % ctx
.connection_dn
652 "dn" : ctx
.connection_dn
,
653 "objectclass" : "nTDSConnection",
654 "enabledconnection" : "TRUE",
656 "fromServer" : ctx
.dc_ntds_dn
}
660 print "Adding SPNs to %s" % ctx
.acct_dn
662 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
663 for i
in range(len(ctx
.SPNs
)):
664 ctx
.SPNs
[i
] = ctx
.SPNs
[i
].replace("$NTDSGUID", str(ctx
.ntds_guid
))
665 m
["servicePrincipalName"] = ldb
.MessageElement(ctx
.SPNs
,
666 ldb
.FLAG_MOD_REPLACE
,
667 "servicePrincipalName")
670 # The account password set operation should normally be done over
671 # LDAP. Windows 2000 DCs however allow this only with SSL
672 # connections which are hard to set up and otherwise refuse with
673 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
675 print "Setting account password for %s" % ctx
.samname
677 ctx
.samdb
.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
678 % ldb
.binary_encode(ctx
.samname
),
680 force_change_at_next_login
=False,
681 username
=ctx
.samname
)
682 except ldb
.LdbError
, (num
, _
):
683 if num
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
685 ctx
.net
.set_password(account_name
=ctx
.samname
,
686 domain_name
=ctx
.domain_name
,
687 newpassword
=ctx
.acct_pass
.encode('utf-8'))
689 res
= ctx
.samdb
.search(base
=ctx
.acct_dn
, scope
=ldb
.SCOPE_BASE
,
690 attrs
=["msDS-KeyVersionNumber"])
691 if "msDS-KeyVersionNumber" in res
[0]:
692 ctx
.key_version_number
= int(res
[0]["msDS-KeyVersionNumber"][0])
694 ctx
.key_version_number
= None
696 print("Enabling account")
698 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
699 m
["userAccountControl"] = ldb
.MessageElement(str(ctx
.userAccountControl
),
700 ldb
.FLAG_MOD_REPLACE
,
701 "userAccountControl")
704 if ctx
.dns_backend
.startswith("BIND9_"):
705 ctx
.dnspass
= samba
.generate_random_password(128, 255)
707 recs
= ctx
.samdb
.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
708 {"DNSDOMAIN": ctx
.dnsdomain
,
709 "DOMAINDN": ctx
.base_dn
,
710 "HOSTNAME" : ctx
.myname
,
711 "DNSPASS_B64": b64encode(ctx
.dnspass
.encode('utf-16-le')),
712 "DNSNAME" : ctx
.dnshostname
}))
713 for changetype
, msg
in recs
:
714 assert changetype
== ldb
.CHANGETYPE_NONE
715 dns_acct_dn
= msg
["dn"]
716 print "Adding DNS account %s with dns/ SPN" % msg
["dn"]
718 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
719 del msg
["clearTextPassword"]
720 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
721 del msg
["isCriticalSystemObject"]
722 # Disable account until password is set
723 msg
["userAccountControl"] = str(samba
.dsdb
.UF_NORMAL_ACCOUNT |
724 samba
.dsdb
.UF_ACCOUNTDISABLE
)
727 except ldb
.LdbError
, (num
, _
):
728 if num
!= ldb
.ERR_ENTRY_ALREADY_EXISTS
:
731 # The account password set operation should normally be done over
732 # LDAP. Windows 2000 DCs however allow this only with SSL
733 # connections which are hard to set up and otherwise refuse with
734 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
736 print "Setting account password for dns-%s" % ctx
.myname
738 ctx
.samdb
.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
739 % ldb
.binary_encode(ctx
.myname
),
741 force_change_at_next_login
=False,
742 username
=ctx
.samname
)
743 except ldb
.LdbError
, (num
, _
):
744 if num
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
746 ctx
.net
.set_password(account_name
="dns-%s" % ctx
.myname
,
747 domain_name
=ctx
.domain_name
,
748 newpassword
=ctx
.dnspass
)
750 res
= ctx
.samdb
.search(base
=dns_acct_dn
, scope
=ldb
.SCOPE_BASE
,
751 attrs
=["msDS-KeyVersionNumber"])
752 if "msDS-KeyVersionNumber" in res
[0]:
753 ctx
.dns_key_version_number
= int(res
[0]["msDS-KeyVersionNumber"][0])
755 ctx
.dns_key_version_number
= None
757 def join_add_objects2(ctx
):
758 """add the various objects needed for the join, for subdomains post replication"""
760 print "Adding %s" % ctx
.partition_dn
761 name_map
= {'SubdomainAdmins': "%s-%s" % (str(ctx
.domsid
), security
.DOMAIN_RID_ADMINS
)}
762 sd_binary
= descriptor
.get_paritions_crossref_subdomain_descriptor(ctx
.forestsid
, name_map
=name_map
)
764 "dn" : ctx
.partition_dn
,
765 "objectclass" : "crossRef",
766 "objectCategory" : "CN=Cross-Ref,%s" % ctx
.schema_dn
,
767 "nCName" : ctx
.base_dn
,
768 "nETBIOSName" : ctx
.domain_name
,
769 "dnsRoot": ctx
.dnsdomain
,
770 "trustParent" : ctx
.parent_partition_dn
,
771 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_NC|samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_DOMAIN
),
772 "ntSecurityDescriptor" : sd_binary
,
775 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
776 rec
["msDS-Behavior-Version"] = str(ctx
.behavior_version
)
778 rec2
= ctx
.join_ntdsdsa_obj()
780 objects
= ctx
.DsAddEntry([rec
, rec2
])
781 if len(objects
) != 2:
782 raise DCJoinException("Expected 2 objects from DsAddEntry")
784 ctx
.ntds_guid
= objects
[1].guid
786 print("Replicating partition DN")
787 ctx
.repl
.replicate(ctx
.partition_dn
,
788 misc
.GUID("00000000-0000-0000-0000-000000000000"),
790 exop
=drsuapi
.DRSUAPI_EXOP_REPL_OBJ
,
791 replica_flags
=drsuapi
.DRSUAPI_DRS_WRIT_REP
)
793 print("Replicating NTDS DN")
794 ctx
.repl
.replicate(ctx
.ntds_dn
,
795 misc
.GUID("00000000-0000-0000-0000-000000000000"),
797 exop
=drsuapi
.DRSUAPI_EXOP_REPL_OBJ
,
798 replica_flags
=drsuapi
.DRSUAPI_DRS_WRIT_REP
)
800 def join_provision(ctx
):
801 """Provision the local SAM."""
803 print "Calling bare provision"
805 smbconf
= ctx
.lp
.configfile
807 presult
= provision(ctx
.logger
, system_session(), smbconf
=smbconf
,
808 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_DRS
, realm
=ctx
.realm
,
809 rootdn
=ctx
.root_dn
, domaindn
=ctx
.base_dn
,
810 schemadn
=ctx
.schema_dn
, configdn
=ctx
.config_dn
,
811 serverdn
=ctx
.server_dn
, domain
=ctx
.domain_name
,
812 hostname
=ctx
.myname
, domainsid
=ctx
.domsid
,
813 machinepass
=ctx
.acct_pass
, serverrole
="active directory domain controller",
814 sitename
=ctx
.site
, lp
=ctx
.lp
, ntdsguid
=ctx
.ntds_guid
,
815 use_ntvfs
=ctx
.use_ntvfs
, dns_backend
=ctx
.dns_backend
)
816 print "Provision OK for domain DN %s" % presult
.domaindn
817 ctx
.local_samdb
= presult
.samdb
819 ctx
.paths
= presult
.paths
820 ctx
.names
= presult
.names
822 # Fix up the forestsid, it may be different if we are joining as a subdomain
823 ctx
.names
.forestsid
= ctx
.forestsid
825 def join_provision_own_domain(ctx
):
826 """Provision the local SAM."""
828 # we now operate exclusively on the local database, which
829 # we need to reopen in order to get the newly created schema
830 print("Reconnecting to local samdb")
831 ctx
.samdb
= SamDB(url
=ctx
.local_samdb
.url
,
832 session_info
=system_session(),
833 lp
=ctx
.local_samdb
.lp
,
835 ctx
.samdb
.set_invocation_id(str(ctx
.invocation_id
))
836 ctx
.local_samdb
= ctx
.samdb
838 ctx
.logger
.info("Finding domain GUID from ncName")
839 res
= ctx
.local_samdb
.search(base
=ctx
.partition_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=['ncName'],
840 controls
=["extended_dn:1:1", "reveal_internals:0"])
842 if 'nCName' not in res
[0]:
843 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx
.partition_dn
, ctx
.samdb
.url
))
846 ctx
.names
.domainguid
= str(misc
.GUID(ldb
.Dn(ctx
.samdb
, res
[0]['ncName'][0]).get_extended_component('GUID')))
848 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res
[0]['ncName'][0])
850 ctx
.logger
.info("Got domain GUID %s" % ctx
.names
.domainguid
)
852 ctx
.logger
.info("Calling own domain provision")
854 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
856 presult
= provision_fill(ctx
.local_samdb
, secrets_ldb
,
857 ctx
.logger
, ctx
.names
, ctx
.paths
,
858 dom_for_fun_level
=DS_DOMAIN_FUNCTION_2003
,
859 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_SUBDOMAIN
,
860 machinepass
=ctx
.acct_pass
, serverrole
="active directory domain controller",
861 lp
=ctx
.lp
, hostip
=ctx
.names
.hostip
, hostip6
=ctx
.names
.hostip6
,
862 dns_backend
=ctx
.dns_backend
, adminpass
=ctx
.adminpass
)
863 print("Provision OK for domain %s" % ctx
.names
.dnsdomain
)
865 def join_replicate(ctx
):
866 """Replicate the SAM."""
868 print "Starting replication"
869 ctx
.local_samdb
.transaction_start()
871 source_dsa_invocation_id
= misc
.GUID(ctx
.samdb
.get_invocation_id())
872 if ctx
.ntds_guid
is None:
873 print("Using DS_BIND_GUID_W2K3")
874 destination_dsa_guid
= misc
.GUID(drsuapi
.DRSUAPI_DS_BIND_GUID_W2K3
)
876 destination_dsa_guid
= ctx
.ntds_guid
879 repl_creds
= Credentials()
880 repl_creds
.guess(ctx
.lp
)
881 repl_creds
.set_kerberos_state(DONT_USE_KERBEROS
)
882 repl_creds
.set_username(ctx
.samname
)
883 repl_creds
.set_password(ctx
.acct_pass
.encode('utf-8'))
885 repl_creds
= ctx
.creds
887 binding_options
= "seal"
888 if ctx
.lp
.log_level() >= 5:
889 binding_options
+= ",print"
890 repl
= drs_utils
.drs_Replicate(
891 "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
892 ctx
.lp
, repl_creds
, ctx
.local_samdb
, ctx
.invocation_id
)
894 repl
.replicate(ctx
.schema_dn
, source_dsa_invocation_id
,
895 destination_dsa_guid
, schema
=True, rodc
=ctx
.RODC
,
896 replica_flags
=ctx
.replica_flags
)
897 repl
.replicate(ctx
.config_dn
, source_dsa_invocation_id
,
898 destination_dsa_guid
, rodc
=ctx
.RODC
,
899 replica_flags
=ctx
.replica_flags
)
900 if not ctx
.subdomain
:
901 # Replicate first the critical object for the basedn
902 if not ctx
.domain_replica_flags
& drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
:
903 print "Replicating critical objects from the base DN of the domain"
904 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
905 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
906 destination_dsa_guid
, rodc
=ctx
.RODC
,
907 replica_flags
=ctx
.domain_replica_flags
)
908 ctx
.domain_replica_flags ^
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
909 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
910 destination_dsa_guid
, rodc
=ctx
.RODC
,
911 replica_flags
=ctx
.domain_replica_flags
)
912 print "Done with always replicated NC (base, config, schema)"
914 # At this point we should already have an entry in the ForestDNS
915 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
916 # indicate that we hold a replica for this NC.
917 for nc
in (ctx
.domaindns_zone
, ctx
.forestdns_zone
):
918 if nc
in ctx
.nc_list
:
919 print "Replicating %s" % (str(nc
))
920 repl
.replicate(nc
, source_dsa_invocation_id
,
921 destination_dsa_guid
, rodc
=ctx
.RODC
,
922 replica_flags
=ctx
.replica_flags
)
925 repl
.replicate(ctx
.acct_dn
, source_dsa_invocation_id
,
926 destination_dsa_guid
,
927 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
928 repl
.replicate(ctx
.new_krbtgt_dn
, source_dsa_invocation_id
,
929 destination_dsa_guid
,
930 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
931 elif ctx
.rid_manager_dn
!= None:
932 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
934 repl
.replicate(ctx
.rid_manager_dn
, source_dsa_invocation_id
,
935 destination_dsa_guid
,
936 exop
=drsuapi
.DRSUAPI_EXOP_FSMO_RID_ALLOC
)
937 except samba
.DsExtendedError
, (enum
, estr
):
938 if enum
== drsuapi
.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER
:
939 print "WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx
.server
940 print "NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup."
945 ctx
.source_dsa_invocation_id
= source_dsa_invocation_id
946 ctx
.destination_dsa_guid
= destination_dsa_guid
948 print "Committing SAM database"
950 ctx
.local_samdb
.transaction_cancel()
953 ctx
.local_samdb
.transaction_commit()
955 def send_DsReplicaUpdateRefs(ctx
, dn
):
956 r
= drsuapi
.DsReplicaUpdateRefsRequest1()
957 r
.naming_context
= drsuapi
.DsReplicaObjectIdentifier()
958 r
.naming_context
.dn
= str(dn
)
959 r
.naming_context
.guid
= misc
.GUID("00000000-0000-0000-0000-000000000000")
960 r
.naming_context
.sid
= security
.dom_sid("S-0-0")
961 r
.dest_dsa_guid
= ctx
.ntds_guid
962 r
.dest_dsa_dns_name
= "%s._msdcs.%s" % (str(ctx
.ntds_guid
), ctx
.dnsforest
)
963 r
.options
= drsuapi
.DRSUAPI_DRS_ADD_REF | drsuapi
.DRSUAPI_DRS_DEL_REF
965 r
.options |
= drsuapi
.DRSUAPI_DRS_WRIT_REP
967 if ctx
.drsuapi
is None:
968 ctx
.drsuapi_connect()
970 ctx
.drsuapi
.DsReplicaUpdateRefs(ctx
.drsuapi_handle
, 1, r
)
972 def join_finalise(ctx
):
973 """Finalise the join, mark us synchronised and setup secrets db."""
975 # FIXME we shouldn't do this in all cases
977 # If for some reasons we joined in another site than the one of
978 # DC we just replicated from then we don't need to send the updatereplicateref
979 # as replication between sites is time based and on the initiative of the
981 if not ctx
.clone_only
:
982 ctx
.logger
.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
983 for nc
in ctx
.nc_list
:
984 ctx
.send_DsReplicaUpdateRefs(nc
)
986 if not ctx
.clone_only
and ctx
.RODC
:
987 print "Setting RODC invocationId"
988 ctx
.local_samdb
.set_invocation_id(str(ctx
.invocation_id
))
989 ctx
.local_samdb
.set_opaque_integer("domainFunctionality",
990 ctx
.behavior_version
)
992 m
.dn
= ldb
.Dn(ctx
.local_samdb
, "%s" % ctx
.ntds_dn
)
993 m
["invocationId"] = ldb
.MessageElement(ndr_pack(ctx
.invocation_id
),
994 ldb
.FLAG_MOD_REPLACE
,
996 ctx
.local_samdb
.modify(m
)
998 # Note: as RODC the invocationId is only stored
999 # on the RODC itself, the other DCs never see it.
1001 # Thats is why we fix up the replPropertyMetaData stamp
1002 # for the 'invocationId' attribute, we need to change
1003 # the 'version' to '0', this is what windows 2008r2 does as RODC
1005 # This means if the object on a RWDC ever gets a invocationId
1006 # attribute, it will have version '1' (or higher), which will
1007 # will overwrite the RODC local value.
1008 ctx
.local_samdb
.set_attribute_replmetadata_version(m
.dn
,
1012 ctx
.logger
.info("Setting isSynchronized and dsServiceName")
1014 m
.dn
= ldb
.Dn(ctx
.local_samdb
, '@ROOTDSE')
1015 m
["isSynchronized"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isSynchronized")
1017 # We want to appear to be the server we just cloned
1019 guid
= ctx
.remote_dc_ntds_guid
1021 guid
= ctx
.ntds_guid
1023 m
["dsServiceName"] = ldb
.MessageElement("<GUID=%s>" % str(guid
),
1024 ldb
.FLAG_MOD_REPLACE
, "dsServiceName")
1025 ctx
.local_samdb
.modify(m
)
1027 if ctx
.clone_only
or ctx
.subdomain
:
1030 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
1032 ctx
.logger
.info("Setting up secrets database")
1033 secretsdb_self_join(secrets_ldb
, domain
=ctx
.domain_name
,
1035 dnsdomain
=ctx
.dnsdomain
,
1036 netbiosname
=ctx
.myname
,
1037 domainsid
=ctx
.domsid
,
1038 machinepass
=ctx
.acct_pass
,
1039 secure_channel_type
=ctx
.secure_channel_type
,
1040 key_version_number
=ctx
.key_version_number
)
1042 if ctx
.dns_backend
.startswith("BIND9_"):
1043 setup_bind9_dns(ctx
.local_samdb
, secrets_ldb
,
1044 ctx
.names
, ctx
.paths
, ctx
.lp
, ctx
.logger
,
1045 dns_backend
=ctx
.dns_backend
,
1046 dnspass
=ctx
.dnspass
, os_level
=ctx
.behavior_version
,
1047 targetdir
=ctx
.targetdir
,
1048 key_version_number
=ctx
.dns_key_version_number
)
1050 def join_setup_trusts(ctx
):
1051 """provision the local SAM."""
1053 print "Setup domain trusts with server %s" % ctx
.server
1054 binding_options
= "" # why doesn't signing work here? w2k8r2 claims no session key
1055 lsaconn
= lsa
.lsarpc("ncacn_np:%s[%s]" % (ctx
.server
, binding_options
),
1058 objectAttr
= lsa
.ObjectAttribute()
1059 objectAttr
.sec_qos
= lsa
.QosInfo()
1061 pol_handle
= lsaconn
.OpenPolicy2(''.decode('utf-8'),
1062 objectAttr
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
1064 info
= lsa
.TrustDomainInfoInfoEx()
1065 info
.domain_name
.string
= ctx
.dnsdomain
1066 info
.netbios_name
.string
= ctx
.domain_name
1067 info
.sid
= ctx
.domsid
1068 info
.trust_direction
= lsa
.LSA_TRUST_DIRECTION_INBOUND | lsa
.LSA_TRUST_DIRECTION_OUTBOUND
1069 info
.trust_type
= lsa
.LSA_TRUST_TYPE_UPLEVEL
1070 info
.trust_attributes
= lsa
.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1073 oldname
= lsa
.String()
1074 oldname
.string
= ctx
.dnsdomain
1075 oldinfo
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, oldname
,
1076 lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
1077 print("Removing old trust record for %s (SID %s)" % (ctx
.dnsdomain
, oldinfo
.info_ex
.sid
))
1078 lsaconn
.DeleteTrustedDomain(pol_handle
, oldinfo
.info_ex
.sid
)
1079 except RuntimeError:
1082 password_blob
= string_to_byte_array(ctx
.trustdom_pass
.encode('utf-16-le'))
1084 clear_value
= drsblobs
.AuthInfoClear()
1085 clear_value
.size
= len(password_blob
)
1086 clear_value
.password
= password_blob
1088 clear_authentication_information
= drsblobs
.AuthenticationInformation()
1089 clear_authentication_information
.LastUpdateTime
= samba
.unix2nttime(int(time
.time()))
1090 clear_authentication_information
.AuthType
= lsa
.TRUST_AUTH_TYPE_CLEAR
1091 clear_authentication_information
.AuthInfo
= clear_value
1093 authentication_information_array
= drsblobs
.AuthenticationInformationArray()
1094 authentication_information_array
.count
= 1
1095 authentication_information_array
.array
= [clear_authentication_information
]
1097 outgoing
= drsblobs
.trustAuthInOutBlob()
1099 outgoing
.current
= authentication_information_array
1101 trustpass
= drsblobs
.trustDomainPasswords()
1102 confounder
= [3] * 512
1104 for i
in range(512):
1105 confounder
[i
] = random
.randint(0, 255)
1107 trustpass
.confounder
= confounder
1109 trustpass
.outgoing
= outgoing
1110 trustpass
.incoming
= outgoing
1112 trustpass_blob
= ndr_pack(trustpass
)
1114 encrypted_trustpass
= arcfour_encrypt(lsaconn
.session_key
, trustpass_blob
)
1116 auth_blob
= lsa
.DATA_BUF2()
1117 auth_blob
.size
= len(encrypted_trustpass
)
1118 auth_blob
.data
= string_to_byte_array(encrypted_trustpass
)
1120 auth_info
= lsa
.TrustDomainInfoAuthInfoInternal()
1121 auth_info
.auth_blob
= auth_blob
1123 trustdom_handle
= lsaconn
.CreateTrustedDomainEx2(pol_handle
,
1126 security
.SEC_STD_DELETE
)
1129 "dn" : "cn=%s,cn=system,%s" % (ctx
.dnsforest
, ctx
.base_dn
),
1130 "objectclass" : "trustedDomain",
1131 "trustType" : str(info
.trust_type
),
1132 "trustAttributes" : str(info
.trust_attributes
),
1133 "trustDirection" : str(info
.trust_direction
),
1134 "flatname" : ctx
.forest_domain_name
,
1135 "trustPartner" : ctx
.dnsforest
,
1136 "trustAuthIncoming" : ndr_pack(outgoing
),
1137 "trustAuthOutgoing" : ndr_pack(outgoing
),
1138 "securityIdentifier" : ndr_pack(ctx
.forestsid
)
1140 ctx
.local_samdb
.add(rec
)
1143 "dn" : "cn=%s$,cn=users,%s" % (ctx
.forest_domain_name
, ctx
.base_dn
),
1144 "objectclass" : "user",
1145 "userAccountControl" : str(samba
.dsdb
.UF_INTERDOMAIN_TRUST_ACCOUNT
),
1146 "clearTextPassword" : ctx
.trustdom_pass
.encode('utf-16-le'),
1147 "samAccountName" : "%s$" % ctx
.forest_domain_name
1149 ctx
.local_samdb
.add(rec
)
1153 # nc_list is the list of naming context (NC) for which we will
1154 # replicate in and send a updateRef command to the partner DC
1156 # full_nc_list is the list of naming context (NC) we hold
1157 # read/write copies of. These are not subsets of each other.
1158 ctx
.nc_list
= [ ctx
.config_dn
, ctx
.schema_dn
]
1159 ctx
.full_nc_list
= [ ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
1161 if ctx
.subdomain
and ctx
.dns_backend
!= "NONE":
1162 ctx
.full_nc_list
+= [ctx
.domaindns_zone
]
1164 elif not ctx
.subdomain
:
1165 ctx
.nc_list
+= [ctx
.base_dn
]
1167 if ctx
.dns_backend
!= "NONE":
1168 ctx
.nc_list
+= [ctx
.domaindns_zone
]
1169 ctx
.nc_list
+= [ctx
.forestdns_zone
]
1170 ctx
.full_nc_list
+= [ctx
.domaindns_zone
]
1171 ctx
.full_nc_list
+= [ctx
.forestdns_zone
]
1173 if not ctx
.clone_only
:
1174 if ctx
.promote_existing
:
1175 ctx
.promote_possible()
1177 ctx
.cleanup_old_join()
1180 if not ctx
.clone_only
:
1181 ctx
.join_add_objects()
1182 ctx
.join_provision()
1183 ctx
.join_replicate()
1184 if (not ctx
.clone_only
and ctx
.subdomain
):
1185 ctx
.join_add_objects2()
1186 ctx
.join_provision_own_domain()
1187 ctx
.join_setup_trusts()
1191 print "Join failed - cleaning up"
1194 if not ctx
.clone_only
:
1195 ctx
.cleanup_old_join()
1199 def join_RODC(logger
=None, server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
1200 targetdir
=None, domain
=None, domain_critical_only
=False,
1201 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
1202 promote_existing
=False):
1203 """Join as a RODC."""
1205 ctx
= dc_join(logger
, server
, creds
, lp
, site
, netbios_name
, targetdir
, domain
,
1206 machinepass
, use_ntvfs
, dns_backend
, promote_existing
)
1208 lp
.set("workgroup", ctx
.domain_name
)
1209 logger
.info("workgroup is %s" % ctx
.domain_name
)
1211 lp
.set("realm", ctx
.realm
)
1212 logger
.info("realm is %s" % ctx
.realm
)
1214 ctx
.krbtgt_dn
= "CN=krbtgt_%s,CN=Users,%s" % (ctx
.myname
, ctx
.base_dn
)
1216 # setup some defaults for accounts that should be replicated to this RODC
1217 ctx
.never_reveal_sid
= [
1218 "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_DENY
),
1219 "<SID=%s>" % security
.SID_BUILTIN_ADMINISTRATORS
,
1220 "<SID=%s>" % security
.SID_BUILTIN_SERVER_OPERATORS
,
1221 "<SID=%s>" % security
.SID_BUILTIN_BACKUP_OPERATORS
,
1222 "<SID=%s>" % security
.SID_BUILTIN_ACCOUNT_OPERATORS
]
1223 ctx
.reveal_sid
= "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_ALLOW
)
1225 mysid
= ctx
.get_mysid()
1226 admin_dn
= "<SID=%s>" % mysid
1227 ctx
.managedby
= admin_dn
1229 ctx
.userAccountControl
= (samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT |
1230 samba
.dsdb
.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1231 samba
.dsdb
.UF_PARTIAL_SECRETS_ACCOUNT
)
1233 ctx
.SPNs
.extend([ "RestrictedKrbHost/%s" % ctx
.myname
,
1234 "RestrictedKrbHost/%s" % ctx
.dnshostname
])
1236 ctx
.connection_dn
= "CN=RODC Connection (FRS),%s" % ctx
.ntds_dn
1237 ctx
.secure_channel_type
= misc
.SEC_CHAN_RODC
1239 ctx
.replica_flags |
= ( drsuapi
.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1240 drsuapi
.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP
)
1241 ctx
.domain_replica_flags
= ctx
.replica_flags
1242 if domain_critical_only
:
1243 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
1247 logger
.info("Joined domain %s (SID %s) as an RODC" % (ctx
.domain_name
, ctx
.domsid
))
1250 def join_DC(logger
=None, server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
1251 targetdir
=None, domain
=None, domain_critical_only
=False,
1252 machinepass
=None, use_ntvfs
=False, dns_backend
=None,
1253 promote_existing
=False):
1255 ctx
= dc_join(logger
, server
, creds
, lp
, site
, netbios_name
, targetdir
, domain
,
1256 machinepass
, use_ntvfs
, dns_backend
, promote_existing
)
1258 lp
.set("workgroup", ctx
.domain_name
)
1259 logger
.info("workgroup is %s" % ctx
.domain_name
)
1261 lp
.set("realm", ctx
.realm
)
1262 logger
.info("realm is %s" % ctx
.realm
)
1264 ctx
.userAccountControl
= samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT | samba
.dsdb
.UF_TRUSTED_FOR_DELEGATION
1266 ctx
.SPNs
.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx
.dnsdomain
)
1267 ctx
.secure_channel_type
= misc
.SEC_CHAN_BDC
1269 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1270 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1271 ctx
.domain_replica_flags
= ctx
.replica_flags
1272 if domain_critical_only
:
1273 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
1276 logger
.info("Joined domain %s (SID %s) as a DC" % (ctx
.domain_name
, ctx
.domsid
))
1278 def join_clone(logger
=None, server
=None, creds
=None, lp
=None,
1279 targetdir
=None, domain
=None, include_secrets
=False):
1281 ctx
= dc_join(logger
, server
, creds
, lp
, site
=None, netbios_name
=None, targetdir
=targetdir
, domain
=domain
,
1282 machinepass
=None, use_ntvfs
=False, dns_backend
="NONE", promote_existing
=False, clone_only
=True)
1284 lp
.set("workgroup", ctx
.domain_name
)
1285 logger
.info("workgroup is %s" % ctx
.domain_name
)
1287 lp
.set("realm", ctx
.realm
)
1288 logger
.info("realm is %s" % ctx
.realm
)
1290 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1291 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1292 if not include_secrets
:
1293 ctx
.replica_flags |
= drsuapi
.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1294 ctx
.domain_replica_flags
= ctx
.replica_flags
1297 logger
.info("Cloned domain %s (SID %s)" % (ctx
.domain_name
, ctx
.domsid
))
1299 def join_subdomain(logger
=None, server
=None, creds
=None, lp
=None, site
=None,
1300 netbios_name
=None, targetdir
=None, parent_domain
=None, dnsdomain
=None,
1301 netbios_domain
=None, machinepass
=None, adminpass
=None, use_ntvfs
=False,
1304 ctx
= dc_join(logger
, server
, creds
, lp
, site
, netbios_name
, targetdir
, parent_domain
,
1305 machinepass
, use_ntvfs
, dns_backend
)
1306 ctx
.subdomain
= True
1307 if adminpass
is None:
1308 ctx
.adminpass
= samba
.generate_random_password(12, 32)
1310 ctx
.adminpass
= adminpass
1311 ctx
.parent_domain_name
= ctx
.domain_name
1312 ctx
.domain_name
= netbios_domain
1313 ctx
.realm
= dnsdomain
1314 ctx
.parent_dnsdomain
= ctx
.dnsdomain
1315 ctx
.parent_partition_dn
= ctx
.get_parent_partition_dn()
1316 ctx
.dnsdomain
= dnsdomain
1317 ctx
.partition_dn
= "CN=%s,CN=Partitions,%s" % (ctx
.domain_name
, ctx
.config_dn
)
1318 ctx
.naming_master
= ctx
.get_naming_master()
1319 if ctx
.naming_master
!= ctx
.server
:
1320 logger
.info("Reconnecting to naming master %s" % ctx
.naming_master
)
1321 ctx
.server
= ctx
.naming_master
1322 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
1323 session_info
=system_session(),
1324 credentials
=ctx
.creds
, lp
=ctx
.lp
)
1325 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=['dnsHostName'],
1327 ctx
.server
= res
[0]["dnsHostName"]
1328 logger
.info("DNS name of new naming master is %s" % ctx
.server
)
1330 ctx
.base_dn
= samba
.dn_from_dns_name(dnsdomain
)
1331 ctx
.forestsid
= ctx
.domsid
1332 ctx
.domsid
= security
.random_sid()
1334 ctx
.dnshostname
= "%s.%s" % (ctx
.myname
.lower(), ctx
.dnsdomain
)
1335 # Windows uses 240 bytes as UTF16 so we do
1336 ctx
.trustdom_pass
= samba
.generate_random_machine_password(120, 120)
1338 ctx
.userAccountControl
= samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT | samba
.dsdb
.UF_TRUSTED_FOR_DELEGATION
1340 ctx
.SPNs
.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx
.dnsdomain
)
1341 ctx
.secure_channel_type
= misc
.SEC_CHAN_BDC
1343 ctx
.replica_flags |
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
1344 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS
)
1345 ctx
.domain_replica_flags
= ctx
.replica_flags
1348 ctx
.logger
.info("Created domain %s (SID %s) as a DC" % (ctx
.domain_name
, ctx
.domsid
))