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