VERSION: Disable GIT_SNAPSHOT for the 4.9.13 release.
[Samba.git] / python / samba / join.py
blob36a854711ff680a21c42a3ac55251dbab3e29410
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
49 from samba.netcmd import CommandError
51 class DCJoinException(Exception):
53 def __init__(self, msg):
54 super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
57 class DCJoinContext(object):
58 """Perform a DC join."""
60 def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
61 netbios_name=None, targetdir=None, domain=None,
62 machinepass=None, use_ntvfs=False, dns_backend=None,
63 promote_existing=False, plaintext_secrets=False,
64 backend_store=None, forced_local_samdb=None):
65 if site is None:
66 site = "Default-First-Site-Name"
68 ctx.logger = logger
69 ctx.creds = creds
70 ctx.lp = lp
71 ctx.site = site
72 ctx.targetdir = targetdir
73 ctx.use_ntvfs = use_ntvfs
74 ctx.plaintext_secrets = plaintext_secrets
75 ctx.backend_store = backend_store
77 ctx.promote_existing = promote_existing
78 ctx.promote_from_dn = None
80 ctx.nc_list = []
81 ctx.full_nc_list = []
83 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
84 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
86 ctx.server = server
87 ctx.forced_local_samdb = forced_local_samdb
89 if forced_local_samdb:
90 ctx.samdb = forced_local_samdb
91 ctx.server = ctx.samdb.url
92 else:
93 if not ctx.server:
94 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
95 ctx.server = ctx.find_dc(domain)
96 ctx.logger.info("Found DC %s" % ctx.server)
97 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
98 session_info=system_session(),
99 credentials=ctx.creds, lp=ctx.lp)
101 try:
102 ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
103 except ldb.LdbError as e4:
104 (enum, estr) = e4.args
105 raise DCJoinException(estr)
108 ctx.base_dn = str(ctx.samdb.get_default_basedn())
109 ctx.root_dn = str(ctx.samdb.get_root_basedn())
110 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
111 ctx.config_dn = str(ctx.samdb.get_config_basedn())
112 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
113 ctx.forestsid = ctx.domsid
114 ctx.domain_name = ctx.get_domain_name()
115 ctx.forest_domain_name = ctx.get_forest_domain_name()
116 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
118 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
119 ctx.dc_dnsHostName = ctx.get_dnsHostName()
120 ctx.behavior_version = ctx.get_behavior_version()
122 if machinepass is not None:
123 ctx.acct_pass = machinepass
124 else:
125 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
127 ctx.dnsdomain = ctx.samdb.domain_dns_name()
129 # the following are all dependent on the new DC's netbios_name (which
130 # we expect to always be specified, except when cloning a DC)
131 if netbios_name:
132 # work out the DNs of all the objects we will be adding
133 ctx.myname = netbios_name
134 ctx.samname = "%s$" % ctx.myname
135 ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
136 ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
137 ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
138 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
139 ctx.dnsforest = ctx.samdb.forest_dns_name()
141 topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
142 if ctx.dn_exists(topology_base):
143 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
144 else:
145 ctx.topology_dn = None
147 ctx.SPNs = [ "HOST/%s" % ctx.myname,
148 "HOST/%s" % ctx.dnshostname,
149 "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
151 res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
152 attrs=["rIDManagerReference"],
153 base=ctx.base_dn)
155 ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
157 ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
158 ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
160 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
161 res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
162 attrs=[],
163 base=ctx.samdb.get_partitions_dn(),
164 expression=expr)
165 if dns_backend is None:
166 ctx.dns_backend = "NONE"
167 else:
168 if len(res_domaindns) == 0:
169 ctx.dns_backend = "NONE"
170 print("NO DNS zone information found in source domain, not replicating DNS")
171 else:
172 ctx.dns_backend = dns_backend
174 ctx.realm = ctx.dnsdomain
176 ctx.tmp_samdb = None
178 ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
179 drsuapi.DRSUAPI_DRS_PER_SYNC |
180 drsuapi.DRSUAPI_DRS_GET_ANC |
181 drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
182 drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
184 # these elements are optional
185 ctx.never_reveal_sid = None
186 ctx.reveal_sid = None
187 ctx.connection_dn = None
188 ctx.RODC = False
189 ctx.krbtgt_dn = None
190 ctx.drsuapi = None
191 ctx.managedby = None
192 ctx.subdomain = False
193 ctx.adminpass = None
194 ctx.partition_dn = None
196 ctx.dns_a_dn = None
197 ctx.dns_cname_dn = None
199 # Do not normally register 127. addresses but allow override for selftest
200 ctx.force_all_ips = False
202 def del_noerror(ctx, dn, recursive=False):
203 if recursive:
204 try:
205 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
206 except Exception:
207 return
208 for r in res:
209 ctx.del_noerror(r.dn, recursive=True)
210 try:
211 ctx.samdb.delete(dn)
212 print("Deleted %s" % dn)
213 except Exception:
214 pass
216 def cleanup_old_accounts(ctx, force=False):
217 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
218 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
219 attrs=["msDS-krbTgtLink", "objectSID"])
220 if len(res) == 0:
221 return
223 if not force:
224 creds = Credentials()
225 creds.guess(ctx.lp)
226 try:
227 creds.set_machine_account(ctx.lp)
228 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
229 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
230 session_info=system_session(),
231 credentials=creds, lp=ctx.lp)
232 except:
233 pass
234 else:
235 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
236 if token_res[0]["tokenGroups"][0] \
237 == res[0]["objectSID"][0]:
238 raise DCJoinException("Not removing account %s which "
239 "looks like a Samba DC account "
240 "matching the password we already have. "
241 "To override, remove secrets.ldb and secrets.tdb"
242 % ctx.samname)
244 ctx.del_noerror(res[0].dn, recursive=True)
246 if "msDS-Krbtgtlink" in res[0]:
247 new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
248 ctx.del_noerror(ctx.new_krbtgt_dn)
250 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
251 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
252 (ldb.binary_encode("dns-%s" % ctx.myname),
253 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
254 attrs=[])
255 if res:
256 ctx.del_noerror(res[0].dn, recursive=True)
258 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
259 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
260 attrs=[])
261 if res:
262 raise DCJoinException("Not removing account %s which looks like "
263 "a Samba DNS service account but does not "
264 "have servicePrincipalName=%s" %
265 (ldb.binary_encode("dns-%s" % ctx.myname),
266 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
269 def cleanup_old_join(ctx, force=False):
270 """Remove any DNs from a previous join."""
271 # find the krbtgt link
272 if not ctx.subdomain:
273 ctx.cleanup_old_accounts(force=force)
275 if ctx.connection_dn is not None:
276 ctx.del_noerror(ctx.connection_dn)
277 if ctx.krbtgt_dn is not None:
278 ctx.del_noerror(ctx.krbtgt_dn)
279 ctx.del_noerror(ctx.ntds_dn)
280 ctx.del_noerror(ctx.server_dn, recursive=True)
281 if ctx.topology_dn:
282 ctx.del_noerror(ctx.topology_dn)
283 if ctx.partition_dn:
284 ctx.del_noerror(ctx.partition_dn)
286 if ctx.subdomain:
287 binding_options = "sign"
288 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
289 ctx.lp, ctx.creds)
291 objectAttr = lsa.ObjectAttribute()
292 objectAttr.sec_qos = lsa.QosInfo()
294 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
295 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
297 name = lsa.String()
298 name.string = ctx.realm
299 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
301 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
303 name = lsa.String()
304 name.string = ctx.forest_domain_name
305 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
307 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
309 if ctx.dns_a_dn:
310 ctx.del_noerror(ctx.dns_a_dn)
312 if ctx.dns_cname_dn:
313 ctx.del_noerror(ctx.dns_cname_dn)
317 def promote_possible(ctx):
318 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
319 if ctx.subdomain:
320 # This shouldn't happen
321 raise Exception("Can not promote into a subdomain")
323 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
324 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
325 attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
326 if len(res) == 0:
327 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
328 if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
329 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
330 if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
331 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
333 ctx.promote_from_dn = res[0].dn
336 def find_dc(ctx, domain):
337 """find a writeable DC for the given domain"""
338 try:
339 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
340 except NTSTATUSError as error:
341 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
342 (domain, error.args[1]))
343 except Exception:
344 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
345 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
346 ctx.site = ctx.cldap_ret.client_site
347 return ctx.cldap_ret.pdc_dns_name
350 def get_behavior_version(ctx):
351 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
352 if "msDS-Behavior-Version" in res[0]:
353 return int(res[0]["msDS-Behavior-Version"][0])
354 else:
355 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
357 def get_dnsHostName(ctx):
358 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
359 return res[0]["dnsHostName"][0]
361 def get_domain_name(ctx):
362 '''get netbios name of the domain from the partitions record'''
363 partitions_dn = ctx.samdb.get_partitions_dn()
364 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
365 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
366 return res[0]["nETBIOSName"][0]
368 def get_forest_domain_name(ctx):
369 '''get netbios name of the domain from the partitions record'''
370 partitions_dn = ctx.samdb.get_partitions_dn()
371 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
372 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
373 return res[0]["nETBIOSName"][0]
375 def get_parent_partition_dn(ctx):
376 '''get the parent domain partition DN from parent DNS name'''
377 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
378 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
379 (ldb.binary_encode(ctx.parent_dnsdomain),
380 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
381 return str(res[0].dn)
383 def get_naming_master(ctx):
384 '''get the parent domain partition DN from parent DNS name'''
385 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
386 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
387 if not 'fSMORoleOwner' in res[0]:
388 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
389 try:
390 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
391 except KeyError:
392 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
394 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
395 return master_host
397 def get_mysid(ctx):
398 '''get the SID of the connected user. Only works with w2k8 and later,
399 so only used for RODC join'''
400 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
401 binsid = res[0]["tokenGroups"][0]
402 return ctx.samdb.schema_format_value("objectSID", binsid)
404 def dn_exists(ctx, dn):
405 '''check if a DN exists'''
406 try:
407 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
408 except ldb.LdbError as e5:
409 (enum, estr) = e5.args
410 if enum == ldb.ERR_NO_SUCH_OBJECT:
411 return False
412 raise
413 return True
415 def add_krbtgt_account(ctx):
416 '''RODCs need a special krbtgt account'''
417 print("Adding %s" % ctx.krbtgt_dn)
418 rec = {
419 "dn" : ctx.krbtgt_dn,
420 "objectclass" : "user",
421 "useraccountcontrol" : str(samba.dsdb.UF_NORMAL_ACCOUNT |
422 samba.dsdb.UF_ACCOUNTDISABLE),
423 "showinadvancedviewonly" : "TRUE",
424 "description" : "krbtgt for %s" % ctx.samname}
425 ctx.samdb.add(rec, ["rodc_join:1:1"])
427 # now we need to search for the samAccountName attribute on the krbtgt DN,
428 # as this will have been magically set to the krbtgt number
429 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
430 ctx.krbtgt_name = res[0]["samAccountName"][0]
432 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
434 m = ldb.Message()
435 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
436 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
437 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
438 ctx.samdb.modify(m)
440 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
441 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
442 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
444 def drsuapi_connect(ctx):
445 '''make a DRSUAPI connection to the naming master'''
446 binding_options = "seal"
447 if ctx.lp.log_level() >= 9:
448 binding_options += ",print"
449 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
450 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
451 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
453 def create_tmp_samdb(ctx):
454 '''create a temporary samdb object for schema queries'''
455 ctx.tmp_schema = Schema(ctx.domsid,
456 schemadn=ctx.schema_dn)
457 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
458 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
459 am_rodc=False)
460 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
462 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
463 '''build a DsReplicaAttributeCtr object'''
464 r = drsuapi.DsReplicaAttribute()
465 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
466 r.value_ctr = 1
469 def DsAddEntry(ctx, recs):
470 '''add a record via the DRSUAPI DsAddEntry call'''
471 if ctx.drsuapi is None:
472 ctx.drsuapi_connect()
473 if ctx.tmp_samdb is None:
474 ctx.create_tmp_samdb()
476 objects = []
477 for rec in recs:
478 id = drsuapi.DsReplicaObjectIdentifier()
479 id.dn = rec['dn']
481 attrs = []
482 for a in rec:
483 if a == 'dn':
484 continue
485 if not isinstance(rec[a], list):
486 v = [rec[a]]
487 else:
488 v = rec[a]
489 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
490 attrs.append(rattr)
492 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
493 attribute_ctr.num_attributes = len(attrs)
494 attribute_ctr.attributes = attrs
496 object = drsuapi.DsReplicaObject()
497 object.identifier = id
498 object.attribute_ctr = attribute_ctr
500 list_object = drsuapi.DsReplicaObjectListItem()
501 list_object.object = object
502 objects.append(list_object)
504 req2 = drsuapi.DsAddEntryRequest2()
505 req2.first_object = objects[0]
506 prev = req2.first_object
507 for o in objects[1:]:
508 prev.next_object = o
509 prev = o
511 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
512 if level == 2:
513 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
514 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
515 raise RuntimeError("DsAddEntry failed")
516 if ctr.extended_err[0] != werror.WERR_SUCCESS:
517 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
518 raise RuntimeError("DsAddEntry failed")
519 if level == 3:
520 if ctr.err_ver != 1:
521 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
522 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
523 if ctr.err_data.info is None:
524 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
525 else:
526 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
527 ctr.err_data.info.extended_err))
528 raise RuntimeError("DsAddEntry failed")
529 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
530 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
531 raise RuntimeError("DsAddEntry failed")
533 return ctr.objects
535 def join_ntdsdsa_obj(ctx):
536 '''return the ntdsdsa object to add'''
538 print("Adding %s" % ctx.ntds_dn)
539 rec = {
540 "dn" : ctx.ntds_dn,
541 "objectclass" : "nTDSDSA",
542 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
543 "dMDLocation" : ctx.schema_dn}
545 nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
547 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
548 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
550 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
551 rec["msDS-HasDomainNCs"] = ctx.base_dn
553 if ctx.RODC:
554 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
555 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
556 rec["options"] = "37"
557 else:
558 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
559 rec["HasMasterNCs"] = []
560 for nc in nc_list:
561 if nc in ctx.full_nc_list:
562 rec["HasMasterNCs"].append(nc)
563 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
564 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
565 rec["options"] = "1"
566 rec["invocationId"] = ndr_pack(ctx.invocation_id)
568 return rec
570 def join_add_ntdsdsa(ctx):
571 '''add the ntdsdsa object'''
573 rec = ctx.join_ntdsdsa_obj()
574 if ctx.forced_local_samdb:
575 ctx.samdb.add(rec, controls=["relax:0"])
576 elif ctx.RODC:
577 ctx.samdb.add(rec, ["rodc_join:1:1"])
578 else:
579 ctx.DsAddEntry([rec])
581 # find the GUID of our NTDS DN
582 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
583 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
585 def join_add_objects(ctx, specified_sid=None):
586 '''add the various objects needed for the join'''
587 if ctx.acct_dn:
588 print("Adding %s" % ctx.acct_dn)
589 rec = {
590 "dn" : ctx.acct_dn,
591 "objectClass": "computer",
592 "displayname": ctx.samname,
593 "samaccountname" : ctx.samname,
594 "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
595 "dnshostname" : ctx.dnshostname}
596 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
597 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
598 elif ctx.promote_existing:
599 rec['msDS-SupportedEncryptionTypes'] = []
600 if ctx.managedby:
601 rec["managedby"] = ctx.managedby
602 elif ctx.promote_existing:
603 rec["managedby"] = []
605 if ctx.never_reveal_sid:
606 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
607 elif ctx.promote_existing:
608 rec["msDS-NeverRevealGroup"] = []
610 if ctx.reveal_sid:
611 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
612 elif ctx.promote_existing:
613 rec["msDS-RevealOnDemandGroup"] = []
615 if specified_sid:
616 rec["objectSid"] = ndr_pack(specified_sid)
618 if ctx.promote_existing:
619 if ctx.promote_from_dn != ctx.acct_dn:
620 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
621 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
622 else:
623 controls = None
624 if specified_sid is not None:
625 controls = ["relax:0"]
626 ctx.samdb.add(rec, controls=controls)
628 if ctx.krbtgt_dn:
629 ctx.add_krbtgt_account()
631 if ctx.server_dn:
632 print("Adding %s" % ctx.server_dn)
633 rec = {
634 "dn": ctx.server_dn,
635 "objectclass" : "server",
636 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
637 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
638 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
639 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
640 # windows seems to add the dnsHostName later
641 "dnsHostName" : ctx.dnshostname}
643 if ctx.acct_dn:
644 rec["serverReference"] = ctx.acct_dn
646 ctx.samdb.add(rec)
648 if ctx.subdomain:
649 # the rest is done after replication
650 ctx.ntds_guid = None
651 return
653 if ctx.ntds_dn:
654 ctx.join_add_ntdsdsa()
656 # Add the Replica-Locations or RO-Replica-Locations attributes
657 # TODO Is this supposed to be for the schema partition too?
658 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
659 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
660 attrs=[],
661 base=ctx.samdb.get_partitions_dn(),
662 expression=expr), ctx.domaindns_zone)
664 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
665 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
666 attrs=[],
667 base=ctx.samdb.get_partitions_dn(),
668 expression=expr), ctx.forestdns_zone)
670 for part, zone in (domain, forest):
671 if zone not in ctx.nc_list:
672 continue
674 if len(part) == 1:
675 m = ldb.Message()
676 m.dn = part[0].dn
677 attr = "msDS-NC-Replica-Locations"
678 if ctx.RODC:
679 attr = "msDS-NC-RO-Replica-Locations"
681 m[attr] = ldb.MessageElement(ctx.ntds_dn,
682 ldb.FLAG_MOD_ADD, attr)
683 ctx.samdb.modify(m)
685 if ctx.connection_dn is not None:
686 print("Adding %s" % ctx.connection_dn)
687 rec = {
688 "dn" : ctx.connection_dn,
689 "objectclass" : "nTDSConnection",
690 "enabledconnection" : "TRUE",
691 "options" : "65",
692 "fromServer" : ctx.dc_ntds_dn}
693 ctx.samdb.add(rec)
695 if ctx.acct_dn:
696 print("Adding SPNs to %s" % ctx.acct_dn)
697 m = ldb.Message()
698 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
699 for i in range(len(ctx.SPNs)):
700 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
701 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
702 ldb.FLAG_MOD_REPLACE,
703 "servicePrincipalName")
704 ctx.samdb.modify(m)
706 # The account password set operation should normally be done over
707 # LDAP. Windows 2000 DCs however allow this only with SSL
708 # connections which are hard to set up and otherwise refuse with
709 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
710 # over SAMR.
711 print("Setting account password for %s" % ctx.samname)
712 try:
713 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
714 % ldb.binary_encode(ctx.samname),
715 ctx.acct_pass,
716 force_change_at_next_login=False,
717 username=ctx.samname)
718 except ldb.LdbError as e2:
719 (num, _) = e2.args
720 if num != ldb.ERR_UNWILLING_TO_PERFORM:
721 pass
722 ctx.net.set_password(account_name=ctx.samname,
723 domain_name=ctx.domain_name,
724 newpassword=ctx.acct_pass.encode('utf-8'))
726 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
727 attrs=["msDS-KeyVersionNumber",
728 "objectSID"])
729 if "msDS-KeyVersionNumber" in res[0]:
730 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
731 else:
732 ctx.key_version_number = None
734 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
735 res[0]["objectSid"][0])
737 print("Enabling account")
738 m = ldb.Message()
739 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
740 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
741 ldb.FLAG_MOD_REPLACE,
742 "userAccountControl")
743 ctx.samdb.modify(m)
745 if ctx.dns_backend.startswith("BIND9_"):
746 ctx.dnspass = samba.generate_random_password(128, 255)
748 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
749 {"DNSDOMAIN": ctx.dnsdomain,
750 "DOMAINDN": ctx.base_dn,
751 "HOSTNAME" : ctx.myname,
752 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
753 "DNSNAME" : ctx.dnshostname}))
754 for changetype, msg in recs:
755 assert changetype == ldb.CHANGETYPE_NONE
756 dns_acct_dn = msg["dn"]
757 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
759 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
760 del msg["clearTextPassword"]
761 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
762 del msg["isCriticalSystemObject"]
763 # Disable account until password is set
764 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
765 samba.dsdb.UF_ACCOUNTDISABLE)
766 try:
767 ctx.samdb.add(msg)
768 except ldb.LdbError as e:
769 (num, _) = e.args
770 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
771 raise
773 # The account password set operation should normally be done over
774 # LDAP. Windows 2000 DCs however allow this only with SSL
775 # connections which are hard to set up and otherwise refuse with
776 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
777 # over SAMR.
778 print("Setting account password for dns-%s" % ctx.myname)
779 try:
780 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
781 % ldb.binary_encode(ctx.myname),
782 ctx.dnspass,
783 force_change_at_next_login=False,
784 username=ctx.samname)
785 except ldb.LdbError as e3:
786 (num, _) = e3.args
787 if num != ldb.ERR_UNWILLING_TO_PERFORM:
788 raise
789 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
790 domain_name=ctx.domain_name,
791 newpassword=ctx.dnspass)
793 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
794 attrs=["msDS-KeyVersionNumber"])
795 if "msDS-KeyVersionNumber" in res[0]:
796 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
797 else:
798 ctx.dns_key_version_number = None
800 def join_add_objects2(ctx):
801 """add the various objects needed for the join, for subdomains post replication"""
803 print("Adding %s" % ctx.partition_dn)
804 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
805 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
806 rec = {
807 "dn" : ctx.partition_dn,
808 "objectclass" : "crossRef",
809 "objectCategory" : "CN=Cross-Ref,%s" % ctx.schema_dn,
810 "nCName" : ctx.base_dn,
811 "nETBIOSName" : ctx.domain_name,
812 "dnsRoot": ctx.dnsdomain,
813 "trustParent" : ctx.parent_partition_dn,
814 "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
815 "ntSecurityDescriptor" : sd_binary,
818 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
819 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
821 rec2 = ctx.join_ntdsdsa_obj()
823 objects = ctx.DsAddEntry([rec, rec2])
824 if len(objects) != 2:
825 raise DCJoinException("Expected 2 objects from DsAddEntry")
827 ctx.ntds_guid = objects[1].guid
829 print("Replicating partition DN")
830 ctx.repl.replicate(ctx.partition_dn,
831 misc.GUID("00000000-0000-0000-0000-000000000000"),
832 ctx.ntds_guid,
833 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
834 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
836 print("Replicating NTDS DN")
837 ctx.repl.replicate(ctx.ntds_dn,
838 misc.GUID("00000000-0000-0000-0000-000000000000"),
839 ctx.ntds_guid,
840 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
841 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
843 def join_provision(ctx):
844 """Provision the local SAM."""
846 print("Calling bare provision")
848 smbconf = ctx.lp.configfile
850 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
851 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
852 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
853 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
854 serverdn=ctx.server_dn, domain=ctx.domain_name,
855 hostname=ctx.myname, domainsid=ctx.domsid,
856 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
857 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
858 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
859 plaintext_secrets=ctx.plaintext_secrets,
860 backend_store=ctx.backend_store
862 print("Provision OK for domain DN %s" % presult.domaindn)
863 ctx.local_samdb = presult.samdb
864 ctx.lp = presult.lp
865 ctx.paths = presult.paths
866 ctx.names = presult.names
868 # Fix up the forestsid, it may be different if we are joining as a subdomain
869 ctx.names.forestsid = ctx.forestsid
871 def join_provision_own_domain(ctx):
872 """Provision the local SAM."""
874 # we now operate exclusively on the local database, which
875 # we need to reopen in order to get the newly created schema
876 print("Reconnecting to local samdb")
877 ctx.samdb = SamDB(url=ctx.local_samdb.url,
878 session_info=system_session(),
879 lp=ctx.local_samdb.lp,
880 global_schema=False)
881 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
882 ctx.local_samdb = ctx.samdb
884 ctx.logger.info("Finding domain GUID from ncName")
885 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
886 controls=["extended_dn:1:1", "reveal_internals:0"])
888 if 'nCName' not in res[0]:
889 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
891 try:
892 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
893 except KeyError:
894 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
896 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
898 ctx.logger.info("Calling own domain provision")
900 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
902 presult = provision_fill(ctx.local_samdb, secrets_ldb,
903 ctx.logger, ctx.names, ctx.paths,
904 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
905 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
906 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
907 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
908 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
909 print("Provision OK for domain %s" % ctx.names.dnsdomain)
911 def create_replicator(ctx, repl_creds, binding_options):
912 '''Creates a new DRS object for managing replications'''
913 return drs_utils.drs_Replicate(
914 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
915 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
917 def join_replicate(ctx):
918 """Replicate the SAM."""
920 print("Starting replication")
921 ctx.local_samdb.transaction_start()
922 try:
923 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
924 if ctx.ntds_guid is None:
925 print("Using DS_BIND_GUID_W2K3")
926 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
927 else:
928 destination_dsa_guid = ctx.ntds_guid
930 if ctx.RODC:
931 repl_creds = Credentials()
932 repl_creds.guess(ctx.lp)
933 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
934 repl_creds.set_username(ctx.samname)
935 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
936 else:
937 repl_creds = ctx.creds
939 binding_options = "seal"
940 if ctx.lp.log_level() >= 9:
941 binding_options += ",print"
943 repl = ctx.create_replicator(repl_creds, binding_options)
945 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
946 destination_dsa_guid, schema=True, rodc=ctx.RODC,
947 replica_flags=ctx.replica_flags)
948 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
949 destination_dsa_guid, rodc=ctx.RODC,
950 replica_flags=ctx.replica_flags)
951 if not ctx.subdomain:
952 # Replicate first the critical object for the basedn
953 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
954 print("Replicating critical objects from the base DN of the domain")
955 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
956 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
957 destination_dsa_guid, rodc=ctx.RODC,
958 replica_flags=ctx.domain_replica_flags)
959 ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
960 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
961 destination_dsa_guid, rodc=ctx.RODC,
962 replica_flags=ctx.domain_replica_flags)
963 print("Done with always replicated NC (base, config, schema)")
965 # At this point we should already have an entry in the ForestDNS
966 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
967 # indicate that we hold a replica for this NC.
968 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
969 if nc in ctx.nc_list:
970 print("Replicating %s" % (str(nc)))
971 repl.replicate(nc, source_dsa_invocation_id,
972 destination_dsa_guid, rodc=ctx.RODC,
973 replica_flags=ctx.replica_flags)
975 if ctx.RODC:
976 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
977 destination_dsa_guid,
978 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
979 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
980 destination_dsa_guid,
981 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
982 elif ctx.rid_manager_dn != None:
983 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
984 try:
985 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
986 destination_dsa_guid,
987 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
988 except samba.DsExtendedError as e1:
989 (enum, estr) = e1.args
990 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
991 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
992 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
993 else:
994 raise
996 ctx.repl = repl
997 ctx.source_dsa_invocation_id = source_dsa_invocation_id
998 ctx.destination_dsa_guid = destination_dsa_guid
1000 print("Committing SAM database")
1001 except:
1002 ctx.local_samdb.transaction_cancel()
1003 raise
1004 else:
1005 ctx.local_samdb.transaction_commit()
1007 def send_DsReplicaUpdateRefs(ctx, dn):
1008 r = drsuapi.DsReplicaUpdateRefsRequest1()
1009 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
1010 r.naming_context.dn = str(dn)
1011 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
1012 r.naming_context.sid = security.dom_sid("S-0-0")
1013 r.dest_dsa_guid = ctx.ntds_guid
1014 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1015 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1016 if not ctx.RODC:
1017 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1019 if ctx.drsuapi is None:
1020 ctx.drsuapi_connect()
1022 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1024 def join_add_dns_records(ctx):
1025 """Remotely Add a DNS record to the target DC. We assume that if we
1026 replicate DNS that the server holds the DNS roles and can accept
1027 updates.
1029 This avoids issues getting replication going after the DC
1030 first starts as the rest of the domain does not have to
1031 wait for samba_dnsupdate to run successfully.
1033 Specifically, we add the records implied by the DsReplicaUpdateRefs
1034 call above.
1036 We do not just run samba_dnsupdate as we want to strictly
1037 operate against the DC we just joined:
1038 - We do not want to query another DNS server
1039 - We do not want to obtain a Kerberos ticket
1040 (as the KDC we select may not be the DC we just joined,
1041 and so may not be in sync with the password we just set)
1042 - We do not wish to set the _ldap records until we have started
1043 - We do not wish to use NTLM (the --use-samba-tool mode forces
1044 NTLM)
1048 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1049 record_type = dnsp.DNS_TYPE_A
1050 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1051 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1053 zone = ctx.dnsdomain
1054 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1055 name = ctx.myname
1056 msdcs_cname = str(ctx.ntds_guid)
1057 cname_target = "%s.%s" % (name, zone)
1058 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1060 ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
1061 (len(IPs), name, zone))
1063 binding_options = "sign"
1064 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1065 ctx.lp, ctx.creds)
1068 name_found = True
1070 sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1072 change_owner_sd = security.descriptor()
1073 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1074 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1075 (str(ctx.domsid),
1076 security.DOMAIN_RID_DCS))
1078 # TODO: Remove any old records from the primary DNS name
1079 try:
1080 (buflen, res) \
1081 = dns_conn.DnssrvEnumRecords2(client_version,
1083 ctx.server,
1084 zone,
1085 name,
1086 None,
1087 dnsp.DNS_TYPE_ALL,
1088 select_flags,
1089 None,
1090 None)
1091 except WERRORError as e:
1092 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1093 name_found = False
1094 pass
1096 if name_found:
1097 for rec in res.rec:
1098 for record in rec.records:
1099 if record.wType == dnsp.DNS_TYPE_A or \
1100 record.wType == dnsp.DNS_TYPE_AAAA:
1101 # delete record
1102 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1103 del_rec_buf.rec = record
1104 try:
1105 dns_conn.DnssrvUpdateRecord2(client_version,
1107 ctx.server,
1108 zone,
1109 name,
1110 None,
1111 del_rec_buf)
1112 except WERRORError as e:
1113 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1114 pass
1115 else:
1116 raise
1118 for IP in IPs:
1119 if IP.find(':') != -1:
1120 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1121 % (name, zone, IP))
1122 rec = AAAARecord(IP)
1123 else:
1124 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1125 % (name, zone, IP))
1126 rec = ARecord(IP)
1128 # Add record
1129 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1130 add_rec_buf.rec = rec
1131 dns_conn.DnssrvUpdateRecord2(client_version,
1133 ctx.server,
1134 zone,
1135 name,
1136 add_rec_buf,
1137 None)
1139 if (len(IPs) > 0):
1140 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1141 (ctx.dns_a_dn, ldap_record) \
1142 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1143 dns_partition=domaindns_zone_dn)
1145 # Make the DC own the DNS record, not the administrator
1146 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1147 controls=["sd_flags:1:%d"
1148 % (security.SECINFO_OWNER
1149 | security.SECINFO_GROUP)])
1152 # Add record
1153 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1154 % (msdcs_cname, msdcs_zone, cname_target))
1156 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1157 rec = CNameRecord(cname_target)
1158 add_rec_buf.rec = rec
1159 dns_conn.DnssrvUpdateRecord2(client_version,
1161 ctx.server,
1162 msdcs_zone,
1163 msdcs_cname,
1164 add_rec_buf,
1165 None)
1167 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1168 (ctx.dns_cname_dn, ldap_record) \
1169 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1170 dns_partition=forestdns_zone_dn)
1172 # Make the DC own the DNS record, not the administrator
1173 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1174 controls=["sd_flags:1:%d"
1175 % (security.SECINFO_OWNER
1176 | security.SECINFO_GROUP)])
1178 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1179 "will be created samba_dnsupdate on first startup")
1182 def join_replicate_new_dns_records(ctx):
1183 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1184 if nc in ctx.nc_list:
1185 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1186 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1187 ctx.ntds_guid, rodc=ctx.RODC,
1188 replica_flags=ctx.replica_flags,
1189 full_sync=False)
1193 def join_finalise(ctx):
1194 """Finalise the join, mark us synchronised and setup secrets db."""
1196 # FIXME we shouldn't do this in all cases
1198 # If for some reasons we joined in another site than the one of
1199 # DC we just replicated from then we don't need to send the updatereplicateref
1200 # as replication between sites is time based and on the initiative of the
1201 # requesting DC
1202 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1203 for nc in ctx.nc_list:
1204 ctx.send_DsReplicaUpdateRefs(nc)
1206 if ctx.RODC:
1207 print("Setting RODC invocationId")
1208 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1209 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1210 ctx.behavior_version)
1211 m = ldb.Message()
1212 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1213 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1214 ldb.FLAG_MOD_REPLACE,
1215 "invocationId")
1216 ctx.local_samdb.modify(m)
1218 # Note: as RODC the invocationId is only stored
1219 # on the RODC itself, the other DCs never see it.
1221 # Thats is why we fix up the replPropertyMetaData stamp
1222 # for the 'invocationId' attribute, we need to change
1223 # the 'version' to '0', this is what windows 2008r2 does as RODC
1225 # This means if the object on a RWDC ever gets a invocationId
1226 # attribute, it will have version '1' (or higher), which will
1227 # will overwrite the RODC local value.
1228 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1229 "invocationId",
1232 ctx.logger.info("Setting isSynchronized and dsServiceName")
1233 m = ldb.Message()
1234 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1235 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1237 guid = ctx.ntds_guid
1238 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1239 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1240 ctx.local_samdb.modify(m)
1242 if ctx.subdomain:
1243 return
1245 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1247 ctx.logger.info("Setting up secrets database")
1248 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1249 realm=ctx.realm,
1250 dnsdomain=ctx.dnsdomain,
1251 netbiosname=ctx.myname,
1252 domainsid=ctx.domsid,
1253 machinepass=ctx.acct_pass,
1254 secure_channel_type=ctx.secure_channel_type,
1255 key_version_number=ctx.key_version_number)
1257 if ctx.dns_backend.startswith("BIND9_"):
1258 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1259 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1260 dns_backend=ctx.dns_backend,
1261 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1262 targetdir=ctx.targetdir,
1263 key_version_number=ctx.dns_key_version_number)
1265 def join_setup_trusts(ctx):
1266 """provision the local SAM."""
1268 print("Setup domain trusts with server %s" % ctx.server)
1269 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1270 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1271 ctx.lp, ctx.creds)
1273 objectAttr = lsa.ObjectAttribute()
1274 objectAttr.sec_qos = lsa.QosInfo()
1276 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1277 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1279 info = lsa.TrustDomainInfoInfoEx()
1280 info.domain_name.string = ctx.dnsdomain
1281 info.netbios_name.string = ctx.domain_name
1282 info.sid = ctx.domsid
1283 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1284 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1285 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1287 try:
1288 oldname = lsa.String()
1289 oldname.string = ctx.dnsdomain
1290 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1291 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1292 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1293 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1294 except RuntimeError:
1295 pass
1297 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1299 clear_value = drsblobs.AuthInfoClear()
1300 clear_value.size = len(password_blob)
1301 clear_value.password = password_blob
1303 clear_authentication_information = drsblobs.AuthenticationInformation()
1304 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1305 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1306 clear_authentication_information.AuthInfo = clear_value
1308 authentication_information_array = drsblobs.AuthenticationInformationArray()
1309 authentication_information_array.count = 1
1310 authentication_information_array.array = [clear_authentication_information]
1312 outgoing = drsblobs.trustAuthInOutBlob()
1313 outgoing.count = 1
1314 outgoing.current = authentication_information_array
1316 trustpass = drsblobs.trustDomainPasswords()
1317 confounder = [3] * 512
1319 for i in range(512):
1320 confounder[i] = random.randint(0, 255)
1322 trustpass.confounder = confounder
1324 trustpass.outgoing = outgoing
1325 trustpass.incoming = outgoing
1327 trustpass_blob = ndr_pack(trustpass)
1329 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1331 auth_blob = lsa.DATA_BUF2()
1332 auth_blob.size = len(encrypted_trustpass)
1333 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1335 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1336 auth_info.auth_blob = auth_blob
1338 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1339 info,
1340 auth_info,
1341 security.SEC_STD_DELETE)
1343 rec = {
1344 "dn" : "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1345 "objectclass" : "trustedDomain",
1346 "trustType" : str(info.trust_type),
1347 "trustAttributes" : str(info.trust_attributes),
1348 "trustDirection" : str(info.trust_direction),
1349 "flatname" : ctx.forest_domain_name,
1350 "trustPartner" : ctx.dnsforest,
1351 "trustAuthIncoming" : ndr_pack(outgoing),
1352 "trustAuthOutgoing" : ndr_pack(outgoing),
1353 "securityIdentifier" : ndr_pack(ctx.forestsid)
1355 ctx.local_samdb.add(rec)
1357 rec = {
1358 "dn" : "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1359 "objectclass" : "user",
1360 "userAccountControl" : str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1361 "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le'),
1362 "samAccountName" : "%s$" % ctx.forest_domain_name
1364 ctx.local_samdb.add(rec)
1367 def build_nc_lists(ctx):
1368 # nc_list is the list of naming context (NC) for which we will
1369 # replicate in and send a updateRef command to the partner DC
1371 # full_nc_list is the list of naming context (NC) we hold
1372 # read/write copies of. These are not subsets of each other.
1373 ctx.nc_list = [ ctx.config_dn, ctx.schema_dn ]
1374 ctx.full_nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
1376 if ctx.subdomain and ctx.dns_backend != "NONE":
1377 ctx.full_nc_list += [ctx.domaindns_zone]
1379 elif not ctx.subdomain:
1380 ctx.nc_list += [ctx.base_dn]
1382 if ctx.dns_backend != "NONE":
1383 ctx.nc_list += [ctx.domaindns_zone]
1384 ctx.nc_list += [ctx.forestdns_zone]
1385 ctx.full_nc_list += [ctx.domaindns_zone]
1386 ctx.full_nc_list += [ctx.forestdns_zone]
1388 def do_join(ctx):
1389 ctx.build_nc_lists()
1391 if ctx.promote_existing:
1392 ctx.promote_possible()
1393 else:
1394 ctx.cleanup_old_join()
1396 try:
1397 ctx.join_add_objects()
1398 ctx.join_provision()
1399 ctx.join_replicate()
1400 if ctx.subdomain:
1401 ctx.join_add_objects2()
1402 ctx.join_provision_own_domain()
1403 ctx.join_setup_trusts()
1405 if ctx.dns_backend != "NONE":
1406 ctx.join_add_dns_records()
1407 ctx.join_replicate_new_dns_records()
1409 ctx.join_finalise()
1410 except:
1411 try:
1412 print("Join failed - cleaning up")
1413 except IOError:
1414 pass
1415 ctx.cleanup_old_join()
1416 raise
1419 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1420 targetdir=None, domain=None, domain_critical_only=False,
1421 machinepass=None, use_ntvfs=False, dns_backend=None,
1422 promote_existing=False, plaintext_secrets=False,
1423 backend_store=None):
1424 """Join as a RODC."""
1426 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1427 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1428 promote_existing, plaintext_secrets,
1429 backend_store=backend_store)
1431 lp.set("workgroup", ctx.domain_name)
1432 logger.info("workgroup is %s" % ctx.domain_name)
1434 lp.set("realm", ctx.realm)
1435 logger.info("realm is %s" % ctx.realm)
1437 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1439 # setup some defaults for accounts that should be replicated to this RODC
1440 ctx.never_reveal_sid = [
1441 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1442 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1443 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1444 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1445 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1446 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1448 mysid = ctx.get_mysid()
1449 admin_dn = "<SID=%s>" % mysid
1450 ctx.managedby = admin_dn
1452 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1453 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1454 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1456 ctx.SPNs.extend([ "RestrictedKrbHost/%s" % ctx.myname,
1457 "RestrictedKrbHost/%s" % ctx.dnshostname ])
1459 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1460 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1461 ctx.RODC = True
1462 ctx.replica_flags |= ( drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1463 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1464 ctx.domain_replica_flags = ctx.replica_flags
1465 if domain_critical_only:
1466 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1468 ctx.do_join()
1470 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1473 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1474 targetdir=None, domain=None, domain_critical_only=False,
1475 machinepass=None, use_ntvfs=False, dns_backend=None,
1476 promote_existing=False, plaintext_secrets=False,
1477 backend_store=None):
1478 """Join as a DC."""
1479 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1480 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1481 promote_existing, plaintext_secrets,
1482 backend_store=backend_store)
1484 lp.set("workgroup", ctx.domain_name)
1485 logger.info("workgroup is %s" % ctx.domain_name)
1487 lp.set("realm", ctx.realm)
1488 logger.info("realm is %s" % ctx.realm)
1490 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1492 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1493 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1495 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1496 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1497 ctx.domain_replica_flags = ctx.replica_flags
1498 if domain_critical_only:
1499 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1501 ctx.do_join()
1502 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1504 def join_clone(logger=None, server=None, creds=None, lp=None,
1505 targetdir=None, domain=None, include_secrets=False,
1506 dns_backend="NONE"):
1507 """Creates a local clone of a remote DC."""
1508 ctx = DCCloneContext(logger, server, creds, lp, targetdir=targetdir,
1509 domain=domain, dns_backend=dns_backend,
1510 include_secrets=include_secrets)
1512 lp.set("workgroup", ctx.domain_name)
1513 logger.info("workgroup is %s" % ctx.domain_name)
1515 lp.set("realm", ctx.realm)
1516 logger.info("realm is %s" % ctx.realm)
1518 ctx.do_join()
1519 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1520 return ctx
1522 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1523 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1524 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1525 dns_backend=None, plaintext_secrets=False,
1526 backend_store=None):
1527 """Join as a DC."""
1528 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1529 targetdir, parent_domain, machinepass, use_ntvfs,
1530 dns_backend, plaintext_secrets,
1531 backend_store=backend_store)
1532 ctx.subdomain = True
1533 if adminpass is None:
1534 ctx.adminpass = samba.generate_random_password(12, 32)
1535 else:
1536 ctx.adminpass = adminpass
1537 ctx.parent_domain_name = ctx.domain_name
1538 ctx.domain_name = netbios_domain
1539 ctx.realm = dnsdomain
1540 ctx.parent_dnsdomain = ctx.dnsdomain
1541 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1542 ctx.dnsdomain = dnsdomain
1543 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1544 ctx.naming_master = ctx.get_naming_master()
1545 if ctx.naming_master != ctx.server:
1546 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1547 ctx.server = ctx.naming_master
1548 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1549 session_info=system_session(),
1550 credentials=ctx.creds, lp=ctx.lp)
1551 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1552 controls=[])
1553 ctx.server = res[0]["dnsHostName"]
1554 logger.info("DNS name of new naming master is %s" % ctx.server)
1556 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1557 ctx.forestsid = ctx.domsid
1558 ctx.domsid = security.random_sid()
1559 ctx.acct_dn = None
1560 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1561 # Windows uses 240 bytes as UTF16 so we do
1562 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1564 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1566 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1567 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1569 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1570 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1571 ctx.domain_replica_flags = ctx.replica_flags
1573 ctx.do_join()
1574 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1577 class DCCloneContext(DCJoinContext):
1578 """Clones a remote DC."""
1580 def __init__(ctx, logger=None, server=None, creds=None, lp=None,
1581 targetdir=None, domain=None, dns_backend=None,
1582 include_secrets=False):
1583 super(DCCloneContext, ctx).__init__(logger, server, creds, lp,
1584 targetdir=targetdir, domain=domain,
1585 dns_backend=dns_backend)
1587 # As we don't want to create or delete these DNs, we set them to None
1588 ctx.server_dn = None
1589 ctx.ntds_dn = None
1590 ctx.acct_dn = None
1591 ctx.myname = ctx.server.split('.')[0]
1592 ctx.ntds_guid = None
1593 ctx.rid_manager_dn = None
1595 # Save this early
1596 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
1598 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1599 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1600 if not include_secrets:
1601 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1602 ctx.domain_replica_flags = ctx.replica_flags
1604 def join_finalise(ctx):
1605 ctx.logger.info("Setting isSynchronized and dsServiceName")
1606 m = ldb.Message()
1607 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1608 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE,
1609 "isSynchronized")
1611 # We want to appear to be the server we just cloned
1612 guid = ctx.remote_dc_ntds_guid
1613 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1614 ldb.FLAG_MOD_REPLACE,
1615 "dsServiceName")
1616 ctx.local_samdb.modify(m)
1618 def do_join(ctx):
1619 ctx.build_nc_lists()
1621 # When cloning a DC, we just want to provision a DC locally, then
1622 # grab the remote DC's entire DB via DRS replication
1623 ctx.join_provision()
1624 ctx.join_replicate()
1625 ctx.join_finalise()
1628 # Used to create a renamed backup of a DC. Renaming the domain means that the
1629 # cloned/backup DC can be started without interfering with the production DC.
1630 class DCCloneAndRenameContext(DCCloneContext):
1631 """Clones a remote DC, renaming the domain along the way."""
1633 def __init__(ctx, new_base_dn, new_domain_name, new_realm, logger=None,
1634 server=None, creds=None, lp=None, targetdir=None, domain=None,
1635 dns_backend=None, include_secrets=True):
1636 super(DCCloneAndRenameContext, ctx).__init__(logger, server, creds, lp,
1637 targetdir=targetdir,
1638 domain=domain,
1639 dns_backend=dns_backend,
1640 include_secrets=include_secrets)
1641 # store the new DN (etc) that we want the cloned DB to use
1642 ctx.new_base_dn = new_base_dn
1643 ctx.new_domain_name = new_domain_name
1644 ctx.new_realm = new_realm
1646 def create_replicator(ctx, repl_creds, binding_options):
1647 """Creates a new DRS object for managing replications"""
1649 # We want to rename all the domain objects, and the simplest way to do
1650 # this is during replication. This is because the base DN of the top-
1651 # level replicated object will flow through to all the objects below it
1652 binding_str = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
1653 return drs_utils.drs_ReplicateRenamer(binding_str, ctx.lp, repl_creds,
1654 ctx.local_samdb,
1655 ctx.invocation_id,
1656 ctx.base_dn, ctx.new_base_dn)
1658 def create_non_global_lp(ctx, global_lp):
1659 '''Creates a non-global LoadParm based on the global LP's settings'''
1661 # the samba code shares a global LoadParm by default. Here we create a
1662 # new LoadParm that retains the global settings, but any changes we
1663 # make to it won't automatically affect the rest of the samba code.
1664 # The easiest way to do this is to dump the global settings to a
1665 # temporary smb.conf file, and then load the temp file into a new
1666 # non-global LoadParm
1667 fd, tmp_file = tempfile.mkstemp()
1668 global_lp.dump(False, tmp_file)
1669 local_lp = samba.param.LoadParm(filename_for_non_global_lp=tmp_file)
1670 os.remove(tmp_file)
1671 return local_lp
1673 def rename_dn(ctx, dn_str):
1674 '''Uses string substitution to replace the base DN'''
1675 old_base_dn = ctx.base_dn
1676 return re.sub('%s$' % old_base_dn, ctx.new_base_dn, dn_str)
1678 # we want to override the normal DCCloneContext's join_provision() so that
1679 # use the new domain DNs during the provision. We do this because:
1680 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1681 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1682 # we couldn't apply the renamed DRS objects during replication)
1683 def join_provision(ctx):
1684 """Provision the local (renamed) SAM."""
1686 print("Provisioning the new (renamed) domain...")
1688 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1689 # to create a new smb.conf. By default, it uses the global LoadParm to
1690 # do this, and so it would overwrite the realm/domain values globally.
1691 # We still need the global LoadParm to retain the old domain's details,
1692 # so we can connect to (and clone) the existing DC.
1693 # So, copy the global settings into a non-global LoadParm, which we can
1694 # then pass into provision(). This generates a new smb.conf correctly,
1695 # without overwriting the global realm/domain values just yet.
1696 non_global_lp = ctx.create_non_global_lp(ctx.lp)
1698 # do the provision with the new/renamed domain DN values
1699 presult = provision(ctx.logger, system_session(),
1700 targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
1701 realm=ctx.new_realm, lp=non_global_lp,
1702 rootdn=ctx.rename_dn(ctx.root_dn), domaindn=ctx.new_base_dn,
1703 schemadn=ctx.rename_dn(ctx.schema_dn),
1704 configdn=ctx.rename_dn(ctx.config_dn),
1705 domain=ctx.new_domain_name, domainsid=ctx.domsid,
1706 serverrole="active directory domain controller",
1707 dns_backend=ctx.dns_backend)
1709 print("Provision OK for renamed domain DN %s" % presult.domaindn)
1710 ctx.local_samdb = presult.samdb
1711 ctx.paths = presult.paths