join.py 77.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# python join code
# Copyright Andrew Tridgell 2010
# Copyright Andrew Bartlett 2010
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

19
from __future__ import print_function
20
21
"""Joining a domain."""

22
23
from samba.auth import system_session
from samba.samdb import SamDB
24
from samba import gensec, Ldb, drs_utils, arcfour_encrypt, string_to_byte_array
25
26
27
import ldb
import samba
import uuid
28
29
from samba.ndr import ndr_pack, ndr_unpack
from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp
30
from samba.dsdb import DS_DOMAIN_FUNCTION_2003
31
from samba.credentials import Credentials, DONT_USE_KERBEROS
32
33
from samba.provision import (secretsdb_self_join, provision, provision_fill,
                             FILL_DRS, FILL_SUBDOMAIN, DEFAULTSITE)
34
from samba.provision.common import setup_path
35
from samba.schema import Schema
36
from samba import descriptor
37
from samba.net import Net
38
from samba.provision.sambadns import setup_bind9_dns
39
from samba import read_and_sub_file
40
from samba import werror
41
from base64 import b64encode
42
from samba import WERRORError, NTSTATUSError
43
from samba import sd_utils
44
from samba.dnsserver import ARecord, AAAARecord, CNameRecord
45
import logging
46
import random
47
import time
48
49
50
import re
import os
import tempfile
51
from samba.compat import text_type
52
from samba.compat import get_string
53
from samba.netcmd import CommandError
54

55

56
57
58
59
60
class DCJoinException(Exception):

    def __init__(self, msg):
        super(DCJoinException, self).__init__("Can't join, error: %s" % msg)

Jelmer Vernooij's avatar
Jelmer Vernooij committed
61

62
class DCJoinContext(object):
63
    """Perform a DC join."""
64

65
    def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
66
67
                 netbios_name=None, targetdir=None, domain=None,
                 machinepass=None, use_ntvfs=False, dns_backend=None,
68
                 promote_existing=False, plaintext_secrets=False,
69
                 backend_store=None, forced_local_samdb=None):
70

71
        ctx.logger = logger
72
73
74
75
        ctx.creds = creds
        ctx.lp = lp
        ctx.site = site
        ctx.targetdir = targetdir
76
        ctx.use_ntvfs = use_ntvfs
77
        ctx.plaintext_secrets = plaintext_secrets
78
        ctx.backend_store = backend_store
79

80
81
82
        ctx.promote_existing = promote_existing
        ctx.promote_from_dn = None

83
        ctx.nc_list = []
84
        ctx.full_nc_list = []
85
86
87
88

        ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
        ctx.net = Net(creds=ctx.creds, lp=ctx.lp)

89
90
        ctx.server = server
        ctx.forced_local_samdb = forced_local_samdb
91

92
93
94
95
        if forced_local_samdb:
            ctx.samdb = forced_local_samdb
            ctx.server = ctx.samdb.url
        else:
96
97
98
99
100
101
102
            if ctx.server:
                # work out the DC's site (if not already specified)
                if site is None:
                    ctx.site = ctx.find_dc_site(ctx.server)
            else:
                # work out the Primary DC for the domain (as well as an
                # appropriate site for the new DC)
103
104
105
106
107
108
                ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
                ctx.server = ctx.find_dc(domain)
                ctx.logger.info("Found DC %s" % ctx.server)
            ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
                              session_info=system_session(),
                              credentials=ctx.creds, lp=ctx.lp)
109

110
111
112
        if ctx.site is None:
            ctx.site = DEFAULTSITE

113
        try:
114
115
116
            ctx.samdb.search(scope=ldb.SCOPE_BASE, attrs=[])
        except ldb.LdbError as e:
            (enum, estr) = e.args
117
118
            raise DCJoinException(estr)

119
120
121
122
        ctx.base_dn = str(ctx.samdb.get_default_basedn())
        ctx.root_dn = str(ctx.samdb.get_root_basedn())
        ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
        ctx.config_dn = str(ctx.samdb.get_config_basedn())
123
        ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
124
        ctx.forestsid = ctx.domsid
125
        ctx.domain_name = ctx.get_domain_name()
126
        ctx.forest_domain_name = ctx.get_forest_domain_name()
