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