4 # Copyright Andrew Tridgell 2010
5 # Copyright Andrew Bartlett 2010
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 """Joining a domain."""
23 from samba
.auth
import system_session
24 from samba
.samdb
import SamDB
25 from samba
import gensec
, Ldb
, drs_utils
26 import ldb
, samba
, sys
, uuid
27 from samba
.ndr
import ndr_pack
28 from samba
.dcerpc
import security
, drsuapi
, misc
, nbt
, lsa
, drsblobs
29 from samba
.credentials
import Credentials
, DONT_USE_KERBEROS
30 from samba
.provision
import secretsdb_self_join
, provision
, provision_fill
, FILL_DRS
, FILL_SUBDOMAIN
31 from samba
.schema
import Schema
32 from samba
.net
import Net
38 # this makes debugging easier
39 talloc
.enable_null_tracking()
41 class DCJoinException(Exception):
43 def __init__(self
, msg
):
44 super(DCJoinException
, self
).__init
__("Can't join, error: %s" % msg
)
47 class dc_join(object):
48 '''perform a DC join'''
50 def __init__(ctx
, server
=None, creds
=None, lp
=None, site
=None,
51 netbios_name
=None, targetdir
=None, domain
=None,
56 ctx
.netbios_name
= netbios_name
57 ctx
.targetdir
= targetdir
59 ctx
.creds
.set_gensec_features(creds
.get_gensec_features() | gensec
.FEATURE_SEAL
)
60 ctx
.net
= Net(creds
=ctx
.creds
, lp
=ctx
.lp
)
62 if server
is not None:
65 print("Finding a writeable DC for domain '%s'" % domain
)
66 ctx
.server
= ctx
.find_dc(domain
)
67 print("Found DC %s" % ctx
.server
)
69 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
70 session_info
=system_session(),
71 credentials
=ctx
.creds
, lp
=ctx
.lp
)
74 ctx
.samdb
.search(scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["dn"])
75 except ldb
.LdbError
, (enum
, estr
):
76 raise DCJoinException(estr
)
79 ctx
.myname
= netbios_name
80 ctx
.samname
= "%s$" % ctx
.myname
81 ctx
.base_dn
= str(ctx
.samdb
.get_default_basedn())
82 ctx
.root_dn
= str(ctx
.samdb
.get_root_basedn())
83 ctx
.schema_dn
= str(ctx
.samdb
.get_schema_basedn())
84 ctx
.config_dn
= str(ctx
.samdb
.get_config_basedn())
85 ctx
.domsid
= ctx
.samdb
.get_domain_sid()
86 ctx
.domain_name
= ctx
.get_domain_name()
87 ctx
.forest_domain_name
= ctx
.get_forest_domain_name()
88 ctx
.invocation_id
= misc
.GUID(str(uuid
.uuid4()))
90 ctx
.dc_ntds_dn
= ctx
.samdb
.get_dsServiceName()
91 ctx
.dc_dnsHostName
= ctx
.get_dnsHostName()
92 ctx
.behavior_version
= ctx
.get_behavior_version()
94 if machinepass
is not None:
95 ctx
.acct_pass
= machinepass
97 ctx
.acct_pass
= samba
.generate_random_password(32, 40)
99 # work out the DNs of all the objects we will be adding
100 ctx
.server_dn
= "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx
.myname
, ctx
.site
, ctx
.config_dn
)
101 ctx
.ntds_dn
= "CN=NTDS Settings,%s" % ctx
.server_dn
102 topology_base
= "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx
.base_dn
103 if ctx
.dn_exists(topology_base
):
104 ctx
.topology_dn
= "CN=%s,%s" % (ctx
.myname
, topology_base
)
106 ctx
.topology_dn
= None
108 ctx
.dnsdomain
= ctx
.samdb
.domain_dns_name()
109 ctx
.dnsforest
= ctx
.samdb
.forest_dns_name()
110 ctx
.dnshostname
= "%s.%s" % (ctx
.myname
, ctx
.dnsdomain
)
112 ctx
.realm
= ctx
.dnsdomain
114 ctx
.acct_dn
= "CN=%s,OU=Domain Controllers,%s" % (ctx
.myname
, ctx
.base_dn
)
118 ctx
.SPNs
= [ "HOST/%s" % ctx
.myname
,
119 "HOST/%s" % ctx
.dnshostname
,
120 "GC/%s/%s" % (ctx
.dnshostname
, ctx
.dnsforest
) ]
122 # these elements are optional
123 ctx
.never_reveal_sid
= None
124 ctx
.reveal_sid
= None
125 ctx
.connection_dn
= None
130 ctx
.subdomain
= False
132 def del_noerror(ctx
, dn
, recursive
=False):
135 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["dn"])
139 ctx
.del_noerror(r
.dn
, recursive
=True)
142 print "Deleted %s" % dn
146 def cleanup_old_join(ctx
):
147 '''remove any DNs from a previous join'''
149 # find the krbtgt link
150 print("checking sAMAccountName")
154 res
= ctx
.samdb
.search(base
=ctx
.samdb
.get_default_basedn(),
155 expression
='sAMAccountName=%s' % ldb
.binary_encode(ctx
.samname
),
156 attrs
=["msDS-krbTgtLink"])
158 ctx
.del_noerror(res
[0].dn
, recursive
=True)
159 if ctx
.connection_dn
is not None:
160 ctx
.del_noerror(ctx
.connection_dn
)
161 if ctx
.krbtgt_dn
is not None:
162 ctx
.del_noerror(ctx
.krbtgt_dn
)
163 ctx
.del_noerror(ctx
.ntds_dn
)
164 ctx
.del_noerror(ctx
.server_dn
, recursive
=True)
166 ctx
.del_noerror(ctx
.topology_dn
)
168 ctx
.del_noerror(ctx
.partition_dn
)
170 ctx
.new_krbtgt_dn
= res
[0]["msDS-Krbtgtlink"][0]
171 ctx
.del_noerror(ctx
.new_krbtgt_dn
)
174 binding_options
= "sign"
175 lsaconn
= lsa
.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
178 objectAttr
= lsa
.ObjectAttribute()
179 objectAttr
.sec_qos
= lsa
.QosInfo()
181 pol_handle
= lsaconn
.OpenPolicy2(''.decode('utf-8'),
182 objectAttr
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
185 name
.string
= ctx
.realm
186 info
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, name
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
188 lsaconn
.DeleteTrustedDomain(pol_handle
, info
.info_ex
.sid
)
191 name
.string
= ctx
.forest_domain_name
192 info
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, name
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
194 lsaconn
.DeleteTrustedDomain(pol_handle
, info
.info_ex
.sid
)
199 def find_dc(ctx
, domain
):
200 '''find a writeable DC for the given domain'''
202 ctx
.cldap_ret
= ctx
.net
.finddc(domain
=domain
, flags
=nbt
.NBT_SERVER_LDAP | nbt
.NBT_SERVER_DS | nbt
.NBT_SERVER_WRITABLE
)
204 raise Exception("Failed to find a writeable DC for domain '%s'" % domain
)
205 if ctx
.cldap_ret
.client_site
is not None and ctx
.cldap_ret
.client_site
!= "":
206 ctx
.site
= ctx
.cldap_ret
.client_site
207 return ctx
.cldap_ret
.pdc_dns_name
210 def get_behavior_version(ctx
):
211 res
= ctx
.samdb
.search(base
=ctx
.base_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["msDS-Behavior-Version"])
212 if "msDS-Behavior-Version" in res
[0]:
213 return int(res
[0]["msDS-Behavior-Version"][0])
215 return samba
.dsdb
.DS_DOMAIN_FUNCTION_2000
217 def get_dnsHostName(ctx
):
218 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["dnsHostName"])
219 return res
[0]["dnsHostName"][0]
221 def get_domain_name(ctx
):
222 '''get netbios name of the domain from the partitions record'''
223 partitions_dn
= ctx
.samdb
.get_partitions_dn()
224 res
= ctx
.samdb
.search(base
=partitions_dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["nETBIOSName"],
225 expression
='ncName=%s' % ctx
.samdb
.get_default_basedn())
226 return res
[0]["nETBIOSName"][0]
228 def get_forest_domain_name(ctx
):
229 '''get netbios name of the domain from the partitions record'''
230 partitions_dn
= ctx
.samdb
.get_partitions_dn()
231 res
= ctx
.samdb
.search(base
=partitions_dn
, scope
=ldb
.SCOPE_ONELEVEL
, attrs
=["nETBIOSName"],
232 expression
='ncName=%s' % ctx
.samdb
.get_root_basedn())
233 return res
[0]["nETBIOSName"][0]
235 def get_parent_partition_dn(ctx
):
236 '''get the parent domain partition DN from parent DNS name'''
237 res
= ctx
.samdb
.search(base
=ctx
.config_dn
, attrs
=[],
238 expression
='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
239 (ctx
.parent_dnsdomain
, ldb
.OID_COMPARATOR_AND
, samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_DOMAIN
))
240 return str(res
[0].dn
)
242 def get_naming_master(ctx
):
243 '''get the parent domain partition DN from parent DNS name'''
244 res
= ctx
.samdb
.search(base
='CN=Partitions,%s' % ctx
.config_dn
, attrs
=['fSMORoleOwner'],
245 scope
=ldb
.SCOPE_BASE
, controls
=["extended_dn:1:1"])
246 if not 'fSMORoleOwner' in res
[0]:
247 raise DCJoinException("Can't find naming master on partition DN %s" % ctx
.partition_dn
)
248 master_guid
= str(misc
.GUID(ldb
.Dn(ctx
.samdb
, res
[0]['fSMORoleOwner'][0]).get_extended_component('GUID')))
249 master_host
= '%s._msdcs.%s' % (master_guid
, ctx
.dnsforest
)
253 '''get the SID of the connected user. Only works with w2k8 and later,
254 so only used for RODC join'''
255 res
= ctx
.samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["tokenGroups"])
256 binsid
= res
[0]["tokenGroups"][0]
257 return ctx
.samdb
.schema_format_value("objectSID", binsid
)
259 def dn_exists(ctx
, dn
):
260 '''check if a DN exists'''
262 res
= ctx
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=[])
263 except ldb
.LdbError
, (enum
, estr
):
264 if enum
== ldb
.ERR_NO_SUCH_OBJECT
:
269 def add_krbtgt_account(ctx
):
270 '''RODCs need a special krbtgt account'''
271 print "Adding %s" % ctx
.krbtgt_dn
273 "dn" : ctx
.krbtgt_dn
,
274 "objectclass" : "user",
275 "useraccountcontrol" : str(samba
.dsdb
.UF_NORMAL_ACCOUNT |
276 samba
.dsdb
.UF_ACCOUNTDISABLE
),
277 "showinadvancedviewonly" : "TRUE",
278 "description" : "krbtgt for %s" % ctx
.samname
}
279 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
281 # now we need to search for the samAccountName attribute on the krbtgt DN,
282 # as this will have been magically set to the krbtgt number
283 res
= ctx
.samdb
.search(base
=ctx
.krbtgt_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["samAccountName"])
284 ctx
.krbtgt_name
= res
[0]["samAccountName"][0]
286 print "Got krbtgt_name=%s" % ctx
.krbtgt_name
289 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
290 m
["msDS-krbTgtLink"] = ldb
.MessageElement(ctx
.krbtgt_dn
,
291 ldb
.FLAG_MOD_REPLACE
, "msDS-krbTgtLink")
294 ctx
.new_krbtgt_dn
= "CN=%s,CN=Users,%s" % (ctx
.krbtgt_name
, ctx
.base_dn
)
295 print "Renaming %s to %s" % (ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
)
296 ctx
.samdb
.rename(ctx
.krbtgt_dn
, ctx
.new_krbtgt_dn
)
298 def drsuapi_connect(ctx
):
299 '''make a DRSUAPI connection to the naming master'''
300 binding_options
= "seal"
301 if int(ctx
.lp
.get("log level")) >= 4:
302 binding_options
+= ",print"
303 binding_string
= "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
)
304 ctx
.drsuapi
= drsuapi
.drsuapi(binding_string
, ctx
.lp
, ctx
.creds
)
305 (ctx
.drsuapi_handle
, ctx
.bind_supported_extensions
) = drs_utils
.drs_DsBind(ctx
.drsuapi
)
307 def create_tmp_samdb(ctx
):
308 '''create a temporary samdb object for schema queries'''
309 ctx
.tmp_schema
= Schema(security
.dom_sid(ctx
.domsid
),
310 schemadn
=ctx
.schema_dn
)
311 ctx
.tmp_samdb
= SamDB(session_info
=system_session(), url
=None, auto_connect
=False,
312 credentials
=ctx
.creds
, lp
=ctx
.lp
, global_schema
=False,
314 ctx
.tmp_samdb
.set_schema(ctx
.tmp_schema
)
316 def build_DsReplicaAttribute(ctx
, attrname
, attrvalue
):
317 '''build a DsReplicaAttributeCtr object'''
318 r
= drsuapi
.DsReplicaAttribute()
319 r
.attid
= ctx
.tmp_samdb
.get_attid_from_lDAPDisplayName(attrname
)
323 def DsAddEntry(ctx
, recs
):
324 '''add a record via the DRSUAPI DsAddEntry call'''
325 if ctx
.drsuapi
is None:
326 ctx
.drsuapi_connect()
327 if ctx
.tmp_samdb
is None:
328 ctx
.create_tmp_samdb()
332 id = drsuapi
.DsReplicaObjectIdentifier()
339 if not isinstance(rec
[a
], list):
343 rattr
= ctx
.tmp_samdb
.dsdb_DsReplicaAttribute(ctx
.tmp_samdb
, a
, v
)
346 attribute_ctr
= drsuapi
.DsReplicaAttributeCtr()
347 attribute_ctr
.num_attributes
= len(attrs
)
348 attribute_ctr
.attributes
= attrs
350 object = drsuapi
.DsReplicaObject()
351 object.identifier
= id
352 object.attribute_ctr
= attribute_ctr
354 list_object
= drsuapi
.DsReplicaObjectListItem()
355 list_object
.object = object
356 objects
.append(list_object
)
358 req2
= drsuapi
.DsAddEntryRequest2()
359 req2
.first_object
= objects
[0]
360 prev
= req2
.first_object
361 for o
in objects
[1:]:
365 (level
, ctr
) = ctx
.drsuapi
.DsAddEntry(ctx
.drsuapi_handle
, 2, req2
)
367 if ctr
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
368 print("DsAddEntry failed with dir_err %u" % ctr
.dir_err
)
369 raise RuntimeError("DsAddEntry failed")
370 if ctr
.extended_err
!= (0, 'WERR_OK'):
371 print("DsAddEntry failed with status %s info %s" % (ctr
.extended_err
))
372 raise RuntimeError("DsAddEntry failed")
375 raise RuntimeError("expected err_ver 1, got %u" % ctr
.err_ver
)
376 if ctr
.err_data
.status
!= (0, 'WERR_OK'):
377 print("DsAddEntry failed with status %s info %s" % (ctr
.err_data
.status
,
378 ctr
.err_data
.info
.extended_err
))
379 raise RuntimeError("DsAddEntry failed")
380 if ctr
.err_data
.dir_err
!= drsuapi
.DRSUAPI_DIRERR_OK
:
381 print("DsAddEntry failed with dir_err %u" % ctr
.err_data
.dir_err
)
382 raise RuntimeError("DsAddEntry failed")
386 def join_add_ntdsdsa(ctx
):
387 '''add the ntdsdsa object'''
388 # FIXME: the partition (NC) assignment has to be made dynamic
389 print "Adding %s" % ctx
.ntds_dn
392 "objectclass" : "nTDSDSA",
393 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
394 "dMDLocation" : ctx
.schema_dn
}
396 nc_list
= [ ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
398 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
399 rec
["msDS-Behavior-Version"] = str(samba
.dsdb
.DS_DOMAIN_FUNCTION_2008_R2
)
401 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
402 rec
["msDS-HasDomainNCs"] = ctx
.base_dn
405 rec
["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx
.schema_dn
406 rec
["msDS-HasFullReplicaNCs"] = nc_list
407 rec
["options"] = "37"
408 ctx
.samdb
.add(rec
, ["rodc_join:1:1"])
410 rec
["objectCategory"] = "CN=NTDS-DSA,%s" % ctx
.schema_dn
411 rec
["HasMasterNCs"] = nc_list
412 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
413 rec
["msDS-HasMasterNCs"] = nc_list
415 rec
["invocationId"] = ndr_pack(ctx
.invocation_id
)
416 ctx
.DsAddEntry([rec
])
418 # find the GUID of our NTDS DN
419 res
= ctx
.samdb
.search(base
=ctx
.ntds_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["objectGUID"])
420 ctx
.ntds_guid
= misc
.GUID(ctx
.samdb
.schema_format_value("objectGUID", res
[0]["objectGUID"][0]))
422 def join_add_objects(ctx
):
423 '''add the various objects needed for the join'''
425 print "Adding %s" % ctx
.acct_dn
428 "objectClass": "computer",
429 "displayname": ctx
.samname
,
430 "samaccountname" : ctx
.samname
,
431 "userAccountControl" : str(ctx
.userAccountControl | samba
.dsdb
.UF_ACCOUNTDISABLE
),
432 "dnshostname" : ctx
.dnshostname
}
433 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2008
:
434 rec
['msDS-SupportedEncryptionTypes'] = str(samba
.dsdb
.ENC_ALL_TYPES
)
436 rec
["managedby"] = ctx
.managedby
437 if ctx
.never_reveal_sid
:
438 rec
["msDS-NeverRevealGroup"] = ctx
.never_reveal_sid
440 rec
["msDS-RevealOnDemandGroup"] = ctx
.reveal_sid
444 ctx
.add_krbtgt_account()
446 print "Adding %s" % ctx
.server_dn
449 "objectclass" : "server",
450 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
451 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
452 samba
.dsdb
.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
453 samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
454 # windows seems to add the dnsHostName later
455 "dnsHostName" : ctx
.dnshostname
}
458 rec
["serverReference"] = ctx
.acct_dn
463 # the rest is done after replication
467 ctx
.join_add_ntdsdsa()
469 if ctx
.connection_dn
is not None:
470 print "Adding %s" % ctx
.connection_dn
472 "dn" : ctx
.connection_dn
,
473 "objectclass" : "nTDSConnection",
474 "enabledconnection" : "TRUE",
476 "fromServer" : ctx
.dc_ntds_dn
}
480 print "Adding SPNs to %s" % ctx
.acct_dn
482 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
483 for i
in range(len(ctx
.SPNs
)):
484 ctx
.SPNs
[i
] = ctx
.SPNs
[i
].replace("$NTDSGUID", str(ctx
.ntds_guid
))
485 m
["servicePrincipalName"] = ldb
.MessageElement(ctx
.SPNs
,
487 "servicePrincipalName")
490 print "Setting account password for %s" % ctx
.samname
491 ctx
.samdb
.setpassword("(&(objectClass=user)(sAMAccountName=%s))" % ldb
.binary_encode(ctx
.samname
),
493 force_change_at_next_login
=False,
494 username
=ctx
.samname
)
495 res
= ctx
.samdb
.search(base
=ctx
.acct_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=["msDS-keyVersionNumber"])
496 ctx
.key_version_number
= int(res
[0]["msDS-keyVersionNumber"][0])
498 print("Enabling account")
500 m
.dn
= ldb
.Dn(ctx
.samdb
, ctx
.acct_dn
)
501 m
["userAccountControl"] = ldb
.MessageElement(str(ctx
.userAccountControl
),
502 ldb
.FLAG_MOD_REPLACE
,
503 "userAccountControl")
506 def join_add_objects2(ctx
):
507 '''add the various objects needed for the join, for subdomains post replication'''
509 print "Adding %s" % ctx
.partition_dn
510 # NOTE: windows sends a ntSecurityDescriptor here, we
513 "dn" : ctx
.partition_dn
,
514 "objectclass" : "crossRef",
515 "objectCategory" : "CN=Cross-Ref,%s" % ctx
.schema_dn
,
516 "nCName" : ctx
.base_dn
,
517 "nETBIOSName" : ctx
.domain_name
,
518 "dnsRoot": ctx
.dnsdomain
,
519 "trustParent" : ctx
.parent_partition_dn
,
520 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_NC|samba
.dsdb
.SYSTEM_FLAG_CR_NTDS_DOMAIN
)}
521 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
522 rec
["msDS-Behavior-Version"] = str(ctx
.behavior_version
)
526 "objectclass" : "nTDSDSA",
527 "systemFlags" : str(samba
.dsdb
.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE
),
528 "dMDLocation" : ctx
.schema_dn
}
530 nc_list
= [ ctx
.base_dn
, ctx
.config_dn
, ctx
.schema_dn
]
532 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
533 rec2
["msDS-Behavior-Version"] = str(ctx
.behavior_version
)
535 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
536 rec2
["msDS-HasDomainNCs"] = ctx
.base_dn
538 rec2
["objectCategory"] = "CN=NTDS-DSA,%s" % ctx
.schema_dn
539 rec2
["HasMasterNCs"] = nc_list
540 if ctx
.behavior_version
>= samba
.dsdb
.DS_DOMAIN_FUNCTION_2003
:
541 rec2
["msDS-HasMasterNCs"] = nc_list
542 rec2
["options"] = "1"
543 rec2
["invocationId"] = ndr_pack(ctx
.invocation_id
)
545 objects
= ctx
.DsAddEntry([rec
, rec2
])
546 if len(objects
) != 2:
547 raise DCJoinException("Expected 2 objects from DsAddEntry")
549 ctx
.ntds_guid
= objects
[1].guid
551 print("Replicating partition DN")
552 ctx
.repl
.replicate(ctx
.partition_dn
,
553 misc
.GUID("00000000-0000-0000-0000-000000000000"),
555 exop
=drsuapi
.DRSUAPI_EXOP_REPL_OBJ
,
556 replica_flags
=drsuapi
.DRSUAPI_DRS_WRIT_REP
)
558 print("Replicating NTDS DN")
559 ctx
.repl
.replicate(ctx
.ntds_dn
,
560 misc
.GUID("00000000-0000-0000-0000-000000000000"),
562 exop
=drsuapi
.DRSUAPI_EXOP_REPL_OBJ
,
563 replica_flags
=drsuapi
.DRSUAPI_DRS_WRIT_REP
)
565 def join_provision(ctx
):
566 '''provision the local SAM'''
568 print "Calling bare provision"
570 logger
= logging
.getLogger("provision")
571 logger
.addHandler(logging
.StreamHandler(sys
.stdout
))
572 smbconf
= ctx
.lp
.configfile
574 presult
= provision(logger
, system_session(), None,
575 smbconf
=smbconf
, targetdir
=ctx
.targetdir
, samdb_fill
=FILL_DRS
,
576 realm
=ctx
.realm
, rootdn
=ctx
.root_dn
, domaindn
=ctx
.base_dn
,
577 schemadn
=ctx
.schema_dn
,
578 configdn
=ctx
.config_dn
,
579 serverdn
=ctx
.server_dn
, domain
=ctx
.domain_name
,
580 hostname
=ctx
.myname
, domainsid
=ctx
.domsid
,
581 machinepass
=ctx
.acct_pass
, serverrole
="domain controller",
582 sitename
=ctx
.site
, lp
=ctx
.lp
, ntdsguid
=ctx
.ntds_guid
,
584 print "Provision OK for domain DN %s" % presult
.domaindn
585 ctx
.local_samdb
= presult
.samdb
587 ctx
.paths
= presult
.paths
588 ctx
.names
= presult
.names
590 def join_provision_own_domain(ctx
):
591 '''provision the local SAM'''
593 # we now operate exclusively on the local database, which
594 # we need to reopen in order to get the newly created schema
595 print("Reconnecting to local samdb")
596 ctx
.samdb
= SamDB(url
=ctx
.local_samdb
.url
,
597 session_info
=system_session(),
598 lp
=ctx
.local_samdb
.lp
,
600 ctx
.samdb
.set_invocation_id(str(ctx
.invocation_id
))
601 ctx
.local_samdb
= ctx
.samdb
603 print("Finding domain GUID from ncName")
604 res
= ctx
.local_samdb
.search(base
=ctx
.partition_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=['ncName'],
605 controls
=["extended_dn:1:1"])
606 domguid
= str(misc
.GUID(ldb
.Dn(ctx
.samdb
, res
[0]['ncName'][0]).get_extended_component('GUID')))
607 print("Got domain GUID %s" % domguid
)
609 print("Calling own domain provision")
611 logger
= logging
.getLogger("provision")
612 logger
.addHandler(logging
.StreamHandler(sys
.stdout
))
614 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
616 presult
= provision_fill(ctx
.local_samdb
, secrets_ldb
,
617 logger
, ctx
.names
, ctx
.paths
, domainsid
=security
.dom_sid(ctx
.domsid
),
619 targetdir
=ctx
.targetdir
, samdb_fill
=FILL_SUBDOMAIN
,
620 machinepass
=ctx
.acct_pass
, serverrole
="domain controller",
621 lp
=ctx
.lp
, hostip
=ctx
.names
.hostip
, hostip6
=ctx
.names
.hostip6
,
622 dns_backend
="BIND9_DLZ")
623 print("Provision OK for domain %s" % ctx
.names
.dnsdomain
)
625 def join_replicate(ctx
):
626 '''replicate the SAM'''
628 print "Starting replication"
629 ctx
.local_samdb
.transaction_start()
631 source_dsa_invocation_id
= misc
.GUID(ctx
.samdb
.get_invocation_id())
632 if ctx
.ntds_guid
is None:
633 print("Using DS_BIND_GUID_W2K3")
634 destination_dsa_guid
= misc
.GUID(drsuapi
.DRSUAPI_DS_BIND_GUID_W2K3
)
636 destination_dsa_guid
= ctx
.ntds_guid
639 repl_creds
= Credentials()
640 repl_creds
.guess(ctx
.lp
)
641 repl_creds
.set_kerberos_state(DONT_USE_KERBEROS
)
642 repl_creds
.set_username(ctx
.samname
)
643 repl_creds
.set_password(ctx
.acct_pass
)
645 repl_creds
= ctx
.creds
647 binding_options
= "seal"
648 if int(ctx
.lp
.get("log level")) >= 5:
649 binding_options
+= ",print"
650 repl
= drs_utils
.drs_Replicate(
651 "ncacn_ip_tcp:%s[%s]" % (ctx
.server
, binding_options
),
652 ctx
.lp
, repl_creds
, ctx
.local_samdb
)
654 repl
.replicate(ctx
.schema_dn
, source_dsa_invocation_id
,
655 destination_dsa_guid
, schema
=True, rodc
=ctx
.RODC
,
656 replica_flags
=ctx
.replica_flags
)
657 repl
.replicate(ctx
.config_dn
, source_dsa_invocation_id
,
658 destination_dsa_guid
, rodc
=ctx
.RODC
,
659 replica_flags
=ctx
.replica_flags
)
660 if not ctx
.subdomain
:
661 # Replicate first the critical object for the basedn
662 if not ctx
.domain_replica_flags
& drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
:
663 print "Replicating critical objects from the base DN of the domain"
664 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi
.DRSUAPI_DRS_GET_ANC
665 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
666 destination_dsa_guid
, rodc
=ctx
.RODC
,
667 replica_flags
=ctx
.domain_replica_flags
)
668 ctx
.domain_replica_flags ^
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi
.DRSUAPI_DRS_GET_ANC
670 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_GET_ANC
671 repl
.replicate(ctx
.base_dn
, source_dsa_invocation_id
,
672 destination_dsa_guid
, rodc
=ctx
.RODC
,
673 replica_flags
=ctx
.domain_replica_flags
)
675 repl
.replicate(ctx
.acct_dn
, source_dsa_invocation_id
,
676 destination_dsa_guid
,
677 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
678 repl
.replicate(ctx
.new_krbtgt_dn
, source_dsa_invocation_id
,
679 destination_dsa_guid
,
680 exop
=drsuapi
.DRSUAPI_EXOP_REPL_SECRET
, rodc
=True)
682 ctx
.source_dsa_invocation_id
= source_dsa_invocation_id
683 ctx
.destination_dsa_guid
= destination_dsa_guid
685 print "Committing SAM database"
687 ctx
.local_samdb
.transaction_cancel()
690 ctx
.local_samdb
.transaction_commit()
692 def send_DsReplicaUpdateRefs(ctx
, dn
):
693 r
= drsuapi
.DsReplicaUpdateRefsRequest1()
694 r
.naming_context
= drsuapi
.DsReplicaObjectIdentifier()
695 r
.naming_context
.dn
= str(dn
)
696 r
.naming_context
.guid
= misc
.GUID("00000000-0000-0000-0000-000000000000")
697 r
.naming_context
.sid
= security
.dom_sid("S-0-0")
698 r
.dest_dsa_guid
= ctx
.ntds_guid
699 r
.dest_dsa_dns_name
= "%s._msdcs.%s" % (str(ctx
.ntds_guid
), ctx
.dnsforest
)
700 r
.options
= drsuapi
.DRSUAPI_DRS_ADD_REF | drsuapi
.DRSUAPI_DRS_DEL_REF
702 r
.options |
= drsuapi
.DRSUAPI_DRS_WRIT_REP
705 ctx
.drsuapi
.DsReplicaUpdateRefs(ctx
.drsuapi_handle
, 1, r
)
707 def join_finalise(ctx
):
708 '''finalise the join, mark us synchronised and setup secrets db'''
710 print "Sending DsReplicateUpdateRefs for all the partitions"
711 ctx
.send_DsReplicaUpdateRefs(ctx
.schema_dn
)
712 ctx
.send_DsReplicaUpdateRefs(ctx
.config_dn
)
713 ctx
.send_DsReplicaUpdateRefs(ctx
.base_dn
)
715 print "Setting isSynchronized and dsServiceName"
717 m
.dn
= ldb
.Dn(ctx
.local_samdb
, '@ROOTDSE')
718 m
["isSynchronized"] = ldb
.MessageElement("TRUE", ldb
.FLAG_MOD_REPLACE
, "isSynchronized")
719 m
["dsServiceName"] = ldb
.MessageElement("<GUID=%s>" % str(ctx
.ntds_guid
),
720 ldb
.FLAG_MOD_REPLACE
, "dsServiceName")
721 ctx
.local_samdb
.modify(m
)
726 secrets_ldb
= Ldb(ctx
.paths
.secrets
, session_info
=system_session(), lp
=ctx
.lp
)
728 print "Setting up secrets database"
729 secretsdb_self_join(secrets_ldb
, domain
=ctx
.domain_name
,
731 dnsdomain
=ctx
.dnsdomain
,
732 netbiosname
=ctx
.myname
,
733 domainsid
=security
.dom_sid(ctx
.domsid
),
734 machinepass
=ctx
.acct_pass
,
735 secure_channel_type
=ctx
.secure_channel_type
,
736 key_version_number
=ctx
.key_version_number
)
738 def join_setup_trusts(ctx
):
739 '''provision the local SAM'''
741 def arcfour_encrypt(key
, data
):
742 from Crypto
.Cipher
import ARC4
744 return c
.encrypt(data
)
746 def string_to_array(string
):
747 blob
= [0] * len(string
)
749 for i
in range(len(string
)):
750 blob
[i
] = ord(string
[i
])
754 print "Setup domain trusts with server %s" % ctx
.server
755 binding_options
= "" # why doesn't signing work here? w2k8r2 claims no session key
756 lsaconn
= lsa
.lsarpc("ncacn_np:%s[%s]" % (ctx
.server
, binding_options
),
759 objectAttr
= lsa
.ObjectAttribute()
760 objectAttr
.sec_qos
= lsa
.QosInfo()
762 pol_handle
= lsaconn
.OpenPolicy2(''.decode('utf-8'),
763 objectAttr
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
765 info
= lsa
.TrustDomainInfoInfoEx()
766 info
.domain_name
.string
= ctx
.dnsdomain
767 info
.netbios_name
.string
= ctx
.domain_name
768 info
.sid
= security
.dom_sid(ctx
.domsid
)
769 info
.trust_direction
= lsa
.LSA_TRUST_DIRECTION_INBOUND | lsa
.LSA_TRUST_DIRECTION_OUTBOUND
770 info
.trust_type
= lsa
.LSA_TRUST_TYPE_UPLEVEL
771 info
.trust_attributes
= lsa
.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
774 oldname
= lsa
.String()
775 oldname
.string
= ctx
.dnsdomain
776 oldinfo
= lsaconn
.QueryTrustedDomainInfoByName(pol_handle
, oldname
,
777 lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
778 print("Removing old trust record for %s (SID %s)" % (ctx
.dnsdomain
, oldinfo
.info_ex
.sid
))
779 lsaconn
.DeleteTrustedDomain(pol_handle
, oldinfo
.info_ex
.sid
)
783 password_blob
= string_to_array(ctx
.trustdom_pass
.encode('utf-16-le'))
785 clear_value
= drsblobs
.AuthInfoClear()
786 clear_value
.size
= len(password_blob
)
787 clear_value
.password
= password_blob
789 clear_authentication_information
= drsblobs
.AuthenticationInformation()
790 clear_authentication_information
.LastUpdateTime
= samba
.unix2nttime(int(time
.time()))
791 clear_authentication_information
.AuthType
= lsa
.TRUST_AUTH_TYPE_CLEAR
792 clear_authentication_information
.AuthInfo
= clear_value
794 authentication_information_array
= drsblobs
.AuthenticationInformationArray()
795 authentication_information_array
.count
= 1
796 authentication_information_array
.array
= [clear_authentication_information
]
798 outgoing
= drsblobs
.trustAuthInOutBlob()
800 outgoing
.current
= authentication_information_array
802 trustpass
= drsblobs
.trustDomainPasswords()
803 confounder
= [3] * 512
806 confounder
[i
] = random
.randint(0, 255)
808 trustpass
.confounder
= confounder
810 trustpass
.outgoing
= outgoing
811 trustpass
.incoming
= outgoing
813 trustpass_blob
= ndr_pack(trustpass
)
815 encrypted_trustpass
= arcfour_encrypt(lsaconn
.session_key
, trustpass_blob
)
817 auth_blob
= lsa
.DATA_BUF2()
818 auth_blob
.size
= len(encrypted_trustpass
)
819 auth_blob
.data
= string_to_array(encrypted_trustpass
)
821 auth_info
= lsa
.TrustDomainInfoAuthInfoInternal()
822 auth_info
.auth_blob
= auth_blob
824 trustdom_handle
= lsaconn
.CreateTrustedDomainEx2(pol_handle
,
827 security
.SEC_STD_DELETE
)
830 "dn" : "cn=%s,cn=system,%s" % (ctx
.dnsforest
, ctx
.base_dn
),
831 "objectclass" : "trustedDomain",
832 "trustType" : str(info
.trust_type
),
833 "trustAttributes" : str(info
.trust_attributes
),
834 "trustDirection" : str(info
.trust_direction
),
835 "flatname" : ctx
.forest_domain_name
,
836 "trustPartner" : ctx
.dnsforest
,
837 "trustAuthIncoming" : ndr_pack(outgoing
),
838 "trustAuthOutgoing" : ndr_pack(outgoing
)
840 ctx
.local_samdb
.add(rec
)
843 "dn" : "cn=%s$,cn=users,%s" % (ctx
.forest_domain_name
, ctx
.base_dn
),
844 "objectclass" : "user",
845 "userAccountControl" : str(samba
.dsdb
.UF_INTERDOMAIN_TRUST_ACCOUNT
),
846 "clearTextPassword" : ctx
.trustdom_pass
.encode('utf-16-le')
848 ctx
.local_samdb
.add(rec
)
852 ctx
.cleanup_old_join()
854 ctx
.join_add_objects()
858 ctx
.join_add_objects2()
859 ctx
.join_provision_own_domain()
860 ctx
.join_setup_trusts()
863 print "Join failed - cleaning up"
864 ctx
.cleanup_old_join()
868 def join_RODC(server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
869 targetdir
=None, domain
=None, domain_critical_only
=False,
873 ctx
= dc_join(server
, creds
, lp
, site
, netbios_name
, targetdir
, domain
,
876 lp
.set("workgroup", ctx
.domain_name
)
877 print("workgroup is %s" % ctx
.domain_name
)
879 lp
.set("realm", ctx
.realm
)
880 print("realm is %s" % ctx
.realm
)
882 ctx
.krbtgt_dn
= "CN=krbtgt_%s,CN=Users,%s" % (ctx
.myname
, ctx
.base_dn
)
884 # setup some defaults for accounts that should be replicated to this RODC
885 ctx
.never_reveal_sid
= [ "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_DENY
),
886 "<SID=%s>" % security
.SID_BUILTIN_ADMINISTRATORS
,
887 "<SID=%s>" % security
.SID_BUILTIN_SERVER_OPERATORS
,
888 "<SID=%s>" % security
.SID_BUILTIN_BACKUP_OPERATORS
,
889 "<SID=%s>" % security
.SID_BUILTIN_ACCOUNT_OPERATORS
]
890 ctx
.reveal_sid
= "<SID=%s-%s>" % (ctx
.domsid
, security
.DOMAIN_RID_RODC_ALLOW
)
892 mysid
= ctx
.get_mysid()
893 admin_dn
= "<SID=%s>" % mysid
894 ctx
.managedby
= admin_dn
896 ctx
.userAccountControl
= (samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT |
897 samba
.dsdb
.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
898 samba
.dsdb
.UF_PARTIAL_SECRETS_ACCOUNT
)
900 ctx
.SPNs
.extend([ "RestrictedKrbHost/%s" % ctx
.myname
,
901 "RestrictedKrbHost/%s" % ctx
.dnshostname
])
903 ctx
.connection_dn
= "CN=RODC Connection (FRS),%s" % ctx
.ntds_dn
904 ctx
.secure_channel_type
= misc
.SEC_CHAN_RODC
906 ctx
.replica_flags
= (drsuapi
.DRSUAPI_DRS_INIT_SYNC |
907 drsuapi
.DRSUAPI_DRS_PER_SYNC |
908 drsuapi
.DRSUAPI_DRS_GET_ANC |
909 drsuapi
.DRSUAPI_DRS_NEVER_SYNCED |
910 drsuapi
.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
911 drsuapi
.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP
)
912 ctx
.domain_replica_flags
= ctx
.replica_flags
913 if domain_critical_only
:
914 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
919 print "Joined domain %s (SID %s) as an RODC" % (ctx
.domain_name
, ctx
.domsid
)
922 def join_DC(server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
923 targetdir
=None, domain
=None, domain_critical_only
=False,
926 ctx
= dc_join(server
, creds
, lp
, site
, netbios_name
, targetdir
, domain
,
929 lp
.set("workgroup", ctx
.domain_name
)
930 print("workgroup is %s" % ctx
.domain_name
)
932 lp
.set("realm", ctx
.realm
)
933 print("realm is %s" % ctx
.realm
)
935 ctx
.userAccountControl
= samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT | samba
.dsdb
.UF_TRUSTED_FOR_DELEGATION
937 ctx
.SPNs
.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx
.dnsdomain
)
938 ctx
.secure_channel_type
= misc
.SEC_CHAN_BDC
940 ctx
.replica_flags
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
941 drsuapi
.DRSUAPI_DRS_INIT_SYNC |
942 drsuapi
.DRSUAPI_DRS_PER_SYNC |
943 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS |
944 drsuapi
.DRSUAPI_DRS_NEVER_SYNCED
)
945 ctx
.domain_replica_flags
= ctx
.replica_flags
946 if domain_critical_only
:
947 ctx
.domain_replica_flags |
= drsuapi
.DRSUAPI_DRS_CRITICAL_ONLY
950 print "Joined domain %s (SID %s) as a DC" % (ctx
.domain_name
, ctx
.domsid
)
952 def join_subdomain(server
=None, creds
=None, lp
=None, site
=None, netbios_name
=None,
953 targetdir
=None, parent_domain
=None, dnsdomain
=None, netbios_domain
=None,
956 ctx
= dc_join(server
, creds
, lp
, site
, netbios_name
, targetdir
, parent_domain
,
959 ctx
.parent_domain_name
= ctx
.domain_name
960 ctx
.domain_name
= netbios_domain
961 ctx
.realm
= dnsdomain
962 ctx
.parent_dnsdomain
= ctx
.dnsdomain
963 ctx
.parent_partition_dn
= ctx
.get_parent_partition_dn()
964 ctx
.dnsdomain
= dnsdomain
965 ctx
.partition_dn
= "CN=%s,CN=Partitions,%s" % (ctx
.domain_name
, ctx
.config_dn
)
966 ctx
.naming_master
= ctx
.get_naming_master()
967 if ctx
.naming_master
!= ctx
.server
:
968 print("Reconnecting to naming master %s" % ctx
.naming_master
)
969 ctx
.server
= ctx
.naming_master
970 ctx
.samdb
= SamDB(url
="ldap://%s" % ctx
.server
,
971 session_info
=system_session(),
972 credentials
=ctx
.creds
, lp
=ctx
.lp
)
974 ctx
.base_dn
= samba
.dn_from_dns_name(dnsdomain
)
975 ctx
.domsid
= str(security
.random_sid())
977 ctx
.dnshostname
= "%s.%s" % (ctx
.myname
, ctx
.dnsdomain
)
978 ctx
.trustdom_pass
= samba
.generate_random_password(128, 128)
980 ctx
.userAccountControl
= samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT | samba
.dsdb
.UF_TRUSTED_FOR_DELEGATION
982 ctx
.SPNs
.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx
.dnsdomain
)
983 ctx
.secure_channel_type
= misc
.SEC_CHAN_BDC
985 ctx
.replica_flags
= (drsuapi
.DRSUAPI_DRS_WRIT_REP |
986 drsuapi
.DRSUAPI_DRS_INIT_SYNC |
987 drsuapi
.DRSUAPI_DRS_PER_SYNC |
988 drsuapi
.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS |
989 drsuapi
.DRSUAPI_DRS_NEVER_SYNCED
)
990 ctx
.domain_replica_flags
= ctx
.replica_flags
993 print "Created domain %s (SID %s) as a DC" % (ctx
.domain_name
, ctx
.domsid
)