127
        ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
128

129
        ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
130
        ctx.dc_dnsHostName = ctx.get_dnsHostName()
131
132
        ctx.behavior_version = ctx.get_behavior_version()

133
134
135
        if machinepass is not None:
            ctx.acct_pass = machinepass
        else:
136
            ctx.acct_pass = samba.generate_random_machine_password(128, 255)
137

138
        ctx.dnsdomain = ctx.samdb.domain_dns_name()
139
140
141
142

        # the following are all dependent on the new DC's netbios_name (which
        # we expect to always be specified, except when cloning a DC)
        if netbios_name:
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
            # work out the DNs of all the objects we will be adding
            ctx.myname = netbios_name
            ctx.samname = "%s$" % ctx.myname
            ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
            ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
            ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
            ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
            ctx.dnsforest = ctx.samdb.forest_dns_name()

            topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
            if ctx.dn_exists(topology_base):
                ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
            else:
                ctx.topology_dn = None

Joe Guo's avatar
Joe Guo committed
158
            ctx.SPNs = ["HOST/%s" % ctx.myname,
159
160
                        "HOST/%s" % ctx.dnshostname,
                        "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest)]
161

162
163
164
165
166
167
            res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
                                               attrs=["rIDManagerReference"],
                                               base=ctx.base_dn)

            ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]

168
        ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
169
        ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
170

171
        expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
172
173
174
        res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
                                         attrs=[],
                                         base=ctx.samdb.get_partitions_dn(),
175
                                         expression=expr)
176
177
178
179
180
        if dns_backend is None:
            ctx.dns_backend = "NONE"
        else:
            if len(res_domaindns) == 0:
                ctx.dns_backend = "NONE"
181
                print("NO DNS zone information found in source domain, not replicating DNS")
182
183
184
            else:
                ctx.dns_backend = dns_backend

185
        ctx.realm = ctx.dnsdomain
186

187
188
        ctx.tmp_samdb = None

189
190
191
        ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
                             drsuapi.DRSUAPI_DRS_PER_SYNC |
                             drsuapi.DRSUAPI_DRS_GET_ANC |
192
                             drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
193
194
                             drsuapi.DRSUAPI_DRS_NEVER_SYNCED)

195
196
197
198
199
200
201
202
        # these elements are optional
        ctx.never_reveal_sid = None
        ctx.reveal_sid = None
        ctx.connection_dn = None
        ctx.RODC = False
        ctx.krbtgt_dn = None
        ctx.drsuapi = None
        ctx.managedby = None
203
        ctx.subdomain = False
204
        ctx.adminpass = None
205
        ctx.partition_dn = None
206

207
208
209
210
211
212
        ctx.dns_a_dn = None
        ctx.dns_cname_dn = None

        # Do not normally register 127. addresses but allow override for selftest
        ctx.force_all_ips = False

213
214
215
216
    def del_noerror(ctx, dn, recursive=False):
        if recursive:
            try:
                res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
217
            except Exception:
218
219
220
                return
            for r in res:
                ctx.del_noerror(r.dn, recursive=True)
221
        try:
222
            ctx.samdb.delete(dn)
223
            print("Deleted %s" % dn)
224
        except Exception:
225
226
            pass

227
    def cleanup_old_accounts(ctx, force=False):
228
229
230
231
232
233
        res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
                               expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
                               attrs=["msDS-krbTgtLink", "objectSID"])
        if len(res) == 0:
            return

234
235
236
237
238
239
240
241
        if not force:
            creds = Credentials()
            creds.guess(ctx.lp)
            try:
                creds.set_machine_account(ctx.lp)
                creds.set_kerberos_state(ctx.creds.get_kerberos_state())
                machine_samdb = SamDB(url="ldap://%s" % ctx.server,
                                      session_info=system_session(),
242
                                      credentials=creds, lp=ctx.lp)
243
244
245
246
247
248
249
            except:
                pass
            else:
                token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
                if token_res[0]["tokenGroups"][0] \
                   == res[0]["objectSID"][0]:
                    raise DCJoinException("Not removing account %s which "
250
251
252
253
                                          "looks like a Samba DC account "
                                          "matching the password we already have.  "
                                          "To override, remove secrets.ldb and secrets.tdb"
                                          % ctx.samname)
