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