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