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