auth/credentials: don't ignore "client use kerberos" and --use-kerberos for machine...
[Samba.git] / python / samba / join.py
blob7b09445a52adb3e878e1b70393115fe20844df87
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 """Joining a domain."""
21 from samba.auth import system_session
22 from samba.samdb import SamDB
23 from samba import gensec, Ldb, drs_utils, arcfour_encrypt
24 import ldb
25 import samba
26 import uuid
27 from samba.ndr import ndr_pack, ndr_unpack
28 from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp
29 from samba.credentials import Credentials, DONT_USE_KERBEROS
30 from samba.provision import (secretsdb_self_join, provision, provision_fill,
31 FILL_DRS, FILL_SUBDOMAIN, DEFAULTSITE)
32 from samba.provision.common import setup_path
33 from samba.schema import Schema
34 from samba import descriptor
35 from samba.net import Net
36 from samba.provision.sambadns import setup_bind9_dns
37 from samba import read_and_sub_file
38 from samba import werror
39 from base64 import b64encode
40 from samba import WERRORError, NTSTATUSError
41 from samba import sd_utils
42 from samba.dnsserver import ARecord, AAAARecord, CNAMERecord
43 import random
44 import time
45 import re
46 import os
47 import tempfile
48 from collections import OrderedDict
49 from samba.common import get_string
50 from samba.netcmd import CommandError
51 from samba import dsdb, functional_level
54 class DCJoinException(Exception):
56 def __init__(self, msg):
57 super().__init__("Can't join, error: %s" % msg)
60 class DCJoinContext(object):
61 """Perform a DC join."""
63 def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
64 netbios_name=None, targetdir=None, domain=None,
65 machinepass=None, use_ntvfs=False, dns_backend=None,
66 promote_existing=False, plaintext_secrets=False,
67 backend_store=None,
68 backend_store_size=None,
69 forced_local_samdb=None):
71 ctx.logger = logger
72 ctx.creds = creds
73 ctx.lp = lp
74 ctx.site = site
75 ctx.targetdir = targetdir
76 ctx.use_ntvfs = use_ntvfs
77 ctx.plaintext_secrets = plaintext_secrets
78 ctx.backend_store = backend_store
79 ctx.backend_store_size = backend_store_size
81 ctx.promote_existing = promote_existing
82 ctx.promote_from_dn = None
84 ctx.nc_list = []
85 ctx.full_nc_list = []
87 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
88 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
90 ctx.server = server
91 ctx.forced_local_samdb = forced_local_samdb
93 if forced_local_samdb:
94 ctx.samdb = forced_local_samdb
95 ctx.server = ctx.samdb.url
96 else:
97 if ctx.server:
98 # work out the DC's site (if not already specified)
99 if site is None:
100 ctx.site = ctx.find_dc_site(ctx.server)
101 else:
102 # work out the Primary DC for the domain (as well as an
103 # appropriate site for the new DC)
104 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
105 ctx.server = ctx.find_dc(domain)
106 ctx.logger.info("Found DC %s" % ctx.server)
107 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
108 session_info=system_session(),
109 credentials=ctx.creds, lp=ctx.lp)
111 if ctx.site is None:
112 ctx.site = DEFAULTSITE
114 try:
115 ctx.samdb.search(scope=ldb.SCOPE_BASE, attrs=[])
116 except ldb.LdbError as e:
117 (enum, estr) = e.args
118 raise DCJoinException(estr)
120 ctx.base_dn = str(ctx.samdb.get_default_basedn())
121 ctx.root_dn = str(ctx.samdb.get_root_basedn())
122 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
123 ctx.config_dn = str(ctx.samdb.get_config_basedn())
124 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
125 ctx.forestsid = ctx.domsid
126 ctx.domain_name = ctx.get_domain_name()
127 ctx.forest_domain_name = ctx.get_forest_domain_name()
128 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
130 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
131 ctx.dc_dnsHostName = ctx.get_dnsHostName()
132 ctx.behavior_version = ctx.get_behavior_version()
134 if machinepass is not None:
135 ctx.acct_pass = machinepass
136 else:
137 ctx.acct_pass = samba.generate_random_machine_password(120, 120)
139 ctx.dnsdomain = ctx.samdb.domain_dns_name()
141 # the following are all dependent on the new DC's netbios_name (which
142 # we expect to always be specified, except when cloning a DC)
143 if netbios_name:
144 # work out the DNs of all the objects we will be adding
145 ctx.myname = netbios_name
146 ctx.samname = "%s$" % ctx.myname
147 ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
148 ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
149 ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
150 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
151 ctx.dnsforest = ctx.samdb.forest_dns_name()
153 topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
154 if ctx.dn_exists(topology_base):
155 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
156 else:
157 ctx.topology_dn = None
159 ctx.SPNs = ["HOST/%s" % ctx.myname,
160 "HOST/%s" % ctx.dnshostname,
161 "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest)]
163 res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
164 attrs=["rIDManagerReference"],
165 base=ctx.base_dn)
167 ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
169 ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
170 ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
172 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
173 res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
174 attrs=[],
175 base=ctx.samdb.get_partitions_dn(),
176 expression=expr)
177 if dns_backend is None:
178 ctx.dns_backend = "NONE"
179 else:
180 if len(res_domaindns) == 0:
181 ctx.dns_backend = "NONE"
182 print("NO DNS zone information found in source domain, not replicating DNS")
183 else:
184 ctx.dns_backend = dns_backend
186 ctx.realm = ctx.dnsdomain
188 ctx.tmp_samdb = None
190 ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
191 drsuapi.DRSUAPI_DRS_PER_SYNC |
192 drsuapi.DRSUAPI_DRS_GET_ANC |
193 drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
194 drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
196 # these elements are optional
197 ctx.never_reveal_sid = None
198 ctx.reveal_sid = None
199 ctx.connection_dn = None
200 ctx.RODC = False
201 ctx.krbtgt_dn = None
202 ctx.drsuapi = None
203 ctx.managedby = None
204 ctx.subdomain = False
205 ctx.adminpass = None
206 ctx.partition_dn = None
208 ctx.dns_a_dn = None
209 ctx.dns_cname_dn = None
211 # Do not normally register 127. addresses but allow override for selftest
212 ctx.force_all_ips = False
214 def del_noerror(ctx, dn, recursive=False):
215 if recursive:
216 try:
217 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
218 except Exception:
219 return
220 for r in res:
221 ctx.del_noerror(r.dn, recursive=True)
222 try:
223 ctx.samdb.delete(dn)
224 print("Deleted %s" % dn)
225 except Exception:
226 pass
228 def cleanup_old_accounts(ctx, force=False):
229 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
230 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
231 attrs=["msDS-krbTgtLink", "objectSID"])
232 if len(res) == 0:
233 return
235 if not force:
236 creds = Credentials()
237 creds.guess(ctx.lp)
238 try:
239 creds.set_machine_account(ctx.lp)
240 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
241 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
242 session_info=system_session(),
243 credentials=creds, lp=ctx.lp)
244 except:
245 pass
246 else:
247 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
248 if token_res[0]["tokenGroups"][0] \
249 == res[0]["objectSID"][0]:
250 raise DCJoinException("Not removing account %s which "
251 "looks like a Samba DC account "
252 "matching the password we already have. "
253 "To override, remove secrets.ldb and secrets.tdb"
254 % ctx.samname)
256 ctx.del_noerror(res[0].dn, recursive=True)
258 krbtgt_dn = res[0].get('msDS-KrbTgtLink', idx=0)
259 if krbtgt_dn is not None:
260 ctx.new_krbtgt_dn = krbtgt_dn
261 ctx.del_noerror(ctx.new_krbtgt_dn)
263 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
264 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
265 (ldb.binary_encode("dns-%s" % ctx.myname),
266 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
267 attrs=[])
268 if res:
269 ctx.del_noerror(res[0].dn, recursive=True)
271 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
272 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
273 attrs=[])
274 if res:
275 raise DCJoinException("Not removing account %s which looks like "
276 "a Samba DNS service account but does not "
277 "have servicePrincipalName=%s" %
278 (ldb.binary_encode("dns-%s" % ctx.myname),
279 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
281 def cleanup_old_join(ctx, force=False):
282 """Remove any DNs from a previous join."""
283 # find the krbtgt link
284 if not ctx.subdomain:
285 ctx.cleanup_old_accounts(force=force)
287 if ctx.connection_dn is not None:
288 ctx.del_noerror(ctx.connection_dn)
289 if ctx.krbtgt_dn is not None:
290 ctx.del_noerror(ctx.krbtgt_dn)
291 ctx.del_noerror(ctx.ntds_dn)
292 ctx.del_noerror(ctx.server_dn, recursive=True)
293 if ctx.topology_dn:
294 ctx.del_noerror(ctx.topology_dn)
295 if ctx.partition_dn:
296 ctx.del_noerror(ctx.partition_dn)
298 if ctx.subdomain:
299 binding_options = "sign"
300 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
301 ctx.lp, ctx.creds)
303 objectAttr = lsa.ObjectAttribute()
304 objectAttr.sec_qos = lsa.QosInfo()
306 pol_handle = lsaconn.OpenPolicy2('',
307 objectAttr,
308 security.SEC_FLAG_MAXIMUM_ALLOWED)
310 name = lsa.String()
311 name.string = ctx.realm
312 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
314 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
316 name = lsa.String()
317 name.string = ctx.forest_domain_name
318 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
320 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
322 if ctx.dns_a_dn:
323 ctx.del_noerror(ctx.dns_a_dn)
325 if ctx.dns_cname_dn:
326 ctx.del_noerror(ctx.dns_cname_dn)
328 def promote_possible(ctx):
329 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
330 if ctx.subdomain:
331 # This shouldn't happen
332 raise Exception("Can not promote into a subdomain")
334 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
335 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
336 attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
337 if len(res) == 0:
338 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
339 if "msDS-KrbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
340 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
341 if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
342 samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
343 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
345 ctx.promote_from_dn = res[0].dn
347 def find_dc(ctx, domain):
348 """find a writeable DC for the given domain"""
349 try:
350 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
351 except NTSTATUSError as error:
352 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
353 (domain, error.args[1]))
354 except Exception:
355 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
356 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
357 ctx.site = ctx.cldap_ret.client_site
358 return ctx.cldap_ret.pdc_dns_name
360 def find_dc_site(ctx, server):
361 site = None
362 cldap_ret = ctx.net.finddc(address=server,
363 flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
364 if cldap_ret.client_site is not None and cldap_ret.client_site != "":
365 site = cldap_ret.client_site
366 return site
368 def get_behavior_version(ctx):
369 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
370 if "msDS-Behavior-Version" in res[0]:
371 return int(res[0]["msDS-Behavior-Version"][0])
372 else:
373 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
375 def get_dnsHostName(ctx):
376 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
377 return str(res[0]["dnsHostName"][0])
379 def get_domain_name(ctx):
380 """get netbios name of the domain from the partitions record"""
381 partitions_dn = ctx.samdb.get_partitions_dn()
382 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
383 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
384 return str(res[0]["nETBIOSName"][0])
386 def get_forest_domain_name(ctx):
387 """get netbios name of the domain from the partitions record"""
388 partitions_dn = ctx.samdb.get_partitions_dn()
389 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
390 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
391 return str(res[0]["nETBIOSName"][0])
393 def get_parent_partition_dn(ctx):
394 """get the parent domain partition DN from parent DNS name"""
395 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
396 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
397 (ldb.binary_encode(ctx.parent_dnsdomain),
398 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
399 return str(res[0].dn)
401 def get_mysid(ctx):
402 """get the SID of the connected user. Only works with w2k8 and later,
403 so only used for RODC join"""
404 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
405 binsid = res[0]["tokenGroups"][0]
406 return get_string(ctx.samdb.schema_format_value("objectSID", binsid))
408 def dn_exists(ctx, dn):
409 """check if a DN exists"""
410 try:
411 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
412 except ldb.LdbError as e5:
413 (enum, estr) = e5.args
414 if enum == ldb.ERR_NO_SUCH_OBJECT:
415 return False
416 raise
417 return True
419 def add_krbtgt_account(ctx):
420 """RODCs need a special krbtgt account"""
421 print("Adding %s" % ctx.krbtgt_dn)
422 rec = {
423 "dn": ctx.krbtgt_dn,
424 "objectclass": "user",
425 "useraccountcontrol": str(samba.dsdb.UF_NORMAL_ACCOUNT |
426 samba.dsdb.UF_ACCOUNTDISABLE),
427 "showinadvancedviewonly": "TRUE",
428 "description": "krbtgt for %s" % ctx.samname}
429 ctx.samdb.add(rec, ["rodc_join:1:1"])
431 # now we need to search for the samAccountName attribute on the krbtgt DN,
432 # as this will have been magically set to the krbtgt number
433 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
434 ctx.krbtgt_name = res[0]["samAccountName"][0]
436 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
438 m = ldb.Message()
439 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
440 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
441 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
442 ctx.samdb.modify(m)
444 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
445 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
446 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
448 def drsuapi_connect(ctx):
449 """make a DRSUAPI connection to the naming master"""
450 binding_options = "seal"
451 if ctx.lp.log_level() >= 9:
452 binding_options += ",print"
453 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
454 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
455 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
457 def create_tmp_samdb(ctx):
458 """create a temporary samdb object for schema queries"""
459 ctx.tmp_schema = Schema(ctx.domsid,
460 schemadn=ctx.schema_dn)
461 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
462 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
463 am_rodc=False)
464 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
466 def DsAddEntry(ctx, recs):
467 """add a record via the DRSUAPI DsAddEntry call"""
468 if ctx.drsuapi is None:
469 ctx.drsuapi_connect()
470 if ctx.tmp_samdb is None:
471 ctx.create_tmp_samdb()
473 objects = []
474 for rec in recs:
475 id = drsuapi.DsReplicaObjectIdentifier()
476 id.dn = rec['dn']
478 attrs = []
479 for a in rec:
480 if a == 'dn':
481 continue
482 if not isinstance(rec[a], list):
483 v = [rec[a]]
484 else:
485 v = rec[a]
486 v = [x.encode('utf8') if isinstance(x, str) else x for x in v]
487 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
488 attrs.append(rattr)
490 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
491 attribute_ctr.num_attributes = len(attrs)
492 attribute_ctr.attributes = attrs
494 object = drsuapi.DsReplicaObject()
495 object.identifier = id
496 object.attribute_ctr = attribute_ctr
498 list_object = drsuapi.DsReplicaObjectListItem()
499 list_object.object = object
500 objects.append(list_object)
502 req2 = drsuapi.DsAddEntryRequest2()
503 req2.first_object = objects[0]
504 prev = req2.first_object
505 for o in objects[1:]:
506 prev.next_object = o
507 prev = o
509 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
510 if level == 2:
511 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
512 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
513 raise RuntimeError("DsAddEntry failed")
514 if ctr.extended_err[0] != werror.WERR_SUCCESS:
515 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
516 raise RuntimeError("DsAddEntry failed")
517 if level == 3:
518 if ctr.err_ver != 1:
519 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
520 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
521 if ctr.err_data.info is None:
522 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
523 else:
524 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
525 ctr.err_data.info.extended_err))
526 raise RuntimeError("DsAddEntry failed")
527 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
528 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
529 raise RuntimeError("DsAddEntry failed")
531 return ctr.objects
533 def join_ntdsdsa_obj(ctx):
534 """return the ntdsdsa object to add"""
536 print("Adding %s" % ctx.ntds_dn)
538 # When joining Windows, the order of certain attributes (mostly only
539 # msDS-HasMasterNCs and HasMasterNCs) seems to matter
540 rec = OrderedDict([
541 ("dn", ctx.ntds_dn),
542 ("objectclass", "nTDSDSA"),
543 ("systemFlags", str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE)),
544 ("dMDLocation", ctx.schema_dn)])
546 nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
548 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
549 # This allows an override via smb.conf or --option using
550 # "ad dc functional level" to make us seem like 2016 to
551 # join such a domain for (say) a migration, or to test the
552 # partially implemented 2016 support.
553 domainControllerFunctionality = functional_level.dc_level_from_lp(ctx.lp)
554 rec["msDS-Behavior-Version"] = str(domainControllerFunctionality)
556 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
557 rec["msDS-HasDomainNCs"] = ctx.base_dn
559 if ctx.RODC:
560 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
561 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
562 rec["options"] = "37"
563 else:
564 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
566 # Note that Windows seems to have an undocumented requirement that
567 # the msDS-HasMasterNCs attribute occurs before HasMasterNCs
568 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
569 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
571 rec["HasMasterNCs"] = []
572 for nc in nc_list:
573 if nc in ctx.full_nc_list:
574 rec["HasMasterNCs"].append(nc)
576 rec["options"] = "1"
577 rec["invocationId"] = ndr_pack(ctx.invocation_id)
579 return rec
581 def join_add_ntdsdsa(ctx):
582 """add the ntdsdsa object"""
584 rec = ctx.join_ntdsdsa_obj()
585 if ctx.forced_local_samdb:
586 ctx.samdb.add(rec, controls=["relax:0"])
587 elif ctx.RODC:
588 ctx.samdb.add(rec, ["rodc_join:1:1"])
589 else:
590 ctx.DsAddEntry([rec])
592 # find the GUID of our NTDS DN
593 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
594 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
596 def join_add_objects(ctx, specified_sid=None):
597 """add the various objects needed for the join"""
598 if ctx.acct_dn:
599 print("Adding %s" % ctx.acct_dn)
600 rec = {
601 "dn": ctx.acct_dn,
602 "objectClass": "computer",
603 "displayname": ctx.samname,
604 "samaccountname": ctx.samname,
605 "userAccountControl": str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
606 "dnshostname": ctx.dnshostname}
607 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
608 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
609 elif ctx.promote_existing:
610 rec['msDS-SupportedEncryptionTypes'] = []
611 if ctx.managedby:
612 rec["managedby"] = ctx.managedby
613 elif ctx.promote_existing:
614 rec["managedby"] = []
616 if ctx.never_reveal_sid:
617 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
618 elif ctx.promote_existing:
619 rec["msDS-NeverRevealGroup"] = []
621 if ctx.reveal_sid:
622 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
623 elif ctx.promote_existing:
624 rec["msDS-RevealOnDemandGroup"] = []
626 if specified_sid:
627 rec["objectSid"] = ndr_pack(specified_sid)
629 if ctx.promote_existing:
630 if ctx.promote_from_dn != ctx.acct_dn:
631 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
632 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
633 else:
634 controls = None
635 if specified_sid is not None:
636 controls = ["relax:0"]
637 ctx.samdb.add(rec, controls=controls)
639 if ctx.krbtgt_dn:
640 ctx.add_krbtgt_account()
642 if ctx.server_dn:
643 print("Adding %s" % ctx.server_dn)
644 rec = {
645 "dn": ctx.server_dn,
646 "objectclass": "server",
647 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
648 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
649 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
650 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
651 # windows seems to add the dnsHostName later
652 "dnsHostName": ctx.dnshostname}
654 if ctx.acct_dn:
655 rec["serverReference"] = ctx.acct_dn
657 ctx.samdb.add(rec)
659 if ctx.subdomain:
660 # the rest is done after replication
661 ctx.ntds_guid = None
662 return
664 if ctx.ntds_dn:
665 ctx.join_add_ntdsdsa()
667 # Add the Replica-Locations or RO-Replica-Locations attributes
668 # TODO Is this supposed to be for the schema partition too?
669 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
670 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
671 attrs=[],
672 base=ctx.samdb.get_partitions_dn(),
673 expression=expr), ctx.domaindns_zone)
675 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
676 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
677 attrs=[],
678 base=ctx.samdb.get_partitions_dn(),
679 expression=expr), ctx.forestdns_zone)
681 for part, zone in (domain, forest):
682 if zone not in ctx.nc_list:
683 continue
685 if len(part) == 1:
686 m = ldb.Message()
687 m.dn = part[0].dn
688 attr = "msDS-NC-Replica-Locations"
689 if ctx.RODC:
690 attr = "msDS-NC-RO-Replica-Locations"
692 m[attr] = ldb.MessageElement(ctx.ntds_dn,
693 ldb.FLAG_MOD_ADD, attr)
694 ctx.samdb.modify(m)
696 if ctx.connection_dn is not None:
697 print("Adding %s" % ctx.connection_dn)
698 rec = {
699 "dn": ctx.connection_dn,
700 "objectclass": "nTDSConnection",
701 "enabledconnection": "TRUE",
702 "options": "65",
703 "fromServer": ctx.dc_ntds_dn}
704 ctx.samdb.add(rec)
706 if ctx.acct_dn:
707 print("Adding SPNs to %s" % ctx.acct_dn)
708 m = ldb.Message()
709 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
710 for i in range(len(ctx.SPNs)):
711 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
712 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
713 ldb.FLAG_MOD_REPLACE,
714 "servicePrincipalName")
715 ctx.samdb.modify(m)
717 # The account password set operation should normally be done over
718 # LDAP. Windows 2000 DCs however allow this only with SSL
719 # connections which are hard to set up and otherwise refuse with
720 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
721 # over SAMR.
722 print("Setting account password for %s" % ctx.samname)
723 try:
724 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
725 % ldb.binary_encode(ctx.samname),
726 ctx.acct_pass,
727 force_change_at_next_login=False,
728 username=ctx.samname)
729 except ldb.LdbError as e2:
730 (num, _) = e2.args
731 if num != ldb.ERR_UNWILLING_TO_PERFORM:
732 raise
733 ctx.net.set_password(account_name=ctx.samname,
734 domain_name=ctx.domain_name,
735 newpassword=ctx.acct_pass)
737 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
738 attrs=["msDS-KeyVersionNumber",
739 "objectSID"])
740 if "msDS-KeyVersionNumber" in res[0]:
741 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
742 else:
743 ctx.key_version_number = None
745 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
746 res[0]["objectSid"][0])
748 print("Enabling account")
749 m = ldb.Message()
750 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
751 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
752 ldb.FLAG_MOD_REPLACE,
753 "userAccountControl")
754 ctx.samdb.modify(m)
756 if ctx.dns_backend.startswith("BIND9_"):
757 ctx.dnspass = samba.generate_random_password(128, 255)
759 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
760 {"DNSDOMAIN": ctx.dnsdomain,
761 "DOMAINDN": ctx.base_dn,
762 "HOSTNAME": ctx.myname,
763 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
764 "DNSNAME": ctx.dnshostname}))
765 for changetype, msg in recs:
766 assert changetype == ldb.CHANGETYPE_NONE
767 dns_acct_dn = msg["dn"]
768 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
770 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
771 del msg["clearTextPassword"]
772 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
773 del msg["isCriticalSystemObject"]
774 # Disable account until password is set
775 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
776 samba.dsdb.UF_ACCOUNTDISABLE)
777 try:
778 ctx.samdb.add(msg)
779 except ldb.LdbError as e:
780 (num, _) = e.args
781 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
782 raise
784 # The account password set operation should normally be done over
785 # LDAP. Windows 2000 DCs however allow this only with SSL
786 # connections which are hard to set up and otherwise refuse with
787 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
788 # over SAMR.
789 print("Setting account password for dns-%s" % ctx.myname)
790 try:
791 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
792 % ldb.binary_encode(ctx.myname),
793 ctx.dnspass,
794 force_change_at_next_login=False,
795 username=ctx.samname)
796 except ldb.LdbError as e3:
797 (num, _) = e3.args
798 if num != ldb.ERR_UNWILLING_TO_PERFORM:
799 raise
800 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
801 domain_name=ctx.domain_name,
802 newpassword=ctx.dnspass)
804 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
805 attrs=["msDS-KeyVersionNumber"])
806 if "msDS-KeyVersionNumber" in res[0]:
807 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
808 else:
809 ctx.dns_key_version_number = None
811 def join_add_objects2(ctx):
812 """add the various objects needed for the join, for subdomains post replication"""
814 print("Adding %s" % ctx.partition_dn)
815 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
816 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
817 rec = {
818 "dn": ctx.partition_dn,
819 "objectclass": "crossRef",
820 "objectCategory": "CN=Cross-Ref,%s" % ctx.schema_dn,
821 "nCName": ctx.base_dn,
822 "nETBIOSName": ctx.domain_name,
823 "dnsRoot": ctx.dnsdomain,
824 "trustParent": ctx.parent_partition_dn,
825 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC |samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
826 "ntSecurityDescriptor": sd_binary,
829 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
830 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
832 rec2 = ctx.join_ntdsdsa_obj()
834 objects = ctx.DsAddEntry([rec, rec2])
835 if len(objects) != 2:
836 raise DCJoinException("Expected 2 objects from DsAddEntry")
838 ctx.ntds_guid = objects[1].guid
840 print("Replicating partition DN")
841 ctx.repl.replicate(ctx.partition_dn,
842 misc.GUID("00000000-0000-0000-0000-000000000000"),
843 ctx.ntds_guid,
844 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
845 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
847 print("Replicating NTDS DN")
848 ctx.repl.replicate(ctx.ntds_dn,
849 misc.GUID("00000000-0000-0000-0000-000000000000"),
850 ctx.ntds_guid,
851 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
852 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
854 def join_provision(ctx):
855 """Provision the local SAM."""
857 print("Calling bare provision")
859 smbconf = ctx.lp.configfile
861 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
862 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
863 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
864 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
865 serverdn=ctx.server_dn, domain=ctx.domain_name,
866 hostname=ctx.myname, domainsid=ctx.domsid,
867 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
868 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
869 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
870 plaintext_secrets=ctx.plaintext_secrets,
871 backend_store=ctx.backend_store,
872 backend_store_size=ctx.backend_store_size,
873 batch_mode=True)
874 print("Provision OK for domain DN %s" % presult.domaindn)
875 ctx.local_samdb = presult.samdb
876 ctx.lp = presult.lp
877 ctx.paths = presult.paths
878 ctx.names = presult.names
880 # Fix up the forestsid, it may be different if we are joining as a subdomain
881 ctx.names.forestsid = ctx.forestsid
883 def join_provision_own_domain(ctx):
884 """Provision the local SAM."""
886 # we now operate exclusively on the local database, which
887 # we need to reopen in order to get the newly created schema
888 # we set the transaction_index_cache_size to 200,000 to ensure it is
889 # not too small, if it's too small the performance of the join will
890 # be negatively impacted.
891 print("Reconnecting to local samdb")
892 ctx.samdb = SamDB(url=ctx.local_samdb.url,
893 options=[
894 "transaction_index_cache_size:200000"],
895 session_info=system_session(),
896 lp=ctx.local_samdb.lp,
897 global_schema=False)
898 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
899 ctx.local_samdb = ctx.samdb
901 ctx.logger.info("Finding domain GUID from ncName")
902 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
903 controls=["extended_dn:1:1", "reveal_internals:0"])
905 if 'nCName' not in res[0]:
906 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
908 try:
909 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
910 except KeyError:
911 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
913 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
915 ctx.logger.info("Calling own domain provision")
917 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
919 provision_fill(ctx.local_samdb, secrets_ldb,
920 ctx.logger, ctx.names, ctx.paths,
921 dom_for_fun_level=ctx.behavior_version,
922 samdb_fill=FILL_SUBDOMAIN,
923 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
924 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
925 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
927 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2012:
928 adprep_level = ctx.behavior_version
930 updates_allowed_overridden = False
931 if ctx.lp.get("dsdb:schema update allowed") is None:
932 ctx.lp.set("dsdb:schema update allowed", "yes")
933 print("Temporarily overriding 'dsdb:schema update allowed' setting")
934 updates_allowed_overridden = True
936 ctx.samdb.transaction_start()
937 try:
938 from samba.domain_update import DomainUpdate
940 domain = DomainUpdate(ctx.local_samdb, fix=True)
941 domain.check_updates_functional_level(adprep_level,
942 samba.dsdb.DS_DOMAIN_FUNCTION_2008,
943 update_revision=True)
945 ctx.samdb.transaction_commit()
946 except Exception as e:
947 ctx.samdb.transaction_cancel()
948 raise DCJoinException("DomainUpdate() failed: %s" % e)
950 if updates_allowed_overridden:
951 ctx.lp.set("dsdb:schema update allowed", "no")
953 print("Provision OK for domain %s" % ctx.names.dnsdomain)
955 def create_replicator(ctx, repl_creds, binding_options):
956 """Creates a new DRS object for managing replications"""
957 return drs_utils.drs_Replicate(
958 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
959 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
961 def join_replicate(ctx):
962 """Replicate the SAM."""
964 ctx.logger.info("Starting replication")
966 # A global transaction is started so that linked attributes
967 # are applied at the very end, once all partitions are
968 # replicated. This helps get all cross-partition links.
969 ctx.local_samdb.transaction_start()
970 try:
971 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
972 if ctx.ntds_guid is None:
973 print("Using DS_BIND_GUID_W2K3")
974 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
975 else:
976 destination_dsa_guid = ctx.ntds_guid
978 if ctx.RODC:
979 repl_creds = Credentials()
980 repl_creds.guess(ctx.lp)
981 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
982 repl_creds.set_username(ctx.samname)
983 repl_creds.set_password(ctx.acct_pass)
984 else:
985 repl_creds = ctx.creds
987 binding_options = "seal"
988 if ctx.lp.log_level() >= 9:
989 binding_options += ",print"
991 repl = ctx.create_replicator(repl_creds, binding_options)
993 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
994 destination_dsa_guid, schema=True, rodc=ctx.RODC,
995 replica_flags=ctx.replica_flags)
996 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
997 destination_dsa_guid, rodc=ctx.RODC,
998 replica_flags=ctx.replica_flags)
999 if not ctx.subdomain:
1000 # Replicate first the critical objects for the basedn
1002 # We do this to match Windows. The default case is to
1003 # do a critical objects replication, then a second
1004 # with all objects.
1006 print("Replicating critical objects from the base DN of the domain")
1007 try:
1008 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
1009 destination_dsa_guid, rodc=ctx.RODC,
1010 replica_flags=ctx.domain_replica_flags | drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
1011 except WERRORError as e:
1013 if e.args[0] == werror.WERR_DS_DRA_MISSING_PARENT:
1014 ctx.logger.warning("First pass of replication with "
1015 "DRSUAPI_DRS_CRITICAL_ONLY "
1016 "not possible due to a missing parent object. "
1017 "This is typical of a Samba "
1018 "4.5 or earlier server. "
1019 "We will replicate all the objects instead.")
1020 else:
1021 raise
1023 # Now replicate all the objects in the domain (unless
1024 # we were run with --critical-only).
1026 # Doing the replication of users as a second pass
1027 # matches more closely the Windows behaviour, which is
1028 # actually to do this on first startup.
1030 # Use --critical-only if you want that (but you don't
1031 # really, it is better to see any errors here).
1032 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
1033 try:
1034 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
1035 destination_dsa_guid, rodc=ctx.RODC,
1036 replica_flags=ctx.domain_replica_flags)
1037 except WERRORError as e:
1039 if e.args[0] == werror.WERR_DS_DRA_MISSING_PARENT and \
1040 ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
1041 ctx.logger.warning("Replication with DRSUAPI_DRS_CRITICAL_ONLY "
1042 "failed due to a missing parent object. "
1043 "This may be a Samba 4.5 or earlier server "
1044 "and is not compatible with --critical-only")
1045 raise
1047 print("Done with always replicated NC (base, config, schema)")
1049 # At this point we should already have an entry in the ForestDNS
1050 # and DomainDNS NC (those under CN=Partitions,DC=...) in order to
1051 # indicate that we hold a replica for this NC.
1052 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1053 if nc in ctx.nc_list:
1054 print("Replicating %s" % (str(nc)))
1055 repl.replicate(nc, source_dsa_invocation_id,
1056 destination_dsa_guid, rodc=ctx.RODC,
1057 replica_flags=ctx.replica_flags)
1059 if ctx.RODC:
1060 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
1061 destination_dsa_guid,
1062 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
1063 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
1064 destination_dsa_guid,
1065 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
1066 elif ctx.rid_manager_dn is not None:
1067 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
1068 try:
1069 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
1070 destination_dsa_guid,
1071 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
1072 except samba.DsExtendedError as e1:
1073 (enum, estr) = e1.args
1074 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
1075 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
1076 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
1077 else:
1078 raise
1080 ctx.repl = repl
1081 ctx.source_dsa_invocation_id = source_dsa_invocation_id
1082 ctx.destination_dsa_guid = destination_dsa_guid
1084 ctx.logger.info("Committing SAM database - this may take some time")
1085 except:
1086 ctx.local_samdb.transaction_cancel()
1087 raise
1088 else:
1090 # This is a special case, we have completed a full
1091 # replication so if a link comes to us that points to a
1092 # deleted object, and we asked for all objects already, we
1093 # just have to ignore it, the chance to re-try the
1094 # replication with GET_TGT has long gone. This can happen
1095 # if the object is deleted and sent to us after the link
1096 # was sent, as we are processing all links in the
1097 # transaction_commit().
1098 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
1099 ctx.local_samdb.set_opaque(dsdb.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME,
1101 ctx.local_samdb.transaction_commit()
1102 ctx.local_samdb.set_opaque(dsdb.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME,
1104 ctx.logger.info("Committed SAM database")
1106 # A large replication may have caused our LDB connection to the
1107 # remote DC to timeout, so check the connection is still alive
1108 ctx.refresh_ldb_connection()
1110 def refresh_ldb_connection(ctx):
1111 try:
1112 # query the rootDSE to check the connection
1113 ctx.samdb.search(scope=ldb.SCOPE_BASE, attrs=[])
1114 except ldb.LdbError as e:
1115 (enum, estr) = e.args
1117 # if the connection was disconnected, then reconnect
1118 if (enum == ldb.ERR_OPERATIONS_ERROR and
1119 ('NT_STATUS_CONNECTION_DISCONNECTED' in estr or
1120 'NT_STATUS_CONNECTION_RESET' in estr)):
1121 ctx.logger.warning("LDB connection disconnected. Reconnecting")
1122 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1123 session_info=system_session(),
1124 credentials=ctx.creds, lp=ctx.lp)
1125 else:
1126 raise DCJoinException(estr)
1128 def send_DsReplicaUpdateRefs(ctx, dn):
1129 r = drsuapi.DsReplicaUpdateRefsRequest1()
1130 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
1131 r.naming_context.dn = str(dn)
1132 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
1133 r.naming_context.sid = security.dom_sid("S-0-0")
1134 r.dest_dsa_guid = ctx.ntds_guid
1135 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1136 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1137 if not ctx.RODC:
1138 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1140 if ctx.drsuapi is None:
1141 ctx.drsuapi_connect()
1143 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1145 def join_add_dns_records(ctx):
1146 """Remotely Add a DNS record to the target DC. We assume that if we
1147 replicate DNS that the server holds the DNS roles and can accept
1148 updates.
1150 This avoids issues getting replication going after the DC
1151 first starts as the rest of the domain does not have to
1152 wait for samba_dnsupdate to run successfully.
1154 Specifically, we add the records implied by the DsReplicaUpdateRefs
1155 call above.
1157 We do not just run samba_dnsupdate as we want to strictly
1158 operate against the DC we just joined:
1159 - We do not want to query another DNS server
1160 - We do not want to obtain a Kerberos ticket
1161 (as the KDC we select may not be the DC we just joined,
1162 and so may not be in sync with the password we just set)
1163 - We do not wish to set the _ldap records until we have started
1164 - We do not wish to use NTLM (the --use-samba-tool mode forces
1165 NTLM)
1169 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1170 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1171 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1173 zone = ctx.dnsdomain
1174 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1175 name = ctx.myname
1176 msdcs_cname = str(ctx.ntds_guid)
1177 cname_target = "%s.%s" % (name, zone)
1178 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1180 ctx.logger.info("Adding %d remote DNS records for %s.%s" %
1181 (len(IPs), name, zone))
1183 binding_options = "sign"
1184 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1185 ctx.lp, ctx.creds)
1187 name_found = True
1189 sd_helper = sd_utils.SDUtils(ctx.samdb)
1191 change_owner_sd = security.descriptor()
1192 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1193 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1194 (str(ctx.domsid),
1195 security.DOMAIN_RID_DCS))
1197 # TODO: Remove any old records from the primary DNS name
1198 try:
1199 (buflen, res) \
1200 = dns_conn.DnssrvEnumRecords2(client_version,
1202 ctx.server,
1203 zone,
1204 name,
1205 None,
1206 dnsp.DNS_TYPE_ALL,
1207 select_flags,
1208 None,
1209 None)
1210 except WERRORError as e:
1211 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1212 name_found = False
1214 if name_found:
1215 for rec in res.rec:
1216 for record in rec.records:
1217 if record.wType == dnsp.DNS_TYPE_A or \
1218 record.wType == dnsp.DNS_TYPE_AAAA:
1219 # delete record
1220 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1221 del_rec_buf.rec = record
1222 try:
1223 dns_conn.DnssrvUpdateRecord2(client_version,
1225 ctx.server,
1226 zone,
1227 name,
1228 None,
1229 del_rec_buf)
1230 except WERRORError as e:
1231 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1232 pass
1233 else:
1234 raise
1236 for IP in IPs:
1237 if IP.find(':') != -1:
1238 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1239 % (name, zone, IP))
1240 rec = AAAARecord(IP)
1241 else:
1242 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1243 % (name, zone, IP))
1244 rec = ARecord(IP)
1246 # Add record
1247 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1248 add_rec_buf.rec = rec
1249 dns_conn.DnssrvUpdateRecord2(client_version,
1251 ctx.server,
1252 zone,
1253 name,
1254 add_rec_buf,
1255 None)
1257 if (len(IPs) > 0):
1258 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1259 (ctx.dns_a_dn, ldap_record) \
1260 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1261 dns_partition=domaindns_zone_dn)
1263 # Make the DC own the DNS record, not the administrator
1264 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1265 controls=["sd_flags:1:%d"
1266 % (security.SECINFO_OWNER
1267 | security.SECINFO_GROUP)])
1269 # Add record
1270 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1271 % (msdcs_cname, msdcs_zone, cname_target))
1273 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1274 rec = CNAMERecord(cname_target)
1275 add_rec_buf.rec = rec
1276 dns_conn.DnssrvUpdateRecord2(client_version,
1278 ctx.server,
1279 msdcs_zone,
1280 msdcs_cname,
1281 add_rec_buf,
1282 None)
1284 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1285 (ctx.dns_cname_dn, ldap_record) \
1286 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1287 dns_partition=forestdns_zone_dn)
1289 # Make the DC own the DNS record, not the administrator
1290 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1291 controls=["sd_flags:1:%d"
1292 % (security.SECINFO_OWNER
1293 | security.SECINFO_GROUP)])
1295 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1296 "will be created samba_dnsupdate on first startup")
1298 def join_replicate_new_dns_records(ctx):
1299 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1300 if nc in ctx.nc_list:
1301 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1302 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1303 ctx.ntds_guid, rodc=ctx.RODC,
1304 replica_flags=ctx.replica_flags,
1305 full_sync=False)
1307 def join_finalise(ctx):
1308 """Finalise the join, mark us synchronised and setup secrets db."""
1310 # FIXME we shouldn't do this in all cases
1312 # If for some reasons we joined in another site than the one of
1313 # DC we just replicated from then we don't need to send the updatereplicateref
1314 # as replication between sites is time based and on the initiative of the
1315 # requesting DC
1316 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1317 for nc in ctx.nc_list:
1318 ctx.send_DsReplicaUpdateRefs(nc)
1320 if ctx.RODC:
1321 print("Setting RODC invocationId")
1322 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1323 ctx.local_samdb.set_opaque("domainFunctionality",
1324 ctx.behavior_version)
1325 m = ldb.Message()
1326 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1327 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1328 ldb.FLAG_MOD_REPLACE,
1329 "invocationId")
1330 ctx.local_samdb.modify(m)
1332 # Note: as RODC the invocationId is only stored
1333 # on the RODC itself, the other DCs never see it.
1335 # That's is why we fix up the replPropertyMetaData stamp
1336 # for the 'invocationId' attribute, we need to change
1337 # the 'version' to '0', this is what windows 2008r2 does as RODC
1339 # This means if the object on a RWDC ever gets a invocationId
1340 # attribute, it will have version '1' (or higher), which will
1341 # will overwrite the RODC local value.
1342 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1343 "invocationId",
1346 ctx.logger.info("Setting isSynchronized and dsServiceName")
1347 m = ldb.Message()
1348 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1349 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1351 guid = ctx.ntds_guid
1352 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1353 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1354 ctx.local_samdb.modify(m)
1356 if ctx.subdomain:
1357 return
1359 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1361 ctx.logger.info("Setting up secrets database")
1362 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1363 realm=ctx.realm,
1364 dnsdomain=ctx.dnsdomain,
1365 netbiosname=ctx.myname,
1366 domainsid=ctx.domsid,
1367 machinepass=ctx.acct_pass,
1368 secure_channel_type=ctx.secure_channel_type,
1369 key_version_number=ctx.key_version_number)
1371 if ctx.dns_backend.startswith("BIND9_"):
1372 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1373 ctx.names, ctx.paths, ctx.logger,
1374 dns_backend=ctx.dns_backend,
1375 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1376 key_version_number=ctx.dns_key_version_number)
1378 def join_setup_trusts(ctx):
1379 """provision the local SAM."""
1381 print("Setup domain trusts with server %s" % ctx.server)
1382 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1383 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1384 ctx.lp, ctx.creds)
1386 objectAttr = lsa.ObjectAttribute()
1387 objectAttr.sec_qos = lsa.QosInfo()
1389 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1390 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1392 info = lsa.TrustDomainInfoInfoEx()
1393 info.domain_name.string = ctx.dnsdomain
1394 info.netbios_name.string = ctx.domain_name
1395 info.sid = ctx.domsid
1396 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1397 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1398 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1400 try:
1401 oldname = lsa.String()
1402 oldname.string = ctx.dnsdomain
1403 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1404 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1405 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1406 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1407 except RuntimeError:
1408 pass
1410 password_blob = list(ctx.trustdom_pass.encode('utf-16-le'))
1412 clear_value = drsblobs.AuthInfoClear()
1413 clear_value.size = len(password_blob)
1414 clear_value.password = password_blob
1416 clear_authentication_information = drsblobs.AuthenticationInformation()
1417 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1418 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1419 clear_authentication_information.AuthInfo = clear_value
1421 authentication_information_array = drsblobs.AuthenticationInformationArray()
1422 authentication_information_array.count = 1
1423 authentication_information_array.array = [clear_authentication_information]
1425 outgoing = drsblobs.trustAuthInOutBlob()
1426 outgoing.count = 1
1427 outgoing.current = authentication_information_array
1429 trustpass = drsblobs.trustDomainPasswords()
1430 confounder = [3] * 512
1432 for i in range(512):
1433 confounder[i] = random.randint(0, 255)
1435 trustpass.confounder = confounder
1437 trustpass.outgoing = outgoing
1438 trustpass.incoming = outgoing
1440 trustpass_blob = ndr_pack(trustpass)
1442 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1444 auth_blob = lsa.DATA_BUF2()
1445 auth_blob.size = len(encrypted_trustpass)
1446 auth_blob.data = list(encrypted_trustpass)
1448 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1449 auth_info.auth_blob = auth_blob
1451 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1452 info,
1453 auth_info,
1454 security.SEC_STD_DELETE)
1456 rec = {
1457 "dn": "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1458 "objectclass": "trustedDomain",
1459 "trustType": str(info.trust_type),
1460 "trustAttributes": str(info.trust_attributes),
1461 "trustDirection": str(info.trust_direction),
1462 "flatname": ctx.forest_domain_name,
1463 "trustPartner": ctx.dnsforest,
1464 "trustAuthIncoming": ndr_pack(outgoing),
1465 "trustAuthOutgoing": ndr_pack(outgoing),
1466 "securityIdentifier": ndr_pack(ctx.forestsid)
1468 ctx.local_samdb.add(rec)
1470 rec = {
1471 "dn": "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1472 "objectclass": "user",
1473 "userAccountControl": str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1474 "clearTextPassword": ctx.trustdom_pass.encode('utf-16-le'),
1475 "samAccountName": "%s$" % ctx.forest_domain_name
1477 ctx.local_samdb.add(rec)
1479 def build_nc_lists(ctx):
1480 # nc_list is the list of naming context (NC) for which we will
1481 # replicate in and send a updateRef command to the partner DC
1483 # full_nc_list is the list of naming context (NC) we hold
1484 # read/write copies of. These are not subsets of each other.
1485 ctx.nc_list = [ctx.config_dn, ctx.schema_dn]
1486 ctx.full_nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
1488 if ctx.subdomain and ctx.dns_backend != "NONE":
1489 ctx.full_nc_list += [ctx.domaindns_zone]
1491 elif not ctx.subdomain:
1492 ctx.nc_list += [ctx.base_dn]
1494 if ctx.dns_backend != "NONE":
1495 ctx.nc_list += [ctx.domaindns_zone]
1496 ctx.nc_list += [ctx.forestdns_zone]
1497 ctx.full_nc_list += [ctx.domaindns_zone]
1498 ctx.full_nc_list += [ctx.forestdns_zone]
1500 def do_join(ctx):
1501 ctx.build_nc_lists()
1503 if ctx.promote_existing:
1504 ctx.promote_possible()
1505 else:
1506 ctx.cleanup_old_join()
1508 try:
1509 ctx.join_add_objects()
1510 ctx.join_provision()
1511 ctx.join_replicate()
1512 if ctx.subdomain:
1513 ctx.join_add_objects2()
1514 ctx.join_provision_own_domain()
1515 ctx.join_setup_trusts()
1517 if ctx.dns_backend != "NONE":
1518 ctx.join_add_dns_records()
1519 ctx.join_replicate_new_dns_records()
1521 ctx.join_finalise()
1522 except:
1523 try:
1524 print("Join failed - cleaning up")
1525 except IOError:
1526 pass
1528 # cleanup the failed join (checking we still have a live LDB
1529 # connection to the remote DC first)
1530 ctx.refresh_ldb_connection()
1531 ctx.cleanup_old_join()
1532 raise
1535 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1536 targetdir=None, domain=None, domain_critical_only=False,
1537 machinepass=None, use_ntvfs=False, dns_backend=None,
1538 promote_existing=False, plaintext_secrets=False,
1539 backend_store=None,
1540 backend_store_size=None):
1541 """Join as a RODC."""
1543 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1544 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1545 promote_existing, plaintext_secrets,
1546 backend_store=backend_store,
1547 backend_store_size=backend_store_size)
1549 lp.set("workgroup", ctx.domain_name)
1550 logger.info("workgroup is %s" % ctx.domain_name)
1552 lp.set("realm", ctx.realm)
1553 logger.info("realm is %s" % ctx.realm)
1555 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1557 # setup some defaults for accounts that should be replicated to this RODC
1558 ctx.never_reveal_sid = [
1559 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1560 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1561 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1562 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1563 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1564 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1566 mysid = ctx.get_mysid()
1567 admin_dn = "<SID=%s>" % mysid
1568 ctx.managedby = admin_dn
1570 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1571 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1572 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1574 ctx.SPNs.extend(["RestrictedKrbHost/%s" % ctx.myname,
1575 "RestrictedKrbHost/%s" % ctx.dnshostname])
1577 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1578 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1579 ctx.RODC = True
1580 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1581 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1582 ctx.domain_replica_flags = ctx.replica_flags
1583 if domain_critical_only:
1584 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1586 ctx.do_join()
1588 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1591 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1592 targetdir=None, domain=None, domain_critical_only=False,
1593 machinepass=None, use_ntvfs=False, dns_backend=None,
1594 promote_existing=False, plaintext_secrets=False,
1595 backend_store=None,
1596 backend_store_size=None):
1597 """Join as a DC."""
1598 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1599 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1600 promote_existing, plaintext_secrets,
1601 backend_store=backend_store,
1602 backend_store_size=backend_store_size)
1604 lp.set("workgroup", ctx.domain_name)
1605 logger.info("workgroup is %s" % ctx.domain_name)
1607 lp.set("realm", ctx.realm)
1608 logger.info("realm is %s" % ctx.realm)
1610 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1612 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1613 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1615 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1616 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1617 ctx.domain_replica_flags = ctx.replica_flags
1618 if domain_critical_only:
1619 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1621 ctx.do_join()
1622 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1625 def join_clone(logger=None, server=None, creds=None, lp=None,
1626 targetdir=None, domain=None, include_secrets=False,
1627 dns_backend="NONE", backend_store=None,
1628 backend_store_size=None):
1629 """Creates a local clone of a remote DC."""
1630 ctx = DCCloneContext(logger, server, creds, lp, targetdir=targetdir,
1631 domain=domain, dns_backend=dns_backend,
1632 include_secrets=include_secrets,
1633 backend_store=backend_store,
1634 backend_store_size=backend_store_size)
1636 lp.set("workgroup", ctx.domain_name)
1637 logger.info("workgroup is %s" % ctx.domain_name)
1639 lp.set("realm", ctx.realm)
1640 logger.info("realm is %s" % ctx.realm)
1642 ctx.do_join()
1643 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1644 return ctx
1647 class DCCloneContext(DCJoinContext):
1648 """Clones a remote DC."""
1650 def __init__(ctx, logger=None, server=None, creds=None, lp=None,
1651 targetdir=None, domain=None, dns_backend=None,
1652 include_secrets=False, backend_store=None,
1653 backend_store_size=None):
1654 super().__init__(logger, server, creds, lp,
1655 targetdir=targetdir, domain=domain,
1656 dns_backend=dns_backend,
1657 backend_store=backend_store,
1658 backend_store_size=backend_store_size)
1660 # As we don't want to create or delete these DNs, we set them to None
1661 ctx.server_dn = None
1662 ctx.ntds_dn = None
1663 ctx.acct_dn = None
1664 ctx.myname = ctx.server.split('.')[0]
1665 ctx.ntds_guid = None
1666 ctx.rid_manager_dn = None
1668 # Save this early
1669 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
1671 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1672 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1673 if not include_secrets:
1674 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1675 ctx.domain_replica_flags = ctx.replica_flags
1677 def join_finalise(ctx):
1678 ctx.logger.info("Setting isSynchronized and dsServiceName")
1679 m = ldb.Message()
1680 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1681 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE,
1682 "isSynchronized")
1684 # We want to appear to be the server we just cloned
1685 guid = ctx.remote_dc_ntds_guid
1686 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1687 ldb.FLAG_MOD_REPLACE,
1688 "dsServiceName")
1689 ctx.local_samdb.modify(m)
1691 def do_join(ctx):
1692 ctx.build_nc_lists()
1694 # When cloning a DC, we just want to provision a DC locally, then
1695 # grab the remote DC's entire DB via DRS replication
1696 ctx.join_provision()
1697 ctx.join_replicate()
1698 ctx.join_finalise()
1701 # Used to create a renamed backup of a DC. Renaming the domain means that the
1702 # cloned/backup DC can be started without interfering with the production DC.
1703 class DCCloneAndRenameContext(DCCloneContext):
1704 """Clones a remote DC, renaming the domain along the way."""
1706 def __init__(ctx, new_base_dn, new_domain_name, new_realm, logger=None,
1707 server=None, creds=None, lp=None, targetdir=None, domain=None,
1708 dns_backend=None, include_secrets=True, backend_store=None):
1709 super().__init__(logger, server, creds, lp,
1710 targetdir=targetdir,
1711 domain=domain,
1712 dns_backend=dns_backend,
1713 include_secrets=include_secrets,
1714 backend_store=backend_store)
1715 # store the new DN (etc) that we want the cloned DB to use
1716 ctx.new_base_dn = new_base_dn
1717 ctx.new_domain_name = new_domain_name
1718 ctx.new_realm = new_realm
1720 def create_replicator(ctx, repl_creds, binding_options):
1721 """Creates a new DRS object for managing replications"""
1723 # We want to rename all the domain objects, and the simplest way to do
1724 # this is during replication. This is because the base DN of the top-
1725 # level replicated object will flow through to all the objects below it
1726 binding_str = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
1727 return drs_utils.drs_ReplicateRenamer(binding_str, ctx.lp, repl_creds,
1728 ctx.local_samdb,
1729 ctx.invocation_id,
1730 ctx.base_dn, ctx.new_base_dn)
1732 def create_non_global_lp(ctx, global_lp):
1733 """Creates a non-global LoadParm based on the global LP's settings"""
1735 # the samba code shares a global LoadParm by default. Here we create a
1736 # new LoadParm that retains the global settings, but any changes we
1737 # make to it won't automatically affect the rest of the samba code.
1738 # The easiest way to do this is to dump the global settings to a
1739 # temporary smb.conf file, and then load the temp file into a new
1740 # non-global LoadParm
1741 fd, tmp_file = tempfile.mkstemp()
1742 global_lp.dump(False, tmp_file)
1743 local_lp = samba.param.LoadParm(filename_for_non_global_lp=tmp_file)
1744 os.remove(tmp_file)
1745 return local_lp
1747 def rename_dn(ctx, dn_str):
1748 """Uses string substitution to replace the base DN"""
1749 old_base_dn = ctx.base_dn
1750 return re.sub('%s$' % old_base_dn, ctx.new_base_dn, dn_str)
1752 # we want to override the normal DCCloneContext's join_provision() so that
1753 # use the new domain DNs during the provision. We do this because:
1754 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1755 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1756 # we couldn't apply the renamed DRS objects during replication)
1757 def join_provision(ctx):
1758 """Provision the local (renamed) SAM."""
1760 print("Provisioning the new (renamed) domain...")
1762 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1763 # to create a new smb.conf. By default, it uses the global LoadParm to
1764 # do this, and so it would overwrite the realm/domain values globally.
1765 # We still need the global LoadParm to retain the old domain's details,
1766 # so we can connect to (and clone) the existing DC.
1767 # So, copy the global settings into a non-global LoadParm, which we can
1768 # then pass into provision(). This generates a new smb.conf correctly,
1769 # without overwriting the global realm/domain values just yet.
1770 non_global_lp = ctx.create_non_global_lp(ctx.lp)
1772 # do the provision with the new/renamed domain DN values
1773 presult = provision(ctx.logger, system_session(),
1774 targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
1775 realm=ctx.new_realm, lp=non_global_lp,
1776 rootdn=ctx.rename_dn(ctx.root_dn), domaindn=ctx.new_base_dn,
1777 schemadn=ctx.rename_dn(ctx.schema_dn),
1778 configdn=ctx.rename_dn(ctx.config_dn),
1779 domain=ctx.new_domain_name, domainsid=ctx.domsid,
1780 serverrole="active directory domain controller",
1781 dns_backend=ctx.dns_backend,
1782 backend_store=ctx.backend_store)
1784 print("Provision OK for renamed domain DN %s" % presult.domaindn)
1785 ctx.local_samdb = presult.samdb
1786 ctx.paths = presult.paths