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