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