smbd: Don't set security_descriptor_hash_v4->time
[Samba.git] / python / samba / join.py
blobe453aab11980f9294ec5e6ff469cc629ef556e55
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, string_to_byte_array
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.dsdb import DS_DOMAIN_FUNCTION_2003
30 from samba.credentials import Credentials, DONT_USE_KERBEROS
31 from samba.provision import (secretsdb_self_join, provision, provision_fill,
32 FILL_DRS, FILL_SUBDOMAIN, DEFAULTSITE)
33 from samba.provision.common import setup_path
34 from samba.schema import Schema
35 from samba import descriptor
36 from samba.net import Net
37 from samba.provision.sambadns import setup_bind9_dns
38 from samba import read_and_sub_file
39 from samba import werror
40 from base64 import b64encode
41 from samba import WERRORError, NTSTATUSError
42 from samba import sd_utils
43 from samba.dnsserver import ARecord, AAAARecord, CNAMERecord
44 import logging
45 import random
46 import time
47 import re
48 import os
49 import tempfile
50 from collections import OrderedDict
51 from samba.common import get_string
52 from samba.netcmd import CommandError
53 from samba import dsdb, functional_level
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(120, 120)
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 krbtgt_dn = res[0].get('msDS-KrbTgtLink', idx=0)
261 if krbtgt_dn is not None:
262 ctx.new_krbtgt_dn = krbtgt_dn
263 ctx.del_noerror(ctx.new_krbtgt_dn)
265 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
266 expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
267 (ldb.binary_encode("dns-%s" % ctx.myname),
268 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
269 attrs=[])
270 if res:
271 ctx.del_noerror(res[0].dn, recursive=True)
273 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
274 expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
275 attrs=[])
276 if res:
277 raise DCJoinException("Not removing account %s which looks like "
278 "a Samba DNS service account but does not "
279 "have servicePrincipalName=%s" %
280 (ldb.binary_encode("dns-%s" % ctx.myname),
281 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
283 def cleanup_old_join(ctx, force=False):
284 """Remove any DNs from a previous join."""
285 # find the krbtgt link
286 if not ctx.subdomain:
287 ctx.cleanup_old_accounts(force=force)
289 if ctx.connection_dn is not None:
290 ctx.del_noerror(ctx.connection_dn)
291 if ctx.krbtgt_dn is not None:
292 ctx.del_noerror(ctx.krbtgt_dn)
293 ctx.del_noerror(ctx.ntds_dn)
294 ctx.del_noerror(ctx.server_dn, recursive=True)
295 if ctx.topology_dn:
296 ctx.del_noerror(ctx.topology_dn)
297 if ctx.partition_dn:
298 ctx.del_noerror(ctx.partition_dn)
300 if ctx.subdomain:
301 binding_options = "sign"
302 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
303 ctx.lp, ctx.creds)
305 objectAttr = lsa.ObjectAttribute()
306 objectAttr.sec_qos = lsa.QosInfo()
308 pol_handle = lsaconn.OpenPolicy2('',
309 objectAttr,
310 security.SEC_FLAG_MAXIMUM_ALLOWED)
312 name = lsa.String()
313 name.string = ctx.realm
314 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
316 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
318 name = lsa.String()
319 name.string = ctx.forest_domain_name
320 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
322 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
324 if ctx.dns_a_dn:
325 ctx.del_noerror(ctx.dns_a_dn)
327 if ctx.dns_cname_dn:
328 ctx.del_noerror(ctx.dns_cname_dn)
330 def promote_possible(ctx):
331 """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
332 if ctx.subdomain:
333 # This shouldn't happen
334 raise Exception("Can not promote into a subdomain")
336 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
337 expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
338 attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
339 if len(res) == 0:
340 raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
341 if "msDS-KrbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
342 raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
343 if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
344 samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
345 raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
347 ctx.promote_from_dn = res[0].dn
349 def find_dc(ctx, domain):
350 """find a writeable DC for the given domain"""
351 try:
352 ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
353 except NTSTATUSError as error:
354 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
355 (domain, error.args[1]))
356 except Exception:
357 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
358 if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
359 ctx.site = ctx.cldap_ret.client_site
360 return ctx.cldap_ret.pdc_dns_name
362 def find_dc_site(ctx, server):
363 site = None
364 cldap_ret = ctx.net.finddc(address=server,
365 flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
366 if cldap_ret.client_site is not None and cldap_ret.client_site != "":
367 site = cldap_ret.client_site
368 return site
370 def get_behavior_version(ctx):
371 res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
372 if "msDS-Behavior-Version" in res[0]:
373 return int(res[0]["msDS-Behavior-Version"][0])
374 else:
375 return samba.dsdb.DS_DOMAIN_FUNCTION_2000
377 def get_dnsHostName(ctx):
378 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
379 return str(res[0]["dnsHostName"][0])
381 def get_domain_name(ctx):
382 '''get netbios name of the domain from the partitions record'''
383 partitions_dn = ctx.samdb.get_partitions_dn()
384 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
385 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
386 return str(res[0]["nETBIOSName"][0])
388 def get_forest_domain_name(ctx):
389 '''get netbios name of the domain from the partitions record'''
390 partitions_dn = ctx.samdb.get_partitions_dn()
391 res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
392 expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
393 return str(res[0]["nETBIOSName"][0])
395 def get_parent_partition_dn(ctx):
396 '''get the parent domain partition DN from parent DNS name'''
397 res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
398 expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
399 (ldb.binary_encode(ctx.parent_dnsdomain),
400 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
401 return str(res[0].dn)
403 def get_mysid(ctx):
404 '''get the SID of the connected user. Only works with w2k8 and later,
405 so only used for RODC join'''
406 res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
407 binsid = res[0]["tokenGroups"][0]
408 return get_string(ctx.samdb.schema_format_value("objectSID", binsid))
410 def dn_exists(ctx, dn):
411 '''check if a DN exists'''
412 try:
413 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
414 except ldb.LdbError as e5:
415 (enum, estr) = e5.args
416 if enum == ldb.ERR_NO_SUCH_OBJECT:
417 return False
418 raise
419 return True
421 def add_krbtgt_account(ctx):
422 '''RODCs need a special krbtgt account'''
423 print("Adding %s" % ctx.krbtgt_dn)
424 rec = {
425 "dn": ctx.krbtgt_dn,
426 "objectclass": "user",
427 "useraccountcontrol": str(samba.dsdb.UF_NORMAL_ACCOUNT |
428 samba.dsdb.UF_ACCOUNTDISABLE),
429 "showinadvancedviewonly": "TRUE",
430 "description": "krbtgt for %s" % ctx.samname}
431 ctx.samdb.add(rec, ["rodc_join:1:1"])
433 # now we need to search for the samAccountName attribute on the krbtgt DN,
434 # as this will have been magically set to the krbtgt number
435 res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
436 ctx.krbtgt_name = res[0]["samAccountName"][0]
438 print("Got krbtgt_name=%s" % ctx.krbtgt_name)
440 m = ldb.Message()
441 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
442 m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
443 ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
444 ctx.samdb.modify(m)
446 ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
447 print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
448 ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
450 def drsuapi_connect(ctx):
451 '''make a DRSUAPI connection to the naming master'''
452 binding_options = "seal"
453 if ctx.lp.log_level() >= 9:
454 binding_options += ",print"
455 binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
456 ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
457 (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
459 def create_tmp_samdb(ctx):
460 '''create a temporary samdb object for schema queries'''
461 ctx.tmp_schema = Schema(ctx.domsid,
462 schemadn=ctx.schema_dn)
463 ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
464 credentials=ctx.creds, lp=ctx.lp, global_schema=False,
465 am_rodc=False)
466 ctx.tmp_samdb.set_schema(ctx.tmp_schema)
468 def build_DsReplicaAttribute(ctx, attrname, attrvalue):
469 '''build a DsReplicaAttributeCtr object'''
470 r = drsuapi.DsReplicaAttribute()
471 r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
472 r.value_ctr = 1
474 def DsAddEntry(ctx, recs):
475 '''add a record via the DRSUAPI DsAddEntry call'''
476 if ctx.drsuapi is None:
477 ctx.drsuapi_connect()
478 if ctx.tmp_samdb is None:
479 ctx.create_tmp_samdb()
481 objects = []
482 for rec in recs:
483 id = drsuapi.DsReplicaObjectIdentifier()
484 id.dn = rec['dn']
486 attrs = []
487 for a in rec:
488 if a == 'dn':
489 continue
490 if not isinstance(rec[a], list):
491 v = [rec[a]]
492 else:
493 v = rec[a]
494 v = [x.encode('utf8') if isinstance(x, str) else x for x in v]
495 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
496 attrs.append(rattr)
498 attribute_ctr = drsuapi.DsReplicaAttributeCtr()
499 attribute_ctr.num_attributes = len(attrs)
500 attribute_ctr.attributes = attrs
502 object = drsuapi.DsReplicaObject()
503 object.identifier = id
504 object.attribute_ctr = attribute_ctr
506 list_object = drsuapi.DsReplicaObjectListItem()
507 list_object.object = object
508 objects.append(list_object)
510 req2 = drsuapi.DsAddEntryRequest2()
511 req2.first_object = objects[0]
512 prev = req2.first_object
513 for o in objects[1:]:
514 prev.next_object = o
515 prev = o
517 (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
518 if level == 2:
519 if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
520 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
521 raise RuntimeError("DsAddEntry failed")
522 if ctr.extended_err[0] != werror.WERR_SUCCESS:
523 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
524 raise RuntimeError("DsAddEntry failed")
525 if level == 3:
526 if ctr.err_ver != 1:
527 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
528 if ctr.err_data.status[0] != werror.WERR_SUCCESS:
529 if ctr.err_data.info is None:
530 print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
531 else:
532 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
533 ctr.err_data.info.extended_err))
534 raise RuntimeError("DsAddEntry failed")
535 if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
536 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
537 raise RuntimeError("DsAddEntry failed")
539 return ctr.objects
541 def join_ntdsdsa_obj(ctx):
542 '''return the ntdsdsa object to add'''
544 print("Adding %s" % ctx.ntds_dn)
546 # When joining Windows, the order of certain attributes (mostly only
547 # msDS-HasMasterNCs and HasMasterNCs) seems to matter
548 rec = OrderedDict([
549 ("dn", ctx.ntds_dn),
550 ("objectclass", "nTDSDSA"),
551 ("systemFlags", str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE)),
552 ("dMDLocation", ctx.schema_dn)])
554 nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
556 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
557 # This allows an override via smb.conf or --option using
558 # "ad dc functional level" to make us seem like 2016 to
559 # join such a domain for (say) a migration, or to test the
560 # partially implemented 2016 support.
561 domainControllerFunctionality = functional_level.dc_level_from_lp(ctx.lp)
562 rec["msDS-Behavior-Version"] = str(domainControllerFunctionality)
564 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
565 rec["msDS-HasDomainNCs"] = ctx.base_dn
567 if ctx.RODC:
568 rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
569 rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
570 rec["options"] = "37"
571 else:
572 rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
574 # Note that Windows seems to have an undocumented requirement that
575 # the msDS-HasMasterNCs attribute occurs before HasMasterNCs
576 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
577 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
579 rec["HasMasterNCs"] = []
580 for nc in nc_list:
581 if nc in ctx.full_nc_list:
582 rec["HasMasterNCs"].append(nc)
584 rec["options"] = "1"
585 rec["invocationId"] = ndr_pack(ctx.invocation_id)
587 return rec
589 def join_add_ntdsdsa(ctx):
590 '''add the ntdsdsa object'''
592 rec = ctx.join_ntdsdsa_obj()
593 if ctx.forced_local_samdb:
594 ctx.samdb.add(rec, controls=["relax:0"])
595 elif ctx.RODC:
596 ctx.samdb.add(rec, ["rodc_join:1:1"])
597 else:
598 ctx.DsAddEntry([rec])
600 # find the GUID of our NTDS DN
601 res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
602 ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
604 def join_add_objects(ctx, specified_sid=None):
605 '''add the various objects needed for the join'''
606 if ctx.acct_dn:
607 print("Adding %s" % ctx.acct_dn)
608 rec = {
609 "dn": ctx.acct_dn,
610 "objectClass": "computer",
611 "displayname": ctx.samname,
612 "samaccountname": ctx.samname,
613 "userAccountControl": str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
614 "dnshostname": ctx.dnshostname}
615 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
616 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
617 elif ctx.promote_existing:
618 rec['msDS-SupportedEncryptionTypes'] = []
619 if ctx.managedby:
620 rec["managedby"] = ctx.managedby
621 elif ctx.promote_existing:
622 rec["managedby"] = []
624 if ctx.never_reveal_sid:
625 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
626 elif ctx.promote_existing:
627 rec["msDS-NeverRevealGroup"] = []
629 if ctx.reveal_sid:
630 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
631 elif ctx.promote_existing:
632 rec["msDS-RevealOnDemandGroup"] = []
634 if specified_sid:
635 rec["objectSid"] = ndr_pack(specified_sid)
637 if ctx.promote_existing:
638 if ctx.promote_from_dn != ctx.acct_dn:
639 ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
640 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
641 else:
642 controls = None
643 if specified_sid is not None:
644 controls = ["relax:0"]
645 ctx.samdb.add(rec, controls=controls)
647 if ctx.krbtgt_dn:
648 ctx.add_krbtgt_account()
650 if ctx.server_dn:
651 print("Adding %s" % ctx.server_dn)
652 rec = {
653 "dn": ctx.server_dn,
654 "objectclass": "server",
655 # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
656 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
657 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
658 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
659 # windows seems to add the dnsHostName later
660 "dnsHostName": ctx.dnshostname}
662 if ctx.acct_dn:
663 rec["serverReference"] = ctx.acct_dn
665 ctx.samdb.add(rec)
667 if ctx.subdomain:
668 # the rest is done after replication
669 ctx.ntds_guid = None
670 return
672 if ctx.ntds_dn:
673 ctx.join_add_ntdsdsa()
675 # Add the Replica-Locations or RO-Replica-Locations attributes
676 # TODO Is this supposed to be for the schema partition too?
677 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
678 domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
679 attrs=[],
680 base=ctx.samdb.get_partitions_dn(),
681 expression=expr), ctx.domaindns_zone)
683 expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
684 forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
685 attrs=[],
686 base=ctx.samdb.get_partitions_dn(),
687 expression=expr), ctx.forestdns_zone)
689 for part, zone in (domain, forest):
690 if zone not in ctx.nc_list:
691 continue
693 if len(part) == 1:
694 m = ldb.Message()
695 m.dn = part[0].dn
696 attr = "msDS-NC-Replica-Locations"
697 if ctx.RODC:
698 attr = "msDS-NC-RO-Replica-Locations"
700 m[attr] = ldb.MessageElement(ctx.ntds_dn,
701 ldb.FLAG_MOD_ADD, attr)
702 ctx.samdb.modify(m)
704 if ctx.connection_dn is not None:
705 print("Adding %s" % ctx.connection_dn)
706 rec = {
707 "dn": ctx.connection_dn,
708 "objectclass": "nTDSConnection",
709 "enabledconnection": "TRUE",
710 "options": "65",
711 "fromServer": ctx.dc_ntds_dn}
712 ctx.samdb.add(rec)
714 if ctx.acct_dn:
715 print("Adding SPNs to %s" % ctx.acct_dn)
716 m = ldb.Message()
717 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
718 for i in range(len(ctx.SPNs)):
719 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
720 m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
721 ldb.FLAG_MOD_REPLACE,
722 "servicePrincipalName")
723 ctx.samdb.modify(m)
725 # The account password set operation should normally be done over
726 # LDAP. Windows 2000 DCs however allow this only with SSL
727 # connections which are hard to set up and otherwise refuse with
728 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
729 # over SAMR.
730 print("Setting account password for %s" % ctx.samname)
731 try:
732 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
733 % ldb.binary_encode(ctx.samname),
734 ctx.acct_pass,
735 force_change_at_next_login=False,
736 username=ctx.samname)
737 except ldb.LdbError as e2:
738 (num, _) = e2.args
739 if num != ldb.ERR_UNWILLING_TO_PERFORM:
740 raise
741 ctx.net.set_password(account_name=ctx.samname,
742 domain_name=ctx.domain_name,
743 newpassword=ctx.acct_pass)
745 res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
746 attrs=["msDS-KeyVersionNumber",
747 "objectSID"])
748 if "msDS-KeyVersionNumber" in res[0]:
749 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
750 else:
751 ctx.key_version_number = None
753 ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
754 res[0]["objectSid"][0])
756 print("Enabling account")
757 m = ldb.Message()
758 m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
759 m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
760 ldb.FLAG_MOD_REPLACE,
761 "userAccountControl")
762 ctx.samdb.modify(m)
764 if ctx.dns_backend.startswith("BIND9_"):
765 ctx.dnspass = samba.generate_random_password(128, 255)
767 recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
768 {"DNSDOMAIN": ctx.dnsdomain,
769 "DOMAINDN": ctx.base_dn,
770 "HOSTNAME": ctx.myname,
771 "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
772 "DNSNAME": ctx.dnshostname}))
773 for changetype, msg in recs:
774 assert changetype == ldb.CHANGETYPE_NONE
775 dns_acct_dn = msg["dn"]
776 print("Adding DNS account %s with dns/ SPN" % msg["dn"])
778 # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
779 del msg["clearTextPassword"]
780 # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
781 del msg["isCriticalSystemObject"]
782 # Disable account until password is set
783 msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
784 samba.dsdb.UF_ACCOUNTDISABLE)
785 try:
786 ctx.samdb.add(msg)
787 except ldb.LdbError as e:
788 (num, _) = e.args
789 if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
790 raise
792 # The account password set operation should normally be done over
793 # LDAP. Windows 2000 DCs however allow this only with SSL
794 # connections which are hard to set up and otherwise refuse with
795 # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
796 # over SAMR.
797 print("Setting account password for dns-%s" % ctx.myname)
798 try:
799 ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
800 % ldb.binary_encode(ctx.myname),
801 ctx.dnspass,
802 force_change_at_next_login=False,
803 username=ctx.samname)
804 except ldb.LdbError as e3:
805 (num, _) = e3.args
806 if num != ldb.ERR_UNWILLING_TO_PERFORM:
807 raise
808 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
809 domain_name=ctx.domain_name,
810 newpassword=ctx.dnspass)
812 res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
813 attrs=["msDS-KeyVersionNumber"])
814 if "msDS-KeyVersionNumber" in res[0]:
815 ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
816 else:
817 ctx.dns_key_version_number = None
819 def join_add_objects2(ctx):
820 """add the various objects needed for the join, for subdomains post replication"""
822 print("Adding %s" % ctx.partition_dn)
823 name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
824 sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
825 rec = {
826 "dn": ctx.partition_dn,
827 "objectclass": "crossRef",
828 "objectCategory": "CN=Cross-Ref,%s" % ctx.schema_dn,
829 "nCName": ctx.base_dn,
830 "nETBIOSName": ctx.domain_name,
831 "dnsRoot": ctx.dnsdomain,
832 "trustParent": ctx.parent_partition_dn,
833 "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC |samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
834 "ntSecurityDescriptor": sd_binary,
837 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
838 rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
840 rec2 = ctx.join_ntdsdsa_obj()
842 objects = ctx.DsAddEntry([rec, rec2])
843 if len(objects) != 2:
844 raise DCJoinException("Expected 2 objects from DsAddEntry")
846 ctx.ntds_guid = objects[1].guid
848 print("Replicating partition DN")
849 ctx.repl.replicate(ctx.partition_dn,
850 misc.GUID("00000000-0000-0000-0000-000000000000"),
851 ctx.ntds_guid,
852 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
853 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
855 print("Replicating NTDS DN")
856 ctx.repl.replicate(ctx.ntds_dn,
857 misc.GUID("00000000-0000-0000-0000-000000000000"),
858 ctx.ntds_guid,
859 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
860 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
862 def join_provision(ctx):
863 """Provision the local SAM."""
865 print("Calling bare provision")
867 smbconf = ctx.lp.configfile
869 presult = provision(ctx.logger, system_session(), smbconf=smbconf,
870 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
871 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
872 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
873 serverdn=ctx.server_dn, domain=ctx.domain_name,
874 hostname=ctx.myname, domainsid=ctx.domsid,
875 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
876 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
877 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
878 plaintext_secrets=ctx.plaintext_secrets,
879 backend_store=ctx.backend_store,
880 backend_store_size=ctx.backend_store_size,
881 batch_mode=True)
882 print("Provision OK for domain DN %s" % presult.domaindn)
883 ctx.local_samdb = presult.samdb
884 ctx.lp = presult.lp
885 ctx.paths = presult.paths
886 ctx.names = presult.names
888 # Fix up the forestsid, it may be different if we are joining as a subdomain
889 ctx.names.forestsid = ctx.forestsid
891 def join_provision_own_domain(ctx):
892 """Provision the local SAM."""
894 # we now operate exclusively on the local database, which
895 # we need to reopen in order to get the newly created schema
896 # we set the transaction_index_cache_size to 200,000 to ensure it is
897 # not too small, if it's too small the performance of the join will
898 # be negatively impacted.
899 print("Reconnecting to local samdb")
900 ctx.samdb = SamDB(url=ctx.local_samdb.url,
901 options=[
902 "transaction_index_cache_size:200000"],
903 session_info=system_session(),
904 lp=ctx.local_samdb.lp,
905 global_schema=False)
906 ctx.samdb.set_invocation_id(str(ctx.invocation_id))
907 ctx.local_samdb = ctx.samdb
909 ctx.logger.info("Finding domain GUID from ncName")
910 res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
911 controls=["extended_dn:1:1", "reveal_internals:0"])
913 if 'nCName' not in res[0]:
914 raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
916 try:
917 ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
918 except KeyError:
919 raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
921 ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
923 ctx.logger.info("Calling own domain provision")
925 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
927 provision_fill(ctx.local_samdb, secrets_ldb,
928 ctx.logger, ctx.names, ctx.paths,
929 dom_for_fun_level=ctx.behavior_version,
930 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
931 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
932 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
933 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
935 if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2012:
936 adprep_level = ctx.behavior_version
938 updates_allowed_overridden = False
939 if lp.get("dsdb:schema update allowed") is None:
940 lp.set("dsdb:schema update allowed", "yes")
941 print("Temporarily overriding 'dsdb:schema update allowed' setting")
942 updates_allowed_overridden = True
944 samdb.transaction_start()
945 try:
946 from samba.domain_update import DomainUpdate
948 domain = DomainUpdate(ctx.local_samdb, fix=True)
949 domain.check_updates_functional_level(adprep_level,
950 samba.dsdb.DS_DOMAIN_FUNCTION_2008,
951 update_revision=True)
953 samdb.transaction_commit()
954 except Exception as e:
955 samdb.transaction_cancel()
956 raise DCJoinException("DomainUpdate() failed: %s" % e)
958 if updates_allowed_overridden:
959 lp.set("dsdb:schema update allowed", "no")
961 print("Provision OK for domain %s" % ctx.names.dnsdomain)
963 def create_replicator(ctx, repl_creds, binding_options):
964 '''Creates a new DRS object for managing replications'''
965 return drs_utils.drs_Replicate(
966 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
967 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
969 def join_replicate(ctx):
970 """Replicate the SAM."""
972 ctx.logger.info("Starting replication")
974 # A global transaction is started so that linked attributes
975 # are applied at the very end, once all partitions are
976 # replicated. This helps get all cross-partition links.
977 ctx.local_samdb.transaction_start()
978 try:
979 source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
980 if ctx.ntds_guid is None:
981 print("Using DS_BIND_GUID_W2K3")
982 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
983 else:
984 destination_dsa_guid = ctx.ntds_guid
986 if ctx.RODC:
987 repl_creds = Credentials()
988 repl_creds.guess(ctx.lp)
989 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
990 repl_creds.set_username(ctx.samname)
991 repl_creds.set_password(ctx.acct_pass)
992 else:
993 repl_creds = ctx.creds
995 binding_options = "seal"
996 if ctx.lp.log_level() >= 9:
997 binding_options += ",print"
999 repl = ctx.create_replicator(repl_creds, binding_options)
1001 repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
1002 destination_dsa_guid, schema=True, rodc=ctx.RODC,
1003 replica_flags=ctx.replica_flags)
1004 repl.replicate(ctx.config_dn, source_dsa_invocation_id,
1005 destination_dsa_guid, rodc=ctx.RODC,
1006 replica_flags=ctx.replica_flags)
1007 if not ctx.subdomain:
1008 # Replicate first the critical objects for the basedn
1010 # We do this to match Windows. The default case is to
1011 # do a critical objects replication, then a second
1012 # with all objects.
1014 print("Replicating critical objects from the base DN of the domain")
1015 try:
1016 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
1017 destination_dsa_guid, rodc=ctx.RODC,
1018 replica_flags=ctx.domain_replica_flags | drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
1019 except WERRORError as e:
1021 if e.args[0] == werror.WERR_DS_DRA_MISSING_PARENT:
1022 ctx.logger.warning("First pass of replication with "
1023 "DRSUAPI_DRS_CRITICAL_ONLY "
1024 "not possible due to a missing parent object. "
1025 "This is typical of a Samba "
1026 "4.5 or earlier server. "
1027 "We will replicate all the objects instead.")
1028 else:
1029 raise
1031 # Now replicate all the objects in the domain (unless
1032 # we were run with --critical-only).
1034 # Doing the replication of users as a second pass
1035 # matches more closely the Windows behaviour, which is
1036 # actually to do this on first startup.
1038 # Use --critical-only if you want that (but you don't
1039 # really, it is better to see any errors here).
1040 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
1041 try:
1042 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
1043 destination_dsa_guid, rodc=ctx.RODC,
1044 replica_flags=ctx.domain_replica_flags)
1045 except WERRORError as e:
1047 if e.args[0] == werror.WERR_DS_DRA_MISSING_PARENT and \
1048 ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
1049 ctx.logger.warning("Replication with DRSUAPI_DRS_CRITICAL_ONLY "
1050 "failed due to a missing parent object. "
1051 "This may be a Samba 4.5 or earlier server "
1052 "and is not compatible with --critical-only")
1053 raise
1055 print("Done with always replicated NC (base, config, schema)")
1057 # At this point we should already have an entry in the ForestDNS
1058 # and DomainDNS NC (those under CN=Partions,DC=...) in order to
1059 # indicate that we hold a replica for this NC.
1060 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1061 if nc in ctx.nc_list:
1062 print("Replicating %s" % (str(nc)))
1063 repl.replicate(nc, source_dsa_invocation_id,
1064 destination_dsa_guid, rodc=ctx.RODC,
1065 replica_flags=ctx.replica_flags)
1067 if ctx.RODC:
1068 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
1069 destination_dsa_guid,
1070 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
1071 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
1072 destination_dsa_guid,
1073 exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
1074 elif ctx.rid_manager_dn is not None:
1075 # Try and get a RID Set if we can. This is only possible against the RID Master. Warn otherwise.
1076 try:
1077 repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
1078 destination_dsa_guid,
1079 exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
1080 except samba.DsExtendedError as e1:
1081 (enum, estr) = e1.args
1082 if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
1083 print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
1084 print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
1085 else:
1086 raise
1088 ctx.repl = repl
1089 ctx.source_dsa_invocation_id = source_dsa_invocation_id
1090 ctx.destination_dsa_guid = destination_dsa_guid
1092 ctx.logger.info("Committing SAM database - this may take some time")
1093 except:
1094 ctx.local_samdb.transaction_cancel()
1095 raise
1096 else:
1098 # This is a special case, we have completed a full
1099 # replication so if a link comes to us that points to a
1100 # deleted object, and we asked for all objects already, we
1101 # just have to ignore it, the chance to re-try the
1102 # replication with GET_TGT has long gone. This can happen
1103 # if the object is deleted and sent to us after the link
1104 # was sent, as we are processing all links in the
1105 # transaction_commit().
1106 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
1107 ctx.local_samdb.set_opaque_integer(dsdb.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME,
1109 ctx.local_samdb.transaction_commit()
1110 ctx.local_samdb.set_opaque_integer(dsdb.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME,
1112 ctx.logger.info("Committed SAM database")
1114 # A large replication may have caused our LDB connection to the
1115 # remote DC to timeout, so check the connection is still alive
1116 ctx.refresh_ldb_connection()
1118 def refresh_ldb_connection(ctx):
1119 try:
1120 # query the rootDSE to check the connection
1121 ctx.samdb.search(scope=ldb.SCOPE_BASE, attrs=[])
1122 except ldb.LdbError as e:
1123 (enum, estr) = e.args
1125 # if the connection was disconnected, then reconnect
1126 if (enum == ldb.ERR_OPERATIONS_ERROR and
1127 ('NT_STATUS_CONNECTION_DISCONNECTED' in estr or
1128 'NT_STATUS_CONNECTION_RESET' in estr)):
1129 ctx.logger.warning("LDB connection disconnected. Reconnecting")
1130 ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1131 session_info=system_session(),
1132 credentials=ctx.creds, lp=ctx.lp)
1133 else:
1134 raise DCJoinException(estr)
1136 def send_DsReplicaUpdateRefs(ctx, dn):
1137 r = drsuapi.DsReplicaUpdateRefsRequest1()
1138 r.naming_context = drsuapi.DsReplicaObjectIdentifier()
1139 r.naming_context.dn = str(dn)
1140 r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
1141 r.naming_context.sid = security.dom_sid("S-0-0")
1142 r.dest_dsa_guid = ctx.ntds_guid
1143 r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1144 r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1145 if not ctx.RODC:
1146 r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1148 if ctx.drsuapi is None:
1149 ctx.drsuapi_connect()
1151 ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1153 def join_add_dns_records(ctx):
1154 """Remotely Add a DNS record to the target DC. We assume that if we
1155 replicate DNS that the server holds the DNS roles and can accept
1156 updates.
1158 This avoids issues getting replication going after the DC
1159 first starts as the rest of the domain does not have to
1160 wait for samba_dnsupdate to run successfully.
1162 Specifically, we add the records implied by the DsReplicaUpdateRefs
1163 call above.
1165 We do not just run samba_dnsupdate as we want to strictly
1166 operate against the DC we just joined:
1167 - We do not want to query another DNS server
1168 - We do not want to obtain a Kerberos ticket
1169 (as the KDC we select may not be the DC we just joined,
1170 and so may not be in sync with the password we just set)
1171 - We do not wish to set the _ldap records until we have started
1172 - We do not wish to use NTLM (the --use-samba-tool mode forces
1173 NTLM)
1177 client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1178 select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1179 dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1181 zone = ctx.dnsdomain
1182 msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1183 name = ctx.myname
1184 msdcs_cname = str(ctx.ntds_guid)
1185 cname_target = "%s.%s" % (name, zone)
1186 IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1188 ctx.logger.info("Adding %d remote DNS records for %s.%s" %
1189 (len(IPs), name, zone))
1191 binding_options = "sign"
1192 dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1193 ctx.lp, ctx.creds)
1195 name_found = True
1197 sd_helper = sd_utils.SDUtils(ctx.samdb)
1199 change_owner_sd = security.descriptor()
1200 change_owner_sd.owner_sid = ctx.new_dc_account_sid
1201 change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1202 (str(ctx.domsid),
1203 security.DOMAIN_RID_DCS))
1205 # TODO: Remove any old records from the primary DNS name
1206 try:
1207 (buflen, res) \
1208 = dns_conn.DnssrvEnumRecords2(client_version,
1210 ctx.server,
1211 zone,
1212 name,
1213 None,
1214 dnsp.DNS_TYPE_ALL,
1215 select_flags,
1216 None,
1217 None)
1218 except WERRORError as e:
1219 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1220 name_found = False
1222 if name_found:
1223 for rec in res.rec:
1224 for record in rec.records:
1225 if record.wType == dnsp.DNS_TYPE_A or \
1226 record.wType == dnsp.DNS_TYPE_AAAA:
1227 # delete record
1228 del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1229 del_rec_buf.rec = record
1230 try:
1231 dns_conn.DnssrvUpdateRecord2(client_version,
1233 ctx.server,
1234 zone,
1235 name,
1236 None,
1237 del_rec_buf)
1238 except WERRORError as e:
1239 if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1240 pass
1241 else:
1242 raise
1244 for IP in IPs:
1245 if IP.find(':') != -1:
1246 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1247 % (name, zone, IP))
1248 rec = AAAARecord(IP)
1249 else:
1250 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1251 % (name, zone, IP))
1252 rec = ARecord(IP)
1254 # Add record
1255 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1256 add_rec_buf.rec = rec
1257 dns_conn.DnssrvUpdateRecord2(client_version,
1259 ctx.server,
1260 zone,
1261 name,
1262 add_rec_buf,
1263 None)
1265 if (len(IPs) > 0):
1266 domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1267 (ctx.dns_a_dn, ldap_record) \
1268 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1269 dns_partition=domaindns_zone_dn)
1271 # Make the DC own the DNS record, not the administrator
1272 sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1273 controls=["sd_flags:1:%d"
1274 % (security.SECINFO_OWNER
1275 | security.SECINFO_GROUP)])
1277 # Add record
1278 ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1279 % (msdcs_cname, msdcs_zone, cname_target))
1281 add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1282 rec = CNAMERecord(cname_target)
1283 add_rec_buf.rec = rec
1284 dns_conn.DnssrvUpdateRecord2(client_version,
1286 ctx.server,
1287 msdcs_zone,
1288 msdcs_cname,
1289 add_rec_buf,
1290 None)
1292 forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1293 (ctx.dns_cname_dn, ldap_record) \
1294 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1295 dns_partition=forestdns_zone_dn)
1297 # Make the DC own the DNS record, not the administrator
1298 sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1299 controls=["sd_flags:1:%d"
1300 % (security.SECINFO_OWNER
1301 | security.SECINFO_GROUP)])
1303 ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1304 "will be created samba_dnsupdate on first startup")
1306 def join_replicate_new_dns_records(ctx):
1307 for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1308 if nc in ctx.nc_list:
1309 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1310 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1311 ctx.ntds_guid, rodc=ctx.RODC,
1312 replica_flags=ctx.replica_flags,
1313 full_sync=False)
1315 def join_finalise(ctx):
1316 """Finalise the join, mark us synchronised and setup secrets db."""
1318 # FIXME we shouldn't do this in all cases
1320 # If for some reasons we joined in another site than the one of
1321 # DC we just replicated from then we don't need to send the updatereplicateref
1322 # as replication between sites is time based and on the initiative of the
1323 # requesting DC
1324 ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1325 for nc in ctx.nc_list:
1326 ctx.send_DsReplicaUpdateRefs(nc)
1328 if ctx.RODC:
1329 print("Setting RODC invocationId")
1330 ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1331 ctx.local_samdb.set_opaque_integer("domainFunctionality",
1332 ctx.behavior_version)
1333 m = ldb.Message()
1334 m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1335 m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1336 ldb.FLAG_MOD_REPLACE,
1337 "invocationId")
1338 ctx.local_samdb.modify(m)
1340 # Note: as RODC the invocationId is only stored
1341 # on the RODC itself, the other DCs never see it.
1343 # Thats is why we fix up the replPropertyMetaData stamp
1344 # for the 'invocationId' attribute, we need to change
1345 # the 'version' to '0', this is what windows 2008r2 does as RODC
1347 # This means if the object on a RWDC ever gets a invocationId
1348 # attribute, it will have version '1' (or higher), which will
1349 # will overwrite the RODC local value.
1350 ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1351 "invocationId",
1354 ctx.logger.info("Setting isSynchronized and dsServiceName")
1355 m = ldb.Message()
1356 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1357 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1359 guid = ctx.ntds_guid
1360 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1361 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1362 ctx.local_samdb.modify(m)
1364 if ctx.subdomain:
1365 return
1367 secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1369 ctx.logger.info("Setting up secrets database")
1370 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1371 realm=ctx.realm,
1372 dnsdomain=ctx.dnsdomain,
1373 netbiosname=ctx.myname,
1374 domainsid=ctx.domsid,
1375 machinepass=ctx.acct_pass,
1376 secure_channel_type=ctx.secure_channel_type,
1377 key_version_number=ctx.key_version_number)
1379 if ctx.dns_backend.startswith("BIND9_"):
1380 setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1381 ctx.names, ctx.paths, ctx.lp, ctx.logger,
1382 dns_backend=ctx.dns_backend,
1383 dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1384 targetdir=ctx.targetdir,
1385 key_version_number=ctx.dns_key_version_number)
1387 def join_setup_trusts(ctx):
1388 """provision the local SAM."""
1390 print("Setup domain trusts with server %s" % ctx.server)
1391 binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
1392 lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1393 ctx.lp, ctx.creds)
1395 objectAttr = lsa.ObjectAttribute()
1396 objectAttr.sec_qos = lsa.QosInfo()
1398 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1399 objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1401 info = lsa.TrustDomainInfoInfoEx()
1402 info.domain_name.string = ctx.dnsdomain
1403 info.netbios_name.string = ctx.domain_name
1404 info.sid = ctx.domsid
1405 info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1406 info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1407 info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1409 try:
1410 oldname = lsa.String()
1411 oldname.string = ctx.dnsdomain
1412 oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1413 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1414 print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1415 lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1416 except RuntimeError:
1417 pass
1419 password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1421 clear_value = drsblobs.AuthInfoClear()
1422 clear_value.size = len(password_blob)
1423 clear_value.password = password_blob
1425 clear_authentication_information = drsblobs.AuthenticationInformation()
1426 clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1427 clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1428 clear_authentication_information.AuthInfo = clear_value
1430 authentication_information_array = drsblobs.AuthenticationInformationArray()
1431 authentication_information_array.count = 1
1432 authentication_information_array.array = [clear_authentication_information]
1434 outgoing = drsblobs.trustAuthInOutBlob()
1435 outgoing.count = 1
1436 outgoing.current = authentication_information_array
1438 trustpass = drsblobs.trustDomainPasswords()
1439 confounder = [3] * 512
1441 for i in range(512):
1442 confounder[i] = random.randint(0, 255)
1444 trustpass.confounder = confounder
1446 trustpass.outgoing = outgoing
1447 trustpass.incoming = outgoing
1449 trustpass_blob = ndr_pack(trustpass)
1451 encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1453 auth_blob = lsa.DATA_BUF2()
1454 auth_blob.size = len(encrypted_trustpass)
1455 auth_blob.data = string_to_byte_array(encrypted_trustpass)
1457 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1458 auth_info.auth_blob = auth_blob
1460 trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1461 info,
1462 auth_info,
1463 security.SEC_STD_DELETE)
1465 rec = {
1466 "dn": "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1467 "objectclass": "trustedDomain",
1468 "trustType": str(info.trust_type),
1469 "trustAttributes": str(info.trust_attributes),
1470 "trustDirection": str(info.trust_direction),
1471 "flatname": ctx.forest_domain_name,
1472 "trustPartner": ctx.dnsforest,
1473 "trustAuthIncoming": ndr_pack(outgoing),
1474 "trustAuthOutgoing": ndr_pack(outgoing),
1475 "securityIdentifier": ndr_pack(ctx.forestsid)
1477 ctx.local_samdb.add(rec)
1479 rec = {
1480 "dn": "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1481 "objectclass": "user",
1482 "userAccountControl": str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1483 "clearTextPassword": ctx.trustdom_pass.encode('utf-16-le'),
1484 "samAccountName": "%s$" % ctx.forest_domain_name
1486 ctx.local_samdb.add(rec)
1488 def build_nc_lists(ctx):
1489 # nc_list is the list of naming context (NC) for which we will
1490 # replicate in and send a updateRef command to the partner DC
1492 # full_nc_list is the list of naming context (NC) we hold
1493 # read/write copies of. These are not subsets of each other.
1494 ctx.nc_list = [ctx.config_dn, ctx.schema_dn]
1495 ctx.full_nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
1497 if ctx.subdomain and ctx.dns_backend != "NONE":
1498 ctx.full_nc_list += [ctx.domaindns_zone]
1500 elif not ctx.subdomain:
1501 ctx.nc_list += [ctx.base_dn]
1503 if ctx.dns_backend != "NONE":
1504 ctx.nc_list += [ctx.domaindns_zone]
1505 ctx.nc_list += [ctx.forestdns_zone]
1506 ctx.full_nc_list += [ctx.domaindns_zone]
1507 ctx.full_nc_list += [ctx.forestdns_zone]
1509 def do_join(ctx):
1510 ctx.build_nc_lists()
1512 if ctx.promote_existing:
1513 ctx.promote_possible()
1514 else:
1515 ctx.cleanup_old_join()
1517 try:
1518 ctx.join_add_objects()
1519 ctx.join_provision()
1520 ctx.join_replicate()
1521 if ctx.subdomain:
1522 ctx.join_add_objects2()
1523 ctx.join_provision_own_domain()
1524 ctx.join_setup_trusts()
1526 if ctx.dns_backend != "NONE":
1527 ctx.join_add_dns_records()
1528 ctx.join_replicate_new_dns_records()
1530 ctx.join_finalise()
1531 except:
1532 try:
1533 print("Join failed - cleaning up")
1534 except IOError:
1535 pass
1537 # cleanup the failed join (checking we still have a live LDB
1538 # connection to the remote DC first)
1539 ctx.refresh_ldb_connection()
1540 ctx.cleanup_old_join()
1541 raise
1544 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1545 targetdir=None, domain=None, domain_critical_only=False,
1546 machinepass=None, use_ntvfs=False, dns_backend=None,
1547 promote_existing=False, plaintext_secrets=False,
1548 backend_store=None,
1549 backend_store_size=None):
1550 """Join as a RODC."""
1552 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1553 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1554 promote_existing, plaintext_secrets,
1555 backend_store=backend_store,
1556 backend_store_size=backend_store_size)
1558 lp.set("workgroup", ctx.domain_name)
1559 logger.info("workgroup is %s" % ctx.domain_name)
1561 lp.set("realm", ctx.realm)
1562 logger.info("realm is %s" % ctx.realm)
1564 ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1566 # setup some defaults for accounts that should be replicated to this RODC
1567 ctx.never_reveal_sid = [
1568 "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1569 "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1570 "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1571 "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1572 "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1573 ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1575 mysid = ctx.get_mysid()
1576 admin_dn = "<SID=%s>" % mysid
1577 ctx.managedby = admin_dn
1579 ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1580 samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1581 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1583 ctx.SPNs.extend(["RestrictedKrbHost/%s" % ctx.myname,
1584 "RestrictedKrbHost/%s" % ctx.dnshostname])
1586 ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1587 ctx.secure_channel_type = misc.SEC_CHAN_RODC
1588 ctx.RODC = True
1589 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1590 drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1591 ctx.domain_replica_flags = ctx.replica_flags
1592 if domain_critical_only:
1593 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1595 ctx.do_join()
1597 logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1600 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1601 targetdir=None, domain=None, domain_critical_only=False,
1602 machinepass=None, use_ntvfs=False, dns_backend=None,
1603 promote_existing=False, plaintext_secrets=False,
1604 backend_store=None,
1605 backend_store_size=None):
1606 """Join as a DC."""
1607 ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
1608 targetdir, domain, machinepass, use_ntvfs, dns_backend,
1609 promote_existing, plaintext_secrets,
1610 backend_store=backend_store,
1611 backend_store_size=backend_store_size)
1613 lp.set("workgroup", ctx.domain_name)
1614 logger.info("workgroup is %s" % ctx.domain_name)
1616 lp.set("realm", ctx.realm)
1617 logger.info("realm is %s" % ctx.realm)
1619 ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1621 ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1622 ctx.secure_channel_type = misc.SEC_CHAN_BDC
1624 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1625 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1626 ctx.domain_replica_flags = ctx.replica_flags
1627 if domain_critical_only:
1628 ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1630 ctx.do_join()
1631 logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1634 def join_clone(logger=None, server=None, creds=None, lp=None,
1635 targetdir=None, domain=None, include_secrets=False,
1636 dns_backend="NONE", backend_store=None,
1637 backend_store_size=None):
1638 """Creates a local clone of a remote DC."""
1639 ctx = DCCloneContext(logger, server, creds, lp, targetdir=targetdir,
1640 domain=domain, dns_backend=dns_backend,
1641 include_secrets=include_secrets,
1642 backend_store=backend_store,
1643 backend_store_size=backend_store_size)
1645 lp.set("workgroup", ctx.domain_name)
1646 logger.info("workgroup is %s" % ctx.domain_name)
1648 lp.set("realm", ctx.realm)
1649 logger.info("realm is %s" % ctx.realm)
1651 ctx.do_join()
1652 logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1653 return ctx
1656 class DCCloneContext(DCJoinContext):
1657 """Clones a remote DC."""
1659 def __init__(ctx, logger=None, server=None, creds=None, lp=None,
1660 targetdir=None, domain=None, dns_backend=None,
1661 include_secrets=False, backend_store=None,
1662 backend_store_size=None):
1663 super(DCCloneContext, ctx).__init__(logger, server, creds, lp,
1664 targetdir=targetdir, domain=domain,
1665 dns_backend=dns_backend,
1666 backend_store=backend_store,
1667 backend_store_size=backend_store_size)
1669 # As we don't want to create or delete these DNs, we set them to None
1670 ctx.server_dn = None
1671 ctx.ntds_dn = None
1672 ctx.acct_dn = None
1673 ctx.myname = ctx.server.split('.')[0]
1674 ctx.ntds_guid = None
1675 ctx.rid_manager_dn = None
1677 # Save this early
1678 ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
1680 ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1681 drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1682 if not include_secrets:
1683 ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1684 ctx.domain_replica_flags = ctx.replica_flags
1686 def join_finalise(ctx):
1687 ctx.logger.info("Setting isSynchronized and dsServiceName")
1688 m = ldb.Message()
1689 m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1690 m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE,
1691 "isSynchronized")
1693 # We want to appear to be the server we just cloned
1694 guid = ctx.remote_dc_ntds_guid
1695 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1696 ldb.FLAG_MOD_REPLACE,
1697 "dsServiceName")
1698 ctx.local_samdb.modify(m)
1700 def do_join(ctx):
1701 ctx.build_nc_lists()
1703 # When cloning a DC, we just want to provision a DC locally, then
1704 # grab the remote DC's entire DB via DRS replication
1705 ctx.join_provision()
1706 ctx.join_replicate()
1707 ctx.join_finalise()
1710 # Used to create a renamed backup of a DC. Renaming the domain means that the
1711 # cloned/backup DC can be started without interfering with the production DC.
1712 class DCCloneAndRenameContext(DCCloneContext):
1713 """Clones a remote DC, renaming the domain along the way."""
1715 def __init__(ctx, new_base_dn, new_domain_name, new_realm, logger=None,
1716 server=None, creds=None, lp=None, targetdir=None, domain=None,
1717 dns_backend=None, include_secrets=True, backend_store=None):
1718 super(DCCloneAndRenameContext, ctx).__init__(logger, server, creds, lp,
1719 targetdir=targetdir,
1720 domain=domain,
1721 dns_backend=dns_backend,
1722 include_secrets=include_secrets,
1723 backend_store=backend_store)
1724 # store the new DN (etc) that we want the cloned DB to use
1725 ctx.new_base_dn = new_base_dn
1726 ctx.new_domain_name = new_domain_name
1727 ctx.new_realm = new_realm
1729 def create_replicator(ctx, repl_creds, binding_options):
1730 """Creates a new DRS object for managing replications"""
1732 # We want to rename all the domain objects, and the simplest way to do
1733 # this is during replication. This is because the base DN of the top-
1734 # level replicated object will flow through to all the objects below it
1735 binding_str = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
1736 return drs_utils.drs_ReplicateRenamer(binding_str, ctx.lp, repl_creds,
1737 ctx.local_samdb,
1738 ctx.invocation_id,
1739 ctx.base_dn, ctx.new_base_dn)
1741 def create_non_global_lp(ctx, global_lp):
1742 '''Creates a non-global LoadParm based on the global LP's settings'''
1744 # the samba code shares a global LoadParm by default. Here we create a
1745 # new LoadParm that retains the global settings, but any changes we
1746 # make to it won't automatically affect the rest of the samba code.
1747 # The easiest way to do this is to dump the global settings to a
1748 # temporary smb.conf file, and then load the temp file into a new
1749 # non-global LoadParm
1750 fd, tmp_file = tempfile.mkstemp()
1751 global_lp.dump(False, tmp_file)
1752 local_lp = samba.param.LoadParm(filename_for_non_global_lp=tmp_file)
1753 os.remove(tmp_file)
1754 return local_lp
1756 def rename_dn(ctx, dn_str):
1757 '''Uses string substitution to replace the base DN'''
1758 old_base_dn = ctx.base_dn
1759 return re.sub('%s$' % old_base_dn, ctx.new_base_dn, dn_str)
1761 # we want to override the normal DCCloneContext's join_provision() so that
1762 # use the new domain DNs during the provision. We do this because:
1763 # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
1764 # - it sets up a default SAM DB that uses the new Schema DNs (without which
1765 # we couldn't apply the renamed DRS objects during replication)
1766 def join_provision(ctx):
1767 """Provision the local (renamed) SAM."""
1769 print("Provisioning the new (renamed) domain...")
1771 # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
1772 # to create a new smb.conf. By default, it uses the global LoadParm to
1773 # do this, and so it would overwrite the realm/domain values globally.
1774 # We still need the global LoadParm to retain the old domain's details,
1775 # so we can connect to (and clone) the existing DC.
1776 # So, copy the global settings into a non-global LoadParm, which we can
1777 # then pass into provision(). This generates a new smb.conf correctly,
1778 # without overwriting the global realm/domain values just yet.
1779 non_global_lp = ctx.create_non_global_lp(ctx.lp)
1781 # do the provision with the new/renamed domain DN values
1782 presult = provision(ctx.logger, system_session(),
1783 targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
1784 realm=ctx.new_realm, lp=non_global_lp,
1785 rootdn=ctx.rename_dn(ctx.root_dn), domaindn=ctx.new_base_dn,
1786 schemadn=ctx.rename_dn(ctx.schema_dn),
1787 configdn=ctx.rename_dn(ctx.config_dn),
1788 domain=ctx.new_domain_name, domainsid=ctx.domsid,
1789 serverrole="active directory domain controller",
1790 dns_backend=ctx.dns_backend,
1791 backend_store=ctx.backend_store)
1793 print("Provision OK for renamed domain DN %s" % presult.domaindn)
1794 ctx.local_samdb = presult.samdb
1795 ctx.paths = presult.paths