254
255
256
257
258

        ctx.del_noerror(res[0].dn, recursive=True)

        if "msDS-Krbtgtlink" in res[0]:
            new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
259
            ctx.del_noerror(ctx.new_krbtgt_dn)
260
261
262
263
264
265
266
267
268
269
270

        res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
                               expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
                               (ldb.binary_encode("dns-%s" % ctx.myname),
                                ldb.binary_encode("dns/%s" % ctx.dnshostname)),
                               attrs=[])
        if res:
            ctx.del_noerror(res[0].dn, recursive=True)

        res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
                               expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
271
                               attrs=[])
272
273
        if res:
            raise DCJoinException("Not removing account %s which looks like "
274
275
276
277
                                  "a Samba DNS service account but does not "
                                  "have servicePrincipalName=%s" %
                                  (ldb.binary_encode("dns-%s" % ctx.myname),
                                   ldb.binary_encode("dns/%s" % ctx.dnshostname)))
278

279
    def cleanup_old_join(ctx, force=False):
280
        """Remove any DNs from a previous join."""
281
282
        # find the krbtgt link
        if not ctx.subdomain:
283
            ctx.cleanup_old_accounts(force=force)
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317

        if ctx.connection_dn is not None:
            ctx.del_noerror(ctx.connection_dn)
        if ctx.krbtgt_dn is not None:
            ctx.del_noerror(ctx.krbtgt_dn)
        ctx.del_noerror(ctx.ntds_dn)
        ctx.del_noerror(ctx.server_dn, recursive=True)
        if ctx.topology_dn:
            ctx.del_noerror(ctx.topology_dn)
        if ctx.partition_dn:
            ctx.del_noerror(ctx.partition_dn)

        if ctx.subdomain:
            binding_options = "sign"
            lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
                                 ctx.lp, ctx.creds)

            objectAttr = lsa.ObjectAttribute()
            objectAttr.sec_qos = lsa.QosInfo()

            pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
                                             objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)

            name = lsa.String()
            name.string = ctx.realm
            info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)

            lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)

            name = lsa.String()
            name.string = ctx.forest_domain_name
            info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)

            lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
318

319
320
321
322
323
324
        if ctx.dns_a_dn:
            ctx.del_noerror(ctx.dns_a_dn)

        if ctx.dns_cname_dn:
            ctx.del_noerror(ctx.dns_cname_dn)

325
    def promote_possible(ctx):
