2 # Copyright Andrew Tridgell 2010
3 # Copyright Andrew Bartlett 2010
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 """Joining a domain."""
21 from samba.auth import system_session
22 from samba.samdb import SamDB
23 from samba import gensec, Ldb, drs_utils, arcfour_encrypt, string_to_byte_array
24 import ldb, samba, sys, uuid
25 from samba.ndr import ndr_pack, ndr_unpack
26 from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp
27 from samba.dsdb import DS_DOMAIN_FUNCTION_2003
28 from samba.credentials import Credentials, DONT_USE_KERBEROS
29 from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN
30 from samba.provision.common import setup_path
31 from samba.schema import Schema
32 from samba import descriptor
33 from samba.net import Net
34 from samba.provision.sambadns import setup_bind9_dns
35 from samba import read_and_sub_file
36 from samba import werror
37 from base64 import b64encode
38 from samba import WERRORError
39 from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord
40 from samba import sd_utils
46 class DCJoinException(Exception):
48 def __init__(self, msg):
49 super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
52 class dc_join(object):
53 """Perform a DC join."""
55 def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
56 netbios_name=None, targetdir=None, domain=None,
57 machinepass=None, use_ntvfs=False, dns_backend=None,
58 promote_existing=False, clone_only=False,
59 plaintext_secrets=False):
61 site = "Default-First-Site-Name"
63 ctx.clone_only=clone_only
69 ctx.targetdir = targetdir
70 ctx.use_ntvfs = use_ntvfs
71 ctx.plaintext_secrets = plaintext_secrets
73 ctx.promote_existing = promote_existing
74 ctx.promote_from_dn = None
79 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
80 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
82 if server is not None:
85 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
86 ctx.server = ctx.find_dc(domain)
87 ctx.logger.info("Found DC %s" % ctx.server)
89 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
90 session_info=system_session(),
91 credentials=ctx.creds, lp=ctx.lp)
94 ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
95 except ldb.LdbError, (enum, estr):
96 raise DCJoinException(estr)
99 ctx.base_dn = str(ctx.samdb.get_default_basedn())
100 ctx.root_dn = str(ctx.samdb.get_root_basedn())
101 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
102 ctx.config_dn = str(ctx.samdb.get_config_basedn())
103 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
104 ctx.forestsid = ctx.domsid
105 ctx.domain_name = ctx.get_domain_name()
106 ctx.forest_domain_name = ctx.get_forest_domain_name()
107 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
109 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
110 ctx.dc_dnsHostName = ctx.get_dnsHostName()
111 ctx.behavior_version = ctx.get_behavior_version()
113 if machinepass is not None:
114 ctx.acct_pass = machinepass
116 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
118 ctx.dnsdomain = ctx.samdb.domain_dns_name()
120 # As we don't want to create or delete these DNs, we set them to None
124 ctx.myname = ctx.server.split('.')[0]
126 ctx.rid_manager_dn = None
129 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
131 # work out the DNs of all the objects we will be adding
132 ctx.myname = netbios_name
133 ctx.samname = "%s$" % ctx.myname
134 ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
135 ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
136 ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
137 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
138 ctx.dnsforest = ctx.samdb.forest_dns_name()
140 topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
141 if ctx.dn_exists(topology_base):
142 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
144 ctx.topology_dn = None
146 ctx.SPNs = [ "HOST/%s" % ctx.myname,
147 "HOST/%s" % ctx.dnshostname,
148 "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
150 res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
151 attrs=["rIDManagerReference"],
154 ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
156 ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
157 ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
159 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
160 res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
162 base=ctx.samdb.get_partitions_dn(),
164 if dns_backend is None:
165 ctx.dns_backend = "NONE"
167 if len(res_domaindns) == 0:
168 ctx.dns_backend = "NONE"
169 print "NO DNS zone information found in source domain, not replicating DNS"
171 ctx.dns_backend = dns_backend
173 ctx.realm = ctx.dnsdomain
177 ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
178 drsuapi.DRSUAPI_DRS_PER_SYNC |
179 drsuapi.DRSUAPI_DRS_GET_ANC |
180 drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
181 drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
183 # these elements are optional
184 ctx.never_reveal_sid = None
185 ctx.reveal_sid = None
186 ctx.connection_dn = None
191 ctx.subdomain = False
193 ctx.partition_dn = None
196 ctx.dns_cname_dn = None
198 # Do not normally register 127. addresses but allow override for selftest
199 ctx.force_all_ips = False
201 def del_noerror(ctx, dn, recursive=False):
204 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
208 ctx.del_noerror(r.dn, recursive=True)
211 print "Deleted %s" % dn
215 def cleanup_old_accounts(ctx, force=False):
216 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
217 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
218 attrs=["msDS-krbTgtLink", "objectSID"])
223 creds = Credentials()
226 creds.set_machine_account(ctx.lp)
227 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
228 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
229 session_info=system_session(),
230 credentials=creds, lp=ctx.lp)
234 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
235 if token_res[0]["tokenGroups"][0] \
236 == res[0]["objectSID"][0]:
237 raise DCJoinException("Not removing account %s which "
238 "looks like a Samba DC account "
239 "maching the password we already have. "
240 "To override, remove secrets.ldb and secrets.tdb"
243 ctx.del_noerror(res[0].dn, recursive=True)
245 if "msDS-Krbtgtlink" in res[0]:
246 new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
247 ctx.del_noerror(ctx.new_krbtgt_dn)
249 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
250 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
251 (ldb.binary_encode("dns-%s" % ctx.myname),
252 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
255 ctx.del_noerror(res[0].dn, recursive=True)
257 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
258 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
261 raise DCJoinException("Not removing account %s which looks like "
262 "a Samba DNS service account but does not "
263 "have servicePrincipalName=%s" %
264 (ldb.binary_encode("dns-%s" % ctx.myname),
265 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
268 def cleanup_old_join(ctx, force=False):
269 """Remove any DNs from a previous join."""
270 # find the krbtgt link
271 if not ctx.subdomain:
272 ctx.cleanup_old_accounts(force=force)
274 if ctx.connection_dn is not None:
275 ctx.del_noerror(ctx.connection_dn)
276 if ctx.krbtgt_dn is not None:
277 ctx.del_noerror(ctx.krbtgt_dn)
278 ctx.del_noerror(ctx.ntds_dn)
279 ctx.del_noerror(ctx.server_dn, recursive=True)
281 ctx.del_noerror(ctx.topology_dn)
283 ctx.del_noerror(ctx.partition_dn)
286 binding_options = "sign"
287 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
290 objectAttr = lsa.ObjectAttribute()
291 objectAttr.sec_qos = lsa.QosInfo()
293 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
294 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
297 name.string = ctx.realm
298 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
300 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
303 name.string = ctx.forest_domain_name
304 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
306 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
309 ctx.del_noerror(ctx.dns_a_dn)
312 ctx.del_noerror(ctx.dns_cname_dn)
316 def promote_possible(ctx):
317 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
319 # This shouldn't happen
320 raise Exception("Can not promote into a subdomain")
322 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
323 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
324 attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
326 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
327 if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
328 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
329 if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
330 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
332 ctx.promote_from_dn = res[0].dn
335 def find_dc(ctx, domain):
336 """find a writeable DC for the given domain"""
338 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
340 raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
341 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
342 ctx.site = ctx.cldap_ret.client_site
343 return ctx.cldap_ret.pdc_dns_name
346 def get_behavior_version(ctx):
347 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
348 if "msDS-Behavior-Version" in res[0]:
349 return int(res[0]["msDS-Behavior-Version"][0])
351 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
353 def get_dnsHostName(ctx):
354 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
355 return res[0]["dnsHostName"][0]
357 def get_domain_name(ctx):
358 '''get netbios name of the domain from the partitions record'''
359 partitions_dn = ctx.samdb.get_partitions_dn()
360 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
361 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
362 return res[0]["nETBIOSName"][0]
364 def get_forest_domain_name(ctx):
365 '''get netbios name of the domain from the partitions record'''
366 partitions_dn = ctx.samdb.get_partitions_dn()
367 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
368 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
369 return res[0]["nETBIOSName"][0]
371 def get_parent_partition_dn(ctx):
372 '''get the parent domain partition DN from parent DNS name'''
373 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
374 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
375 (ldb.binary_encode(ctx.parent_dnsdomain),
376 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
377 return str(res[0].dn)
379 def get_naming_master(ctx):
380 '''get the parent domain partition DN from parent DNS name'''
381 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
382 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
383 if not 'fSMORoleOwner' in res[0]:
384 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
386 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0]).get_extended_component('GUID')))
388 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
390 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
394 '''get the SID of the connected user. Only works with w2k8 and later,
395 so only used for RODC join'''
396 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
397 binsid = res[0]["tokenGroups"][0]
398 return ctx.samdb.schema_format_value("objectSID", binsid)
400 def dn_exists(ctx, dn):
401 '''check if a DN exists'''
403 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
404 except ldb.LdbError, (enum, estr):
405 if enum == ldb.ERR_NO_SUCH_OBJECT:
410 def add_krbtgt_account(ctx):
411 '''RODCs need a special krbtgt account'''
412 print "Adding %s" % ctx.krbtgt_dn
414 "dn" : ctx.krbtgt_dn,
415 "objectclass" : "user",
416 "useraccountcontrol" : str(samba.dsdb.UF_NORMAL_ACCOUNT |
417 samba.dsdb.UF_ACCOUNTDISABLE),
418 "showinadvancedviewonly" : "TRUE",
419 "description" : "krbtgt for %s" % ctx.samname}
420 ctx.samdb.add(rec, ["rodc_join:1:1"])
422 # now we need to search for the samAccountName attribute on the krbtgt DN,
423 # as this will have been magically set to the krbtgt number
424 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
425 ctx.krbtgt_name = res[0]["samAccountName"][0]
427 print "Got krbtgt_name=%s" % ctx.krbtgt_name
430 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
431 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
432 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
435 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
436 print "Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn)
437 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
439 def drsuapi_connect(ctx):
440 '''make a DRSUAPI connection to the naming master'''
441 binding_options = "seal"
442 if ctx.lp.log_level() >= 9:
443 binding_options += ",print"
444 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
445 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
446 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
448 def create_tmp_samdb(ctx):
449 '''create a temporary samdb object for schema queries'''
450 ctx.tmp_schema = Schema(ctx.domsid,
451 schemadn=ctx.schema_dn)
452 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
453 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
455 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
457 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
458 '''build a DsReplicaAttributeCtr object'''
459 r = drsuapi.DsReplicaAttribute()
460 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
464 def DsAddEntry(ctx, recs):
465 '''add a record via the DRSUAPI DsAddEntry call'''
466 if ctx.drsuapi is None:
467 ctx.drsuapi_connect()
468 if ctx.tmp_samdb is None:
469 ctx.create_tmp_samdb()
473 id = drsuapi.DsReplicaObjectIdentifier()
480 if not isinstance(rec[a], list):
484 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
487 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
488 attribute_ctr.num_attributes = len(attrs)
489 attribute_ctr.attributes = attrs
491 object = drsuapi.DsReplicaObject()
492 object.identifier = id
493 object.attribute_ctr = attribute_ctr
495 list_object = drsuapi.DsReplicaObjectListItem()
496 list_object.object = object
497 objects.append(list_object)
499 req2 = drsuapi.DsAddEntryRequest2()
500 req2.first_object = objects[0]
501 prev = req2.first_object
502 for o in objects[1:]:
506 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
508 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
509 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
510 raise RuntimeError("DsAddEntry failed")
511 if ctr.extended_err[0] != werror.WERR_SUCCESS:
512 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
513 raise RuntimeError("DsAddEntry failed")
516 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
517 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
518 if ctr.err_data.info is None:
519 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
521 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
522 ctr.err_data.info.extended_err))
523 raise RuntimeError("DsAddEntry failed")
524 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
525 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
526 raise RuntimeError("DsAddEntry failed")
530 def join_ntdsdsa_obj(ctx):
531 '''return the ntdsdsa object to add'''
533 print "Adding %s" % ctx.ntds_dn
536 "objectclass" : "nTDSDSA",
537 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
538 "dMDLocation" : ctx.schema_dn}
540 nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
542 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
543 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
545 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
546 rec["msDS-HasDomainNCs"] = ctx.base_dn
549 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
550 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
551 rec["options"] = "37"
553 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
554 rec["HasMasterNCs"] = []
556 if nc in ctx.full_nc_list:
557 rec["HasMasterNCs"].append(nc)
558 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
559 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
561 rec["invocationId"] = ndr_pack(ctx.invocation_id)
565 def join_add_ntdsdsa(ctx):
566 '''add the ntdsdsa object'''
568 rec = ctx.join_ntdsdsa_obj()
570 ctx.samdb.add(rec, ["rodc_join:1:1"])
572 ctx.DsAddEntry([rec])
574 # find the GUID of our NTDS DN
575 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
576 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
578 def join_add_objects(ctx):
579 '''add the various objects needed for the join'''
581 print "Adding %s" % ctx.acct_dn
584 "objectClass": "computer",
585 "displayname": ctx.samname,
586 "samaccountname" : ctx.samname,
587 "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
588 "dnshostname" : ctx.dnshostname}
589 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
590 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
591 elif ctx.promote_existing:
592 rec['msDS-SupportedEncryptionTypes'] = []
594 rec["managedby"] = ctx.managedby
595 elif ctx.promote_existing:
596 rec["managedby"] = []
598 if ctx.never_reveal_sid:
599 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
600 elif ctx.promote_existing:
601 rec["msDS-NeverRevealGroup"] = []
604 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
605 elif ctx.promote_existing:
606 rec["msDS-RevealOnDemandGroup"] = []
608 if ctx.promote_existing:
609 if ctx.promote_from_dn != ctx.acct_dn:
610 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
611 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
616 ctx.add_krbtgt_account()
619 print "Adding %s" % ctx.server_dn
622 "objectclass" : "server",
623 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
624 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
625 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
626 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
627 # windows seems to add the dnsHostName later
628 "dnsHostName" : ctx.dnshostname}
631 rec["serverReference"] = ctx.acct_dn
636 # the rest is done after replication
641 ctx.join_add_ntdsdsa()
643 # Add the Replica-Locations or RO-Replica-Locations attributes
644 # TODO Is this supposed to be for the schema partition too?
645 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
646 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
648 base=ctx.samdb.get_partitions_dn(),
649 expression=expr), ctx.domaindns_zone)
651 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
652 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
654 base=ctx.samdb.get_partitions_dn(),
655 expression=expr), ctx.forestdns_zone)
657 for part, zone in (domain, forest):
658 if zone not in ctx.nc_list:
664 attr = "msDS-NC-Replica-Locations"
666 attr = "msDS-NC-RO-Replica-Locations"
668 m[attr] = ldb.MessageElement(ctx.ntds_dn,
669 ldb.FLAG_MOD_ADD, attr)
672 if ctx.connection_dn is not None:
673 print "Adding %s" % ctx.connection_dn
675 "dn" : ctx.connection_dn,
676 "objectclass" : "nTDSConnection",
677 "enabledconnection" : "TRUE",
679 "fromServer" : ctx.dc_ntds_dn}
683 print "Adding SPNs to %s" % ctx.acct_dn
685 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
686 for i in range(len(ctx.SPNs)):
687 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
688 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
689 ldb.FLAG_MOD_REPLACE,
690 "servicePrincipalName")
693 # The account password set operation should normally be done over
694 # LDAP. Windows 2000 DCs however allow this only with SSL
695 # connections which are hard to set up and otherwise refuse with
696 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
698 print "Setting account password for %s" % ctx.samname
700 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
701 % ldb.binary_encode(ctx.samname),
703 force_change_at_next_login=False,
704 username=ctx.samname)
705 except ldb.LdbError, (num, _):
706 if num != ldb.ERR_UNWILLING_TO_PERFORM:
708 ctx.net.set_password(account_name=ctx.samname,
709 domain_name=ctx.domain_name,
710 newpassword=ctx.acct_pass.encode('utf-8'))
712 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
713 attrs=["msDS-KeyVersionNumber",
715 if "msDS-KeyVersionNumber" in res[0]:
716 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
718 ctx.key_version_number = None
720 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
721 res[0]["objectSid"][0])
723 print("Enabling account")
725 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
726 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
727 ldb.FLAG_MOD_REPLACE,
728 "userAccountControl")
731 if ctx.dns_backend.startswith("BIND9_"):
732 ctx.dnspass = samba.generate_random_password(128, 255)
734 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
735 {"DNSDOMAIN": ctx.dnsdomain,
736 "DOMAINDN": ctx.base_dn,
737 "HOSTNAME" : ctx.myname,
738 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')),
739 "DNSNAME" : ctx.dnshostname}))
740 for changetype, msg in recs:
741 assert changetype == ldb.CHANGETYPE_NONE
742 dns_acct_dn = msg["dn"]
743 print "Adding DNS account %s with dns/ SPN" % msg["dn"]
745 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
746 del msg["clearTextPassword"]
747 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
748 del msg["isCriticalSystemObject"]
749 # Disable account until password is set
750 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
751 samba.dsdb.UF_ACCOUNTDISABLE)
754 except ldb.LdbError, (num, _):
755 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
758 # The account password set operation should normally be done over
759 # LDAP. Windows 2000 DCs however allow this only with SSL
760 # connections which are hard to set up and otherwise refuse with
761 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
763 print "Setting account password for dns-%s" % ctx.myname
765 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
766 % ldb.binary_encode(ctx.myname),
768 force_change_at_next_login=False,
769 username=ctx.samname)
770 except ldb.LdbError, (num, _):
771 if num != ldb.ERR_UNWILLING_TO_PERFORM:
773 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
774 domain_name=ctx.domain_name,
775 newpassword=ctx.dnspass)
777 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
778 attrs=["msDS-KeyVersionNumber"])
779 if "msDS-KeyVersionNumber" in res[0]:
780 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
782 ctx.dns_key_version_number = None
784 def join_add_objects2(ctx):
785 """add the various objects needed for the join, for subdomains post replication"""
787 print "Adding %s" % ctx.partition_dn
788 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
789 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
791 "dn" : ctx.partition_dn,
792 "objectclass" : "crossRef",
793 "objectCategory" : "CN=Cross-Ref,%s" % ctx.schema_dn,
794 "nCName" : ctx.base_dn,
795 "nETBIOSName" : ctx.domain_name,
796 "dnsRoot": ctx.dnsdomain,
797 "trustParent" : ctx.parent_partition_dn,
798 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
799 "ntSecurityDescriptor" : sd_binary,
802 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
803 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
805 rec2 = ctx.join_ntdsdsa_obj()
807 objects = ctx.DsAddEntry([rec, rec2])
808 if len(objects) != 2:
809 raise DCJoinException("Expected 2 objects from DsAddEntry")
811 ctx.ntds_guid = objects[1].guid
813 print("Replicating partition DN")
814 ctx.repl.replicate(ctx.partition_dn,
815 misc.GUID("00000000-0000-0000-0000-000000000000"),
817 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
818 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
820 print("Replicating NTDS DN")
821 ctx.repl.replicate(ctx.ntds_dn,
822 misc.GUID("00000000-0000-0000-0000-000000000000"),
824 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
825 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
827 def join_provision(ctx):
828 """Provision the local SAM."""
830 print "Calling bare provision"
832 smbconf = ctx.lp.configfile
834 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
835 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
836 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
837 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
838 serverdn=ctx.server_dn, domain=ctx.domain_name,
839 hostname=ctx.myname, domainsid=ctx.domsid,
840 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
841 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
842 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
843 plaintext_secrets=ctx.plaintext_secrets)
844 print "Provision OK for domain DN %s" % presult.domaindn
845 ctx.local_samdb = presult.samdb
847 ctx.paths = presult.paths
848 ctx.names = presult.names
850 # Fix up the forestsid, it may be different if we are joining as a subdomain
851 ctx.names.forestsid = ctx.forestsid
853 def join_provision_own_domain(ctx):
854 """Provision the local SAM."""
856 # we now operate exclusively on the local database, which
857 # we need to reopen in order to get the newly created schema
858 print("Reconnecting to local samdb")
859 ctx.samdb = SamDB(url=ctx.local_samdb.url,
860 session_info=system_session(),
861 lp=ctx.local_samdb.lp,
863 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
864 ctx.local_samdb = ctx.samdb
866 ctx.logger.info("Finding domain GUID from ncName")
867 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
868 controls=["extended_dn:1:1", "reveal_internals:0"])
870 if 'nCName' not in res[0]:
871 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
874 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID')))
876 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
878 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
880 ctx.logger.info("Calling own domain provision")
882 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
884 presult = provision_fill(ctx.local_samdb, secrets_ldb,
885 ctx.logger, ctx.names, ctx.paths,
886 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
887 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
888 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
889 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
890 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
891 print("Provision OK for domain %s" % ctx.names.dnsdomain)
893 def join_replicate(ctx):
894 """Replicate the SAM."""
896 print "Starting replication"
897 ctx.local_samdb.transaction_start()
899 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
900 if ctx.ntds_guid is None:
901 print("Using DS_BIND_GUID_W2K3")
902 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
904 destination_dsa_guid = ctx.ntds_guid
907 repl_creds = Credentials()
908 repl_creds.guess(ctx.lp)
909 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
910 repl_creds.set_username(ctx.samname)
911 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
913 repl_creds = ctx.creds
915 binding_options = "seal"
916 if ctx.lp.log_level() >= 9:
917 binding_options += ",print"
918 repl = drs_utils.drs_Replicate(
919 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
920 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
922 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
923 destination_dsa_guid, schema=True, rodc=ctx.RODC,
924 replica_flags=ctx.replica_flags)
925 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
926 destination_dsa_guid, rodc=ctx.RODC,
927 replica_flags=ctx.replica_flags)
928 if not ctx.subdomain:
929 # Replicate first the critical object for the basedn
930 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
931 print "Replicating critical objects from the base DN of the domain"
932 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
933 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
934 destination_dsa_guid, rodc=ctx.RODC,
935 replica_flags=ctx.domain_replica_flags)
936 ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
937 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
938 destination_dsa_guid, rodc=ctx.RODC,
939 replica_flags=ctx.domain_replica_flags)
940 print "Done with always replicated NC (base, config, schema)"
942 # At this point we should already have an entry in the ForestDNS
943 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
944 # indicate that we hold a replica for this NC.
945 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
946 if nc in ctx.nc_list:
947 print "Replicating %s" % (str(nc))
948 repl.replicate(nc, source_dsa_invocation_id,
949 destination_dsa_guid, rodc=ctx.RODC,
950 replica_flags=ctx.replica_flags)
953 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
954 destination_dsa_guid,
955 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
956 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
957 destination_dsa_guid,
958 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
959 elif ctx.rid_manager_dn != None:
960 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
962 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
963 destination_dsa_guid,
964 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
965 except samba.DsExtendedError, (enum, estr):
966 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
967 print "WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server
968 print "NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup."
973 ctx.source_dsa_invocation_id = source_dsa_invocation_id
974 ctx.destination_dsa_guid = destination_dsa_guid
976 print "Committing SAM database"
978 ctx.local_samdb.transaction_cancel()
981 ctx.local_samdb.transaction_commit()
983 def send_DsReplicaUpdateRefs(ctx, dn):
984 r = drsuapi.DsReplicaUpdateRefsRequest1()
985 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
986 r.naming_context.dn = str(dn)
987 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
988 r.naming_context.sid = security.dom_sid("S-0-0")
989 r.dest_dsa_guid = ctx.ntds_guid
990 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
991 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
993 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
995 if ctx.drsuapi is None:
996 ctx.drsuapi_connect()
998 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1000 def join_add_dns_records(ctx):
1001 """Remotely Add a DNS record to the target DC. We assume that if we
1002 replicate DNS that the server holds the DNS roles and can accept
1005 This avoids issues getting replication going after the DC
1006 first starts as the rest of the domain does not have to
1007 wait for samba_dnsupdate to run successfully.
1009 Specifically, we add the records implied by the DsReplicaUpdateRefs
1012 We do not just run samba_dnsupdate as we want to strictly
1013 operate against the DC we just joined:
1014 - We do not want to query another DNS server
1015 - We do not want to obtain a Kerberos ticket
1016 (as the KDC we select may not be the DC we just joined,
1017 and so may not be in sync with the password we just set)
1018 - We do not wish to set the _ldap records until we have started
1019 - We do not wish to use NTLM (the --use-samba-tool mode forces
1024 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1025 record_type = dnsp.DNS_TYPE_A
1026 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1027 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1029 zone = ctx.dnsdomain
1030 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1032 msdcs_cname = str(ctx.ntds_guid)
1033 cname_target = "%s.%s" % (name, zone)
1034 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1036 ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
1037 (len(IPs), name, zone))
1039 binding_options = "sign"
1040 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1046 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1048 change_owner_sd = security.descriptor()
1049 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1050 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1052 security.DOMAIN_RID_DCS))
1054 # TODO: Remove any old records from the primary DNS name
1057 = dns_conn.DnssrvEnumRecords2(client_version,
1067 except WERRORError as e:
1068 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1074 for record in rec.records:
1075 if record.wType == dnsp.DNS_TYPE_A or \
1076 record.wType == dnsp.DNS_TYPE_AAAA:
1078 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1079 del_rec_buf.rec = record
1081 dns_conn.DnssrvUpdateRecord2(client_version,
1088 except WERRORError as e:
1089 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1095 if IP.find(':') != -1:
1096 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1098 rec = AAAARecord(IP)
1100 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1105 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1106 add_rec_buf.rec = rec
1107 dns_conn.DnssrvUpdateRecord2(client_version,
1116 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1117 (ctx.dns_a_dn, ldap_record) \
1118 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1119 dns_partition=domaindns_zone_dn)
1121 # Make the DC own the DNS record, not the administrator
1122 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1123 controls=["sd_flags:1:%d"
1124 % (security.SECINFO_OWNER
1125 | security.SECINFO_GROUP)])
1129 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1130 % (msdcs_cname, msdcs_zone, cname_target))
1132 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1133 rec = CNameRecord(cname_target)
1134 add_rec_buf.rec = rec
1135 dns_conn.DnssrvUpdateRecord2(client_version,
1143 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1144 (ctx.dns_cname_dn, ldap_record) \
1145 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1146 dns_partition=forestdns_zone_dn)
1148 # Make the DC own the DNS record, not the administrator
1149 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1150 controls=["sd_flags:1:%d"
1151 % (security.SECINFO_OWNER
1152 | security.SECINFO_GROUP)])
1154 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1155 "will be created samba_dnsupdate on first startup")
1158 def join_replicate_new_dns_records(ctx):
1159 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1160 if nc in ctx.nc_list:
1161 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1162 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1163 ctx.ntds_guid, rodc=ctx.RODC,
1164 replica_flags=ctx.replica_flags,
1169 def join_finalise(ctx):
1170 """Finalise the join, mark us synchronised and setup secrets db."""
1172 # FIXME we shouldn't do this in all cases
1174 # If for some reasons we joined in another site than the one of
1175 # DC we just replicated from then we don't need to send the updatereplicateref
1176 # as replication between sites is time based and on the initiative of the
1178 if not ctx.clone_only:
1179 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1180 for nc in ctx.nc_list:
1181 ctx.send_DsReplicaUpdateRefs(nc)
1183 if not ctx.clone_only and ctx.RODC:
1184 print "Setting RODC invocationId"
1185 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1186 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1187 ctx.behavior_version)
1189 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1190 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1191 ldb.FLAG_MOD_REPLACE,
1193 ctx.local_samdb.modify(m)
1195 # Note: as RODC the invocationId is only stored
1196 # on the RODC itself, the other DCs never see it.
1198 # Thats is why we fix up the replPropertyMetaData stamp
1199 # for the 'invocationId' attribute, we need to change
1200 # the 'version' to '0', this is what windows 2008r2 does as RODC
1202 # This means if the object on a RWDC ever gets a invocationId
1203 # attribute, it will have version '1' (or higher), which will
1204 # will overwrite the RODC local value.
1205 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1209 ctx.logger.info("Setting isSynchronized and dsServiceName")
1211 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1212 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1214 # We want to appear to be the server we just cloned
1216 guid = ctx.remote_dc_ntds_guid
1218 guid = ctx.ntds_guid
1220 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1221 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1222 ctx.local_samdb.modify(m)
1224 if ctx.clone_only or ctx.subdomain:
1227 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1229 ctx.logger.info("Setting up secrets database")
1230 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1232 dnsdomain=ctx.dnsdomain,
1233 netbiosname=ctx.myname,
1234 domainsid=ctx.domsid,
1235 machinepass=ctx.acct_pass,
1236 secure_channel_type=ctx.secure_channel_type,
1237 key_version_number=ctx.key_version_number)
1239 if ctx.dns_backend.startswith("BIND9_"):
1240 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1241 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1242 dns_backend=ctx.dns_backend,
1243 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1244 targetdir=ctx.targetdir,
1245 key_version_number=ctx.dns_key_version_number)
1247 def join_setup_trusts(ctx):
1248 """provision the local SAM."""
1250 print "Setup domain trusts with server %s" % ctx.server
1251 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1252 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1255 objectAttr = lsa.ObjectAttribute()
1256 objectAttr.sec_qos = lsa.QosInfo()
1258 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1259 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1261 info = lsa.TrustDomainInfoInfoEx()
1262 info.domain_name.string = ctx.dnsdomain
1263 info.netbios_name.string = ctx.domain_name
1264 info.sid = ctx.domsid
1265 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1266 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1267 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1270 oldname = lsa.String()
1271 oldname.string = ctx.dnsdomain
1272 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1273 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1274 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1275 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1276 except RuntimeError:
1279 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1281 clear_value = drsblobs.AuthInfoClear()
1282 clear_value.size = len(password_blob)
1283 clear_value.password = password_blob
1285 clear_authentication_information = drsblobs.AuthenticationInformation()
1286 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1287 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1288 clear_authentication_information.AuthInfo = clear_value
1290 authentication_information_array = drsblobs.AuthenticationInformationArray()
1291 authentication_information_array.count = 1
1292 authentication_information_array.array = [clear_authentication_information]
1294 outgoing = drsblobs.trustAuthInOutBlob()
1296 outgoing.current = authentication_information_array
1298 trustpass = drsblobs.trustDomainPasswords()
1299 confounder = [3] * 512
1301 for i in range(512):
1302 confounder[i] = random.randint(0, 255)
1304 trustpass.confounder = confounder
1306 trustpass.outgoing = outgoing
1307 trustpass.incoming = outgoing
1309 trustpass_blob = ndr_pack(trustpass)
1311 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1313 auth_blob = lsa.DATA_BUF2()
1314 auth_blob.size = len(encrypted_trustpass)
1315 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1317 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1318 auth_info.auth_blob = auth_blob
1320 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1323 security.SEC_STD_DELETE)
1326 "dn" : "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1327 "objectclass" : "trustedDomain",
1328 "trustType" : str(info.trust_type),
1329 "trustAttributes" : str(info.trust_attributes),
1330 "trustDirection" : str(info.trust_direction),
1331 "flatname" : ctx.forest_domain_name,
1332 "trustPartner" : ctx.dnsforest,
1333 "trustAuthIncoming" : ndr_pack(outgoing),
1334 "trustAuthOutgoing" : ndr_pack(outgoing),
1335 "securityIdentifier" : ndr_pack(ctx.forestsid)
1337 ctx.local_samdb.add(rec)
1340 "dn" : "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1341 "objectclass" : "user",
1342 "userAccountControl" : str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1343 "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le'),
1344 "samAccountName" : "%s$" % ctx.forest_domain_name
1346 ctx.local_samdb.add(rec)
1350 # nc_list is the list of naming context (NC) for which we will
1351 # replicate in and send a updateRef command to the partner DC
1353 # full_nc_list is the list of naming context (NC) we hold
1354 # read/write copies of. These are not subsets of each other.
1355 ctx.nc_list = [ ctx.config_dn, ctx.schema_dn ]
1356 ctx.full_nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
1358 if ctx.subdomain and ctx.dns_backend != "NONE":
1359 ctx.full_nc_list += [ctx.domaindns_zone]
1361 elif not ctx.subdomain:
1362 ctx.nc_list += [ctx.base_dn]
1364 if ctx.dns_backend != "NONE":
1365 ctx.nc_list += [ctx.domaindns_zone]
1366 ctx.nc_list += [ctx.forestdns_zone]
1367 ctx.full_nc_list += [ctx.domaindns_zone]
1368 ctx.full_nc_list += [ctx.forestdns_zone]
1370 if not ctx.clone_only:
1371 if ctx.promote_existing:
1372 ctx.promote_possible()
1374 ctx.cleanup_old_join()
1377 if not ctx.clone_only:
1378 ctx.join_add_objects()
1379 ctx.join_provision()
1380 ctx.join_replicate()
1381 if (not ctx.clone_only and ctx.subdomain):
1382 ctx.join_add_objects2()
1383 ctx.join_provision_own_domain()
1384 ctx.join_setup_trusts()
1386 if not ctx.clone_only and ctx.dns_backend != "NONE":
1387 ctx.join_add_dns_records()
1388 ctx.join_replicate_new_dns_records()
1393 print "Join failed - cleaning up"
1396 if not ctx.clone_only:
1397 ctx.cleanup_old_join()
1401 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1402 targetdir=None, domain=None, domain_critical_only=False,
1403 machinepass=None, use_ntvfs=False, dns_backend=None,
1404 promote_existing=False, plaintext_secrets=False):
1405 """Join as a RODC."""
1407 ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
1408 machinepass, use_ntvfs, dns_backend, promote_existing,
1411 lp.set("workgroup", ctx.domain_name)
1412 logger.info("workgroup is %s" % ctx.domain_name)
1414 lp.set("realm", ctx.realm)
1415 logger.info("realm is %s" % ctx.realm)
1417 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1419 # setup some defaults for accounts that should be replicated to this RODC
1420 ctx.never_reveal_sid = [
1421 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1422 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1423 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1424 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1425 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1426 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1428 mysid = ctx.get_mysid()
1429 admin_dn = "<SID=%s>" % mysid
1430 ctx.managedby = admin_dn
1432 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1433 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1434 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1436 ctx.SPNs.extend([ "RestrictedKrbHost/%s" % ctx.myname,
1437 "RestrictedKrbHost/%s" % ctx.dnshostname ])
1439 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1440 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1442 ctx.replica_flags |= ( drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1443 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1444 ctx.domain_replica_flags = ctx.replica_flags
1445 if domain_critical_only:
1446 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1450 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1453 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1454 targetdir=None, domain=None, domain_critical_only=False,
1455 machinepass=None, use_ntvfs=False, dns_backend=None,
1456 promote_existing=False, plaintext_secrets=False):
1458 ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
1459 machinepass, use_ntvfs, dns_backend, promote_existing,
1462 lp.set("workgroup", ctx.domain_name)
1463 logger.info("workgroup is %s" % ctx.domain_name)
1465 lp.set("realm", ctx.realm)
1466 logger.info("realm is %s" % ctx.realm)
1468 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1470 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1471 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1473 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1474 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1475 ctx.domain_replica_flags = ctx.replica_flags
1476 if domain_critical_only:
1477 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1480 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1482 def join_clone(logger=None, server=None, creds=None, lp=None,
1483 targetdir=None, domain=None, include_secrets=False):
1485 ctx = dc_join(logger, server, creds, lp, site=None, netbios_name=None, targetdir=targetdir, domain=domain,
1486 machinepass=None, use_ntvfs=False, dns_backend="NONE", promote_existing=False, clone_only=True)
1488 lp.set("workgroup", ctx.domain_name)
1489 logger.info("workgroup is %s" % ctx.domain_name)
1491 lp.set("realm", ctx.realm)
1492 logger.info("realm is %s" % ctx.realm)
1494 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1495 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1496 if not include_secrets:
1497 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1498 ctx.domain_replica_flags = ctx.replica_flags
1501 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1503 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1504 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1505 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1506 dns_backend=None, plaintext_secrets=False):
1508 ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, parent_domain,
1509 machinepass, use_ntvfs, dns_backend, plaintext_secrets)
1510 ctx.subdomain = True
1511 if adminpass is None:
1512 ctx.adminpass = samba.generate_random_password(12, 32)
1514 ctx.adminpass = adminpass
1515 ctx.parent_domain_name = ctx.domain_name
1516 ctx.domain_name = netbios_domain
1517 ctx.realm = dnsdomain
1518 ctx.parent_dnsdomain = ctx.dnsdomain
1519 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1520 ctx.dnsdomain = dnsdomain
1521 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1522 ctx.naming_master = ctx.get_naming_master()
1523 if ctx.naming_master != ctx.server:
1524 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1525 ctx.server = ctx.naming_master
1526 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1527 session_info=system_session(),
1528 credentials=ctx.creds, lp=ctx.lp)
1529 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1531 ctx.server = res[0]["dnsHostName"]
1532 logger.info("DNS name of new naming master is %s" % ctx.server)
1534 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1535 ctx.forestsid = ctx.domsid
1536 ctx.domsid = security.random_sid()
1538 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1539 # Windows uses 240 bytes as UTF16 so we do
1540 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1542 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1544 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1545 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1547 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1548 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1549 ctx.domain_replica_flags = ctx.replica_flags
1552 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))