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):
60 site = "Default-First-Site-Name"
62 ctx.clone_only=clone_only
68 ctx.targetdir = targetdir
69 ctx.use_ntvfs = use_ntvfs
71 ctx.promote_existing = promote_existing
72 ctx.promote_from_dn = None
77 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
78 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
80 if server is not None:
83 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
84 ctx.server = ctx.find_dc(domain)
85 ctx.logger.info("Found DC %s" % ctx.server)
87 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
88 session_info=system_session(),
89 credentials=ctx.creds, lp=ctx.lp)
92 ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
93 except ldb.LdbError, (enum, estr):
94 raise DCJoinException(estr)
97 ctx.base_dn = str(ctx.samdb.get_default_basedn())
98 ctx.root_dn = str(ctx.samdb.get_root_basedn())
99 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
100 ctx.config_dn = str(ctx.samdb.get_config_basedn())
101 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
102 ctx.forestsid = ctx.domsid
103 ctx.domain_name = ctx.get_domain_name()
104 ctx.forest_domain_name = ctx.get_forest_domain_name()
105 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
107 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
108 ctx.dc_dnsHostName = ctx.get_dnsHostName()
109 ctx.behavior_version = ctx.get_behavior_version()
111 if machinepass is not None:
112 ctx.acct_pass = machinepass
114 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
116 ctx.dnsdomain = ctx.samdb.domain_dns_name()
118 # As we don't want to create or delete these DNs, we set them to None
122 ctx.myname = ctx.server.split('.')[0]
124 ctx.rid_manager_dn = None
127 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
129 # work out the DNs of all the objects we will be adding
130 ctx.myname = netbios_name
131 ctx.samname = "%s$" % ctx.myname
132 ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
133 ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
134 ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
135 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
136 ctx.dnsforest = ctx.samdb.forest_dns_name()
138 topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
139 if ctx.dn_exists(topology_base):
140 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
142 ctx.topology_dn = None
144 ctx.SPNs = [ "HOST/%s" % ctx.myname,
145 "HOST/%s" % ctx.dnshostname,
146 "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
148 res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
149 attrs=["rIDManagerReference"],
152 ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
154 ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
155 ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
157 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
158 res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
160 base=ctx.samdb.get_partitions_dn(),
162 if dns_backend is None:
163 ctx.dns_backend = "NONE"
165 if len(res_domaindns) == 0:
166 ctx.dns_backend = "NONE"
167 print "NO DNS zone information found in source domain, not replicating DNS"
169 ctx.dns_backend = dns_backend
171 ctx.realm = ctx.dnsdomain
175 ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
176 drsuapi.DRSUAPI_DRS_PER_SYNC |
177 drsuapi.DRSUAPI_DRS_GET_ANC |
178 drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
179 drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
181 # these elements are optional
182 ctx.never_reveal_sid = None
183 ctx.reveal_sid = None
184 ctx.connection_dn = None
189 ctx.subdomain = False
191 ctx.partition_dn = None
194 ctx.dns_cname_dn = None
196 # Do not normally register 127. addresses but allow override for selftest
197 ctx.force_all_ips = False
199 def del_noerror(ctx, dn, recursive=False):
202 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
206 ctx.del_noerror(r.dn, recursive=True)
209 print "Deleted %s" % dn
213 def cleanup_old_accounts(ctx, force=False):
214 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
215 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
216 attrs=["msDS-krbTgtLink", "objectSID"])
221 creds = Credentials()
224 creds.set_machine_account(ctx.lp)
225 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
226 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
227 session_info=system_session(),
228 credentials=creds, lp=ctx.lp)
232 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
233 if token_res[0]["tokenGroups"][0] \
234 == res[0]["objectSID"][0]:
235 raise DCJoinException("Not removing account %s which "
236 "looks like a Samba DC account "
237 "maching the password we already have. "
238 "To override, remove secrets.ldb and secrets.tdb"
241 ctx.del_noerror(res[0].dn, recursive=True)
243 if "msDS-Krbtgtlink" in res[0]:
244 new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
245 ctx.del_noerror(ctx.new_krbtgt_dn)
247 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
248 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
249 (ldb.binary_encode("dns-%s" % ctx.myname),
250 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
253 ctx.del_noerror(res[0].dn, recursive=True)
255 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
256 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
259 raise DCJoinException("Not removing account %s which looks like "
260 "a Samba DNS service account but does not "
261 "have servicePrincipalName=%s" %
262 (ldb.binary_encode("dns-%s" % ctx.myname),
263 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
266 def cleanup_old_join(ctx, force=False):
267 """Remove any DNs from a previous join."""
268 # find the krbtgt link
269 if not ctx.subdomain:
270 ctx.cleanup_old_accounts(force=force)
272 if ctx.connection_dn is not None:
273 ctx.del_noerror(ctx.connection_dn)
274 if ctx.krbtgt_dn is not None:
275 ctx.del_noerror(ctx.krbtgt_dn)
276 ctx.del_noerror(ctx.ntds_dn)
277 ctx.del_noerror(ctx.server_dn, recursive=True)
279 ctx.del_noerror(ctx.topology_dn)
281 ctx.del_noerror(ctx.partition_dn)
284 binding_options = "sign"
285 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
288 objectAttr = lsa.ObjectAttribute()
289 objectAttr.sec_qos = lsa.QosInfo()
291 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
292 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
295 name.string = ctx.realm
296 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
298 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
301 name.string = ctx.forest_domain_name
302 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
304 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
307 ctx.del_noerror(ctx.dns_a_dn)
310 ctx.del_noerror(ctx.dns_cname_dn)
314 def promote_possible(ctx):
315 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
317 # This shouldn't happen
318 raise Exception("Can not promote into a subdomain")
320 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
321 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
322 attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
324 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
325 if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
326 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
327 if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
328 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
330 ctx.promote_from_dn = res[0].dn
333 def find_dc(ctx, domain):
334 """find a writeable DC for the given domain"""
336 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
338 raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
339 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
340 ctx.site = ctx.cldap_ret.client_site
341 return ctx.cldap_ret.pdc_dns_name
344 def get_behavior_version(ctx):
345 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
346 if "msDS-Behavior-Version" in res[0]:
347 return int(res[0]["msDS-Behavior-Version"][0])
349 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
351 def get_dnsHostName(ctx):
352 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
353 return res[0]["dnsHostName"][0]
355 def get_domain_name(ctx):
356 '''get netbios name of the domain from the partitions record'''
357 partitions_dn = ctx.samdb.get_partitions_dn()
358 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
359 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
360 return res[0]["nETBIOSName"][0]
362 def get_forest_domain_name(ctx):
363 '''get netbios name of the domain from the partitions record'''
364 partitions_dn = ctx.samdb.get_partitions_dn()
365 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
366 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
367 return res[0]["nETBIOSName"][0]
369 def get_parent_partition_dn(ctx):
370 '''get the parent domain partition DN from parent DNS name'''
371 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
372 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
373 (ldb.binary_encode(ctx.parent_dnsdomain),
374 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
375 return str(res[0].dn)
377 def get_naming_master(ctx):
378 '''get the parent domain partition DN from parent DNS name'''
379 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
380 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
381 if not 'fSMORoleOwner' in res[0]:
382 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
384 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0]).get_extended_component('GUID')))
386 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
388 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
392 '''get the SID of the connected user. Only works with w2k8 and later,
393 so only used for RODC join'''
394 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
395 binsid = res[0]["tokenGroups"][0]
396 return ctx.samdb.schema_format_value("objectSID", binsid)
398 def dn_exists(ctx, dn):
399 '''check if a DN exists'''
401 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
402 except ldb.LdbError, (enum, estr):
403 if enum == ldb.ERR_NO_SUCH_OBJECT:
408 def add_krbtgt_account(ctx):
409 '''RODCs need a special krbtgt account'''
410 print "Adding %s" % ctx.krbtgt_dn
412 "dn" : ctx.krbtgt_dn,
413 "objectclass" : "user",
414 "useraccountcontrol" : str(samba.dsdb.UF_NORMAL_ACCOUNT |
415 samba.dsdb.UF_ACCOUNTDISABLE),
416 "showinadvancedviewonly" : "TRUE",
417 "description" : "krbtgt for %s" % ctx.samname}
418 ctx.samdb.add(rec, ["rodc_join:1:1"])
420 # now we need to search for the samAccountName attribute on the krbtgt DN,
421 # as this will have been magically set to the krbtgt number
422 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
423 ctx.krbtgt_name = res[0]["samAccountName"][0]
425 print "Got krbtgt_name=%s" % ctx.krbtgt_name
428 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
429 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
430 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
433 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
434 print "Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn)
435 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
437 def drsuapi_connect(ctx):
438 '''make a DRSUAPI connection to the naming master'''
439 binding_options = "seal"
440 if ctx.lp.log_level() >= 4:
441 binding_options += ",print"
442 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
443 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
444 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
446 def create_tmp_samdb(ctx):
447 '''create a temporary samdb object for schema queries'''
448 ctx.tmp_schema = Schema(ctx.domsid,
449 schemadn=ctx.schema_dn)
450 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
451 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
453 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
455 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
456 '''build a DsReplicaAttributeCtr object'''
457 r = drsuapi.DsReplicaAttribute()
458 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
462 def DsAddEntry(ctx, recs):
463 '''add a record via the DRSUAPI DsAddEntry call'''
464 if ctx.drsuapi is None:
465 ctx.drsuapi_connect()
466 if ctx.tmp_samdb is None:
467 ctx.create_tmp_samdb()
471 id = drsuapi.DsReplicaObjectIdentifier()
478 if not isinstance(rec[a], list):
482 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
485 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
486 attribute_ctr.num_attributes = len(attrs)
487 attribute_ctr.attributes = attrs
489 object = drsuapi.DsReplicaObject()
490 object.identifier = id
491 object.attribute_ctr = attribute_ctr
493 list_object = drsuapi.DsReplicaObjectListItem()
494 list_object.object = object
495 objects.append(list_object)
497 req2 = drsuapi.DsAddEntryRequest2()
498 req2.first_object = objects[0]
499 prev = req2.first_object
500 for o in objects[1:]:
504 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
506 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
507 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
508 raise RuntimeError("DsAddEntry failed")
509 if ctr.extended_err[0] != werror.WERR_SUCCESS:
510 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
511 raise RuntimeError("DsAddEntry failed")
514 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
515 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
516 if ctr.err_data.info is None:
517 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
519 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
520 ctr.err_data.info.extended_err))
521 raise RuntimeError("DsAddEntry failed")
522 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
523 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
524 raise RuntimeError("DsAddEntry failed")
528 def join_ntdsdsa_obj(ctx):
529 '''return the ntdsdsa object to add'''
531 print "Adding %s" % ctx.ntds_dn
534 "objectclass" : "nTDSDSA",
535 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
536 "dMDLocation" : ctx.schema_dn}
538 nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
540 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
541 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
543 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
544 rec["msDS-HasDomainNCs"] = ctx.base_dn
547 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
548 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
549 rec["options"] = "37"
551 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
552 rec["HasMasterNCs"] = []
554 if nc in ctx.full_nc_list:
555 rec["HasMasterNCs"].append(nc)
556 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
557 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
559 rec["invocationId"] = ndr_pack(ctx.invocation_id)
563 def join_add_ntdsdsa(ctx):
564 '''add the ntdsdsa object'''
566 rec = ctx.join_ntdsdsa_obj()
568 ctx.samdb.add(rec, ["rodc_join:1:1"])
570 ctx.DsAddEntry([rec])
572 # find the GUID of our NTDS DN
573 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
574 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
576 def join_add_objects(ctx):
577 '''add the various objects needed for the join'''
579 print "Adding %s" % ctx.acct_dn
582 "objectClass": "computer",
583 "displayname": ctx.samname,
584 "samaccountname" : ctx.samname,
585 "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
586 "dnshostname" : ctx.dnshostname}
587 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
588 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
589 elif ctx.promote_existing:
590 rec['msDS-SupportedEncryptionTypes'] = []
592 rec["managedby"] = ctx.managedby
593 elif ctx.promote_existing:
594 rec["managedby"] = []
596 if ctx.never_reveal_sid:
597 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
598 elif ctx.promote_existing:
599 rec["msDS-NeverRevealGroup"] = []
602 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
603 elif ctx.promote_existing:
604 rec["msDS-RevealOnDemandGroup"] = []
606 if ctx.promote_existing:
607 if ctx.promote_from_dn != ctx.acct_dn:
608 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
609 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
614 ctx.add_krbtgt_account()
617 print "Adding %s" % ctx.server_dn
620 "objectclass" : "server",
621 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
622 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
623 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
624 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
625 # windows seems to add the dnsHostName later
626 "dnsHostName" : ctx.dnshostname}
629 rec["serverReference"] = ctx.acct_dn
634 # the rest is done after replication
639 ctx.join_add_ntdsdsa()
641 # Add the Replica-Locations or RO-Replica-Locations attributes
642 # TODO Is this supposed to be for the schema partition too?
643 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
644 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
646 base=ctx.samdb.get_partitions_dn(),
647 expression=expr), ctx.domaindns_zone)
649 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
650 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
652 base=ctx.samdb.get_partitions_dn(),
653 expression=expr), ctx.forestdns_zone)
655 for part, zone in (domain, forest):
656 if zone not in ctx.nc_list:
662 attr = "msDS-NC-Replica-Locations"
664 attr = "msDS-NC-RO-Replica-Locations"
666 m[attr] = ldb.MessageElement(ctx.ntds_dn,
667 ldb.FLAG_MOD_ADD, attr)
670 if ctx.connection_dn is not None:
671 print "Adding %s" % ctx.connection_dn
673 "dn" : ctx.connection_dn,
674 "objectclass" : "nTDSConnection",
675 "enabledconnection" : "TRUE",
677 "fromServer" : ctx.dc_ntds_dn}
681 print "Adding SPNs to %s" % ctx.acct_dn
683 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
684 for i in range(len(ctx.SPNs)):
685 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
686 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
687 ldb.FLAG_MOD_REPLACE,
688 "servicePrincipalName")
691 # The account password set operation should normally be done over
692 # LDAP. Windows 2000 DCs however allow this only with SSL
693 # connections which are hard to set up and otherwise refuse with
694 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
696 print "Setting account password for %s" % ctx.samname
698 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
699 % ldb.binary_encode(ctx.samname),
701 force_change_at_next_login=False,
702 username=ctx.samname)
703 except ldb.LdbError, (num, _):
704 if num != ldb.ERR_UNWILLING_TO_PERFORM:
706 ctx.net.set_password(account_name=ctx.samname,
707 domain_name=ctx.domain_name,
708 newpassword=ctx.acct_pass.encode('utf-8'))
710 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
711 attrs=["msDS-KeyVersionNumber",
713 if "msDS-KeyVersionNumber" in res[0]:
714 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
716 ctx.key_version_number = None
718 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
719 res[0]["objectSid"][0])
721 print("Enabling account")
723 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
724 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
725 ldb.FLAG_MOD_REPLACE,
726 "userAccountControl")
729 if ctx.dns_backend.startswith("BIND9_"):
730 ctx.dnspass = samba.generate_random_password(128, 255)
732 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
733 {"DNSDOMAIN": ctx.dnsdomain,
734 "DOMAINDN": ctx.base_dn,
735 "HOSTNAME" : ctx.myname,
736 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')),
737 "DNSNAME" : ctx.dnshostname}))
738 for changetype, msg in recs:
739 assert changetype == ldb.CHANGETYPE_NONE
740 dns_acct_dn = msg["dn"]
741 print "Adding DNS account %s with dns/ SPN" % msg["dn"]
743 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
744 del msg["clearTextPassword"]
745 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
746 del msg["isCriticalSystemObject"]
747 # Disable account until password is set
748 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
749 samba.dsdb.UF_ACCOUNTDISABLE)
752 except ldb.LdbError, (num, _):
753 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
756 # The account password set operation should normally be done over
757 # LDAP. Windows 2000 DCs however allow this only with SSL
758 # connections which are hard to set up and otherwise refuse with
759 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
761 print "Setting account password for dns-%s" % ctx.myname
763 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
764 % ldb.binary_encode(ctx.myname),
766 force_change_at_next_login=False,
767 username=ctx.samname)
768 except ldb.LdbError, (num, _):
769 if num != ldb.ERR_UNWILLING_TO_PERFORM:
771 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
772 domain_name=ctx.domain_name,
773 newpassword=ctx.dnspass)
775 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
776 attrs=["msDS-KeyVersionNumber"])
777 if "msDS-KeyVersionNumber" in res[0]:
778 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
780 ctx.dns_key_version_number = None
782 def join_add_objects2(ctx):
783 """add the various objects needed for the join, for subdomains post replication"""
785 print "Adding %s" % ctx.partition_dn
786 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
787 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
789 "dn" : ctx.partition_dn,
790 "objectclass" : "crossRef",
791 "objectCategory" : "CN=Cross-Ref,%s" % ctx.schema_dn,
792 "nCName" : ctx.base_dn,
793 "nETBIOSName" : ctx.domain_name,
794 "dnsRoot": ctx.dnsdomain,
795 "trustParent" : ctx.parent_partition_dn,
796 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
797 "ntSecurityDescriptor" : sd_binary,
800 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
801 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
803 rec2 = ctx.join_ntdsdsa_obj()
805 objects = ctx.DsAddEntry([rec, rec2])
806 if len(objects) != 2:
807 raise DCJoinException("Expected 2 objects from DsAddEntry")
809 ctx.ntds_guid = objects[1].guid
811 print("Replicating partition DN")
812 ctx.repl.replicate(ctx.partition_dn,
813 misc.GUID("00000000-0000-0000-0000-000000000000"),
815 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
816 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
818 print("Replicating NTDS DN")
819 ctx.repl.replicate(ctx.ntds_dn,
820 misc.GUID("00000000-0000-0000-0000-000000000000"),
822 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
823 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
825 def join_provision(ctx):
826 """Provision the local SAM."""
828 print "Calling bare provision"
830 smbconf = ctx.lp.configfile
832 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
833 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
834 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
835 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
836 serverdn=ctx.server_dn, domain=ctx.domain_name,
837 hostname=ctx.myname, domainsid=ctx.domsid,
838 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
839 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
840 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend)
841 print "Provision OK for domain DN %s" % presult.domaindn
842 ctx.local_samdb = presult.samdb
844 ctx.paths = presult.paths
845 ctx.names = presult.names
847 # Fix up the forestsid, it may be different if we are joining as a subdomain
848 ctx.names.forestsid = ctx.forestsid
850 def join_provision_own_domain(ctx):
851 """Provision the local SAM."""
853 # we now operate exclusively on the local database, which
854 # we need to reopen in order to get the newly created schema
855 print("Reconnecting to local samdb")
856 ctx.samdb = SamDB(url=ctx.local_samdb.url,
857 session_info=system_session(),
858 lp=ctx.local_samdb.lp,
860 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
861 ctx.local_samdb = ctx.samdb
863 ctx.logger.info("Finding domain GUID from ncName")
864 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
865 controls=["extended_dn:1:1", "reveal_internals:0"])
867 if 'nCName' not in res[0]:
868 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
871 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID')))
873 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
875 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
877 ctx.logger.info("Calling own domain provision")
879 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
881 presult = provision_fill(ctx.local_samdb, secrets_ldb,
882 ctx.logger, ctx.names, ctx.paths,
883 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
884 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
885 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
886 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
887 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
888 print("Provision OK for domain %s" % ctx.names.dnsdomain)
890 def join_replicate(ctx):
891 """Replicate the SAM."""
893 print "Starting replication"
894 ctx.local_samdb.transaction_start()
896 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
897 if ctx.ntds_guid is None:
898 print("Using DS_BIND_GUID_W2K3")
899 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
901 destination_dsa_guid = ctx.ntds_guid
904 repl_creds = Credentials()
905 repl_creds.guess(ctx.lp)
906 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
907 repl_creds.set_username(ctx.samname)
908 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
910 repl_creds = ctx.creds
912 binding_options = "seal"
913 if ctx.lp.log_level() >= 5:
914 binding_options += ",print"
915 repl = drs_utils.drs_Replicate(
916 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
917 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
919 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
920 destination_dsa_guid, schema=True, rodc=ctx.RODC,
921 replica_flags=ctx.replica_flags)
922 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
923 destination_dsa_guid, rodc=ctx.RODC,
924 replica_flags=ctx.replica_flags)
925 if not ctx.subdomain:
926 # Replicate first the critical object for the basedn
927 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
928 print "Replicating critical objects from the base DN of the domain"
929 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
930 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
931 destination_dsa_guid, rodc=ctx.RODC,
932 replica_flags=ctx.domain_replica_flags)
933 ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
934 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
935 destination_dsa_guid, rodc=ctx.RODC,
936 replica_flags=ctx.domain_replica_flags)
937 print "Done with always replicated NC (base, config, schema)"
939 # At this point we should already have an entry in the ForestDNS
940 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
941 # indicate that we hold a replica for this NC.
942 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
943 if nc in ctx.nc_list:
944 print "Replicating %s" % (str(nc))
945 repl.replicate(nc, source_dsa_invocation_id,
946 destination_dsa_guid, rodc=ctx.RODC,
947 replica_flags=ctx.replica_flags)
950 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
951 destination_dsa_guid,
952 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
953 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
954 destination_dsa_guid,
955 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
956 elif ctx.rid_manager_dn != None:
957 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
959 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
960 destination_dsa_guid,
961 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
962 except samba.DsExtendedError, (enum, estr):
963 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
964 print "WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server
965 print "NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup."
970 ctx.source_dsa_invocation_id = source_dsa_invocation_id
971 ctx.destination_dsa_guid = destination_dsa_guid
973 print "Committing SAM database"
975 ctx.local_samdb.transaction_cancel()
978 ctx.local_samdb.transaction_commit()
980 def send_DsReplicaUpdateRefs(ctx, dn):
981 r = drsuapi.DsReplicaUpdateRefsRequest1()
982 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
983 r.naming_context.dn = str(dn)
984 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
985 r.naming_context.sid = security.dom_sid("S-0-0")
986 r.dest_dsa_guid = ctx.ntds_guid
987 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
988 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
990 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
992 if ctx.drsuapi is None:
993 ctx.drsuapi_connect()
995 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
997 def join_add_dns_records(ctx):
998 """Remotely Add a DNS record to the target DC. We assume that if we
999 replicate DNS that the server holds the DNS roles and can accept
1002 This avoids issues getting replication going after the DC
1003 first starts as the rest of the domain does not have to
1004 wait for samba_dnsupdate to run successfully.
1006 Specifically, we add the records implied by the DsReplicaUpdateRefs
1009 We do not just run samba_dnsupdate as we want to strictly
1010 operate against the DC we just joined:
1011 - We do not want to query another DNS server
1012 - We do not want to obtain a Kerberos ticket
1013 (as the KDC we select may not be the DC we just joined,
1014 and so may not be in sync with the password we just set)
1015 - We do not wish to set the _ldap records until we have started
1016 - We do not wish to use NTLM (the --use-samba-tool mode forces
1021 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1022 record_type = dnsp.DNS_TYPE_A
1023 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1024 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1026 zone = ctx.dnsdomain
1027 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1029 msdcs_cname = str(ctx.ntds_guid)
1030 cname_target = "%s.%s" % (name, zone)
1031 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1033 ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
1034 (len(IPs), name, zone))
1036 binding_options = "sign"
1037 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1043 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1045 change_owner_sd = security.descriptor()
1046 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1047 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1049 security.DOMAIN_RID_DCS))
1051 # TODO: Remove any old records from the primary DNS name
1054 = dns_conn.DnssrvEnumRecords2(client_version,
1064 except WERRORError as e:
1065 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1071 for record in rec.records:
1072 if record.wType == dnsp.DNS_TYPE_A or \
1073 record.wType == dnsp.DNS_TYPE_AAAA:
1075 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1076 del_rec_buf.rec = record
1078 dns_conn.DnssrvUpdateRecord2(client_version,
1085 except WERRORError as e:
1086 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1092 if IP.find(':') != -1:
1093 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1095 rec = AAAARecord(IP)
1097 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1102 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1103 add_rec_buf.rec = rec
1104 dns_conn.DnssrvUpdateRecord2(client_version,
1113 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1114 (ctx.dns_a_dn, ldap_record) \
1115 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1116 dns_partition=domaindns_zone_dn)
1118 # Make the DC own the DNS record, not the administrator
1119 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1120 controls=["sd_flags:1:%d"
1121 % (security.SECINFO_OWNER
1122 | security.SECINFO_GROUP)])
1126 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1127 % (msdcs_cname, msdcs_zone, cname_target))
1129 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1130 rec = CNameRecord(cname_target)
1131 add_rec_buf.rec = rec
1132 dns_conn.DnssrvUpdateRecord2(client_version,
1140 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1141 (ctx.dns_cname_dn, ldap_record) \
1142 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1143 dns_partition=forestdns_zone_dn)
1145 # Make the DC own the DNS record, not the administrator
1146 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1147 controls=["sd_flags:1:%d"
1148 % (security.SECINFO_OWNER
1149 | security.SECINFO_GROUP)])
1151 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1152 "will be created samba_dnsupdate on first startup")
1155 def join_replicate_new_dns_records(ctx):
1156 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1157 if nc in ctx.nc_list:
1158 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1159 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1160 ctx.ntds_guid, rodc=ctx.RODC,
1161 replica_flags=ctx.replica_flags,
1166 def join_finalise(ctx):
1167 """Finalise the join, mark us synchronised and setup secrets db."""
1169 # FIXME we shouldn't do this in all cases
1171 # If for some reasons we joined in another site than the one of
1172 # DC we just replicated from then we don't need to send the updatereplicateref
1173 # as replication between sites is time based and on the initiative of the
1175 if not ctx.clone_only:
1176 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1177 for nc in ctx.nc_list:
1178 ctx.send_DsReplicaUpdateRefs(nc)
1180 if not ctx.clone_only and ctx.RODC:
1181 print "Setting RODC invocationId"
1182 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1183 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1184 ctx.behavior_version)
1186 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1187 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1188 ldb.FLAG_MOD_REPLACE,
1190 ctx.local_samdb.modify(m)
1192 # Note: as RODC the invocationId is only stored
1193 # on the RODC itself, the other DCs never see it.
1195 # Thats is why we fix up the replPropertyMetaData stamp
1196 # for the 'invocationId' attribute, we need to change
1197 # the 'version' to '0', this is what windows 2008r2 does as RODC
1199 # This means if the object on a RWDC ever gets a invocationId
1200 # attribute, it will have version '1' (or higher), which will
1201 # will overwrite the RODC local value.
1202 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1206 ctx.logger.info("Setting isSynchronized and dsServiceName")
1208 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1209 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1211 # We want to appear to be the server we just cloned
1213 guid = ctx.remote_dc_ntds_guid
1215 guid = ctx.ntds_guid
1217 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1218 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1219 ctx.local_samdb.modify(m)
1221 if ctx.clone_only or ctx.subdomain:
1224 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1226 ctx.logger.info("Setting up secrets database")
1227 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1229 dnsdomain=ctx.dnsdomain,
1230 netbiosname=ctx.myname,
1231 domainsid=ctx.domsid,
1232 machinepass=ctx.acct_pass,
1233 secure_channel_type=ctx.secure_channel_type,
1234 key_version_number=ctx.key_version_number)
1236 if ctx.dns_backend.startswith("BIND9_"):
1237 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1238 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1239 dns_backend=ctx.dns_backend,
1240 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1241 targetdir=ctx.targetdir,
1242 key_version_number=ctx.dns_key_version_number)
1244 def join_setup_trusts(ctx):
1245 """provision the local SAM."""
1247 print "Setup domain trusts with server %s" % ctx.server
1248 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1249 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1252 objectAttr = lsa.ObjectAttribute()
1253 objectAttr.sec_qos = lsa.QosInfo()
1255 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1256 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1258 info = lsa.TrustDomainInfoInfoEx()
1259 info.domain_name.string = ctx.dnsdomain
1260 info.netbios_name.string = ctx.domain_name
1261 info.sid = ctx.domsid
1262 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1263 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1264 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1267 oldname = lsa.String()
1268 oldname.string = ctx.dnsdomain
1269 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1270 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1271 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1272 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1273 except RuntimeError:
1276 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1278 clear_value = drsblobs.AuthInfoClear()
1279 clear_value.size = len(password_blob)
1280 clear_value.password = password_blob
1282 clear_authentication_information = drsblobs.AuthenticationInformation()
1283 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1284 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1285 clear_authentication_information.AuthInfo = clear_value
1287 authentication_information_array = drsblobs.AuthenticationInformationArray()
1288 authentication_information_array.count = 1
1289 authentication_information_array.array = [clear_authentication_information]
1291 outgoing = drsblobs.trustAuthInOutBlob()
1293 outgoing.current = authentication_information_array
1295 trustpass = drsblobs.trustDomainPasswords()
1296 confounder = [3] * 512
1298 for i in range(512):
1299 confounder[i] = random.randint(0, 255)
1301 trustpass.confounder = confounder
1303 trustpass.outgoing = outgoing
1304 trustpass.incoming = outgoing
1306 trustpass_blob = ndr_pack(trustpass)
1308 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1310 auth_blob = lsa.DATA_BUF2()
1311 auth_blob.size = len(encrypted_trustpass)
1312 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1314 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1315 auth_info.auth_blob = auth_blob
1317 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1320 security.SEC_STD_DELETE)
1323 "dn" : "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1324 "objectclass" : "trustedDomain",
1325 "trustType" : str(info.trust_type),
1326 "trustAttributes" : str(info.trust_attributes),
1327 "trustDirection" : str(info.trust_direction),
1328 "flatname" : ctx.forest_domain_name,
1329 "trustPartner" : ctx.dnsforest,
1330 "trustAuthIncoming" : ndr_pack(outgoing),
1331 "trustAuthOutgoing" : ndr_pack(outgoing),
1332 "securityIdentifier" : ndr_pack(ctx.forestsid)
1334 ctx.local_samdb.add(rec)
1337 "dn" : "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1338 "objectclass" : "user",
1339 "userAccountControl" : str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1340 "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le'),
1341 "samAccountName" : "%s$" % ctx.forest_domain_name
1343 ctx.local_samdb.add(rec)
1347 # nc_list is the list of naming context (NC) for which we will
1348 # replicate in and send a updateRef command to the partner DC
1350 # full_nc_list is the list of naming context (NC) we hold
1351 # read/write copies of. These are not subsets of each other.
1352 ctx.nc_list = [ ctx.config_dn, ctx.schema_dn ]
1353 ctx.full_nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
1355 if ctx.subdomain and ctx.dns_backend != "NONE":
1356 ctx.full_nc_list += [ctx.domaindns_zone]
1358 elif not ctx.subdomain:
1359 ctx.nc_list += [ctx.base_dn]
1361 if ctx.dns_backend != "NONE":
1362 ctx.nc_list += [ctx.domaindns_zone]
1363 ctx.nc_list += [ctx.forestdns_zone]
1364 ctx.full_nc_list += [ctx.domaindns_zone]
1365 ctx.full_nc_list += [ctx.forestdns_zone]
1367 if not ctx.clone_only:
1368 if ctx.promote_existing:
1369 ctx.promote_possible()
1371 ctx.cleanup_old_join()
1374 if not ctx.clone_only:
1375 ctx.join_add_objects()
1376 ctx.join_provision()
1377 ctx.join_replicate()
1378 if (not ctx.clone_only and ctx.subdomain):
1379 ctx.join_add_objects2()
1380 ctx.join_provision_own_domain()
1381 ctx.join_setup_trusts()
1383 if not ctx.clone_only and ctx.dns_backend != "NONE":
1384 ctx.join_add_dns_records()
1385 ctx.join_replicate_new_dns_records()
1390 print "Join failed - cleaning up"
1393 if not ctx.clone_only:
1394 ctx.cleanup_old_join()
1398 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1399 targetdir=None, domain=None, domain_critical_only=False,
1400 machinepass=None, use_ntvfs=False, dns_backend=None,
1401 promote_existing=False):
1402 """Join as a RODC."""
1404 ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
1405 machinepass, use_ntvfs, dns_backend, promote_existing)
1407 lp.set("workgroup", ctx.domain_name)
1408 logger.info("workgroup is %s" % ctx.domain_name)
1410 lp.set("realm", ctx.realm)
1411 logger.info("realm is %s" % ctx.realm)
1413 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1415 # setup some defaults for accounts that should be replicated to this RODC
1416 ctx.never_reveal_sid = [
1417 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1418 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1419 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1420 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1421 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1422 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1424 mysid = ctx.get_mysid()
1425 admin_dn = "<SID=%s>" % mysid
1426 ctx.managedby = admin_dn
1428 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1429 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1430 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1432 ctx.SPNs.extend([ "RestrictedKrbHost/%s" % ctx.myname,
1433 "RestrictedKrbHost/%s" % ctx.dnshostname ])
1435 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1436 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1438 ctx.replica_flags |= ( drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1439 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1440 ctx.domain_replica_flags = ctx.replica_flags
1441 if domain_critical_only:
1442 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1446 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1449 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1450 targetdir=None, domain=None, domain_critical_only=False,
1451 machinepass=None, use_ntvfs=False, dns_backend=None,
1452 promote_existing=False):
1454 ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
1455 machinepass, use_ntvfs, dns_backend, promote_existing)
1457 lp.set("workgroup", ctx.domain_name)
1458 logger.info("workgroup is %s" % ctx.domain_name)
1460 lp.set("realm", ctx.realm)
1461 logger.info("realm is %s" % ctx.realm)
1463 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1465 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1466 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1468 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1469 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1470 ctx.domain_replica_flags = ctx.replica_flags
1471 if domain_critical_only:
1472 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1475 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1477 def join_clone(logger=None, server=None, creds=None, lp=None,
1478 targetdir=None, domain=None, include_secrets=False):
1480 ctx = dc_join(logger, server, creds, lp, site=None, netbios_name=None, targetdir=targetdir, domain=domain,
1481 machinepass=None, use_ntvfs=False, dns_backend="NONE", promote_existing=False, clone_only=True)
1483 lp.set("workgroup", ctx.domain_name)
1484 logger.info("workgroup is %s" % ctx.domain_name)
1486 lp.set("realm", ctx.realm)
1487 logger.info("realm is %s" % ctx.realm)
1489 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1490 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1491 if not include_secrets:
1492 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1493 ctx.domain_replica_flags = ctx.replica_flags
1496 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1498 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1499 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1500 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1503 ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, parent_domain,
1504 machinepass, use_ntvfs, dns_backend)
1505 ctx.subdomain = True
1506 if adminpass is None:
1507 ctx.adminpass = samba.generate_random_password(12, 32)
1509 ctx.adminpass = adminpass
1510 ctx.parent_domain_name = ctx.domain_name
1511 ctx.domain_name = netbios_domain
1512 ctx.realm = dnsdomain
1513 ctx.parent_dnsdomain = ctx.dnsdomain
1514 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1515 ctx.dnsdomain = dnsdomain
1516 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1517 ctx.naming_master = ctx.get_naming_master()
1518 if ctx.naming_master != ctx.server:
1519 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1520 ctx.server = ctx.naming_master
1521 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1522 session_info=system_session(),
1523 credentials=ctx.creds, lp=ctx.lp)
1524 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1526 ctx.server = res[0]["dnsHostName"]
1527 logger.info("DNS name of new naming master is %s" % ctx.server)
1529 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1530 ctx.forestsid = ctx.domsid
1531 ctx.domsid = security.random_sid()
1533 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1534 # Windows uses 240 bytes as UTF16 so we do
1535 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1537 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1539 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1540 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1542 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1543 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1544 ctx.domain_replica_flags = ctx.replica_flags
1547 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))