samba-tool clone-dc-database: Add --backend-store-size option
[Samba.git] / python / samba / join.py
blob794f5577b419caf2b275c404442a524e485e1e5c
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
26 import samba
27 import uuid
28 from samba.ndr import ndr_pack, ndr_unpack
29 from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp
30 from samba.dsdb import DS_DOMAIN_FUNCTION_2003
31 from samba.credentials import Credentials, DONT_USE_KERBEROS
32 from samba.provision import (secretsdb_self_join, provision, provision_fill,
33 FILL_DRS, FILL_SUBDOMAIN, DEFAULTSITE)
34 from samba.provision.common import setup_path
35 from samba.schema import Schema
36 from samba import descriptor
37 from samba.net import Net
38 from samba.provision.sambadns import setup_bind9_dns
39 from samba import read_and_sub_file
40 from samba import werror
41 from base64 import b64encode
42 from samba import WERRORError, NTSTATUSError
43 from samba import sd_utils
44 from samba.dnsserver import ARecord, AAAARecord, CNameRecord
45 import logging
46 import random
47 import time
48 import re
49 import os
50 import tempfile
51 from samba.compat import text_type
52 from samba.compat import get_string
53 from samba.netcmd import CommandError
56 class DCJoinException(Exception):
58 def __init__(self, msg):
59 super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
62 class DCJoinContext(object):
63 """Perform a DC join."""
65 def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
66 netbios_name=None, targetdir=None, domain=None,
67 machinepass=None, use_ntvfs=False, dns_backend=None,
68 promote_existing=False, plaintext_secrets=False,
69 backend_store=None,
70 backend_store_size=None,
71 forced_local_samdb=None):
73 ctx.logger = logger
74 ctx.creds = creds
75 ctx.lp = lp
76 ctx.site = site
77 ctx.targetdir = targetdir
78 ctx.use_ntvfs = use_ntvfs
79 ctx.plaintext_secrets = plaintext_secrets
80 ctx.backend_store = backend_store
81 ctx.backend_store_size = backend_store_size
83 ctx.promote_existing = promote_existing
84 ctx.promote_from_dn = None
86 ctx.nc_list = []
87 ctx.full_nc_list = []
89 ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
90 ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
92 ctx.server = server
93 ctx.forced_local_samdb = forced_local_samdb
95 if forced_local_samdb:
96 ctx.samdb = forced_local_samdb
97 ctx.server = ctx.samdb.url
98 else:
99 if ctx.server:
100 # work out the DC's site (if not already specified)
101 if site is None:
102 ctx.site = ctx.find_dc_site(ctx.server)
103 else:
104 # work out the Primary DC for the domain (as well as an
105 # appropriate site for the new DC)
106 ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
107 ctx.server = ctx.find_dc(domain)
108 ctx.logger.info("Found DC %s" % ctx.server)
109 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
110 session_info=system_session(),
111 credentials=ctx.creds, lp=ctx.lp)
113 if ctx.site is None:
114 ctx.site = DEFAULTSITE
116 try:
117 ctx.samdb.search(scope=ldb.SCOPE_BASE, attrs=[])
118 except ldb.LdbError as e:
119 (enum, estr) = e.args
120 raise DCJoinException(estr)
122 ctx.base_dn = str(ctx.samdb.get_default_basedn())
123 ctx.root_dn = str(ctx.samdb.get_root_basedn())
124 ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
125 ctx.config_dn = str(ctx.samdb.get_config_basedn())
126 ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
127 ctx.forestsid = ctx.domsid
128 ctx.domain_name = ctx.get_domain_name()
129 ctx.forest_domain_name = ctx.get_forest_domain_name()
130 ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
132 ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
133 ctx.dc_dnsHostName = ctx.get_dnsHostName()
134 ctx.behavior_version = ctx.get_behavior_version()
136 if machinepass is not None:
137 ctx.acct_pass = machinepass
138 else:
139 ctx.acct_pass = samba.generate_random_machine_password(128, 255)
141 ctx.dnsdomain = ctx.samdb.domain_dns_name()
143 # the following are all dependent on the new DC's netbios_name (which
144 # we expect to always be specified, except when cloning a DC)
145 if netbios_name:
146 # work out the DNs of all the objects we will be adding
147 ctx.myname = netbios_name
148 ctx.samname = "%s$" % ctx.myname
149 ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
150 ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
151 ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
152 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
153 ctx.dnsforest = ctx.samdb.forest_dns_name()
155 topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
156 if ctx.dn_exists(topology_base):
157 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
158 else:
159 ctx.topology_dn = None
161 ctx.SPNs = ["HOST/%s" % ctx.myname,
162 "HOST/%s" % ctx.dnshostname,
163 "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest)]
165 res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
166 attrs=["rIDManagerReference"],
167 base=ctx.base_dn)
169 ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
171 ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
172 ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
174 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
175 res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
176 attrs=[],
177 base=ctx.samdb.get_partitions_dn(),
178 expression=expr)
179 if dns_backend is None:
180 ctx.dns_backend = "NONE"
181 else:
182 if len(res_domaindns) == 0:
183 ctx.dns_backend = "NONE"
184 print("NO DNS zone information found in source domain, not replicating DNS")
185 else:
186 ctx.dns_backend = dns_backend
188 ctx.realm = ctx.dnsdomain
190 ctx.tmp_samdb = None
192 ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
193 drsuapi.DRSUAPI_DRS_PER_SYNC |
194 drsuapi.DRSUAPI_DRS_GET_ANC |
195 drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
196 drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
198 # these elements are optional
199 ctx.never_reveal_sid = None
200 ctx.reveal_sid = None
201 ctx.connection_dn = None
202 ctx.RODC = False
203 ctx.krbtgt_dn = None
204 ctx.drsuapi = None
205 ctx.managedby = None
206 ctx.subdomain = False
207 ctx.adminpass = None
208 ctx.partition_dn = None
210 ctx.dns_a_dn = None
211 ctx.dns_cname_dn = None
213 # Do not normally register 127. addresses but allow override for selftest
214 ctx.force_all_ips = False
216 def del_noerror(ctx, dn, recursive=False):
217 if recursive:
218 try:
219 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
220 except Exception:
221 return
222 for r in res:
223 ctx.del_noerror(r.dn, recursive=True)
224 try:
225 ctx.samdb.delete(dn)
226 print("Deleted %s" % dn)
227 except Exception:
228 pass
230 def cleanup_old_accounts(ctx, force=False):
231 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
232 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
233 attrs=["msDS-krbTgtLink", "objectSID"])
234 if len(res) == 0:
235 return
237 if not force:
238 creds = Credentials()
239 creds.guess(ctx.lp)
240 try:
241 creds.set_machine_account(ctx.lp)
242 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
243 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
244 session_info=system_session(),
245 credentials=creds, lp=ctx.lp)
246 except:
247 pass
248 else:
249 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
250 if token_res[0]["tokenGroups"][0] \
251 == res[0]["objectSID"][0]:
252 raise DCJoinException("Not removing account %s which "
253 "looks like a Samba DC account "
254 "matching the password we already have. "
255 "To override, remove secrets.ldb and secrets.tdb"
256 % ctx.samname)
258 ctx.del_noerror(res[0].dn, recursive=True)
260 if "msDS-Krbtgtlink" in res[0]:
261 new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
262 ctx.del_noerror(ctx.new_krbtgt_dn)
264 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
265 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
266 (ldb.binary_encode("dns-%s" % ctx.myname),
267 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
268 attrs=[])
269 if res:
270 ctx.del_noerror(res[0].dn, recursive=True)
272 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
273 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
274 attrs=[])
275 if res:
276 raise DCJoinException("Not removing account %s which looks like "
277 "a Samba DNS service account but does not "
278 "have servicePrincipalName=%s" %
279 (ldb.binary_encode("dns-%s" % ctx.myname),
280 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
282 def cleanup_old_join(ctx, force=False):
283 """Remove any DNs from a previous join."""
284 # find the krbtgt link
285 if not ctx.subdomain:
286 ctx.cleanup_old_accounts(force=force)
288 if ctx.connection_dn is not None:
289 ctx.del_noerror(ctx.connection_dn)
290 if ctx.krbtgt_dn is not None:
291 ctx.del_noerror(ctx.krbtgt_dn)
292 ctx.del_noerror(ctx.ntds_dn)
293 ctx.del_noerror(ctx.server_dn, recursive=True)
294 if ctx.topology_dn:
295 ctx.del_noerror(ctx.topology_dn)
296 if ctx.partition_dn:
297 ctx.del_noerror(ctx.partition_dn)
299 if ctx.subdomain:
300 binding_options = "sign"
301 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
302 ctx.lp, ctx.creds)
304 objectAttr = lsa.ObjectAttribute()
305 objectAttr.sec_qos = lsa.QosInfo()
307 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
308 objectAttr, 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_naming_master(ctx):
402 '''get the parent domain partition DN from parent DNS name'''
403 res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
404 scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
405 if 'fSMORoleOwner' not in res[0]:
406 raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
407 try:
408 master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
409 except KeyError:
410 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
412 master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
413 return master_host
415 def get_mysid(ctx):
416 '''get the SID of the connected user. Only works with w2k8 and later,
417 so only used for RODC join'''
418 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
419 binsid = res[0]["tokenGroups"][0]
420 return get_string(ctx.samdb.schema_format_value("objectSID", binsid))
422 def dn_exists(ctx, dn):
423 '''check if a DN exists'''
424 try:
425 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
426 except ldb.LdbError as e5:
427 (enum, estr) = e5.args
428 if enum == ldb.ERR_NO_SUCH_OBJECT:
429 return False
430 raise
431 return True
433 def add_krbtgt_account(ctx):
434 '''RODCs need a special krbtgt account'''
435 print("Adding %s" % ctx.krbtgt_dn)
436 rec = {
437 "dn": ctx.krbtgt_dn,
438 "objectclass": "user",
439 "useraccountcontrol": str(samba.dsdb.UF_NORMAL_ACCOUNT |
440 samba.dsdb.UF_ACCOUNTDISABLE),
441 "showinadvancedviewonly": "TRUE",
442 "description": "krbtgt for %s" % ctx.samname}
443 ctx.samdb.add(rec, ["rodc_join:1:1"])
445 # now we need to search for the samAccountName attribute on the krbtgt DN,
446 # as this will have been magically set to the krbtgt number
447 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
448 ctx.krbtgt_name = res[0]["samAccountName"][0]
450 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
452 m = ldb.Message()
453 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
454 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
455 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
456 ctx.samdb.modify(m)
458 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
459 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
460 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
462 def drsuapi_connect(ctx):
463 '''make a DRSUAPI connection to the naming master'''
464 binding_options = "seal"
465 if ctx.lp.log_level() >= 9:
466 binding_options += ",print"
467 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
468 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
469 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
471 def create_tmp_samdb(ctx):
472 '''create a temporary samdb object for schema queries'''
473 ctx.tmp_schema = Schema(ctx.domsid,
474 schemadn=ctx.schema_dn)
475 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
476 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
477 am_rodc=False)
478 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
480 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
481 '''build a DsReplicaAttributeCtr object'''
482 r = drsuapi.DsReplicaAttribute()
483 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
484 r.value_ctr = 1
486 def DsAddEntry(ctx, recs):
487 '''add a record via the DRSUAPI DsAddEntry call'''
488 if ctx.drsuapi is None:
489 ctx.drsuapi_connect()
490 if ctx.tmp_samdb is None:
491 ctx.create_tmp_samdb()
493 objects = []
494 for rec in recs:
495 id = drsuapi.DsReplicaObjectIdentifier()
496 id.dn = rec['dn']
498 attrs = []
499 for a in rec:
500 if a == 'dn':
501 continue
502 if not isinstance(rec[a], list):
503 v = [rec[a]]
504 else:
505 v = rec[a]
506 v = [x.encode('utf8') if isinstance(x, text_type) else x for x in v]
507 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
508 attrs.append(rattr)
510 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
511 attribute_ctr.num_attributes = len(attrs)
512 attribute_ctr.attributes = attrs
514 object = drsuapi.DsReplicaObject()
515 object.identifier = id
516 object.attribute_ctr = attribute_ctr
518 list_object = drsuapi.DsReplicaObjectListItem()
519 list_object.object = object
520 objects.append(list_object)
522 req2 = drsuapi.DsAddEntryRequest2()
523 req2.first_object = objects[0]
524 prev = req2.first_object
525 for o in objects[1:]:
526 prev.next_object = o
527 prev = o
529 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
530 if level == 2:
531 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
532 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
533 raise RuntimeError("DsAddEntry failed")
534 if ctr.extended_err[0] != werror.WERR_SUCCESS:
535 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
536 raise RuntimeError("DsAddEntry failed")
537 if level == 3:
538 if ctr.err_ver != 1:
539 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
540 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
541 if ctr.err_data.info is None:
542 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
543 else:
544 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
545 ctr.err_data.info.extended_err))
546 raise RuntimeError("DsAddEntry failed")
547 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
548 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
549 raise RuntimeError("DsAddEntry failed")
551 return ctr.objects
553 def join_ntdsdsa_obj(ctx):
554 '''return the ntdsdsa object to add'''
556 print("Adding %s" % ctx.ntds_dn)
557 rec = {
558 "dn": ctx.ntds_dn,
559 "objectclass": "nTDSDSA",
560 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
561 "dMDLocation": ctx.schema_dn}
563 nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
565 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
566 rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
568 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
569 rec["msDS-HasDomainNCs"] = ctx.base_dn
571 if ctx.RODC:
572 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
573 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
574 rec["options"] = "37"
575 else:
576 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
577 rec["HasMasterNCs"] = []
578 for nc in nc_list:
579 if nc in ctx.full_nc_list:
580 rec["HasMasterNCs"].append(nc)
581 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
582 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
583 rec["options"] = "1"
584 rec["invocationId"] = ndr_pack(ctx.invocation_id)
586 return rec
588 def join_add_ntdsdsa(ctx):
589 '''add the ntdsdsa object'''
591 rec = ctx.join_ntdsdsa_obj()
592 if ctx.forced_local_samdb:
593 ctx.samdb.add(rec, controls=["relax:0"])
594 elif ctx.RODC:
595 ctx.samdb.add(rec, ["rodc_join:1:1"])
596 else:
597 ctx.DsAddEntry([rec])
599 # find the GUID of our NTDS DN
600 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
601 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
603 def join_add_objects(ctx, specified_sid=None):
604 '''add the various objects needed for the join'''
605 if ctx.acct_dn:
606 print("Adding %s" % ctx.acct_dn)
607 rec = {
608 "dn": ctx.acct_dn,
609 "objectClass": "computer",
610 "displayname": ctx.samname,
611 "samaccountname": ctx.samname,
612 "userAccountControl": str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
613 "dnshostname": ctx.dnshostname}
614 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
615 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
616 elif ctx.promote_existing:
617 rec['msDS-SupportedEncryptionTypes'] = []
618 if ctx.managedby:
619 rec["managedby"] = ctx.managedby
620 elif ctx.promote_existing:
621 rec["managedby"] = []
623 if ctx.never_reveal_sid:
624 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
625 elif ctx.promote_existing:
626 rec["msDS-NeverRevealGroup"] = []
628 if ctx.reveal_sid:
629 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
630 elif ctx.promote_existing:
631 rec["msDS-RevealOnDemandGroup"] = []
633 if specified_sid:
634 rec["objectSid"] = ndr_pack(specified_sid)
636 if ctx.promote_existing:
637 if ctx.promote_from_dn != ctx.acct_dn:
638 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
639 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
640 else:
641 controls = None
642 if specified_sid is not None:
643 controls = ["relax:0"]
644 ctx.samdb.add(rec, controls=controls)
646 if ctx.krbtgt_dn:
647 ctx.add_krbtgt_account()
649 if ctx.server_dn:
650 print("Adding %s" % ctx.server_dn)
651 rec = {
652 "dn": ctx.server_dn,
653 "objectclass": "server",
654 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
655 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
656 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
657 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
658 # windows seems to add the dnsHostName later
659 "dnsHostName": ctx.dnshostname}
661 if ctx.acct_dn:
662 rec["serverReference"] = ctx.acct_dn
664 ctx.samdb.add(rec)
666 if ctx.subdomain:
667 # the rest is done after replication
668 ctx.ntds_guid = None
669 return
671 if ctx.ntds_dn:
672 ctx.join_add_ntdsdsa()
674 # Add the Replica-Locations or RO-Replica-Locations attributes
675 # TODO Is this supposed to be for the schema partition too?
676 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
677 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
678 attrs=[],
679 base=ctx.samdb.get_partitions_dn(),
680 expression=expr), ctx.domaindns_zone)
682 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
683 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
684 attrs=[],
685 base=ctx.samdb.get_partitions_dn(),
686 expression=expr), ctx.forestdns_zone)
688 for part, zone in (domain, forest):
689 if zone not in ctx.nc_list:
690 continue
692 if len(part) == 1:
693 m = ldb.Message()
694 m.dn = part[0].dn
695 attr = "msDS-NC-Replica-Locations"
696 if ctx.RODC:
697 attr = "msDS-NC-RO-Replica-Locations"
699 m[attr] = ldb.MessageElement(ctx.ntds_dn,
700 ldb.FLAG_MOD_ADD, attr)
701 ctx.samdb.modify(m)
703 if ctx.connection_dn is not None:
704 print("Adding %s" % ctx.connection_dn)
705 rec = {
706 "dn": ctx.connection_dn,
707 "objectclass": "nTDSConnection",
708 "enabledconnection": "TRUE",
709 "options": "65",
710 "fromServer": ctx.dc_ntds_dn}
711 ctx.samdb.add(rec)
713 if ctx.acct_dn:
714 print("Adding SPNs to %s" % ctx.acct_dn)
715 m = ldb.Message()
716 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
717 for i in range(len(ctx.SPNs)):
718 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
719 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
720 ldb.FLAG_MOD_REPLACE,
721 "servicePrincipalName")
722 ctx.samdb.modify(m)
724 # The account password set operation should normally be done over
725 # LDAP. Windows 2000 DCs however allow this only with SSL
726 # connections which are hard to set up and otherwise refuse with
727 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
728 # over SAMR.
729 print("Setting account password for %s" % ctx.samname)
730 try:
731 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
732 % ldb.binary_encode(ctx.samname),
733 ctx.acct_pass,
734 force_change_at_next_login=False,
735 username=ctx.samname)
736 except ldb.LdbError as e2:
737 (num, _) = e2.args
738 if num != ldb.ERR_UNWILLING_TO_PERFORM:
739 pass
740 ctx.net.set_password(account_name=ctx.samname,
741 domain_name=ctx.domain_name,
742 newpassword=ctx.acct_pass)
744 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
745 attrs=["msDS-KeyVersionNumber",
746 "objectSID"])
747 if "msDS-KeyVersionNumber" in res[0]:
748 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
749 else:
750 ctx.key_version_number = None
752 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
753 res[0]["objectSid"][0])
755 print("Enabling account")
756 m = ldb.Message()
757 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
758 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
759 ldb.FLAG_MOD_REPLACE,
760 "userAccountControl")
761 ctx.samdb.modify(m)
763 if ctx.dns_backend.startswith("BIND9_"):
764 ctx.dnspass = samba.generate_random_password(128, 255)
766 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
767 {"DNSDOMAIN": ctx.dnsdomain,
768 "DOMAINDN": ctx.base_dn,
769 "HOSTNAME": ctx.myname,
770 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
771 "DNSNAME": ctx.dnshostname}))
772 for changetype, msg in recs:
773 assert changetype == ldb.CHANGETYPE_NONE
774 dns_acct_dn = msg["dn"]
775 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
777 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
778 del msg["clearTextPassword"]
779 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
780 del msg["isCriticalSystemObject"]
781 # Disable account until password is set
782 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
783 samba.dsdb.UF_ACCOUNTDISABLE)
784 try:
785 ctx.samdb.add(msg)
786 except ldb.LdbError as e:
787 (num, _) = e.args
788 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
789 raise
791 # The account password set operation should normally be done over
792 # LDAP. Windows 2000 DCs however allow this only with SSL
793 # connections which are hard to set up and otherwise refuse with
794 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
795 # over SAMR.
796 print("Setting account password for dns-%s" % ctx.myname)
797 try:
798 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
799 % ldb.binary_encode(ctx.myname),
800 ctx.dnspass,
801 force_change_at_next_login=False,
802 username=ctx.samname)
803 except ldb.LdbError as e3:
804 (num, _) = e3.args
805 if num != ldb.ERR_UNWILLING_TO_PERFORM:
806 raise
807 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
808 domain_name=ctx.domain_name,
809 newpassword=ctx.dnspass)
811 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
812 attrs=["msDS-KeyVersionNumber"])
813 if "msDS-KeyVersionNumber" in res[0]:
814 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
815 else:
816 ctx.dns_key_version_number = None
818 def join_add_objects2(ctx):
819 """add the various objects needed for the join, for subdomains post replication"""
821 print("Adding %s" % ctx.partition_dn)
822 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
823 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
824 rec = {
825 "dn": ctx.partition_dn,
826 "objectclass": "crossRef",
827 "objectCategory": "CN=Cross-Ref,%s" % ctx.schema_dn,
828 "nCName": ctx.base_dn,
829 "nETBIOSName": ctx.domain_name,
830 "dnsRoot": ctx.dnsdomain,
831 "trustParent": ctx.parent_partition_dn,
832 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC |samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
833 "ntSecurityDescriptor": sd_binary,
836 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
837 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
839 rec2 = ctx.join_ntdsdsa_obj()
841 objects = ctx.DsAddEntry([rec, rec2])
842 if len(objects) != 2:
843 raise DCJoinException("Expected 2 objects from DsAddEntry")
845 ctx.ntds_guid = objects[1].guid
847 print("Replicating partition DN")
848 ctx.repl.replicate(ctx.partition_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 print("Replicating NTDS DN")
855 ctx.repl.replicate(ctx.ntds_dn,
856 misc.GUID("00000000-0000-0000-0000-000000000000"),
857 ctx.ntds_guid,
858 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
859 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
861 def join_provision(ctx):
862 """Provision the local SAM."""
864 print("Calling bare provision")
866 smbconf = ctx.lp.configfile
868 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
869 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
870 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
871 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
872 serverdn=ctx.server_dn, domain=ctx.domain_name,
873 hostname=ctx.myname, domainsid=ctx.domsid,
874 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
875 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
876 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
877 plaintext_secrets=ctx.plaintext_secrets,
878 backend_store=ctx.backend_store,
879 backend_store_size=ctx.backend_store_size
881 print("Provision OK for domain DN %s" % presult.domaindn)
882 ctx.local_samdb = presult.samdb
883 ctx.lp = presult.lp
884 ctx.paths = presult.paths
885 ctx.names = presult.names
887 # Fix up the forestsid, it may be different if we are joining as a subdomain
888 ctx.names.forestsid = ctx.forestsid
890 def join_provision_own_domain(ctx):
891 """Provision the local SAM."""
893 # we now operate exclusively on the local database, which
894 # we need to reopen in order to get the newly created schema
895 # we set the transaction_index_cache_size to 200,000 to ensure it is
896 # not too small, if it's too small the performance of the join will
897 # be negatively impacted.
898 print("Reconnecting to local samdb")
899 ctx.samdb = SamDB(url=ctx.local_samdb.url,
900 options=[
901 "transaction_index_cache_size:200000"],
902 session_info=system_session(),
903 lp=ctx.local_samdb.lp,
904 global_schema=False)
905 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
906 ctx.local_samdb = ctx.samdb
908 ctx.logger.info("Finding domain GUID from ncName")
909 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
910 controls=["extended_dn:1:1", "reveal_internals:0"])
912 if 'nCName' not in res[0]:
913 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
915 try:
916 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
917 except KeyError:
918 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
920 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
922 ctx.logger.info("Calling own domain provision")
924 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
926 presult = provision_fill(ctx.local_samdb, secrets_ldb,
927 ctx.logger, ctx.names, ctx.paths,
928 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
929 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
930 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
931 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
932 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
933 print("Provision OK for domain %s" % ctx.names.dnsdomain)
935 def create_replicator(ctx, repl_creds, binding_options):
936 '''Creates a new DRS object for managing replications'''
937 return drs_utils.drs_Replicate(
938 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
939 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
941 def join_replicate(ctx):
942 """Replicate the SAM."""
944 print("Starting replication")
945 ctx.local_samdb.transaction_start()
946 try:
947 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
948 if ctx.ntds_guid is None:
949 print("Using DS_BIND_GUID_W2K3")
950 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
951 else:
952 destination_dsa_guid = ctx.ntds_guid
954 if ctx.RODC:
955 repl_creds = Credentials()
956 repl_creds.guess(ctx.lp)
957 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
958 repl_creds.set_username(ctx.samname)
959 repl_creds.set_password(ctx.acct_pass)
960 else:
961 repl_creds = ctx.creds
963 binding_options = "seal"
964 if ctx.lp.log_level() >= 9:
965 binding_options += ",print"
967 repl = ctx.create_replicator(repl_creds, binding_options)
969 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
970 destination_dsa_guid, schema=True, rodc=ctx.RODC,
971 replica_flags=ctx.replica_flags)
972 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
973 destination_dsa_guid, rodc=ctx.RODC,
974 replica_flags=ctx.replica_flags)
975 if not ctx.subdomain:
976 # Replicate first the critical object for the basedn
977 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
978 print("Replicating critical objects from the base DN of the domain")
979 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
980 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
981 destination_dsa_guid, rodc=ctx.RODC,
982 replica_flags=ctx.domain_replica_flags)
983 ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
984 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
985 destination_dsa_guid, rodc=ctx.RODC,
986 replica_flags=ctx.domain_replica_flags)
987 print("Done with always replicated NC (base, config, schema)")
989 # At this point we should already have an entry in the ForestDNS
990 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
991 # indicate that we hold a replica for this NC.
992 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
993 if nc in ctx.nc_list:
994 print("Replicating %s" % (str(nc)))
995 repl.replicate(nc, source_dsa_invocation_id,
996 destination_dsa_guid, rodc=ctx.RODC,
997 replica_flags=ctx.replica_flags)
999 if ctx.RODC:
1000 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
1001 destination_dsa_guid,
1002 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
1003 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
1004 destination_dsa_guid,
1005 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
1006 elif ctx.rid_manager_dn is not None:
1007 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
1008 try:
1009 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
1010 destination_dsa_guid,
1011 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
1012 except samba.DsExtendedError as e1:
1013 (enum, estr) = e1.args
1014 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
1015 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
1016 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
1017 else:
1018 raise
1020 ctx.repl = repl
1021 ctx.source_dsa_invocation_id = source_dsa_invocation_id
1022 ctx.destination_dsa_guid = destination_dsa_guid
1024 print("Committing SAM database")
1025 except:
1026 ctx.local_samdb.transaction_cancel()
1027 raise
1028 else:
1029 ctx.local_samdb.transaction_commit()
1031 # A large replication may have caused our LDB connection to the
1032 # remote DC to timeout, so check the connection is still alive
1033 ctx.refresh_ldb_connection()
1035 def refresh_ldb_connection(ctx):
1036 try:
1037 # query the rootDSE to check the connection
1038 ctx.samdb.search(scope=ldb.SCOPE_BASE, attrs=[])
1039 except ldb.LdbError as e:
1040 (enum, estr) = e.args
1042 # if the connection was disconnected, then reconnect
1043 if (enum == ldb.ERR_OPERATIONS_ERROR and
1044 ('NT_STATUS_CONNECTION_DISCONNECTED' in estr or
1045 'NT_STATUS_CONNECTION_RESET' in estr)):
1046 ctx.logger.warning("LDB connection disconnected. Reconnecting")
1047 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1048 session_info=system_session(),
1049 credentials=ctx.creds, lp=ctx.lp)
1050 else:
1051 raise DCJoinException(estr)
1053 def send_DsReplicaUpdateRefs(ctx, dn):
1054 r = drsuapi.DsReplicaUpdateRefsRequest1()
1055 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
1056 r.naming_context.dn = str(dn)
1057 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
1058 r.naming_context.sid = security.dom_sid("S-0-0")
1059 r.dest_dsa_guid = ctx.ntds_guid
1060 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1061 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1062 if not ctx.RODC:
1063 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1065 if ctx.drsuapi is None:
1066 ctx.drsuapi_connect()
1068 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1070 def join_add_dns_records(ctx):
1071 """Remotely Add a DNS record to the target DC. We assume that if we
1072 replicate DNS that the server holds the DNS roles and can accept
1073 updates.
1075 This avoids issues getting replication going after the DC
1076 first starts as the rest of the domain does not have to
1077 wait for samba_dnsupdate to run successfully.
1079 Specifically, we add the records implied by the DsReplicaUpdateRefs
1080 call above.
1082 We do not just run samba_dnsupdate as we want to strictly
1083 operate against the DC we just joined:
1084 - We do not want to query another DNS server
1085 - We do not want to obtain a Kerberos ticket
1086 (as the KDC we select may not be the DC we just joined,
1087 and so may not be in sync with the password we just set)
1088 - We do not wish to set the _ldap records until we have started
1089 - We do not wish to use NTLM (the --use-samba-tool mode forces
1090 NTLM)
1094 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1095 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1096 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1098 zone = ctx.dnsdomain
1099 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1100 name = ctx.myname
1101 msdcs_cname = str(ctx.ntds_guid)
1102 cname_target = "%s.%s" % (name, zone)
1103 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1105 ctx.logger.info("Adding %d remote DNS records for %s.%s" %
1106 (len(IPs), name, zone))
1108 binding_options = "sign"
1109 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1110 ctx.lp, ctx.creds)
1112 name_found = True
1114 sd_helper = sd_utils.SDUtils(ctx.samdb)
1116 change_owner_sd = security.descriptor()
1117 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1118 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1119 (str(ctx.domsid),
1120 security.DOMAIN_RID_DCS))
1122 # TODO: Remove any old records from the primary DNS name
1123 try:
1124 (buflen, res) \
1125 = dns_conn.DnssrvEnumRecords2(client_version,
1127 ctx.server,
1128 zone,
1129 name,
1130 None,
1131 dnsp.DNS_TYPE_ALL,
1132 select_flags,
1133 None,
1134 None)
1135 except WERRORError as e:
1136 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1137 name_found = False
1138 pass
1140 if name_found:
1141 for rec in res.rec:
1142 for record in rec.records:
1143 if record.wType == dnsp.DNS_TYPE_A or \
1144 record.wType == dnsp.DNS_TYPE_AAAA:
1145 # delete record
1146 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1147 del_rec_buf.rec = record
1148 try:
1149 dns_conn.DnssrvUpdateRecord2(client_version,
1151 ctx.server,
1152 zone,
1153 name,
1154 None,
1155 del_rec_buf)
1156 except WERRORError as e:
1157 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1158 pass
1159 else:
1160 raise
1162 for IP in IPs:
1163 if IP.find(':') != -1:
1164 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1165 % (name, zone, IP))
1166 rec = AAAARecord(IP)
1167 else:
1168 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1169 % (name, zone, IP))
1170 rec = ARecord(IP)
1172 # Add record
1173 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1174 add_rec_buf.rec = rec
1175 dns_conn.DnssrvUpdateRecord2(client_version,
1177 ctx.server,
1178 zone,
1179 name,
1180 add_rec_buf,
1181 None)
1183 if (len(IPs) > 0):
1184 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1185 (ctx.dns_a_dn, ldap_record) \
1186 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1187 dns_partition=domaindns_zone_dn)
1189 # Make the DC own the DNS record, not the administrator
1190 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1191 controls=["sd_flags:1:%d"
1192 % (security.SECINFO_OWNER
1193 | security.SECINFO_GROUP)])
1195 # Add record
1196 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1197 % (msdcs_cname, msdcs_zone, cname_target))
1199 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1200 rec = CNameRecord(cname_target)
1201 add_rec_buf.rec = rec
1202 dns_conn.DnssrvUpdateRecord2(client_version,
1204 ctx.server,
1205 msdcs_zone,
1206 msdcs_cname,
1207 add_rec_buf,
1208 None)
1210 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1211 (ctx.dns_cname_dn, ldap_record) \
1212 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1213 dns_partition=forestdns_zone_dn)
1215 # Make the DC own the DNS record, not the administrator
1216 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1217 controls=["sd_flags:1:%d"
1218 % (security.SECINFO_OWNER
1219 | security.SECINFO_GROUP)])
1221 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1222 "will be created samba_dnsupdate on first startup")
1224 def join_replicate_new_dns_records(ctx):
1225 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1226 if nc in ctx.nc_list:
1227 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1228 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1229 ctx.ntds_guid, rodc=ctx.RODC,
1230 replica_flags=ctx.replica_flags,
1231 full_sync=False)
1233 def join_finalise(ctx):
1234 """Finalise the join, mark us synchronised and setup secrets db."""
1236 # FIXME we shouldn't do this in all cases
1238 # If for some reasons we joined in another site than the one of
1239 # DC we just replicated from then we don't need to send the updatereplicateref
1240 # as replication between sites is time based and on the initiative of the
1241 # requesting DC
1242 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1243 for nc in ctx.nc_list:
1244 ctx.send_DsReplicaUpdateRefs(nc)
1246 if ctx.RODC:
1247 print("Setting RODC invocationId")
1248 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1249 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1250 ctx.behavior_version)
1251 m = ldb.Message()
1252 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1253 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1254 ldb.FLAG_MOD_REPLACE,
1255 "invocationId")
1256 ctx.local_samdb.modify(m)
1258 # Note: as RODC the invocationId is only stored
1259 # on the RODC itself, the other DCs never see it.
1261 # Thats is why we fix up the replPropertyMetaData stamp
1262 # for the 'invocationId' attribute, we need to change
1263 # the 'version' to '0', this is what windows 2008r2 does as RODC
1265 # This means if the object on a RWDC ever gets a invocationId
1266 # attribute, it will have version '1' (or higher), which will
1267 # will overwrite the RODC local value.
1268 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1269 "invocationId",
1272 ctx.logger.info("Setting isSynchronized and dsServiceName")
1273 m = ldb.Message()
1274 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1275 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1277 guid = ctx.ntds_guid
1278 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1279 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1280 ctx.local_samdb.modify(m)
1282 if ctx.subdomain:
1283 return
1285 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1287 ctx.logger.info("Setting up secrets database")
1288 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1289 realm=ctx.realm,
1290 dnsdomain=ctx.dnsdomain,
1291 netbiosname=ctx.myname,
1292 domainsid=ctx.domsid,
1293 machinepass=ctx.acct_pass,
1294 secure_channel_type=ctx.secure_channel_type,
1295 key_version_number=ctx.key_version_number)
1297 if ctx.dns_backend.startswith("BIND9_"):
1298 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1299 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1300 dns_backend=ctx.dns_backend,
1301 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1302 targetdir=ctx.targetdir,
1303 key_version_number=ctx.dns_key_version_number)
1305 def join_setup_trusts(ctx):
1306 """provision the local SAM."""
1308 print("Setup domain trusts with server %s" % ctx.server)
1309 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1310 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1311 ctx.lp, ctx.creds)
1313 objectAttr = lsa.ObjectAttribute()
1314 objectAttr.sec_qos = lsa.QosInfo()
1316 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1317 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1319 info = lsa.TrustDomainInfoInfoEx()
1320 info.domain_name.string = ctx.dnsdomain
1321 info.netbios_name.string = ctx.domain_name
1322 info.sid = ctx.domsid
1323 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1324 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1325 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1327 try:
1328 oldname = lsa.String()
1329 oldname.string = ctx.dnsdomain
1330 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1331 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1332 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1333 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1334 except RuntimeError:
1335 pass
1337 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1339 clear_value = drsblobs.AuthInfoClear()
1340 clear_value.size = len(password_blob)
1341 clear_value.password = password_blob
1343 clear_authentication_information = drsblobs.AuthenticationInformation()
1344 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1345 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1346 clear_authentication_information.AuthInfo = clear_value
1348 authentication_information_array = drsblobs.AuthenticationInformationArray()
1349 authentication_information_array.count = 1
1350 authentication_information_array.array = [clear_authentication_information]
1352 outgoing = drsblobs.trustAuthInOutBlob()
1353 outgoing.count = 1
1354 outgoing.current = authentication_information_array
1356 trustpass = drsblobs.trustDomainPasswords()
1357 confounder = [3] * 512
1359 for i in range(512):
1360 confounder[i] = random.randint(0, 255)
1362 trustpass.confounder = confounder
1364 trustpass.outgoing = outgoing
1365 trustpass.incoming = outgoing
1367 trustpass_blob = ndr_pack(trustpass)
1369 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1371 auth_blob = lsa.DATA_BUF2()
1372 auth_blob.size = len(encrypted_trustpass)
1373 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1375 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1376 auth_info.auth_blob = auth_blob
1378 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1379 info,
1380 auth_info,
1381 security.SEC_STD_DELETE)
1383 rec = {
1384 "dn": "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1385 "objectclass": "trustedDomain",
1386 "trustType": str(info.trust_type),
1387 "trustAttributes": str(info.trust_attributes),
1388 "trustDirection": str(info.trust_direction),
1389 "flatname": ctx.forest_domain_name,
1390 "trustPartner": ctx.dnsforest,
1391 "trustAuthIncoming": ndr_pack(outgoing),
1392 "trustAuthOutgoing": ndr_pack(outgoing),
1393 "securityIdentifier": ndr_pack(ctx.forestsid)
1395 ctx.local_samdb.add(rec)
1397 rec = {
1398 "dn": "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1399 "objectclass": "user",
1400 "userAccountControl": str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1401 "clearTextPassword": ctx.trustdom_pass.encode('utf-16-le'),
1402 "samAccountName": "%s$" % ctx.forest_domain_name
1404 ctx.local_samdb.add(rec)
1406 def build_nc_lists(ctx):
1407 # nc_list is the list of naming context (NC) for which we will
1408 # replicate in and send a updateRef command to the partner DC
1410 # full_nc_list is the list of naming context (NC) we hold
1411 # read/write copies of. These are not subsets of each other.
1412 ctx.nc_list = [ctx.config_dn, ctx.schema_dn]
1413 ctx.full_nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
1415 if ctx.subdomain and ctx.dns_backend != "NONE":
1416 ctx.full_nc_list += [ctx.domaindns_zone]
1418 elif not ctx.subdomain:
1419 ctx.nc_list += [ctx.base_dn]
1421 if ctx.dns_backend != "NONE":
1422 ctx.nc_list += [ctx.domaindns_zone]
1423 ctx.nc_list += [ctx.forestdns_zone]
1424 ctx.full_nc_list += [ctx.domaindns_zone]
1425 ctx.full_nc_list += [ctx.forestdns_zone]
1427 def do_join(ctx):
1428 ctx.build_nc_lists()
1430 if ctx.promote_existing:
1431 ctx.promote_possible()
1432 else:
1433 ctx.cleanup_old_join()
1435 try:
1436 ctx.join_add_objects()
1437 ctx.join_provision()
1438 ctx.join_replicate()
1439 if ctx.subdomain:
1440 ctx.join_add_objects2()
1441 ctx.join_provision_own_domain()
1442 ctx.join_setup_trusts()
1444 if ctx.dns_backend != "NONE":
1445 ctx.join_add_dns_records()
1446 ctx.join_replicate_new_dns_records()
1448 ctx.join_finalise()
1449 except:
1450 try:
1451 print("Join failed - cleaning up")
1452 except IOError:
1453 pass
1455 # cleanup the failed join (checking we still have a live LDB
1456 # connection to the remote DC first)
1457 ctx.refresh_ldb_connection()
1458 ctx.cleanup_old_join()
1459 raise
1462 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1463 targetdir=None, domain=None, domain_critical_only=False,
1464 machinepass=None, use_ntvfs=False, dns_backend=None,
1465 promote_existing=False, plaintext_secrets=False,
1466 backend_store=None,
1467 backend_store_size=None):
1468 """Join as a RODC."""
1470 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1471 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1472 promote_existing, plaintext_secrets,
1473 backend_store=backend_store,
1474 backend_store_size=backend_store_size)
1476 lp.set("workgroup", ctx.domain_name)
1477 logger.info("workgroup is %s" % ctx.domain_name)
1479 lp.set("realm", ctx.realm)
1480 logger.info("realm is %s" % ctx.realm)
1482 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1484 # setup some defaults for accounts that should be replicated to this RODC
1485 ctx.never_reveal_sid = [
1486 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1487 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1488 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1489 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1490 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1491 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1493 mysid = ctx.get_mysid()
1494 admin_dn = "<SID=%s>" % mysid
1495 ctx.managedby = admin_dn
1497 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1498 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1499 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1501 ctx.SPNs.extend(["RestrictedKrbHost/%s" % ctx.myname,
1502 "RestrictedKrbHost/%s" % ctx.dnshostname])
1504 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1505 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1506 ctx.RODC = True
1507 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1508 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1509 ctx.domain_replica_flags = ctx.replica_flags
1510 if domain_critical_only:
1511 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1513 ctx.do_join()
1515 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1518 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1519 targetdir=None, domain=None, domain_critical_only=False,
1520 machinepass=None, use_ntvfs=False, dns_backend=None,
1521 promote_existing=False, plaintext_secrets=False,
1522 backend_store=None,
1523 backend_store_size=None):
1524 """Join as a DC."""
1525 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1526 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1527 promote_existing, plaintext_secrets,
1528 backend_store=backend_store,
1529 backend_store_size=backend_store_size)
1531 lp.set("workgroup", ctx.domain_name)
1532 logger.info("workgroup is %s" % ctx.domain_name)
1534 lp.set("realm", ctx.realm)
1535 logger.info("realm is %s" % ctx.realm)
1537 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1539 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1540 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1542 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1543 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1544 ctx.domain_replica_flags = ctx.replica_flags
1545 if domain_critical_only:
1546 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1548 ctx.do_join()
1549 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1552 def join_clone(logger=None, server=None, creds=None, lp=None,
1553 targetdir=None, domain=None, include_secrets=False,
1554 dns_backend="NONE", backend_store=None,
1555 backend_store_size=None):
1556 """Creates a local clone of a remote DC."""
1557 ctx = DCCloneContext(logger, server, creds, lp, targetdir=targetdir,
1558 domain=domain, dns_backend=dns_backend,
1559 include_secrets=include_secrets,
1560 backend_store=backend_store,
1561 backend_store_size=backend_store_size)
1563 lp.set("workgroup", ctx.domain_name)
1564 logger.info("workgroup is %s" % ctx.domain_name)
1566 lp.set("realm", ctx.realm)
1567 logger.info("realm is %s" % ctx.realm)
1569 ctx.do_join()
1570 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1571 return ctx
1574 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1575 netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1576 netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1577 dns_backend=None, plaintext_secrets=False,
1578 backend_store=None):
1579 """Join as a DC."""
1580 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1581 targetdir, parent_domain, machinepass, use_ntvfs,
1582 dns_backend, plaintext_secrets,
1583 backend_store=backend_store)
1584 ctx.subdomain = True
1585 if adminpass is None:
1586 ctx.adminpass = samba.generate_random_password(12, 32)
1587 else:
1588 ctx.adminpass = adminpass
1589 ctx.parent_domain_name = ctx.domain_name
1590 ctx.domain_name = netbios_domain
1591 ctx.realm = dnsdomain
1592 ctx.parent_dnsdomain = ctx.dnsdomain
1593 ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1594 ctx.dnsdomain = dnsdomain
1595 ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1596 ctx.naming_master = ctx.get_naming_master()
1597 if ctx.naming_master != ctx.server:
1598 logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1599 ctx.server = ctx.naming_master
1600 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1601 session_info=system_session(),
1602 credentials=ctx.creds, lp=ctx.lp)
1603 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1604 controls=[])
1605 ctx.server = res[0]["dnsHostName"]
1606 logger.info("DNS name of new naming master is %s" % ctx.server)
1608 ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1609 ctx.forestsid = ctx.domsid
1610 ctx.domsid = security.random_sid()
1611 ctx.acct_dn = None
1612 ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1613 # Windows uses 240 bytes as UTF16 so we do
1614 ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1616 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1618 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1619 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1621 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1622 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1623 ctx.domain_replica_flags = ctx.replica_flags
1625 ctx.do_join()
1626 ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1629 class DCCloneContext(DCJoinContext):
1630 """Clones a remote DC."""
1632 def __init__(ctx, logger=None, server=None, creds=None, lp=None,
1633 targetdir=None, domain=None, dns_backend=None,
1634 include_secrets=False, backend_store=None,
1635 backend_store_size=None):
1636 super(DCCloneContext, ctx).__init__(logger, server, creds, lp,
1637 targetdir=targetdir, domain=domain,
1638 dns_backend=dns_backend,
1639 backend_store=backend_store,
1640 backend_store_size=backend_store_size)
1642 # As we don't want to create or delete these DNs, we set them to None
1643 ctx.server_dn = None
1644 ctx.ntds_dn = None
1645 ctx.acct_dn = None
1646 ctx.myname = ctx.server.split('.')[0]
1647 ctx.ntds_guid = None
1648 ctx.rid_manager_dn = None
1650 # Save this early
1651 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
1653 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1654 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1655 if not include_secrets:
1656 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1657 ctx.domain_replica_flags = ctx.replica_flags
1659 def join_finalise(ctx):
1660 ctx.logger.info("Setting isSynchronized and dsServiceName")
1661 m = ldb.Message()
1662 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1663 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE,
1664 "isSynchronized")
1666 # We want to appear to be the server we just cloned
1667 guid = ctx.remote_dc_ntds_guid
1668 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1669 ldb.FLAG_MOD_REPLACE,
1670 "dsServiceName")
1671 ctx.local_samdb.modify(m)
1673 def do_join(ctx):
1674 ctx.build_nc_lists()
1676 # When cloning a DC, we just want to provision a DC locally, then
1677 # grab the remote DC's entire DB via DRS replication
1678 ctx.join_provision()
1679 ctx.join_replicate()
1680 ctx.join_finalise()
1683 # Used to create a renamed backup of a DC. Renaming the domain means that the
1684 # cloned/backup DC can be started without interfering with the production DC.
1685 class DCCloneAndRenameContext(DCCloneContext):
1686 """Clones a remote DC, renaming the domain along the way."""
1688 def __init__(ctx, new_base_dn, new_domain_name, new_realm, logger=None,
1689 server=None, creds=None, lp=None, targetdir=None, domain=None,
1690 dns_backend=None, include_secrets=True, backend_store=None):
1691 super(DCCloneAndRenameContext, ctx).__init__(logger, server, creds, lp,
1692 targetdir=targetdir,
1693 domain=domain,
1694 dns_backend=dns_backend,
1695 include_secrets=include_secrets,
1696 backend_store=backend_store)
1697 # store the new DN (etc) that we want the cloned DB to use
1698 ctx.new_base_dn = new_base_dn
1699 ctx.new_domain_name = new_domain_name
1700 ctx.new_realm = new_realm
1702 def create_replicator(ctx, repl_creds, binding_options):
1703 """Creates a new DRS object for managing replications"""
1705 # We want to rename all the domain objects, and the simplest way to do
1706 # this is during replication. This is because the base DN of the top-
1707 # level replicated object will flow through to all the objects below it
1708 binding_str = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
1709 return drs_utils.drs_ReplicateRenamer(binding_str, ctx.lp, repl_creds,
1710 ctx.local_samdb,
1711 ctx.invocation_id,
1712 ctx.base_dn, ctx.new_base_dn)
1714 def create_non_global_lp(ctx, global_lp):
1715 '''Creates a non-global LoadParm based on the global LP's settings'''
1717 # the samba code shares a global LoadParm by default. Here we create a
1718 # new LoadParm that retains the global settings, but any changes we
1719 # make to it won't automatically affect the rest of the samba code.
1720 # The easiest way to do this is to dump the global settings to a
1721 # temporary smb.conf file, and then load the temp file into a new
1722 # non-global LoadParm
1723 fd, tmp_file = tempfile.mkstemp()
1724 global_lp.dump(False, tmp_file)
1725 local_lp = samba.param.LoadParm(filename_for_non_global_lp=tmp_file)
1726 os.remove(tmp_file)
1727 return local_lp
1729 def rename_dn(ctx, dn_str):
1730 '''Uses string substitution to replace the base DN'''
1731 old_base_dn = ctx.base_dn
1732 return re.sub('%s$' % old_base_dn, ctx.new_base_dn, dn_str)
1734 # we want to override the normal DCCloneContext's join_provision() so that
1735 # use the new domain DNs during the provision. We do this because:
1736 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1737 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1738 # we couldn't apply the renamed DRS objects during replication)
1739 def join_provision(ctx):
1740 """Provision the local (renamed) SAM."""
1742 print("Provisioning the new (renamed) domain...")
1744 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1745 # to create a new smb.conf. By default, it uses the global LoadParm to
1746 # do this, and so it would overwrite the realm/domain values globally.
1747 # We still need the global LoadParm to retain the old domain's details,
1748 # so we can connect to (and clone) the existing DC.
1749 # So, copy the global settings into a non-global LoadParm, which we can
1750 # then pass into provision(). This generates a new smb.conf correctly,
1751 # without overwriting the global realm/domain values just yet.
1752 non_global_lp = ctx.create_non_global_lp(ctx.lp)
1754 # do the provision with the new/renamed domain DN values
1755 presult = provision(ctx.logger, system_session(),
1756 targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
1757 realm=ctx.new_realm, lp=non_global_lp,
1758 rootdn=ctx.rename_dn(ctx.root_dn), domaindn=ctx.new_base_dn,
1759 schemadn=ctx.rename_dn(ctx.schema_dn),
1760 configdn=ctx.rename_dn(ctx.config_dn),
1761 domain=ctx.new_domain_name, domainsid=ctx.domsid,
1762 serverrole="active directory domain controller",
1763 dns_backend=ctx.dns_backend,
1764 backend_store=ctx.backend_store)
1766 print("Provision OK for renamed domain DN %s" % presult.domaindn)
1767 ctx.local_samdb = presult.samdb
1768 ctx.paths = presult.paths