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