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