VERSION: Disable GIT_SNAPSHOT for the 4.9.0rc5 release.
[Samba.git] / python / samba / join.py
blob3b648f5009047a45f87957989749fb5d7af5620c
1 # python join code
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 from __future__ import print_function
20 """Joining a domain."""
22 from samba.auth import system_session
23 from samba.samdb import SamDB
24 from samba import gensec, Ldb, drs_utils, arcfour_encrypt, string_to_byte_array
25 import ldb, samba, sys, uuid
26 from samba.ndr import ndr_pack, ndr_unpack
27 from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp
28 from samba.dsdb import DS_DOMAIN_FUNCTION_2003
29 from samba.credentials import Credentials, DONT_USE_KERBEROS
30 from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN
31 from samba.provision.common import setup_path
32 from samba.schema import Schema
33 from samba import descriptor
34 from samba.net import Net
35 from samba.provision.sambadns import setup_bind9_dns
36 from samba import read_and_sub_file
37 from samba import werror
38 from base64 import b64encode
39 from samba import WERRORError, NTSTATUSError
40 from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord
41 from samba import sd_utils
42 import logging
43 import talloc
44 import random
45 import time
46 import re
47 import os
48 import tempfile
50 class DCJoinException(Exception):
52 def __init__(self, msg):
53 super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
56 class DCJoinContext(object):
57 """Perform a DC join."""
59 def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
60 netbios_name=None, targetdir=None, domain=None,
61 machinepass=None, use_ntvfs=False, dns_backend=None,
62 promote_existing=False, plaintext_secrets=False,
63 backend_store=None, forced_local_samdb=None):
64 if site is None:
65 site = "Default-First-Site-Name"
67 ctx.logger = logger
68 ctx.creds = creds
69 ctx.lp = lp
70 ctx.site = site
71 ctx.targetdir = targetdir
72 ctx.use_ntvfs = use_ntvfs
73 ctx.plaintext_secrets = plaintext_secrets
74 ctx.backend_store = backend_store
76 ctx.promote_existing = promote_existing
77 ctx.promote_from_dn = None
79 ctx.nc_list = []
80 ctx.full_nc_list = []
82 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
83 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
85 ctx.server = server
86 ctx.forced_local_samdb = forced_local_samdb
88 if forced_local_samdb:
89 ctx.samdb = forced_local_samdb
90 ctx.server = ctx.samdb.url
91 else:
92 if not ctx.server:
93 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
94 ctx.server = ctx.find_dc(domain)
95 ctx.logger.info("Found DC %s" % ctx.server)
96 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
97 session_info=system_session(),
98 credentials=ctx.creds, lp=ctx.lp)
100 try:
101 ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
102 except ldb.LdbError as e4:
103 (enum, estr) = e4.args
104 raise DCJoinException(estr)
107 ctx.base_dn = str(ctx.samdb.get_default_basedn())
108 ctx.root_dn = str(ctx.samdb.get_root_basedn())
109 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
110 ctx.config_dn = str(ctx.samdb.get_config_basedn())
111 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
112 ctx.forestsid = ctx.domsid
113 ctx.domain_name = ctx.get_domain_name()
114 ctx.forest_domain_name = ctx.get_forest_domain_name()
115 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
117 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
118 ctx.dc_dnsHostName = ctx.get_dnsHostName()
119 ctx.behavior_version = ctx.get_behavior_version()
121 if machinepass is not None:
122 ctx.acct_pass = machinepass
123 else:
124 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
126 ctx.dnsdomain = ctx.samdb.domain_dns_name()
128 # the following are all dependent on the new DC's netbios_name (which
129 # we expect to always be specified, except when cloning a DC)
130 if netbios_name:
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)
143 else:
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"],
152 base=ctx.base_dn)
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,
161 attrs=[],
162 base=ctx.samdb.get_partitions_dn(),
163 expression=expr)
164 if dns_backend is None:
165 ctx.dns_backend = "NONE"
166 else:
167 if len(res_domaindns) == 0:
168 ctx.dns_backend = "NONE"
169 print("NO DNS zone information found in source domain, not replicating DNS")
170 else:
171 ctx.dns_backend = dns_backend
173 ctx.realm = ctx.dnsdomain
175 ctx.tmp_samdb = None
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
187 ctx.RODC = False
188 ctx.krbtgt_dn = None
189 ctx.drsuapi = None
190 ctx.managedby = None
191 ctx.subdomain = False
192 ctx.adminpass = None
193 ctx.partition_dn = None
195 ctx.dns_a_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):
202 if recursive:
203 try:
204 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
205 except Exception:
206 return
207 for r in res:
208 ctx.del_noerror(r.dn, recursive=True)
209 try:
210 ctx.samdb.delete(dn)
211 print("Deleted %s" % dn)
212 except Exception:
213 pass
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"])
219 if len(res) == 0:
220 return
222 if not force:
223 creds = Credentials()
224 creds.guess(ctx.lp)
225 try:
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)
231 except:
232 pass
233 else:
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 "matching the password we already have. "
240 "To override, remove secrets.ldb and secrets.tdb"
241 % ctx.samname)
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)),
253 attrs=[])
254 if res:
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),
259 attrs=[])
260 if res:
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)
280 if ctx.topology_dn:
281 ctx.del_noerror(ctx.topology_dn)
282 if ctx.partition_dn:
283 ctx.del_noerror(ctx.partition_dn)
285 if ctx.subdomain:
286 binding_options = "sign"
287 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
288 ctx.lp, ctx.creds)
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)
296 name = lsa.String()
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)
302 name = lsa.String()
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)
308 if ctx.dns_a_dn:
309 ctx.del_noerror(ctx.dns_a_dn)
311 if ctx.dns_cname_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"""
318 if ctx.subdomain:
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"])
325 if len(res) == 0:
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"""
337 try:
338 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
339 except NTSTATUSError as error:
340 raise Exception("Failed to find a writeable DC for domain '%s': %s" %
341 (domain, error[1]))
342 except Exception:
343 raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
344 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
345 ctx.site = ctx.cldap_ret.client_site
346 return ctx.cldap_ret.pdc_dns_name
349 def get_behavior_version(ctx):
350 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
351 if "msDS-Behavior-Version" in res[0]:
352 return int(res[0]["msDS-Behavior-Version"][0])
353 else:
354 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
356 def get_dnsHostName(ctx):
357 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
358 return res[0]["dnsHostName"][0]
360 def get_domain_name(ctx):
361 '''get netbios name of the domain from the partitions record'''
362 partitions_dn = ctx.samdb.get_partitions_dn()
363 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
364 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
365 return res[0]["nETBIOSName"][0]
367 def get_forest_domain_name(ctx):
368 '''get netbios name of the domain from the partitions record'''
369 partitions_dn = ctx.samdb.get_partitions_dn()
370 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
371 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
372 return res[0]["nETBIOSName"][0]
374 def get_parent_partition_dn(ctx):
375 '''get the parent domain partition DN from parent DNS name'''
376 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
377 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
378 (ldb.binary_encode(ctx.parent_dnsdomain),
379 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
380 return str(res[0].dn)
382 def get_naming_master(ctx):
383 '''get the parent domain partition DN from parent DNS name'''
384 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
385 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
386 if not 'fSMORoleOwner' in res[0]:
387 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
388 try:
389 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
390 except KeyError:
391 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
393 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
394 return master_host
396 def get_mysid(ctx):
397 '''get the SID of the connected user. Only works with w2k8 and later,
398 so only used for RODC join'''
399 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
400 binsid = res[0]["tokenGroups"][0]
401 return ctx.samdb.schema_format_value("objectSID", binsid)
403 def dn_exists(ctx, dn):
404 '''check if a DN exists'''
405 try:
406 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
407 except ldb.LdbError as e5:
408 (enum, estr) = e5.args
409 if enum == ldb.ERR_NO_SUCH_OBJECT:
410 return False
411 raise
412 return True
414 def add_krbtgt_account(ctx):
415 '''RODCs need a special krbtgt account'''
416 print("Adding %s" % ctx.krbtgt_dn)
417 rec = {
418 "dn" : ctx.krbtgt_dn,
419 "objectclass" : "user",
420 "useraccountcontrol" : str(samba.dsdb.UF_NORMAL_ACCOUNT |
421 samba.dsdb.UF_ACCOUNTDISABLE),
422 "showinadvancedviewonly" : "TRUE",
423 "description" : "krbtgt for %s" % ctx.samname}
424 ctx.samdb.add(rec, ["rodc_join:1:1"])
426 # now we need to search for the samAccountName attribute on the krbtgt DN,
427 # as this will have been magically set to the krbtgt number
428 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
429 ctx.krbtgt_name = res[0]["samAccountName"][0]
431 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
433 m = ldb.Message()
434 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
435 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
436 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
437 ctx.samdb.modify(m)
439 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
440 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
441 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
443 def drsuapi_connect(ctx):
444 '''make a DRSUAPI connection to the naming master'''
445 binding_options = "seal"
446 if ctx.lp.log_level() >= 9:
447 binding_options += ",print"
448 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
449 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
450 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
452 def create_tmp_samdb(ctx):
453 '''create a temporary samdb object for schema queries'''
454 ctx.tmp_schema = Schema(ctx.domsid,
455 schemadn=ctx.schema_dn)
456 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
457 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
458 am_rodc=False)
459 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
461 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
462 '''build a DsReplicaAttributeCtr object'''
463 r = drsuapi.DsReplicaAttribute()
464 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
465 r.value_ctr = 1
468 def DsAddEntry(ctx, recs):
469 '''add a record via the DRSUAPI DsAddEntry call'''
470 if ctx.drsuapi is None:
471 ctx.drsuapi_connect()
472 if ctx.tmp_samdb is None:
473 ctx.create_tmp_samdb()
475 objects = []
476 for rec in recs:
477 id = drsuapi.DsReplicaObjectIdentifier()
478 id.dn = rec['dn']
480 attrs = []
481 for a in rec:
482 if a == 'dn':
483 continue
484 if not isinstance(rec[a], list):
485 v = [rec[a]]
486 else:
487 v = rec[a]
488 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
489 attrs.append(rattr)
491 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
492 attribute_ctr.num_attributes = len(attrs)
493 attribute_ctr.attributes = attrs
495 object = drsuapi.DsReplicaObject()
496 object.identifier = id
497 object.attribute_ctr = attribute_ctr
499 list_object = drsuapi.DsReplicaObjectListItem()
500 list_object.object = object
501 objects.append(list_object)
503 req2 = drsuapi.DsAddEntryRequest2()
504 req2.first_object = objects[0]
505 prev = req2.first_object
506 for o in objects[1:]:
507 prev.next_object = o
508 prev = o
510 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
511 if level == 2:
512 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
513 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
514 raise RuntimeError("DsAddEntry failed")
515 if ctr.extended_err[0] != werror.WERR_SUCCESS:
516 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
517 raise RuntimeError("DsAddEntry failed")
518 if level == 3:
519 if ctr.err_ver != 1:
520 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
521 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
522 if ctr.err_data.info is None:
523 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
524 else:
525 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
526 ctr.err_data.info.extended_err))
527 raise RuntimeError("DsAddEntry failed")
528 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
529 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
530 raise RuntimeError("DsAddEntry failed")
532 return ctr.objects
534 def join_ntdsdsa_obj(ctx):
535 '''return the ntdsdsa object to add'''
537 print("Adding %s" % ctx.ntds_dn)
538 rec = {
539 "dn" : ctx.ntds_dn,
540 "objectclass" : "nTDSDSA",
541 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
542 "dMDLocation" : ctx.schema_dn}
544 nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
546 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
547 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
549 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
550 rec["msDS-HasDomainNCs"] = ctx.base_dn
552 if ctx.RODC:
553 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
554 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
555 rec["options"] = "37"
556 else:
557 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
558 rec["HasMasterNCs"] = []
559 for nc in nc_list:
560 if nc in ctx.full_nc_list:
561 rec["HasMasterNCs"].append(nc)
562 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
563 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
564 rec["options"] = "1"
565 rec["invocationId"] = ndr_pack(ctx.invocation_id)
567 return rec
569 def join_add_ntdsdsa(ctx):
570 '''add the ntdsdsa object'''
572 rec = ctx.join_ntdsdsa_obj()
573 if ctx.forced_local_samdb:
574 ctx.samdb.add(rec, controls=["relax:0"])
575 elif ctx.RODC:
576 ctx.samdb.add(rec, ["rodc_join:1:1"])
577 else:
578 ctx.DsAddEntry([rec])
580 # find the GUID of our NTDS DN
581 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
582 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
584 def join_add_objects(ctx, specified_sid=None):
585 '''add the various objects needed for the join'''
586 if ctx.acct_dn:
587 print("Adding %s" % ctx.acct_dn)
588 rec = {
589 "dn" : ctx.acct_dn,
590 "objectClass": "computer",
591 "displayname": ctx.samname,
592 "samaccountname" : ctx.samname,
593 "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
594 "dnshostname" : ctx.dnshostname}
595 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
596 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
597 elif ctx.promote_existing:
598 rec['msDS-SupportedEncryptionTypes'] = []
599 if ctx.managedby:
600 rec["managedby"] = ctx.managedby
601 elif ctx.promote_existing:
602 rec["managedby"] = []
604 if ctx.never_reveal_sid:
605 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
606 elif ctx.promote_existing:
607 rec["msDS-NeverRevealGroup"] = []
609 if ctx.reveal_sid:
610 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
611 elif ctx.promote_existing:
612 rec["msDS-RevealOnDemandGroup"] = []
614 if specified_sid:
615 rec["objectSid"] = ndr_pack(specified_sid)
617 if ctx.promote_existing:
618 if ctx.promote_from_dn != ctx.acct_dn:
619 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
620 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
621 else:
622 controls = None
623 if specified_sid is not None:
624 controls = ["relax:0"]
625 ctx.samdb.add(rec, controls=controls)
627 if ctx.krbtgt_dn:
628 ctx.add_krbtgt_account()
630 if ctx.server_dn:
631 print("Adding %s" % ctx.server_dn)
632 rec = {
633 "dn": ctx.server_dn,
634 "objectclass" : "server",
635 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
636 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
637 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
638 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
639 # windows seems to add the dnsHostName later
640 "dnsHostName" : ctx.dnshostname}
642 if ctx.acct_dn:
643 rec["serverReference"] = ctx.acct_dn
645 ctx.samdb.add(rec)
647 if ctx.subdomain:
648 # the rest is done after replication
649 ctx.ntds_guid = None
650 return
652 if ctx.ntds_dn:
653 ctx.join_add_ntdsdsa()
655 # Add the Replica-Locations or RO-Replica-Locations attributes
656 # TODO Is this supposed to be for the schema partition too?
657 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
658 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
659 attrs=[],
660 base=ctx.samdb.get_partitions_dn(),
661 expression=expr), ctx.domaindns_zone)
663 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
664 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
665 attrs=[],
666 base=ctx.samdb.get_partitions_dn(),
667 expression=expr), ctx.forestdns_zone)
669 for part, zone in (domain, forest):
670 if zone not in ctx.nc_list:
671 continue
673 if len(part) == 1:
674 m = ldb.Message()
675 m.dn = part[0].dn
676 attr = "msDS-NC-Replica-Locations"
677 if ctx.RODC:
678 attr = "msDS-NC-RO-Replica-Locations"
680 m[attr] = ldb.MessageElement(ctx.ntds_dn,
681 ldb.FLAG_MOD_ADD, attr)
682 ctx.samdb.modify(m)
684 if ctx.connection_dn is not None:
685 print("Adding %s" % ctx.connection_dn)
686 rec = {
687 "dn" : ctx.connection_dn,
688 "objectclass" : "nTDSConnection",
689 "enabledconnection" : "TRUE",
690 "options" : "65",
691 "fromServer" : ctx.dc_ntds_dn}
692 ctx.samdb.add(rec)
694 if ctx.acct_dn:
695 print("Adding SPNs to %s" % ctx.acct_dn)
696 m = ldb.Message()
697 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
698 for i in range(len(ctx.SPNs)):
699 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
700 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
701 ldb.FLAG_MOD_REPLACE,
702 "servicePrincipalName")
703 ctx.samdb.modify(m)
705 # The account password set operation should normally be done over
706 # LDAP. Windows 2000 DCs however allow this only with SSL
707 # connections which are hard to set up and otherwise refuse with
708 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
709 # over SAMR.
710 print("Setting account password for %s" % ctx.samname)
711 try:
712 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
713 % ldb.binary_encode(ctx.samname),
714 ctx.acct_pass,
715 force_change_at_next_login=False,
716 username=ctx.samname)
717 except ldb.LdbError as e2:
718 (num, _) = e2.args
719 if num != ldb.ERR_UNWILLING_TO_PERFORM:
720 pass
721 ctx.net.set_password(account_name=ctx.samname,
722 domain_name=ctx.domain_name,
723 newpassword=ctx.acct_pass.encode('utf-8'))
725 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
726 attrs=["msDS-KeyVersionNumber",
727 "objectSID"])
728 if "msDS-KeyVersionNumber" in res[0]:
729 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
730 else:
731 ctx.key_version_number = None
733 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
734 res[0]["objectSid"][0])
736 print("Enabling account")
737 m = ldb.Message()
738 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
739 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
740 ldb.FLAG_MOD_REPLACE,
741 "userAccountControl")
742 ctx.samdb.modify(m)
744 if ctx.dns_backend.startswith("BIND9_"):
745 ctx.dnspass = samba.generate_random_password(128, 255)
747 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
748 {"DNSDOMAIN": ctx.dnsdomain,
749 "DOMAINDN": ctx.base_dn,
750 "HOSTNAME" : ctx.myname,
751 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
752 "DNSNAME" : ctx.dnshostname}))
753 for changetype, msg in recs:
754 assert changetype == ldb.CHANGETYPE_NONE
755 dns_acct_dn = msg["dn"]
756 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
758 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
759 del msg["clearTextPassword"]
760 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
761 del msg["isCriticalSystemObject"]
762 # Disable account until password is set
763 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
764 samba.dsdb.UF_ACCOUNTDISABLE)
765 try:
766 ctx.samdb.add(msg)
767 except ldb.LdbError as e:
768 (num, _) = e.args
769 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
770 raise
772 # The account password set operation should normally be done over
773 # LDAP. Windows 2000 DCs however allow this only with SSL
774 # connections which are hard to set up and otherwise refuse with
775 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
776 # over SAMR.
777 print("Setting account password for dns-%s" % ctx.myname)
778 try:
779 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
780 % ldb.binary_encode(ctx.myname),
781 ctx.dnspass,
782 force_change_at_next_login=False,
783 username=ctx.samname)
784 except ldb.LdbError as e3:
785 (num, _) = e3.args
786 if num != ldb.ERR_UNWILLING_TO_PERFORM:
787 raise
788 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
789 domain_name=ctx.domain_name,
790 newpassword=ctx.dnspass)
792 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
793 attrs=["msDS-KeyVersionNumber"])
794 if "msDS-KeyVersionNumber" in res[0]:
795 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
796 else:
797 ctx.dns_key_version_number = None
799 def join_add_objects2(ctx):
800 """add the various objects needed for the join, for subdomains post replication"""
802 print("Adding %s" % ctx.partition_dn)
803 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
804 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
805 rec = {
806 "dn" : ctx.partition_dn,
807 "objectclass" : "crossRef",
808 "objectCategory" : "CN=Cross-Ref,%s" % ctx.schema_dn,
809 "nCName" : ctx.base_dn,
810 "nETBIOSName" : ctx.domain_name,
811 "dnsRoot": ctx.dnsdomain,
812 "trustParent" : ctx.parent_partition_dn,
813 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
814 "ntSecurityDescriptor" : sd_binary,
817 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
818 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
820 rec2 = ctx.join_ntdsdsa_obj()
822 objects = ctx.DsAddEntry([rec, rec2])
823 if len(objects) != 2:
824 raise DCJoinException("Expected 2 objects from DsAddEntry")
826 ctx.ntds_guid = objects[1].guid
828 print("Replicating partition DN")
829 ctx.repl.replicate(ctx.partition_dn,
830 misc.GUID("00000000-0000-0000-0000-000000000000"),
831 ctx.ntds_guid,
832 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
833 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
835 print("Replicating NTDS DN")
836 ctx.repl.replicate(ctx.ntds_dn,
837 misc.GUID("00000000-0000-0000-0000-000000000000"),
838 ctx.ntds_guid,
839 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
840 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
842 def join_provision(ctx):
843 """Provision the local SAM."""
845 print("Calling bare provision")
847 smbconf = ctx.lp.configfile
849 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
850 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
851 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
852 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
853 serverdn=ctx.server_dn, domain=ctx.domain_name,
854 hostname=ctx.myname, domainsid=ctx.domsid,
855 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
856 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
857 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
858 plaintext_secrets=ctx.plaintext_secrets,
859 backend_store=ctx.backend_store
861 print("Provision OK for domain DN %s" % presult.domaindn)
862 ctx.local_samdb = presult.samdb
863 ctx.lp = presult.lp
864 ctx.paths = presult.paths
865 ctx.names = presult.names
867 # Fix up the forestsid, it may be different if we are joining as a subdomain
868 ctx.names.forestsid = ctx.forestsid
870 def join_provision_own_domain(ctx):
871 """Provision the local SAM."""
873 # we now operate exclusively on the local database, which
874 # we need to reopen in order to get the newly created schema
875 print("Reconnecting to local samdb")
876 ctx.samdb = SamDB(url=ctx.local_samdb.url,
877 session_info=system_session(),
878 lp=ctx.local_samdb.lp,
879 global_schema=False)
880 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
881 ctx.local_samdb = ctx.samdb
883 ctx.logger.info("Finding domain GUID from ncName")
884 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
885 controls=["extended_dn:1:1", "reveal_internals:0"])
887 if 'nCName' not in res[0]:
888 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
890 try:
891 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
892 except KeyError:
893 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
895 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
897 ctx.logger.info("Calling own domain provision")
899 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
901 presult = provision_fill(ctx.local_samdb, secrets_ldb,
902 ctx.logger, ctx.names, ctx.paths,
903 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
904 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
905 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
906 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
907 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
908 print("Provision OK for domain %s" % ctx.names.dnsdomain)
910 def create_replicator(ctx, repl_creds, binding_options):
911 '''Creates a new DRS object for managing replications'''
912 return drs_utils.drs_Replicate(
913 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
914 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
916 def join_replicate(ctx):
917 """Replicate the SAM."""
919 print("Starting replication")
920 ctx.local_samdb.transaction_start()
921 try:
922 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
923 if ctx.ntds_guid is None:
924 print("Using DS_BIND_GUID_W2K3")
925 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
926 else:
927 destination_dsa_guid = ctx.ntds_guid
929 if ctx.RODC:
930 repl_creds = Credentials()
931 repl_creds.guess(ctx.lp)
932 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
933 repl_creds.set_username(ctx.samname)
934 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
935 else:
936 repl_creds = ctx.creds
938 binding_options = "seal"
939 if ctx.lp.log_level() >= 9:
940 binding_options += ",print"
942 repl = ctx.create_replicator(repl_creds, binding_options)
944 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
945 destination_dsa_guid, schema=True, rodc=ctx.RODC,
946 replica_flags=ctx.replica_flags)
947 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
948 destination_dsa_guid, rodc=ctx.RODC,
949 replica_flags=ctx.replica_flags)
950 if not ctx.subdomain:
951 # Replicate first the critical object for the basedn
952 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
953 print("Replicating critical objects from the base DN of the domain")
954 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
955 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
956 destination_dsa_guid, rodc=ctx.RODC,
957 replica_flags=ctx.domain_replica_flags)
958 ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
959 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
960 destination_dsa_guid, rodc=ctx.RODC,
961 replica_flags=ctx.domain_replica_flags)
962 print("Done with always replicated NC (base, config, schema)")
964 # At this point we should already have an entry in the ForestDNS
965 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
966 # indicate that we hold a replica for this NC.
967 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
968 if nc in ctx.nc_list:
969 print("Replicating %s" % (str(nc)))
970 repl.replicate(nc, source_dsa_invocation_id,
971 destination_dsa_guid, rodc=ctx.RODC,
972 replica_flags=ctx.replica_flags)
974 if ctx.RODC:
975 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
976 destination_dsa_guid,
977 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
978 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
979 destination_dsa_guid,
980 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
981 elif ctx.rid_manager_dn != None:
982 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
983 try:
984 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
985 destination_dsa_guid,
986 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
987 except samba.DsExtendedError as e1:
988 (enum, estr) = e1.args
989 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
990 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
991 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
992 else:
993 raise
995 ctx.repl = repl
996 ctx.source_dsa_invocation_id = source_dsa_invocation_id
997 ctx.destination_dsa_guid = destination_dsa_guid
999 print("Committing SAM database")
1000 except:
1001 ctx.local_samdb.transaction_cancel()
1002 raise
1003 else:
1004 ctx.local_samdb.transaction_commit()
1006 def send_DsReplicaUpdateRefs(ctx, dn):
1007 r = drsuapi.DsReplicaUpdateRefsRequest1()
1008 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
1009 r.naming_context.dn = str(dn)
1010 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
1011 r.naming_context.sid = security.dom_sid("S-0-0")
1012 r.dest_dsa_guid = ctx.ntds_guid
1013 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1014 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1015 if not ctx.RODC:
1016 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1018 if ctx.drsuapi is None:
1019 ctx.drsuapi_connect()
1021 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1023 def join_add_dns_records(ctx):
1024 """Remotely Add a DNS record to the target DC. We assume that if we
1025 replicate DNS that the server holds the DNS roles and can accept
1026 updates.
1028 This avoids issues getting replication going after the DC
1029 first starts as the rest of the domain does not have to
1030 wait for samba_dnsupdate to run successfully.
1032 Specifically, we add the records implied by the DsReplicaUpdateRefs
1033 call above.
1035 We do not just run samba_dnsupdate as we want to strictly
1036 operate against the DC we just joined:
1037 - We do not want to query another DNS server
1038 - We do not want to obtain a Kerberos ticket
1039 (as the KDC we select may not be the DC we just joined,
1040 and so may not be in sync with the password we just set)
1041 - We do not wish to set the _ldap records until we have started
1042 - We do not wish to use NTLM (the --use-samba-tool mode forces
1043 NTLM)
1047 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1048 record_type = dnsp.DNS_TYPE_A
1049 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1050 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1052 zone = ctx.dnsdomain
1053 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1054 name = ctx.myname
1055 msdcs_cname = str(ctx.ntds_guid)
1056 cname_target = "%s.%s" % (name, zone)
1057 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1059 ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
1060 (len(IPs), name, zone))
1062 binding_options = "sign"
1063 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1064 ctx.lp, ctx.creds)
1067 name_found = True
1069 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1071 change_owner_sd = security.descriptor()
1072 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1073 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1074 (str(ctx.domsid),
1075 security.DOMAIN_RID_DCS))
1077 # TODO: Remove any old records from the primary DNS name
1078 try:
1079 (buflen, res) \
1080 = dns_conn.DnssrvEnumRecords2(client_version,
1082 ctx.server,
1083 zone,
1084 name,
1085 None,
1086 dnsp.DNS_TYPE_ALL,
1087 select_flags,
1088 None,
1089 None)
1090 except WERRORError as e:
1091 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1092 name_found = False
1093 pass
1095 if name_found:
1096 for rec in res.rec:
1097 for record in rec.records:
1098 if record.wType == dnsp.DNS_TYPE_A or \
1099 record.wType == dnsp.DNS_TYPE_AAAA:
1100 # delete record
1101 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1102 del_rec_buf.rec = record
1103 try:
1104 dns_conn.DnssrvUpdateRecord2(client_version,
1106 ctx.server,
1107 zone,
1108 name,
1109 None,
1110 del_rec_buf)
1111 except WERRORError as e:
1112 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1113 pass
1114 else:
1115 raise
1117 for IP in IPs:
1118 if IP.find(':') != -1:
1119 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1120 % (name, zone, IP))
1121 rec = AAAARecord(IP)
1122 else:
1123 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1124 % (name, zone, IP))
1125 rec = ARecord(IP)
1127 # Add record
1128 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1129 add_rec_buf.rec = rec
1130 dns_conn.DnssrvUpdateRecord2(client_version,
1132 ctx.server,
1133 zone,
1134 name,
1135 add_rec_buf,
1136 None)
1138 if (len(IPs) > 0):
1139 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1140 (ctx.dns_a_dn, ldap_record) \
1141 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1142 dns_partition=domaindns_zone_dn)
1144 # Make the DC own the DNS record, not the administrator
1145 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1146 controls=["sd_flags:1:%d"
1147 % (security.SECINFO_OWNER
1148 | security.SECINFO_GROUP)])
1151 # Add record
1152 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1153 % (msdcs_cname, msdcs_zone, cname_target))
1155 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1156 rec = CNameRecord(cname_target)
1157 add_rec_buf.rec = rec
1158 dns_conn.DnssrvUpdateRecord2(client_version,
1160 ctx.server,
1161 msdcs_zone,
1162 msdcs_cname,
1163 add_rec_buf,
1164 None)
1166 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1167 (ctx.dns_cname_dn, ldap_record) \
1168 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1169 dns_partition=forestdns_zone_dn)
1171 # Make the DC own the DNS record, not the administrator
1172 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1173 controls=["sd_flags:1:%d"
1174 % (security.SECINFO_OWNER
1175 | security.SECINFO_GROUP)])
1177 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1178 "will be created samba_dnsupdate on first startup")
1181 def join_replicate_new_dns_records(ctx):
1182 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1183 if nc in ctx.nc_list:
1184 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1185 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1186 ctx.ntds_guid, rodc=ctx.RODC,
1187 replica_flags=ctx.replica_flags,
1188 full_sync=False)
1192 def join_finalise(ctx):
1193 """Finalise the join, mark us synchronised and setup secrets db."""
1195 # FIXME we shouldn't do this in all cases
1197 # If for some reasons we joined in another site than the one of
1198 # DC we just replicated from then we don't need to send the updatereplicateref
1199 # as replication between sites is time based and on the initiative of the
1200 # requesting DC
1201 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1202 for nc in ctx.nc_list:
1203 ctx.send_DsReplicaUpdateRefs(nc)
1205 if ctx.RODC:
1206 print("Setting RODC invocationId")
1207 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1208 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1209 ctx.behavior_version)
1210 m = ldb.Message()
1211 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1212 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1213 ldb.FLAG_MOD_REPLACE,
1214 "invocationId")
1215 ctx.local_samdb.modify(m)
1217 # Note: as RODC the invocationId is only stored
1218 # on the RODC itself, the other DCs never see it.
1220 # Thats is why we fix up the replPropertyMetaData stamp
1221 # for the 'invocationId' attribute, we need to change
1222 # the 'version' to '0', this is what windows 2008r2 does as RODC
1224 # This means if the object on a RWDC ever gets a invocationId
1225 # attribute, it will have version '1' (or higher), which will
1226 # will overwrite the RODC local value.
1227 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1228 "invocationId",
1231 ctx.logger.info("Setting isSynchronized and dsServiceName")
1232 m = ldb.Message()
1233 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1234 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1236 guid = ctx.ntds_guid
1237 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1238 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1239 ctx.local_samdb.modify(m)
1241 if ctx.subdomain:
1242 return
1244 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1246 ctx.logger.info("Setting up secrets database")
1247 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1248 realm=ctx.realm,
1249 dnsdomain=ctx.dnsdomain,
1250 netbiosname=ctx.myname,
1251 domainsid=ctx.domsid,
1252 machinepass=ctx.acct_pass,
1253 secure_channel_type=ctx.secure_channel_type,
1254 key_version_number=ctx.key_version_number)
1256 if ctx.dns_backend.startswith("BIND9_"):
1257 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1258 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1259 dns_backend=ctx.dns_backend,
1260 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1261 targetdir=ctx.targetdir,
1262 key_version_number=ctx.dns_key_version_number)
1264 def join_setup_trusts(ctx):
1265 """provision the local SAM."""
1267 print("Setup domain trusts with server %s" % ctx.server)
1268 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1269 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1270 ctx.lp, ctx.creds)
1272 objectAttr = lsa.ObjectAttribute()
1273 objectAttr.sec_qos = lsa.QosInfo()
1275 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1276 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1278 info = lsa.TrustDomainInfoInfoEx()
1279 info.domain_name.string = ctx.dnsdomain
1280 info.netbios_name.string = ctx.domain_name
1281 info.sid = ctx.domsid
1282 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1283 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1284 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1286 try:
1287 oldname = lsa.String()
1288 oldname.string = ctx.dnsdomain
1289 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1290 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1291 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1292 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1293 except RuntimeError:
1294 pass
1296 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1298 clear_value = drsblobs.AuthInfoClear()
1299 clear_value.size = len(password_blob)
1300 clear_value.password = password_blob
1302 clear_authentication_information = drsblobs.AuthenticationInformation()
1303 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1304 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1305 clear_authentication_information.AuthInfo = clear_value
1307 authentication_information_array = drsblobs.AuthenticationInformationArray()
1308 authentication_information_array.count = 1
1309 authentication_information_array.array = [clear_authentication_information]
1311 outgoing = drsblobs.trustAuthInOutBlob()
1312 outgoing.count = 1
1313 outgoing.current = authentication_information_array
1315 trustpass = drsblobs.trustDomainPasswords()
1316 confounder = [3] * 512
1318 for i in range(512):
1319 confounder[i] = random.randint(0, 255)
1321 trustpass.confounder = confounder
1323 trustpass.outgoing = outgoing
1324 trustpass.incoming = outgoing
1326 trustpass_blob = ndr_pack(trustpass)
1328 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1330 auth_blob = lsa.DATA_BUF2()
1331 auth_blob.size = len(encrypted_trustpass)
1332 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1334 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1335 auth_info.auth_blob = auth_blob
1337 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1338 info,
1339 auth_info,
1340 security.SEC_STD_DELETE)
1342 rec = {
1343 "dn" : "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1344 "objectclass" : "trustedDomain",
1345 "trustType" : str(info.trust_type),
1346 "trustAttributes" : str(info.trust_attributes),
1347 "trustDirection" : str(info.trust_direction),
1348 "flatname" : ctx.forest_domain_name,
1349 "trustPartner" : ctx.dnsforest,
1350 "trustAuthIncoming" : ndr_pack(outgoing),
1351 "trustAuthOutgoing" : ndr_pack(outgoing),
1352 "securityIdentifier" : ndr_pack(ctx.forestsid)
1354 ctx.local_samdb.add(rec)
1356 rec = {
1357 "dn" : "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1358 "objectclass" : "user",
1359 "userAccountControl" : str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1360 "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le'),
1361 "samAccountName" : "%s$" % ctx.forest_domain_name
1363 ctx.local_samdb.add(rec)
1366 def build_nc_lists(ctx):
1367 # nc_list is the list of naming context (NC) for which we will
1368 # replicate in and send a updateRef command to the partner DC
1370 # full_nc_list is the list of naming context (NC) we hold
1371 # read/write copies of. These are not subsets of each other.
1372 ctx.nc_list = [ ctx.config_dn, ctx.schema_dn ]
1373 ctx.full_nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
1375 if ctx.subdomain and ctx.dns_backend != "NONE":
1376 ctx.full_nc_list += [ctx.domaindns_zone]
1378 elif not ctx.subdomain:
1379 ctx.nc_list += [ctx.base_dn]
1381 if ctx.dns_backend != "NONE":
1382 ctx.nc_list += [ctx.domaindns_zone]
1383 ctx.nc_list += [ctx.forestdns_zone]
1384 ctx.full_nc_list += [ctx.domaindns_zone]
1385 ctx.full_nc_list += [ctx.forestdns_zone]
1387 def do_join(ctx):
1388 ctx.build_nc_lists()
1390 if ctx.promote_existing:
1391 ctx.promote_possible()
1392 else:
1393 ctx.cleanup_old_join()
1395 try:
1396 ctx.join_add_objects()
1397 ctx.join_provision()
1398 ctx.join_replicate()
1399 if ctx.subdomain:
1400 ctx.join_add_objects2()
1401 ctx.join_provision_own_domain()
1402 ctx.join_setup_trusts()
1404 if ctx.dns_backend != "NONE":
1405 ctx.join_add_dns_records()
1406 ctx.join_replicate_new_dns_records()
1408 ctx.join_finalise()
1409 except:
1410 try:
1411 print("Join failed - cleaning up")
1412 except IOError:
1413 pass
1414 ctx.cleanup_old_join()
1415 raise
1418 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1419 targetdir=None, domain=None, domain_critical_only=False,
1420 machinepass=None, use_ntvfs=False, dns_backend=None,
1421 promote_existing=False, plaintext_secrets=False,
1422 backend_store=None):
1423 """Join as a RODC."""
1425 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1426 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1427 promote_existing, plaintext_secrets,
1428 backend_store=backend_store)
1430 lp.set("workgroup", ctx.domain_name)
1431 logger.info("workgroup is %s" % ctx.domain_name)
1433 lp.set("realm", ctx.realm)
1434 logger.info("realm is %s" % ctx.realm)
1436 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1438 # setup some defaults for accounts that should be replicated to this RODC
1439 ctx.never_reveal_sid = [
1440 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1441 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1442 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1443 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1444 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1445 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1447 mysid = ctx.get_mysid()
1448 admin_dn = "<SID=%s>" % mysid
1449 ctx.managedby = admin_dn
1451 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1452 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1453 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1455 ctx.SPNs.extend([ "RestrictedKrbHost/%s" % ctx.myname,
1456 "RestrictedKrbHost/%s" % ctx.dnshostname ])
1458 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1459 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1460 ctx.RODC = True
1461 ctx.replica_flags |= ( drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1462 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1463 ctx.domain_replica_flags = ctx.replica_flags
1464 if domain_critical_only:
1465 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1467 ctx.do_join()
1469 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1472 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1473 targetdir=None, domain=None, domain_critical_only=False,
1474 machinepass=None, use_ntvfs=False, dns_backend=None,
1475 promote_existing=False, plaintext_secrets=False,
1476 backend_store=None):
1477 """Join as a DC."""
1478 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1479 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1480 promote_existing, plaintext_secrets,
1481 backend_store=backend_store)
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.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1491 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1492 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1494 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1495 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1496 ctx.domain_replica_flags = ctx.replica_flags
1497 if domain_critical_only:
1498 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1500 ctx.do_join()
1501 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1503 def join_clone(logger=None, server=None, creds=None, lp=None,
1504 targetdir=None, domain=None, include_secrets=False,
1505 dns_backend="NONE"):
1506 """Creates a local clone of a remote DC."""
1507 ctx = DCCloneContext(logger, server, creds, lp, targetdir=targetdir,
1508 domain=domain, dns_backend=dns_backend,
1509 include_secrets=include_secrets)
1511 lp.set("workgroup", ctx.domain_name)
1512 logger.info("workgroup is %s" % ctx.domain_name)
1514 lp.set("realm", ctx.realm)
1515 logger.info("realm is %s" % ctx.realm)
1517 ctx.do_join()
1518 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1519 return ctx
1521 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1522 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1523 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1524 dns_backend=None, plaintext_secrets=False,
1525 backend_store=None):
1526 """Join as a DC."""
1527 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1528 targetdir, parent_domain, machinepass, use_ntvfs,
1529 dns_backend, plaintext_secrets,
1530 backend_store=backend_store)
1531 ctx.subdomain = True
1532 if adminpass is None:
1533 ctx.adminpass = samba.generate_random_password(12, 32)
1534 else:
1535 ctx.adminpass = adminpass
1536 ctx.parent_domain_name = ctx.domain_name
1537 ctx.domain_name = netbios_domain
1538 ctx.realm = dnsdomain
1539 ctx.parent_dnsdomain = ctx.dnsdomain
1540 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1541 ctx.dnsdomain = dnsdomain
1542 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1543 ctx.naming_master = ctx.get_naming_master()
1544 if ctx.naming_master != ctx.server:
1545 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1546 ctx.server = ctx.naming_master
1547 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1548 session_info=system_session(),
1549 credentials=ctx.creds, lp=ctx.lp)
1550 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1551 controls=[])
1552 ctx.server = res[0]["dnsHostName"]
1553 logger.info("DNS name of new naming master is %s" % ctx.server)
1555 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1556 ctx.forestsid = ctx.domsid
1557 ctx.domsid = security.random_sid()
1558 ctx.acct_dn = None
1559 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1560 # Windows uses 240 bytes as UTF16 so we do
1561 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1563 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1565 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1566 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1568 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1569 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1570 ctx.domain_replica_flags = ctx.replica_flags
1572 ctx.do_join()
1573 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1576 class DCCloneContext(DCJoinContext):
1577 """Clones a remote DC."""
1579 def __init__(ctx, logger=None, server=None, creds=None, lp=None,
1580 targetdir=None, domain=None, dns_backend=None,
1581 include_secrets=False):
1582 super(DCCloneContext, ctx).__init__(logger, server, creds, lp,
1583 targetdir=targetdir, domain=domain,
1584 dns_backend=dns_backend)
1586 # As we don't want to create or delete these DNs, we set them to None
1587 ctx.server_dn = None
1588 ctx.ntds_dn = None
1589 ctx.acct_dn = None
1590 ctx.myname = ctx.server.split('.')[0]
1591 ctx.ntds_guid = None
1592 ctx.rid_manager_dn = None
1594 # Save this early
1595 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
1597 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1598 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1599 if not include_secrets:
1600 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1601 ctx.domain_replica_flags = ctx.replica_flags
1603 def join_finalise(ctx):
1604 ctx.logger.info("Setting isSynchronized and dsServiceName")
1605 m = ldb.Message()
1606 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1607 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE,
1608 "isSynchronized")
1610 # We want to appear to be the server we just cloned
1611 guid = ctx.remote_dc_ntds_guid
1612 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1613 ldb.FLAG_MOD_REPLACE,
1614 "dsServiceName")
1615 ctx.local_samdb.modify(m)
1617 def do_join(ctx):
1618 ctx.build_nc_lists()
1620 # When cloning a DC, we just want to provision a DC locally, then
1621 # grab the remote DC's entire DB via DRS replication
1622 ctx.join_provision()
1623 ctx.join_replicate()
1624 ctx.join_finalise()
1627 # Used to create a renamed backup of a DC. Renaming the domain means that the
1628 # cloned/backup DC can be started without interfering with the production DC.
1629 class DCCloneAndRenameContext(DCCloneContext):
1630 """Clones a remote DC, renaming the domain along the way."""
1632 def __init__(ctx, new_base_dn, new_domain_name, new_realm, logger=None,
1633 server=None, creds=None, lp=None, targetdir=None, domain=None,
1634 dns_backend=None, include_secrets=True):
1635 super(DCCloneAndRenameContext, ctx).__init__(logger, server, creds, lp,
1636 targetdir=targetdir,
1637 domain=domain,
1638 dns_backend=dns_backend,
1639 include_secrets=include_secrets)
1640 # store the new DN (etc) that we want the cloned DB to use
1641 ctx.new_base_dn = new_base_dn
1642 ctx.new_domain_name = new_domain_name
1643 ctx.new_realm = new_realm
1645 def create_replicator(ctx, repl_creds, binding_options):
1646 """Creates a new DRS object for managing replications"""
1648 # We want to rename all the domain objects, and the simplest way to do
1649 # this is during replication. This is because the base DN of the top-
1650 # level replicated object will flow through to all the objects below it
1651 binding_str = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
1652 return drs_utils.drs_ReplicateRenamer(binding_str, ctx.lp, repl_creds,
1653 ctx.local_samdb,
1654 ctx.invocation_id,
1655 ctx.base_dn, ctx.new_base_dn)
1657 def create_non_global_lp(ctx, global_lp):
1658 '''Creates a non-global LoadParm based on the global LP's settings'''
1660 # the samba code shares a global LoadParm by default. Here we create a
1661 # new LoadParm that retains the global settings, but any changes we
1662 # make to it won't automatically affect the rest of the samba code.
1663 # The easiest way to do this is to dump the global settings to a
1664 # temporary smb.conf file, and then load the temp file into a new
1665 # non-global LoadParm
1666 fd, tmp_file = tempfile.mkstemp()
1667 global_lp.dump(False, tmp_file)
1668 local_lp = samba.param.LoadParm(filename_for_non_global_lp=tmp_file)
1669 os.remove(tmp_file)
1670 return local_lp
1672 def rename_dn(ctx, dn_str):
1673 '''Uses string substitution to replace the base DN'''
1674 old_base_dn = ctx.base_dn
1675 return re.sub('%s$' % old_base_dn, ctx.new_base_dn, dn_str)
1677 # we want to override the normal DCCloneContext's join_provision() so that
1678 # use the new domain DNs during the provision. We do this because:
1679 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1680 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1681 # we couldn't apply the renamed DRS objects during replication)
1682 def join_provision(ctx):
1683 """Provision the local (renamed) SAM."""
1685 print("Provisioning the new (renamed) domain...")
1687 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1688 # to create a new smb.conf. By default, it uses the global LoadParm to
1689 # do this, and so it would overwrite the realm/domain values globally.
1690 # We still need the global LoadParm to retain the old domain's details,
1691 # so we can connect to (and clone) the existing DC.
1692 # So, copy the global settings into a non-global LoadParm, which we can
1693 # then pass into provision(). This generates a new smb.conf correctly,
1694 # without overwriting the global realm/domain values just yet.
1695 non_global_lp = ctx.create_non_global_lp(ctx.lp)
1697 # do the provision with the new/renamed domain DN values
1698 presult = provision(ctx.logger, system_session(),
1699 targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
1700 realm=ctx.new_realm, lp=non_global_lp,
1701 rootdn=ctx.rename_dn(ctx.root_dn), domaindn=ctx.new_base_dn,
1702 schemadn=ctx.rename_dn(ctx.schema_dn),
1703 configdn=ctx.rename_dn(ctx.config_dn),
1704 domain=ctx.new_domain_name, domainsid=ctx.domsid,
1705 serverrole="active directory domain controller",
1706 dns_backend=ctx.dns_backend)
1708 print("Provision OK for renamed domain DN %s" % presult.domaindn)
1709 ctx.local_samdb = presult.samdb
1710 ctx.paths = presult.paths