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