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