selftest: enable py3 for samba.tests.kcc.graph_utils
[samba.git] / python / samba / join.py
1 # python join code
2 # Copyright Andrew Tridgell 2010
3 # Copyright Andrew Bartlett 2010
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 from __future__ import print_function
20 """Joining a domain."""
21
22 from samba.auth import system_session
23 from samba.samdb import SamDB
24 from samba import gensec, Ldb, drs_utils, arcfour_encrypt, string_to_byte_array
25 import ldb, 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
47 class DCJoinException(Exception):
48
49     def __init__(self, msg):
50         super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
51
52
53 class dc_join(object):
54     """Perform a DC join."""
55
56     def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
57                  netbios_name=None, targetdir=None, domain=None,
58                  machinepass=None, use_ntvfs=False, dns_backend=None,
59                  promote_existing=False, clone_only=False,
60                  plaintext_secrets=False):
61         if site is None:
62             site = "Default-First-Site-Name"
63
64         ctx.clone_only=clone_only
65
66         ctx.logger = logger
67         ctx.creds = creds
68         ctx.lp = lp
69         ctx.site = site
70         ctx.targetdir = targetdir
71         ctx.use_ntvfs = use_ntvfs
72         ctx.plaintext_secrets = plaintext_secrets
73
74         ctx.promote_existing = promote_existing
75         ctx.promote_from_dn = None
76
77         ctx.nc_list = []
78         ctx.full_nc_list = []
79
80         ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
81         ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
82
83         if server is not None:
84             ctx.server = server
85         else:
86             ctx.logger.info("Finding a writeable DC for domain '%s'" % domain)
87             ctx.server = ctx.find_dc(domain)
88             ctx.logger.info("Found DC %s" % ctx.server)
89
90         ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
91                           session_info=system_session(),
92                           credentials=ctx.creds, lp=ctx.lp)
93
94         try:
95             ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
96         except ldb.LdbError as e4:
97             (enum, estr) = e4.args
98             raise DCJoinException(estr)
99
100
101         ctx.base_dn = str(ctx.samdb.get_default_basedn())
102         ctx.root_dn = str(ctx.samdb.get_root_basedn())
103         ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
104         ctx.config_dn = str(ctx.samdb.get_config_basedn())
105         ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
106         ctx.forestsid = ctx.domsid
107         ctx.domain_name = ctx.get_domain_name()
108         ctx.forest_domain_name = ctx.get_forest_domain_name()
109         ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
110
111         ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
112         ctx.dc_dnsHostName = ctx.get_dnsHostName()
113         ctx.behavior_version = ctx.get_behavior_version()
114
115         if machinepass is not None:
116             ctx.acct_pass = machinepass
117         else:
118             ctx.acct_pass = samba.generate_random_machine_password(128, 255)
119
120         ctx.dnsdomain = ctx.samdb.domain_dns_name()
121         if clone_only:
122             # As we don't want to create or delete these DNs, we set them to None
123             ctx.server_dn = None
124             ctx.ntds_dn = None
125             ctx.acct_dn = None
126             ctx.myname = ctx.server.split('.')[0]
127             ctx.ntds_guid = None
128             ctx.rid_manager_dn = None
129
130             # Save this early
131             ctx.remote_dc_ntds_guid = ctx.samdb.get_ntds_GUID()
132         else:
133             # work out the DNs of all the objects we will be adding
134             ctx.myname = netbios_name
135             ctx.samname = "%s$" % ctx.myname
136             ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
137             ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
138             ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
139             ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
140             ctx.dnsforest = ctx.samdb.forest_dns_name()
141
142             topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
143             if ctx.dn_exists(topology_base):
144                 ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
145             else:
146                 ctx.topology_dn = None
147
148             ctx.SPNs = [ "HOST/%s" % ctx.myname,
149                          "HOST/%s" % ctx.dnshostname,
150                          "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
151
152             res_rid_manager = ctx.samdb.search(scope=ldb.SCOPE_BASE,
153                                                attrs=["rIDManagerReference"],
154                                                base=ctx.base_dn)
155
156             ctx.rid_manager_dn = res_rid_manager[0]["rIDManagerReference"][0]
157
158         ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
159         ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
160
161         expr = "(&(objectClass=crossRef)(ncName=%s))" % ldb.binary_encode(ctx.domaindns_zone)
162         res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
163                                          attrs=[],
164                                          base=ctx.samdb.get_partitions_dn(),
165                                          expression=expr)
166         if dns_backend is None:
167             ctx.dns_backend = "NONE"
168         else:
169             if len(res_domaindns) == 0:
170                 ctx.dns_backend = "NONE"
171                 print("NO DNS zone information found in source domain, not replicating DNS")
172             else:
173                 ctx.dns_backend = dns_backend
174
175         ctx.realm = ctx.dnsdomain
176
177         ctx.tmp_samdb = None
178
179         ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
180                              drsuapi.DRSUAPI_DRS_PER_SYNC |
181                              drsuapi.DRSUAPI_DRS_GET_ANC |
182                              drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
183                              drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
184
185         # these elements are optional
186         ctx.never_reveal_sid = None
187         ctx.reveal_sid = None
188         ctx.connection_dn = None
189         ctx.RODC = False
190         ctx.krbtgt_dn = None
191         ctx.drsuapi = None
192         ctx.managedby = None
193         ctx.subdomain = False
194         ctx.adminpass = None
195         ctx.partition_dn = None
196
197         ctx.dns_a_dn = None
198         ctx.dns_cname_dn = None
199
200         # Do not normally register 127. addresses but allow override for selftest
201         ctx.force_all_ips = False
202
203     def del_noerror(ctx, dn, recursive=False):
204         if recursive:
205             try:
206                 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
207             except Exception:
208                 return
209             for r in res:
210                 ctx.del_noerror(r.dn, recursive=True)
211         try:
212             ctx.samdb.delete(dn)
213             print("Deleted %s" % dn)
214         except Exception:
215             pass
216
217     def cleanup_old_accounts(ctx, force=False):
218         res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
219                                expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
220                                attrs=["msDS-krbTgtLink", "objectSID"])
221         if len(res) == 0:
222             return
223
224         if not force:
225             creds = Credentials()
226             creds.guess(ctx.lp)
227             try:
228                 creds.set_machine_account(ctx.lp)
229                 creds.set_kerberos_state(ctx.creds.get_kerberos_state())
230                 machine_samdb = SamDB(url="ldap://%s" % ctx.server,
231                                       session_info=system_session(),
232                                     credentials=creds, lp=ctx.lp)
233             except:
234                 pass
235             else:
236                 token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
237                 if token_res[0]["tokenGroups"][0] \
238                    == res[0]["objectSID"][0]:
239                     raise DCJoinException("Not removing account %s which "
240                                        "looks like a Samba DC account "
241                                        "maching the password we already have.  "
242                                        "To override, remove secrets.ldb and secrets.tdb"
243                                     % ctx.samname)
244
245         ctx.del_noerror(res[0].dn, recursive=True)
246
247         if "msDS-Krbtgtlink" in res[0]:
248             new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
249             ctx.del_noerror(ctx.new_krbtgt_dn)
250
251         res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
252                                expression='(&(sAMAccountName=%s)(servicePrincipalName=%s))' %
253                                (ldb.binary_encode("dns-%s" % ctx.myname),
254                                 ldb.binary_encode("dns/%s" % ctx.dnshostname)),
255                                attrs=[])
256         if res:
257             ctx.del_noerror(res[0].dn, recursive=True)
258
259         res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
260                                expression='(sAMAccountName=%s)' % ldb.binary_encode("dns-%s" % ctx.myname),
261                             attrs=[])
262         if res:
263             raise DCJoinException("Not removing account %s which looks like "
264                                "a Samba DNS service account but does not "
265                                "have servicePrincipalName=%s" %
266                                (ldb.binary_encode("dns-%s" % ctx.myname),
267                                 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
268
269
270     def cleanup_old_join(ctx, force=False):
271         """Remove any DNs from a previous join."""
272         # find the krbtgt link
273         if not ctx.subdomain:
274             ctx.cleanup_old_accounts(force=force)
275
276         if ctx.connection_dn is not None:
277             ctx.del_noerror(ctx.connection_dn)
278         if ctx.krbtgt_dn is not None:
279             ctx.del_noerror(ctx.krbtgt_dn)
280         ctx.del_noerror(ctx.ntds_dn)
281         ctx.del_noerror(ctx.server_dn, recursive=True)
282         if ctx.topology_dn:
283             ctx.del_noerror(ctx.topology_dn)
284         if ctx.partition_dn:
285             ctx.del_noerror(ctx.partition_dn)
286
287         if ctx.subdomain:
288             binding_options = "sign"
289             lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
290                                  ctx.lp, ctx.creds)
291
292             objectAttr = lsa.ObjectAttribute()
293             objectAttr.sec_qos = lsa.QosInfo()
294
295             pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
296                                              objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
297
298             name = lsa.String()
299             name.string = ctx.realm
300             info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
301
302             lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
303
304             name = lsa.String()
305             name.string = ctx.forest_domain_name
306             info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
307
308             lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
309
310         if ctx.dns_a_dn:
311             ctx.del_noerror(ctx.dns_a_dn)
312
313         if ctx.dns_cname_dn:
314             ctx.del_noerror(ctx.dns_cname_dn)
315
316
317
318     def promote_possible(ctx):
319         """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
320         if ctx.subdomain:
321             # This shouldn't happen
322             raise Exception("Can not promote into a subdomain")
323
324         res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
325                                expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
326                                attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
327         if len(res) == 0:
328             raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
329         if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
330             raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
331         if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
332             raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
333
334         ctx.promote_from_dn = res[0].dn
335
336
337     def find_dc(ctx, domain):
338         """find a writeable DC for the given domain"""
339         try:
340             ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
341         except NTSTATUSError as error:
342             raise Exception("Failed to find a writeable DC for domain '%s': %s" %
343                             (domain, error[1]))
344         except Exception:
345             raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
346         if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
347             ctx.site = ctx.cldap_ret.client_site
348         return ctx.cldap_ret.pdc_dns_name
349
350
351     def get_behavior_version(ctx):
352         res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
353         if "msDS-Behavior-Version" in res[0]:
354             return int(res[0]["msDS-Behavior-Version"][0])
355         else:
356             return samba.dsdb.DS_DOMAIN_FUNCTION_2000
357
358     def get_dnsHostName(ctx):
359         res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
360         return res[0]["dnsHostName"][0]
361
362     def get_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_default_basedn())))
367         return res[0]["nETBIOSName"][0]
368
369     def get_forest_domain_name(ctx):
370         '''get netbios name of the domain from the partitions record'''
371         partitions_dn = ctx.samdb.get_partitions_dn()
372         res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
373                                expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
374         return res[0]["nETBIOSName"][0]
375
376     def get_parent_partition_dn(ctx):
377         '''get the parent domain partition DN from parent DNS name'''
378         res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
379                                expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
380                                (ldb.binary_encode(ctx.parent_dnsdomain),
381                                 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
382         return str(res[0].dn)
383
384     def get_naming_master(ctx):
385         '''get the parent domain partition DN from parent DNS name'''
386         res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
387                                scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
388         if not 'fSMORoleOwner' in res[0]:
389             raise DCJoinException("Can't find naming master on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
390         try:
391             master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0]).get_extended_component('GUID')))
392         except KeyError:
393             raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['fSMORoleOwner'][0])
394
395         master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
396         return master_host
397
398     def get_mysid(ctx):
399         '''get the SID of the connected user. Only works with w2k8 and later,
400            so only used for RODC join'''
401         res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
402         binsid = res[0]["tokenGroups"][0]
403         return ctx.samdb.schema_format_value("objectSID", binsid)
404
405     def dn_exists(ctx, dn):
406         '''check if a DN exists'''
407         try:
408             res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
409         except ldb.LdbError as e5:
410             (enum, estr) = e5.args
411             if enum == ldb.ERR_NO_SUCH_OBJECT:
412                 return False
413             raise
414         return True
415
416     def add_krbtgt_account(ctx):
417         '''RODCs need a special krbtgt account'''
418         print("Adding %s" % ctx.krbtgt_dn)
419         rec = {
420             "dn" : ctx.krbtgt_dn,
421             "objectclass" : "user",
422             "useraccountcontrol" : str(samba.dsdb.UF_NORMAL_ACCOUNT |
423                                        samba.dsdb.UF_ACCOUNTDISABLE),
424             "showinadvancedviewonly" : "TRUE",
425             "description" : "krbtgt for %s" % ctx.samname}
426         ctx.samdb.add(rec, ["rodc_join:1:1"])
427
428         # now we need to search for the samAccountName attribute on the krbtgt DN,
429         # as this will have been magically set to the krbtgt number
430         res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
431         ctx.krbtgt_name = res[0]["samAccountName"][0]
432
433         print("Got krbtgt_name=%s" % ctx.krbtgt_name)
434
435         m = ldb.Message()
436         m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
437         m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
438                                                   ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
439         ctx.samdb.modify(m)
440
441         ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
442         print("Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn))
443         ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
444
445     def drsuapi_connect(ctx):
446         '''make a DRSUAPI connection to the naming master'''
447         binding_options = "seal"
448         if ctx.lp.log_level() >= 9:
449             binding_options += ",print"
450         binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
451         ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
452         (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
453
454     def create_tmp_samdb(ctx):
455         '''create a temporary samdb object for schema queries'''
456         ctx.tmp_schema = Schema(ctx.domsid,
457                                 schemadn=ctx.schema_dn)
458         ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
459                               credentials=ctx.creds, lp=ctx.lp, global_schema=False,
460                               am_rodc=False)
461         ctx.tmp_samdb.set_schema(ctx.tmp_schema)
462
463     def build_DsReplicaAttribute(ctx, attrname, attrvalue):
464         '''build a DsReplicaAttributeCtr object'''
465         r = drsuapi.DsReplicaAttribute()
466         r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
467         r.value_ctr = 1
468
469
470     def DsAddEntry(ctx, recs):
471         '''add a record via the DRSUAPI DsAddEntry call'''
472         if ctx.drsuapi is None:
473             ctx.drsuapi_connect()
474         if ctx.tmp_samdb is None:
475             ctx.create_tmp_samdb()
476
477         objects = []
478         for rec in recs:
479             id = drsuapi.DsReplicaObjectIdentifier()
480             id.dn = rec['dn']
481
482             attrs = []
483             for a in rec:
484                 if a == 'dn':
485                     continue
486                 if not isinstance(rec[a], list):
487                     v = [rec[a]]
488                 else:
489                     v = rec[a]
490                 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
491                 attrs.append(rattr)
492
493             attribute_ctr = drsuapi.DsReplicaAttributeCtr()
494             attribute_ctr.num_attributes = len(attrs)
495             attribute_ctr.attributes = attrs
496
497             object = drsuapi.DsReplicaObject()
498             object.identifier = id
499             object.attribute_ctr = attribute_ctr
500
501             list_object = drsuapi.DsReplicaObjectListItem()
502             list_object.object = object
503             objects.append(list_object)
504
505         req2 = drsuapi.DsAddEntryRequest2()
506         req2.first_object = objects[0]
507         prev = req2.first_object
508         for o in objects[1:]:
509             prev.next_object = o
510             prev = o
511
512         (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
513         if level == 2:
514             if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
515                 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
516                 raise RuntimeError("DsAddEntry failed")
517             if ctr.extended_err[0] != werror.WERR_SUCCESS:
518                 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
519                 raise RuntimeError("DsAddEntry failed")
520         if level == 3:
521             if ctr.err_ver != 1:
522                 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
523             if ctr.err_data.status[0] != werror.WERR_SUCCESS:
524                 if ctr.err_data.info is None:
525                     print("DsAddEntry failed with status %s, info omitted" % (ctr.err_data.status[1]))
526                 else:
527                     print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status[1],
528                                                                         ctr.err_data.info.extended_err))
529                 raise RuntimeError("DsAddEntry failed")
530             if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
531                 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
532                 raise RuntimeError("DsAddEntry failed")
533
534         return ctr.objects
535
536     def join_ntdsdsa_obj(ctx):
537         '''return the ntdsdsa object to add'''
538
539         print("Adding %s" % ctx.ntds_dn)
540         rec = {
541             "dn" : ctx.ntds_dn,
542             "objectclass" : "nTDSDSA",
543             "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
544             "dMDLocation" : ctx.schema_dn}
545
546         nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
547
548         if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
549             rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
550
551         if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
552             rec["msDS-HasDomainNCs"] = ctx.base_dn
553
554         if ctx.RODC:
555             rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
556             rec["msDS-HasFullReplicaNCs"] = ctx.full_nc_list
557             rec["options"] = "37"
558         else:
559             rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
560             rec["HasMasterNCs"]      = []
561             for nc in nc_list:
562                 if nc in ctx.full_nc_list:
563                     rec["HasMasterNCs"].append(nc)
564             if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
565                 rec["msDS-HasMasterNCs"] = ctx.full_nc_list
566             rec["options"] = "1"
567             rec["invocationId"] = ndr_pack(ctx.invocation_id)
568
569         return rec
570
571     def join_add_ntdsdsa(ctx):
572         '''add the ntdsdsa object'''
573
574         rec = ctx.join_ntdsdsa_obj()
575         if ctx.RODC:
576             ctx.samdb.add(rec, ["rodc_join:1:1"])
577         else:
578             ctx.DsAddEntry([rec])
579
580         # find the GUID of our NTDS DN
581         res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
582         ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
583
584     def join_add_objects(ctx):
585         '''add the various objects needed for the join'''
586         if ctx.acct_dn:
587             print("Adding %s" % ctx.acct_dn)
588             rec = {
589                 "dn" : ctx.acct_dn,
590                 "objectClass": "computer",
591                 "displayname": ctx.samname,
592                 "samaccountname" : ctx.samname,
593                 "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
594                 "dnshostname" : ctx.dnshostname}
595             if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
596                 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
597             elif ctx.promote_existing:
598                 rec['msDS-SupportedEncryptionTypes'] = []
599             if ctx.managedby:
600                 rec["managedby"] = ctx.managedby
601             elif ctx.promote_existing:
602                 rec["managedby"] = []
603
604             if ctx.never_reveal_sid:
605                 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
606             elif ctx.promote_existing:
607                 rec["msDS-NeverRevealGroup"] = []
608
609             if ctx.reveal_sid:
610                 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
611             elif ctx.promote_existing:
612                 rec["msDS-RevealOnDemandGroup"] = []
613
614             if ctx.promote_existing:
615                 if ctx.promote_from_dn != ctx.acct_dn:
616                     ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
617                 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
618             else:
619                 ctx.samdb.add(rec)
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')),
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         print("Provision OK for domain DN %s" % presult.domaindn)
854         ctx.local_samdb = presult.samdb
855         ctx.lp          = presult.lp
856         ctx.paths       = presult.paths
857         ctx.names       = presult.names
858
859         # Fix up the forestsid, it may be different if we are joining as a subdomain
860         ctx.names.forestsid = ctx.forestsid
861
862     def join_provision_own_domain(ctx):
863         """Provision the local SAM."""
864
865         # we now operate exclusively on the local database, which
866         # we need to reopen in order to get the newly created schema
867         print("Reconnecting to local samdb")
868         ctx.samdb = SamDB(url=ctx.local_samdb.url,
869                           session_info=system_session(),
870                           lp=ctx.local_samdb.lp,
871                           global_schema=False)
872         ctx.samdb.set_invocation_id(str(ctx.invocation_id))
873         ctx.local_samdb = ctx.samdb
874
875         ctx.logger.info("Finding domain GUID from ncName")
876         res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
877                                      controls=["extended_dn:1:1", "reveal_internals:0"])
878
879         if 'nCName' not in res[0]:
880             raise DCJoinException("Can't find naming context on partition DN %s in %s" % (ctx.partition_dn, ctx.samdb.url))
881
882         try:
883             ctx.names.domainguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID')))
884         except KeyError:
885             raise DCJoinException("Can't find GUID in naming master on partition DN %s" % res[0]['ncName'][0])
886
887         ctx.logger.info("Got domain GUID %s" % ctx.names.domainguid)
888
889         ctx.logger.info("Calling own domain provision")
890
891         secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
892
893         presult = provision_fill(ctx.local_samdb, secrets_ldb,
894                                  ctx.logger, ctx.names, ctx.paths,
895                                  dom_for_fun_level=DS_DOMAIN_FUNCTION_2003,
896                                  targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
897                                  machinepass=ctx.acct_pass, serverrole="active directory domain controller",
898                                  lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
899                                  dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
900         print("Provision OK for domain %s" % ctx.names.dnsdomain)
901
902     def join_replicate(ctx):
903         """Replicate the SAM."""
904
905         print("Starting replication")
906         ctx.local_samdb.transaction_start()
907         try:
908             source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
909             if ctx.ntds_guid is None:
910                 print("Using DS_BIND_GUID_W2K3")
911                 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
912             else:
913                 destination_dsa_guid = ctx.ntds_guid
914
915             if ctx.RODC:
916                 repl_creds = Credentials()
917                 repl_creds.guess(ctx.lp)
918                 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
919                 repl_creds.set_username(ctx.samname)
920                 repl_creds.set_password(ctx.acct_pass.encode('utf-8'))
921             else:
922                 repl_creds = ctx.creds
923
924             binding_options = "seal"
925             if ctx.lp.log_level() >= 9:
926                 binding_options += ",print"
927             repl = drs_utils.drs_Replicate(
928                 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
929                 ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
930
931             repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
932                     destination_dsa_guid, schema=True, rodc=ctx.RODC,
933                     replica_flags=ctx.replica_flags)
934             repl.replicate(ctx.config_dn, source_dsa_invocation_id,
935                     destination_dsa_guid, rodc=ctx.RODC,
936                     replica_flags=ctx.replica_flags)
937             if not ctx.subdomain:
938                 # Replicate first the critical object for the basedn
939                 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
940                     print("Replicating critical objects from the base DN of the domain")
941                     ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
942                     repl.replicate(ctx.base_dn, source_dsa_invocation_id,
943                                 destination_dsa_guid, rodc=ctx.RODC,
944                                 replica_flags=ctx.domain_replica_flags)
945                     ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
946                 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
947                                destination_dsa_guid, rodc=ctx.RODC,
948                                replica_flags=ctx.domain_replica_flags)
949             print("Done with always replicated NC (base, config, schema)")
950
951             # At this point we should already have an entry in the ForestDNS
952             # and DomainDNS NC (those under CN=Partions,DC=...) in order to
953             # indicate that we hold a replica for this NC.
954             for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
955                 if nc in ctx.nc_list:
956                     print("Replicating %s" % (str(nc)))
957                     repl.replicate(nc, source_dsa_invocation_id,
958                                     destination_dsa_guid, rodc=ctx.RODC,
959                                     replica_flags=ctx.replica_flags)
960
961             if ctx.RODC:
962                 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
963                         destination_dsa_guid,
964                         exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
965                 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
966                         destination_dsa_guid,
967                         exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
968             elif ctx.rid_manager_dn != None:
969                 # Try and get a RID Set if we can.  This is only possible against the RID Master.  Warn otherwise.
970                 try:
971                     repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
972                                    destination_dsa_guid,
973                                    exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
974                 except samba.DsExtendedError as e1:
975                     (enum, estr) = e1.args
976                     if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
977                         print("WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server)
978                         print("NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup.")
979                     else:
980                         raise
981
982             ctx.repl = repl
983             ctx.source_dsa_invocation_id = source_dsa_invocation_id
984             ctx.destination_dsa_guid = destination_dsa_guid
985
986             print("Committing SAM database")
987         except:
988             ctx.local_samdb.transaction_cancel()
989             raise
990         else:
991             ctx.local_samdb.transaction_commit()
992
993     def send_DsReplicaUpdateRefs(ctx, dn):
994         r = drsuapi.DsReplicaUpdateRefsRequest1()
995         r.naming_context = drsuapi.DsReplicaObjectIdentifier()
996         r.naming_context.dn = str(dn)
997         r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
998         r.naming_context.sid = security.dom_sid("S-0-0")
999         r.dest_dsa_guid = ctx.ntds_guid
1000         r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
1001         r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
1002         if not ctx.RODC:
1003             r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
1004
1005         if ctx.drsuapi is None:
1006             ctx.drsuapi_connect()
1007
1008         ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
1009
1010     def join_add_dns_records(ctx):
1011         """Remotely Add a DNS record to the target DC.  We assume that if we
1012            replicate DNS that the server holds the DNS roles and can accept
1013            updates.
1014
1015            This avoids issues getting replication going after the DC
1016            first starts as the rest of the domain does not have to
1017            wait for samba_dnsupdate to run successfully.
1018
1019            Specifically, we add the records implied by the DsReplicaUpdateRefs
1020            call above.
1021
1022            We do not just run samba_dnsupdate as we want to strictly
1023            operate against the DC we just joined:
1024             - We do not want to query another DNS server
1025             - We do not want to obtain a Kerberos ticket
1026               (as the KDC we select may not be the DC we just joined,
1027               and so may not be in sync with the password we just set)
1028             - We do not wish to set the _ldap records until we have started
1029             - We do not wish to use NTLM (the --use-samba-tool mode forces
1030               NTLM)
1031
1032         """
1033
1034         client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
1035         record_type = dnsp.DNS_TYPE_A
1036         select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
1037                        dnsserver.DNS_RPC_VIEW_NO_CHILDREN
1038
1039         zone = ctx.dnsdomain
1040         msdcs_zone = "_msdcs.%s" % ctx.dnsforest
1041         name = ctx.myname
1042         msdcs_cname = str(ctx.ntds_guid)
1043         cname_target = "%s.%s" % (name, zone)
1044         IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
1045
1046         ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
1047                         (len(IPs), name, zone))
1048
1049         binding_options = "sign"
1050         dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
1051                                       ctx.lp, ctx.creds)
1052
1053
1054         name_found = True
1055
1056         sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
1057
1058         change_owner_sd = security.descriptor()
1059         change_owner_sd.owner_sid = ctx.new_dc_account_sid
1060         change_owner_sd.group_sid = security.dom_sid("%s-%d" %
1061                                                      (str(ctx.domsid),
1062                                                       security.DOMAIN_RID_DCS))
1063
1064         # TODO: Remove any old records from the primary DNS name
1065         try:
1066             (buflen, res) \
1067                 = dns_conn.DnssrvEnumRecords2(client_version,
1068                                               0,
1069                                               ctx.server,
1070                                               zone,
1071                                               name,
1072                                               None,
1073                                               dnsp.DNS_TYPE_ALL,
1074                                               select_flags,
1075                                               None,
1076                                               None)
1077         except WERRORError as e:
1078             if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1079                 name_found = False
1080                 pass
1081
1082         if name_found:
1083             for rec in res.rec:
1084                 for record in rec.records:
1085                     if record.wType == dnsp.DNS_TYPE_A or \
1086                        record.wType == dnsp.DNS_TYPE_AAAA:
1087                         # delete record
1088                         del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1089                         del_rec_buf.rec = record
1090                         try:
1091                             dns_conn.DnssrvUpdateRecord2(client_version,
1092                                                          0,
1093                                                          ctx.server,
1094                                                          zone,
1095                                                          name,
1096                                                          None,
1097                                                          del_rec_buf)
1098                         except WERRORError as e:
1099                             if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
1100                                 pass
1101                             else:
1102                                 raise
1103
1104         for IP in IPs:
1105             if IP.find(':') != -1:
1106                 ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
1107                                 % (name, zone, IP))
1108                 rec = AAAARecord(IP)
1109             else:
1110                 ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
1111                                 % (name, zone, IP))
1112                 rec = ARecord(IP)
1113
1114             # Add record
1115             add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1116             add_rec_buf.rec = rec
1117             dns_conn.DnssrvUpdateRecord2(client_version,
1118                                          0,
1119                                          ctx.server,
1120                                          zone,
1121                                          name,
1122                                          add_rec_buf,
1123                                          None)
1124
1125         if (len(IPs) > 0):
1126             domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
1127             (ctx.dns_a_dn, ldap_record) \
1128                 = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
1129                                        dns_partition=domaindns_zone_dn)
1130
1131             # Make the DC own the DNS record, not the administrator
1132             sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
1133                                       controls=["sd_flags:1:%d"
1134                                                 % (security.SECINFO_OWNER
1135                                                    | security.SECINFO_GROUP)])
1136
1137
1138             # Add record
1139             ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
1140                             % (msdcs_cname, msdcs_zone, cname_target))
1141
1142             add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
1143             rec = CNameRecord(cname_target)
1144             add_rec_buf.rec = rec
1145             dns_conn.DnssrvUpdateRecord2(client_version,
1146                                          0,
1147                                          ctx.server,
1148                                          msdcs_zone,
1149                                          msdcs_cname,
1150                                          add_rec_buf,
1151                                          None)
1152
1153             forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
1154             (ctx.dns_cname_dn, ldap_record) \
1155                 = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
1156                                        dns_partition=forestdns_zone_dn)
1157
1158             # Make the DC own the DNS record, not the administrator
1159             sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
1160                                       controls=["sd_flags:1:%d"
1161                                                 % (security.SECINFO_OWNER
1162                                                    | security.SECINFO_GROUP)])
1163
1164         ctx.logger.info("All other DNS records (like _ldap SRV records) " +
1165                         "will be created samba_dnsupdate on first startup")
1166
1167
1168     def join_replicate_new_dns_records(ctx):
1169         for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
1170             if nc in ctx.nc_list:
1171                 ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
1172                 ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
1173                                    ctx.ntds_guid, rodc=ctx.RODC,
1174                                    replica_flags=ctx.replica_flags,
1175                                    full_sync=False)
1176
1177
1178
1179     def join_finalise(ctx):
1180         """Finalise the join, mark us synchronised and setup secrets db."""
1181
1182         # FIXME we shouldn't do this in all cases
1183
1184         # If for some reasons we joined in another site than the one of
1185         # DC we just replicated from then we don't need to send the updatereplicateref
1186         # as replication between sites is time based and on the initiative of the
1187         # requesting DC
1188         if not ctx.clone_only:
1189             ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
1190             for nc in ctx.nc_list:
1191                 ctx.send_DsReplicaUpdateRefs(nc)
1192
1193         if not ctx.clone_only and ctx.RODC:
1194             print("Setting RODC invocationId")
1195             ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
1196             ctx.local_samdb.set_opaque_integer("domainFunctionality",
1197                                                ctx.behavior_version)
1198             m = ldb.Message()
1199             m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
1200             m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
1201                                                    ldb.FLAG_MOD_REPLACE,
1202                                                    "invocationId")
1203             ctx.local_samdb.modify(m)
1204
1205             # Note: as RODC the invocationId is only stored
1206             # on the RODC itself, the other DCs never see it.
1207             #
1208             # Thats is why we fix up the replPropertyMetaData stamp
1209             # for the 'invocationId' attribute, we need to change
1210             # the 'version' to '0', this is what windows 2008r2 does as RODC
1211             #
1212             # This means if the object on a RWDC ever gets a invocationId
1213             # attribute, it will have version '1' (or higher), which will
1214             # will overwrite the RODC local value.
1215             ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
1216                                                                "invocationId",
1217                                                                0)
1218
1219         ctx.logger.info("Setting isSynchronized and dsServiceName")
1220         m = ldb.Message()
1221         m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
1222         m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
1223
1224         # We want to appear to be the server we just cloned
1225         if ctx.clone_only:
1226             guid = ctx.remote_dc_ntds_guid
1227         else:
1228             guid = ctx.ntds_guid
1229
1230         m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
1231                                                 ldb.FLAG_MOD_REPLACE, "dsServiceName")
1232         ctx.local_samdb.modify(m)
1233
1234         if ctx.clone_only or ctx.subdomain:
1235             return
1236
1237         secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
1238
1239         ctx.logger.info("Setting up secrets database")
1240         secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
1241                             realm=ctx.realm,
1242                             dnsdomain=ctx.dnsdomain,
1243                             netbiosname=ctx.myname,
1244                             domainsid=ctx.domsid,
1245                             machinepass=ctx.acct_pass,
1246                             secure_channel_type=ctx.secure_channel_type,
1247                             key_version_number=ctx.key_version_number)
1248
1249         if ctx.dns_backend.startswith("BIND9_"):
1250             setup_bind9_dns(ctx.local_samdb, secrets_ldb,
1251                             ctx.names, ctx.paths, ctx.lp, ctx.logger,
1252                             dns_backend=ctx.dns_backend,
1253                             dnspass=ctx.dnspass, os_level=ctx.behavior_version,
1254                             targetdir=ctx.targetdir,
1255                             key_version_number=ctx.dns_key_version_number)
1256
1257     def join_setup_trusts(ctx):
1258         """provision the local SAM."""
1259
1260         print("Setup domain trusts with server %s" % ctx.server)
1261         binding_options = ""  # why doesn't signing work here? w2k8r2 claims no session key
1262         lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
1263                              ctx.lp, ctx.creds)
1264
1265         objectAttr = lsa.ObjectAttribute()
1266         objectAttr.sec_qos = lsa.QosInfo()
1267
1268         pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
1269                                          objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
1270
1271         info = lsa.TrustDomainInfoInfoEx()
1272         info.domain_name.string = ctx.dnsdomain
1273         info.netbios_name.string = ctx.domain_name
1274         info.sid = ctx.domsid
1275         info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
1276         info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
1277         info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
1278
1279         try:
1280             oldname = lsa.String()
1281             oldname.string = ctx.dnsdomain
1282             oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
1283                                                            lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1284             print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
1285             lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
1286         except RuntimeError:
1287             pass
1288
1289         password_blob = string_to_byte_array(ctx.trustdom_pass.encode('utf-16-le'))
1290
1291         clear_value = drsblobs.AuthInfoClear()
1292         clear_value.size = len(password_blob)
1293         clear_value.password = password_blob
1294
1295         clear_authentication_information = drsblobs.AuthenticationInformation()
1296         clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
1297         clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
1298         clear_authentication_information.AuthInfo = clear_value
1299
1300         authentication_information_array = drsblobs.AuthenticationInformationArray()
1301         authentication_information_array.count = 1
1302         authentication_information_array.array = [clear_authentication_information]
1303
1304         outgoing = drsblobs.trustAuthInOutBlob()
1305         outgoing.count = 1
1306         outgoing.current = authentication_information_array
1307
1308         trustpass = drsblobs.trustDomainPasswords()
1309         confounder = [3] * 512
1310
1311         for i in range(512):
1312             confounder[i] = random.randint(0, 255)
1313
1314         trustpass.confounder = confounder
1315
1316         trustpass.outgoing = outgoing
1317         trustpass.incoming = outgoing
1318
1319         trustpass_blob = ndr_pack(trustpass)
1320
1321         encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
1322
1323         auth_blob = lsa.DATA_BUF2()
1324         auth_blob.size = len(encrypted_trustpass)
1325         auth_blob.data = string_to_byte_array(encrypted_trustpass)
1326
1327         auth_info = lsa.TrustDomainInfoAuthInfoInternal()
1328         auth_info.auth_blob = auth_blob
1329
1330         trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
1331                                                          info,
1332                                                          auth_info,
1333                                                          security.SEC_STD_DELETE)
1334
1335         rec = {
1336             "dn" : "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
1337             "objectclass" : "trustedDomain",
1338             "trustType" : str(info.trust_type),
1339             "trustAttributes" : str(info.trust_attributes),
1340             "trustDirection" : str(info.trust_direction),
1341             "flatname" : ctx.forest_domain_name,
1342             "trustPartner" : ctx.dnsforest,
1343             "trustAuthIncoming" : ndr_pack(outgoing),
1344             "trustAuthOutgoing" : ndr_pack(outgoing),
1345             "securityIdentifier" : ndr_pack(ctx.forestsid)
1346             }
1347         ctx.local_samdb.add(rec)
1348
1349         rec = {
1350             "dn" : "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
1351             "objectclass" : "user",
1352             "userAccountControl" : str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
1353             "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le'),
1354             "samAccountName" : "%s$" % ctx.forest_domain_name
1355             }
1356         ctx.local_samdb.add(rec)
1357
1358
1359     def do_join(ctx):
1360         # nc_list is the list of naming context (NC) for which we will
1361         # replicate in and send a updateRef command to the partner DC
1362
1363         # full_nc_list is the list of naming context (NC) we hold
1364         # read/write copies of.  These are not subsets of each other.
1365         ctx.nc_list = [ ctx.config_dn, ctx.schema_dn ]
1366         ctx.full_nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
1367
1368         if ctx.subdomain and ctx.dns_backend != "NONE":
1369             ctx.full_nc_list += [ctx.domaindns_zone]
1370
1371         elif not ctx.subdomain:
1372             ctx.nc_list += [ctx.base_dn]
1373
1374             if ctx.dns_backend != "NONE":
1375                 ctx.nc_list += [ctx.domaindns_zone]
1376                 ctx.nc_list += [ctx.forestdns_zone]
1377                 ctx.full_nc_list += [ctx.domaindns_zone]
1378                 ctx.full_nc_list += [ctx.forestdns_zone]
1379
1380         if not ctx.clone_only:
1381             if ctx.promote_existing:
1382                 ctx.promote_possible()
1383             else:
1384                 ctx.cleanup_old_join()
1385
1386         try:
1387             if not ctx.clone_only:
1388                 ctx.join_add_objects()
1389             ctx.join_provision()
1390             ctx.join_replicate()
1391             if (not ctx.clone_only and ctx.subdomain):
1392                 ctx.join_add_objects2()
1393                 ctx.join_provision_own_domain()
1394                 ctx.join_setup_trusts()
1395
1396             if not ctx.clone_only and ctx.dns_backend != "NONE":
1397                 ctx.join_add_dns_records()
1398                 ctx.join_replicate_new_dns_records()
1399
1400             ctx.join_finalise()
1401         except:
1402             try:
1403                 print("Join failed - cleaning up")
1404             except IOError:
1405                 pass
1406             if not ctx.clone_only:
1407                 ctx.cleanup_old_join()
1408             raise
1409
1410
1411 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1412               targetdir=None, domain=None, domain_critical_only=False,
1413               machinepass=None, use_ntvfs=False, dns_backend=None,
1414               promote_existing=False, plaintext_secrets=False):
1415     """Join as a RODC."""
1416
1417     ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
1418                   machinepass, use_ntvfs, dns_backend, promote_existing,
1419                   plaintext_secrets)
1420
1421     lp.set("workgroup", ctx.domain_name)
1422     logger.info("workgroup is %s" % ctx.domain_name)
1423
1424     lp.set("realm", ctx.realm)
1425     logger.info("realm is %s" % ctx.realm)
1426
1427     ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1428
1429     # setup some defaults for accounts that should be replicated to this RODC
1430     ctx.never_reveal_sid = [
1431         "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1432         "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1433         "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1434         "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1435         "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1436     ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1437
1438     mysid = ctx.get_mysid()
1439     admin_dn = "<SID=%s>" % mysid
1440     ctx.managedby = admin_dn
1441
1442     ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1443                               samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1444                               samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1445
1446     ctx.SPNs.extend([ "RestrictedKrbHost/%s" % ctx.myname,
1447                       "RestrictedKrbHost/%s" % ctx.dnshostname ])
1448
1449     ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1450     ctx.secure_channel_type = misc.SEC_CHAN_RODC
1451     ctx.RODC = True
1452     ctx.replica_flags |= ( drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1453                            drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1454     ctx.domain_replica_flags = ctx.replica_flags
1455     if domain_critical_only:
1456         ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1457
1458     ctx.do_join()
1459
1460     logger.info("Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid))
1461
1462
1463 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
1464             targetdir=None, domain=None, domain_critical_only=False,
1465             machinepass=None, use_ntvfs=False, dns_backend=None,
1466             promote_existing=False, plaintext_secrets=False):
1467     """Join as a DC."""
1468     ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
1469                   machinepass, use_ntvfs, dns_backend, promote_existing,
1470                   plaintext_secrets)
1471
1472     lp.set("workgroup", ctx.domain_name)
1473     logger.info("workgroup is %s" % ctx.domain_name)
1474
1475     lp.set("realm", ctx.realm)
1476     logger.info("realm is %s" % ctx.realm)
1477
1478     ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1479
1480     ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1481     ctx.secure_channel_type = misc.SEC_CHAN_BDC
1482
1483     ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1484                           drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1485     ctx.domain_replica_flags = ctx.replica_flags
1486     if domain_critical_only:
1487         ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1488
1489     ctx.do_join()
1490     logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
1491
1492 def join_clone(logger=None, server=None, creds=None, lp=None,
1493                targetdir=None, domain=None, include_secrets=False):
1494     """Join as a DC."""
1495     ctx = dc_join(logger, server, creds, lp, site=None, netbios_name=None, targetdir=targetdir, domain=domain,
1496                   machinepass=None, use_ntvfs=False, dns_backend="NONE", promote_existing=False, clone_only=True)
1497
1498     lp.set("workgroup", ctx.domain_name)
1499     logger.info("workgroup is %s" % ctx.domain_name)
1500
1501     lp.set("realm", ctx.realm)
1502     logger.info("realm is %s" % ctx.realm)
1503
1504     ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1505                           drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1506     if not include_secrets:
1507         ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
1508     ctx.domain_replica_flags = ctx.replica_flags
1509
1510     ctx.do_join()
1511     logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
1512
1513 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
1514         netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1515         netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
1516         dns_backend=None, plaintext_secrets=False):
1517     """Join as a DC."""
1518     ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, parent_domain,
1519                   machinepass, use_ntvfs, dns_backend, plaintext_secrets)
1520     ctx.subdomain = True
1521     if adminpass is None:
1522         ctx.adminpass = samba.generate_random_password(12, 32)
1523     else:
1524         ctx.adminpass = adminpass
1525     ctx.parent_domain_name = ctx.domain_name
1526     ctx.domain_name = netbios_domain
1527     ctx.realm = dnsdomain
1528     ctx.parent_dnsdomain = ctx.dnsdomain
1529     ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1530     ctx.dnsdomain = dnsdomain
1531     ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1532     ctx.naming_master = ctx.get_naming_master()
1533     if ctx.naming_master != ctx.server:
1534         logger.info("Reconnecting to naming master %s" % ctx.naming_master)
1535         ctx.server = ctx.naming_master
1536         ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1537                           session_info=system_session(),
1538                           credentials=ctx.creds, lp=ctx.lp)
1539         res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['dnsHostName'],
1540                                controls=[])
1541         ctx.server = res[0]["dnsHostName"]
1542         logger.info("DNS name of new naming master is %s" % ctx.server)
1543
1544     ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1545     ctx.forestsid = ctx.domsid
1546     ctx.domsid = security.random_sid()
1547     ctx.acct_dn = None
1548     ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
1549     # Windows uses 240 bytes as UTF16 so we do
1550     ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
1551
1552     ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1553
1554     ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1555     ctx.secure_channel_type = misc.SEC_CHAN_BDC
1556
1557     ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
1558                           drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
1559     ctx.domain_replica_flags = ctx.replica_flags
1560
1561     ctx.do_join()
1562     ctx.logger.info("Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))