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