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