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