326
        """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
327
328
329
330
331
332
333
334
335
336
337
        if ctx.subdomain:
            # This shouldn't happen
            raise Exception("Can not promote into a subdomain")

        res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
                               expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
                               attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
        if len(res) == 0:
            raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
        if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
            raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
338
339
        if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
                                                    samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
340
            raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
341

342
343
        ctx.promote_from_dn = res[0].dn

344
    def find_dc(ctx, domain):
345
        """find a writeable DC for the given domain"""
346
        try:
347
            ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
VL's avatar
VL committed
348
        except NTSTATUSError as error:
349
350
            raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
                               (domain, error.args[1]))
351
        except Exception:
352
            raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
353
354
        if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
            ctx.site = ctx.cldap_ret.client_site
355
        return ctx.cldap_ret.pdc_dns_name
356

357
358
359
360
361
362
363
364
    def find_dc_site(ctx, server):
        site = None
        cldap_ret = ctx.net.finddc(address=server,
                                   flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
        if cldap_ret.client_site is not None and cldap_ret.client_site != "":
            site = cldap_ret.client_site
        return site

365
366
367
368
369
370
371
    def get_behavior_version(ctx):
        res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
        if "msDS-Behavior-Version" in res[0]:
            return int(res[0]["msDS-Behavior-Version"][0])
        else:
            return samba.dsdb.DS_DOMAIN_FUNCTION_2000

372
373
    def get_dnsHostName(ctx):
        res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
374
        return str(res[0]["dnsHostName"][0])
375

376
    def get_domain_name(ctx):
377
        '''get netbios name of the domain from the partitions record'''
378
379
        partitions_dn = ctx.samdb.get_partitions_dn()
        res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
380
                               expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_default_basedn())))
381
        return str(res[0]["nETBIOSName"][0])
382

383
384
385
386
    def get_forest_domain_name(ctx):
        '''get netbios name of the domain from the partitions record'''
        partitions_dn = ctx.samdb.get_partitions_dn()
        res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
387
                               expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
388
        return str(res[0]["nETBIOSName"][0])
389

390
391
392
393
    def get_parent_partition_dn(ctx):
        '''get the parent domain partition DN from parent DNS name'''
        res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
                               expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
394
395
                               (ldb.binary_encode(ctx.parent_dnsdomain),
                                ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
396
397
        return str(res[0].dn)

398
399
400
401
    def get_naming_master(ctx):
        '''get the parent domain partition DN from parent DNS name'''
        res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
                               scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
402
        if 'fSMORoleOwner' not in res[0]:
403
            raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
404
        try:
405
            master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0].decode('utf8')).get_extended_component('GUID')))
406
407
408
        except KeyError:
            raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])

409
410
411
        master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
        return master_host

412
    def get_mysid(ctx):
413
414
        '''get the SID of the connected user. Only works with w2k8 and later,
           so only used for RODC join'''
415
        res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
416
        binsid = res[0]["tokenGroups"][0]
417
        return get_string(ctx.samdb.schema_format_value("objectSID", binsid))
418

419
420
421
422
    def dn_exists(ctx, dn):
        '''check if a DN exists'''
        try:
            res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
423
424
        except ldb.LdbError as e5:
            (enum, estr) = e5.args
425
426
427
            if enum == ldb.ERR_NO_SUCH_OBJECT:
                return False
            raise
428
429
        return True

430
431
    def add_krbtgt_account(ctx):
        '''RODCs need a special krbtgt account'''
432
        print("Adding %s" % ctx.krbtgt_dn)
433
        rec = {
434
435
436
            "dn": ctx.krbtgt_dn,
            "objectclass": "user",
            "useraccountcontrol": str(samba.dsdb.UF_NORMAL_ACCOUNT |
437
                                      samba.dsdb.UF_ACCOUNTDISABLE),
438
439
            "showinadvancedviewonly": "TRUE",
            "description": "krbtgt for %s" % ctx.samname}
440
441
442
443
444
445
446
        ctx.samdb.add(rec, ["rodc_join:1:1"])

        # now we need to search for the samAccountName attribute on the krbtgt DN,
        # as this will have been magically set to the krbtgt number
        res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
        ctx.krbtgt_name = res[0]["samAccountName"][0]

447
        print("Got krbtgt_name=%s" % ctx.krbtgt_name)
448
449
450
451
452
453
454
455

        m = ldb.Message()
        m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
        m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
                                                  ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
        ctx.samdb.modify(m)

        ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
456
        print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
457
458
        ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)

459
    def drsuapi_connect(ctx):
460
        '''make a DRSUAPI connection to the naming master'''
461
        binding_options = "seal"
462
        if ctx.lp.log_level() >= 9:
463
464
            binding_options += ",print"
        binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
465
        ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
466
        (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
467
468
469

    def create_tmp_samdb(ctx):
        '''create a temporary samdb object for schema queries'''
470
        ctx.tmp_schema = Schema(ctx.domsid,
471
472
473
474
475
476
477
478
479
480
481
482
                                schemadn=ctx.schema_dn)
        ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
                              credentials=ctx.creds, lp=ctx.lp, global_schema=False,
                              am_rodc=False)
        ctx.tmp_samdb.set_schema(ctx.tmp_schema)

    def build_DsReplicaAttribute(ctx, attrname, attrvalue):
        '''build a DsReplicaAttributeCtr object'''
        r = drsuapi.DsReplicaAttribute()
        r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
        r.value_ctr = 1

483
    def DsAddEntry(ctx, recs):
484
485
486
487
488
489
        '''add a record via the DRSUAPI DsAddEntry call'''
        if ctx.drsuapi is None:
            ctx.drsuapi_connect()
        if ctx.tmp_samdb is None:
            ctx.create_tmp_samdb()

490
491
492
493
494
495
496
497
498
499
500
501
502
        objects = []
        for rec in recs:
            id = drsuapi.DsReplicaObjectIdentifier()
            id.dn = rec['dn']

            attrs = []
            for a in rec:
                if a == 'dn':
                    continue
                if not isinstance(rec[a], list):
                    v = [rec[a]]
                else:
                    v = rec[a]
503
                v = [x.encode('utf8') if isinstance(x, text_type) else x for x in v]
504
505
506
507
508
509
510
511
512
513
514
515
516
517
                rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
                attrs.append(rattr)

            attribute_ctr = drsuapi.DsReplicaAttributeCtr()
            attribute_ctr.num_attributes = len(attrs)
            attribute_ctr.attributes = attrs

            object = drsuapi.DsReplicaObject()
            object.identifier = id
            object.attribute_ctr = attribute_ctr

            list_object = drsuapi.DsReplicaObjectListItem()
            list_object.object = object
            objects.append(list_object)
518
519

        req2 = drsuapi.DsAddEntryRequest2()
520
521
522
523
524
        req2.first_object = objects[0]
        prev = req2.first_object
        for o in objects[1:]:
            prev.next_object = o
            prev = o
525
526

        (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
527
528
529
530
        if level == 2:
            if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
                print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
                raise RuntimeError("DsAddEntry failed")
531
            if ctr.extended_err[0] != werror.WERR_SUCCESS:
532
533
534
535
536
                print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
                raise RuntimeError("DsAddEntry failed")
        if level == 3:
            if ctr.err_ver != 1:
                raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
537
            if ctr.err_data.status[0] != werror.WERR_SUCCESS:
538
539
540
541
542
                if ctr.err_data.info is None:
                    print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
                else:
                    print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
                                                                        ctr.err_data.info.extended_err))
543
544
545
546
547
                raise RuntimeError("DsAddEntry failed")
            if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
                print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
                raise RuntimeError("DsAddEntry failed")

548
        return ctr.objects
549

550
551
    def join_ntdsdsa_obj(ctx):
        '''return the ntdsdsa object to add'''
552

553
        print("Adding %s" % ctx.ntds_dn)
554
        rec = {
555
556
557
558
            "dn": ctx.ntds_dn,
            "objectclass": "nTDSDSA",
            "systemFlags": str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
            "dMDLocation": ctx.schema_dn}
559

560
        nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
561
562

        if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
563
            rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
564

565
        if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
566
567
568
569
            rec["msDS-HasDomainNCs"] = ctx.base_dn

        if ctx.RODC:
            rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
570
            rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
571
572
573
            rec["options"] = "37"
        else:
            rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
574
575
576
577
            rec["HasMasterNCs"]      = []
            for nc in nc_list:
                if nc in ctx.full_nc_list:
                    rec["HasMasterNCs"].append(nc)
578
            if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
579
                rec["msDS-HasMasterNCs"] = ctx.full_nc_list
580
            rec["options"] = "1"
581
            rec["invocationId"] = ndr_pack(ctx.invocation_id)
582
583
584
585
586
587
588

        return rec

    def join_add_ntdsdsa(ctx):
        '''add the ntdsdsa object'''

        rec = ctx.join_ntdsdsa_obj()
589
590
591
        if ctx.forced_local_samdb:
            ctx.samdb.add(rec, controls=["relax:0"])
        elif ctx.RODC:
592
593
            ctx.samdb.add(rec, ["rodc_join:1:1"])
        else:
594
            ctx.DsAddEntry([rec])
595
596
597
598
599

        # find the GUID of our NTDS DN
        res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
        ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))

600
    def join_add_objects(ctx, specified_sid=None):
601
        '''add the various objects needed for the join'''
602
        if ctx.acct_dn:
603
            print("Adding %s" % ctx.acct_dn)
604
            rec = {
605
                "dn": ctx.acct_dn,
606
607
                "objectClass": "computer",
                "displayname": ctx.samname,
608
609
610
                "samaccountname": ctx.samname,
                "userAccountControl": str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
                "dnshostname": ctx.dnshostname}
611
612
            if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
                rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
613
614
            elif ctx.promote_existing:
                rec['msDS-SupportedEncryptionTypes'] = []
615
616
            if ctx.managedby:
                rec["managedby"] = ctx.managedby
617
618
619
            elif ctx.promote_existing:
                rec["managedby"] = []

620
621
            if ctx.never_reveal_sid:
                rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
622
623
            elif ctx.promote_existing:
                rec["msDS-NeverRevealGroup"] = []
624

625
626
            if ctx.reveal_sid:
                rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
627
628
629
            elif ctx.promote_existing:
                rec["msDS-RevealOnDemandGroup"] = []

630
631
632
            if specified_sid:
                rec["objectSid"] = ndr_pack(specified_sid)

633
634
635
636
637
            if ctx.promote_existing:
                if ctx.promote_from_dn != ctx.acct_dn:
                    ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
                ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
            else:
638
639
640
641
                controls = None
                if specified_sid is not None:
                    controls = ["relax:0"]
                ctx.samdb.add(rec, controls=controls)
642
643
644
645

        if ctx.krbtgt_dn:
            ctx.add_krbtgt_account()

646
        if ctx.server_dn:
647
            print("Adding %s" % ctx.server_dn)
648
649
            rec = {
                "dn": ctx.server_dn,
650
                "objectclass": "server",
651
                # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
652
                "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
653
654
                                   samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
                                   samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
655
                # windows seems to add the dnsHostName later
656
                "dnsHostName": ctx.dnshostname}
657
658
659

            if ctx.acct_dn:
                rec["serverReference"] = ctx.acct_dn
660

661
            ctx.samdb.add(rec)
662

663
        if ctx.subdomain:
664
665
666
            # the rest is done after replication
            ctx.ntds_guid = None
            return
667

668
669
        if ctx.ntds_dn:
            ctx.join_add_ntdsdsa()
670

671
672
673
            # Add the Replica-Locations or RO-Replica-Locations attributes
            # TODO Is this supposed to be for the schema partition too?
            expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
674
            domain = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
675
676
677
                                       attrs=[],
                                       base=ctx.samdb.get_partitions_dn(),
                                       expression=expr), ctx.domaindns_zone)
678
679

            expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.forestdns_zone)
680
            forest = (ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
681
682
683
                                       attrs=[],
                                       base=ctx.samdb.get_partitions_dn(),
                                       expression=expr), ctx.forestdns_zone)
684
685
686
687

            for part, zone in (domain, forest):
                if zone not in ctx.nc_list:
                    continue
688
689
690
691
692
693
694
695
696
697
698
699

                if len(part) == 1:
                    m = ldb.Message()
                    m.dn = part[0].dn
                    attr = "msDS-NC-Replica-Locations"
                    if ctx.RODC:
                        attr = "msDS-NC-RO-Replica-Locations"

                    m[attr] = ldb.MessageElement(ctx.ntds_dn,
                                                 ldb.FLAG_MOD_ADD, attr)
                    ctx.samdb.modify(m)

700
        if ctx.connection_dn is not None:
701
            print("Adding %s" % ctx.connection_dn)
702
            rec = {
703
704
705
706
707
                "dn": ctx.connection_dn,
                "objectclass": "nTDSConnection",
                "enabledconnection": "TRUE",
                "options": "65",
                "fromServer": ctx.dc_ntds_dn}
708
            ctx.samdb.add(rec)
709

710
        if ctx.acct_dn:
711
            print("Adding SPNs to %s" % ctx.acct_dn)
712
713
714
715
716
            m = ldb.Message()
            m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
            for i in range(len(ctx.SPNs)):
                ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
            m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
717
                                                           ldb.FLAG_MOD_REPLACE,
718
719
720
                                                           "servicePrincipalName")
            ctx.samdb.modify(m)

721
722
723
724
725
            # The account password set operation should normally be done over
            # LDAP. Windows 2000 DCs however allow this only with SSL
            # connections which are hard to set up and otherwise refuse with
            # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
            # over SAMR.
726
            print("Setting account password for %s" % ctx.samname)
727
728
729
730
731
732
            try:
                ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
                                      % ldb.binary_encode(ctx.samname),
                                      ctx.acct_pass,
                                      force_change_at_next_login=False,
                                      username=ctx.samname)
733
734
            except ldb.LdbError as e2:
                (num, _) = e2.args
735
736
737
738
                if num != ldb.ERR_UNWILLING_TO_PERFORM:
                    pass
                ctx.net.set_password(account_name=ctx.samname,
                                     domain_name=ctx.domain_name,
739
                                     newpassword=ctx.acct_pass)
740

741
            res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
742
743
                                   attrs=["msDS-KeyVersionNumber",
                                          "objectSID"])
744
745
746
747
            if "msDS-KeyVersionNumber" in res[0]:
                ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
            else:
                ctx.key_version_number = None
748

749
750
751
            ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
                                                res[0]["objectSid"][0])

752
753
754
755
756
757
758
            print("Enabling account")
            m = ldb.Message()
            m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
            m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
                                                         ldb.FLAG_MOD_REPLACE,
                                                         "userAccountControl")
            ctx.samdb.modify(m)
759

760
761
762
763
        if ctx.dns_backend.startswith("BIND9_"):
            ctx.dnspass = samba.generate_random_password(128, 255)

            recs = ctx.samdb.parse_ldif(read_and_sub_file(setup_path("provision_dns_add_samba.ldif"),
764
765
                                                          {"DNSDOMAIN": ctx.dnsdomain,
                                                           "DOMAINDN": ctx.base_dn,
766
                                                           "HOSTNAME": ctx.myname,
767
                                                           "DNSPASS_B64": b64encode(ctx.dnspass.encode('utf-16-le')).decode('utf8'),
768
                                                           "DNSNAME": ctx.dnshostname}))
769
770
            for changetype, msg in recs:
                assert changetype == ldb.CHANGETYPE_NONE
771
                dns_acct_dn = msg["dn"]
772
                print("Adding DNS account %s with dns/ SPN" % msg["dn"])
773
774
775
776
777

                # Remove dns password (we will set it as a modify, as we can't do clearTextPassword over LDAP)
                del msg["clearTextPassword"]
                # Remove isCriticalSystemObject for similar reasons, it cannot be set over LDAP
                del msg["isCriticalSystemObject"]
778
779
780
                # Disable account until password is set
                msg["userAccountControl"] = str(samba.dsdb.UF_NORMAL_ACCOUNT |
                                                samba.dsdb.UF_ACCOUNTDISABLE)
781
782
                try:
                    ctx.samdb.add(msg)
783
784
                except ldb.LdbError as e:
                    (num, _) = e.args
785
786
787
788
789
790
791
792
                    if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
                        raise

            # The account password set operation should normally be done over
            # LDAP. Windows 2000 DCs however allow this only with SSL
            # connections which are hard to set up and otherwise refuse with
            # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
            # over SAMR.
793
            print("Setting account password for dns-%s" % ctx.myname)
794
795
796
797
798
799
            try:
                ctx.samdb.setpassword("(&(objectClass=user)(samAccountName=dns-%s))"
                                      % ldb.binary_encode(ctx.myname),
                                      ctx.dnspass,
                                      force_change_at_next_login=False,
                                      username=ctx.samname)
800
801
            except ldb.LdbError as e3:
                (num, _) = e3.args
802
                if num != ldb.ERR_UNWILLING_TO_PERFORM:
803
804
                    raise
                ctx.net.set_password(account_name="dns-%s" % ctx.myname,
805
806
807
808
809
810
811
812
813
814
                                     domain_name=ctx.domain_name,
                                     newpassword=ctx.dnspass)

            res = ctx.samdb.search(base=dns_acct_dn, scope=ldb.SCOPE_BASE,
                                   attrs=["msDS-KeyVersionNumber"])
            if "msDS-KeyVersionNumber" in res[0]:
                ctx.dns_key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
            else:
                ctx.dns_key_version_number = None

815
    def join_add_objects2(ctx):
816
        """add the various objects needed for the join, for subdomains post replication"""
817

818
        print("Adding %s" % ctx.partition_dn)
819
820
        name_map = {'SubdomainAdmins': "%s-%s" % (str(ctx.domsid), security.DOMAIN_RID_ADMINS)}
        sd_binary = descriptor.get_paritions_crossref_subdomain_descriptor(ctx.forestsid, name_map=name_map)
821
        rec = {
822
823
824
825
826
            "dn": ctx.partition_dn,
            "objectclass": "crossRef",
            "objectCategory": "CN=Cross-Ref,%s" % ctx.schema_dn,
            "nCName": ctx.base_dn,
            "nETBIOSName": ctx.domain_name,
827
            "dnsRoot": ctx.dnsdomain,
828
            "trustParent": ctx.parent_partition_dn,
829
            "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC |samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
830
            "ntSecurityDescriptor": sd_binary,
831
832
        }

833
834
        if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
            rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
835

836
        rec2 = ctx.join_ntdsdsa_obj()
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856

        objects = ctx.DsAddEntry([rec, rec2])
        if len(objects) != 2:
            raise DCJoinException("Expected 2 objects from DsAddEntry")

        ctx.ntds_guid = objects[1].guid

        print("Replicating partition DN")
        ctx.repl.replicate(ctx.partition_dn,
                           misc.GUID("00000000-0000-0000-0000-000000000000"),
                           ctx.ntds_guid,
                           exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
                           replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)

        print("Replicating NTDS DN")
        ctx.repl.replicate(ctx.ntds_dn,
                           misc.GUID("00000000-0000-0000-0000-000000000000"),
                           ctx.ntds_guid,
                           exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
                           replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
857

858
    def join_provision(ctx):
859
        """Provision the local SAM."""
860

861
        print("Calling bare provision")
862

863
        smbconf = ctx.lp.configfile
864

865
        presult = provision(ctx.logger, system_session(), smbconf=smbconf,
866
867
868
869
870
871
872
873
874
875
                            targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
                            rootdn=ctx.root_dn, domaindn=ctx.base_dn,
                            schemadn=ctx.schema_dn, configdn=ctx.config_dn,
                            serverdn=ctx.server_dn, domain=ctx.domain_name,
                            hostname=ctx.myname, domainsid=ctx.domsid,
                            machinepass=ctx.acct_pass, serverrole="active directory domain controller",
                            sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
                            use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
                            plaintext_secrets=ctx.plaintext_secrets,
                            backend_store=ctx.backend_store
876
                            )
877
        print("Provision OK for domain DN %s" % presult.domaindn)
878
879
        ctx.local_samdb = presult.samdb
        ctx.lp          = presult.lp
880
        ctx.paths       = presult.paths
881
882
        ctx.names       = presult.names

883
884
885
        # Fix up the forestsid, it may be different if we are joining as a subdomain
        ctx.names.forestsid = ctx.forestsid

886
    def join_provision_own_domain(ctx):
887
        """Provision the local SAM."""
888

889
890
891
892
893
894
895
896
897
        # we now operate exclusively on the local database, which
        # we need to reopen in order to get the newly created schema
        print("Reconnecting to local samdb")
        ctx.samdb = SamDB(url=ctx.local_samdb.url,
                          session_info=system_session(),
                          lp=ctx.local_samdb.lp,
                          global_schema=False)
        ctx.samdb.set_invocation_id(str(ctx.invocation_id))
        ctx.local_samdb = ctx.samdb
898

899
        ctx.logger.info("Finding domain GUID from ncName")
900
        res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
901
                                     controls=["extended_dn:1:1", "reveal_internals:0"])
902
903
904
905
906

        if 'nCName' not in res[0]:
            raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))

        try:
907
            ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0].decode('utf8')).get_extended_component('GUID')))
908
909
910
        except KeyError:
            raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])

911
        ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
912

913
        ctx.logger.info("Calling own domain provision")
914
915
916
917

        secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)

        presult = provision_fill(ctx.local_samdb, secrets_ldb,
918
                                 ctx.logger, ctx.names, ctx.paths,
919
                                 dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
920
                                 targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
921
                                 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
922
                                 lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
923
                                 dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
924
        print("Provision OK for domain %s" % ctx.names.dnsdomain)
925

926
927
928
929
930
931
    def create_replicator(ctx, repl_creds, binding_options):
        '''Creates a new DRS object for managing replications'''
        return drs_utils.drs_Replicate(
                "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
                ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)

932
    def join_replicate(ctx):
933
        """Replicate the SAM."""
934

935
        print("Starting replication")
936
        ctx.local_samdb.transaction_start()
937
938
        try:
            source_dsa_invocation_id = misc.GUID(ctx.samdb.ge