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