Move python modules from source4/scripting/python/ to python/.
[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 """Joining a domain."""
20
21 from samba.auth import system_session
22 from samba.samdb import SamDB
23 from samba import gensec, Ldb, drs_utils
24 import ldb, samba, sys, uuid
25 from samba.ndr import ndr_pack
26 from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs
27 from samba.credentials import Credentials, DONT_USE_KERBEROS
28 from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN
29 from samba.schema import Schema
30 from samba.net import Net
31 from samba.provision.sambadns import setup_bind9_dns
32 import logging
33 import talloc
34 import random
35 import time
36
37 # this makes debugging easier
38 talloc.enable_null_tracking()
39
40 class DCJoinException(Exception):
41
42     def __init__(self, msg):
43         super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
44
45
46 class dc_join(object):
47     """Perform a DC join."""
48
49     def __init__(ctx, server=None, creds=None, lp=None, site=None,
50                  netbios_name=None, targetdir=None, domain=None,
51                  machinepass=None, use_ntvfs=False, dns_backend=None,
52                  promote_existing=False):
53         ctx.creds = creds
54         ctx.lp = lp
55         ctx.site = site
56         ctx.netbios_name = netbios_name
57         ctx.targetdir = targetdir
58         ctx.use_ntvfs = use_ntvfs
59
60         ctx.promote_existing = promote_existing
61         ctx.promote_from_dn = None
62
63         ctx.nc_list = []
64         ctx.full_nc_list = []
65
66         ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
67         ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
68
69         if server is not None:
70             ctx.server = server
71         else:
72             print("Finding a writeable DC for domain '%s'" % domain)
73             ctx.server = ctx.find_dc(domain)
74             print("Found DC %s" % ctx.server)
75
76         ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
77                           session_info=system_session(),
78                           credentials=ctx.creds, lp=ctx.lp)
79
80         try:
81             ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
82         except ldb.LdbError, (enum, estr):
83             raise DCJoinException(estr)
84
85
86         ctx.myname = netbios_name
87         ctx.samname = "%s$" % ctx.myname
88         ctx.base_dn = str(ctx.samdb.get_default_basedn())
89         ctx.root_dn = str(ctx.samdb.get_root_basedn())
90         ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
91         ctx.config_dn = str(ctx.samdb.get_config_basedn())
92         ctx.domsid = ctx.samdb.get_domain_sid()
93         ctx.domain_name = ctx.get_domain_name()
94         ctx.forest_domain_name = ctx.get_forest_domain_name()
95         ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
96
97         ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
98         ctx.dc_dnsHostName = ctx.get_dnsHostName()
99         ctx.behavior_version = ctx.get_behavior_version()
100
101         if machinepass is not None:
102             ctx.acct_pass = machinepass
103         else:
104             ctx.acct_pass = samba.generate_random_password(32, 40)
105
106         # work out the DNs of all the objects we will be adding
107         ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
108         ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
109         topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
110         if ctx.dn_exists(topology_base):
111             ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
112         else:
113             ctx.topology_dn = None
114
115         ctx.dnsdomain = ctx.samdb.domain_dns_name()
116         ctx.dnsforest = ctx.samdb.forest_dns_name()
117         ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
118         ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.base_dn
119
120         res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
121                                          attrs=[],
122                                          base=ctx.samdb.get_partitions_dn(),
123                                          expression="(&(objectClass=crossRef)(ncName=%s))" % ctx.domaindns_zone)
124         if dns_backend is None:
125             ctx.dns_backend = "NONE"
126         else:
127             if len(res_domaindns) == 0:
128                 ctx.dns_backend = "NONE"
129                 print "NO DNS zone information found in source domain, not replicating DNS"
130             else:
131                 ctx.dns_backend = dns_backend
132
133         ctx.dnshostname = "%s.%s" % (ctx.myname, ctx.dnsdomain)
134
135         ctx.realm = ctx.dnsdomain
136
137         ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
138
139         ctx.tmp_samdb = None
140
141         ctx.SPNs = [ "HOST/%s" % ctx.myname,
142                      "HOST/%s" % ctx.dnshostname,
143                      "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
144
145         # these elements are optional
146         ctx.never_reveal_sid = None
147         ctx.reveal_sid = None
148         ctx.connection_dn = None
149         ctx.RODC = False
150         ctx.krbtgt_dn = None
151         ctx.drsuapi = None
152         ctx.managedby = None
153         ctx.subdomain = False
154
155     def del_noerror(ctx, dn, recursive=False):
156         if recursive:
157             try:
158                 res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
159             except Exception:
160                 return
161             for r in res:
162                 ctx.del_noerror(r.dn, recursive=True)
163         try:
164             ctx.samdb.delete(dn)
165             print "Deleted %s" % dn
166         except Exception:
167             pass
168
169     def cleanup_old_join(ctx):
170         """Remove any DNs from a previous join."""
171         try:
172             # find the krbtgt link
173             print("checking sAMAccountName")
174             if ctx.subdomain:
175                 res = None
176             else:
177                 res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
178                                        expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
179                                        attrs=["msDS-krbTgtLink"])
180                 if res:
181                     ctx.del_noerror(res[0].dn, recursive=True)
182             if ctx.connection_dn is not None:
183                 ctx.del_noerror(ctx.connection_dn)
184             if ctx.krbtgt_dn is not None:
185                 ctx.del_noerror(ctx.krbtgt_dn)
186             ctx.del_noerror(ctx.ntds_dn)
187             ctx.del_noerror(ctx.server_dn, recursive=True)
188             if ctx.topology_dn:
189                 ctx.del_noerror(ctx.topology_dn)
190             if ctx.partition_dn:
191                 ctx.del_noerror(ctx.partition_dn)
192             if res:
193                 ctx.new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
194                 ctx.del_noerror(ctx.new_krbtgt_dn)
195
196             if ctx.subdomain:
197                 binding_options = "sign"
198                 lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
199                                      ctx.lp, ctx.creds)
200
201                 objectAttr = lsa.ObjectAttribute()
202                 objectAttr.sec_qos = lsa.QosInfo()
203
204                 pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
205                                                  objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
206
207                 name = lsa.String()
208                 name.string = ctx.realm
209                 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
210
211                 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
212
213                 name = lsa.String()
214                 name.string = ctx.forest_domain_name
215                 info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
216
217                 lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
218
219         except Exception:
220             pass
221
222     def promote_possible(ctx):
223         """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
224         if ctx.subdomain:
225             # This shouldn't happen
226             raise Exception("Can not promote into a subdomain")
227
228         res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
229                                expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
230                                attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
231         if len(res) == 0:
232             raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
233         if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
234             raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
235         if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
236             raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
237
238         ctx.promote_from_dn = res[0].dn
239
240
241     def find_dc(ctx, domain):
242         """find a writeable DC for the given domain"""
243         try:
244             ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
245         except Exception:
246             raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
247         if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
248             ctx.site = ctx.cldap_ret.client_site
249         return ctx.cldap_ret.pdc_dns_name
250
251
252     def get_behavior_version(ctx):
253         res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
254         if "msDS-Behavior-Version" in res[0]:
255             return int(res[0]["msDS-Behavior-Version"][0])
256         else:
257             return samba.dsdb.DS_DOMAIN_FUNCTION_2000
258
259     def get_dnsHostName(ctx):
260         res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
261         return res[0]["dnsHostName"][0]
262
263     def get_domain_name(ctx):
264         '''get netbios name of the domain from the partitions record'''
265         partitions_dn = ctx.samdb.get_partitions_dn()
266         res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
267                                expression='ncName=%s' % ctx.samdb.get_default_basedn())
268         return res[0]["nETBIOSName"][0]
269
270     def get_forest_domain_name(ctx):
271         '''get netbios name of the domain from the partitions record'''
272         partitions_dn = ctx.samdb.get_partitions_dn()
273         res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
274                                expression='ncName=%s' % ctx.samdb.get_root_basedn())
275         return res[0]["nETBIOSName"][0]
276
277     def get_parent_partition_dn(ctx):
278         '''get the parent domain partition DN from parent DNS name'''
279         res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
280                                expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
281                                (ctx.parent_dnsdomain, ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
282         return str(res[0].dn)
283
284     def get_naming_master(ctx):
285         '''get the parent domain partition DN from parent DNS name'''
286         res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
287                                scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
288         if not 'fSMORoleOwner' in res[0]:
289             raise DCJoinException("Can't find naming master on partition DN %s" % ctx.partition_dn)
290         master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0]).get_extended_component('GUID')))
291         master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
292         return master_host
293
294     def get_mysid(ctx):
295         '''get the SID of the connected user. Only works with w2k8 and later,
296            so only used for RODC join'''
297         res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"])
298         binsid = res[0]["tokenGroups"][0]
299         return ctx.samdb.schema_format_value("objectSID", binsid)
300
301     def dn_exists(ctx, dn):
302         '''check if a DN exists'''
303         try:
304             res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
305         except ldb.LdbError, (enum, estr):
306             if enum == ldb.ERR_NO_SUCH_OBJECT:
307                 return False
308             raise
309         return True
310
311     def add_krbtgt_account(ctx):
312         '''RODCs need a special krbtgt account'''
313         print "Adding %s" % ctx.krbtgt_dn
314         rec = {
315             "dn" : ctx.krbtgt_dn,
316             "objectclass" : "user",
317             "useraccountcontrol" : str(samba.dsdb.UF_NORMAL_ACCOUNT |
318                                        samba.dsdb.UF_ACCOUNTDISABLE),
319             "showinadvancedviewonly" : "TRUE",
320             "description" : "krbtgt for %s" % ctx.samname}
321         ctx.samdb.add(rec, ["rodc_join:1:1"])
322
323         # now we need to search for the samAccountName attribute on the krbtgt DN,
324         # as this will have been magically set to the krbtgt number
325         res = ctx.samdb.search(base=ctx.krbtgt_dn, scope=ldb.SCOPE_BASE, attrs=["samAccountName"])
326         ctx.krbtgt_name = res[0]["samAccountName"][0]
327
328         print "Got krbtgt_name=%s" % ctx.krbtgt_name
329
330         m = ldb.Message()
331         m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
332         m["msDS-krbTgtLink"] = ldb.MessageElement(ctx.krbtgt_dn,
333                                                   ldb.FLAG_MOD_REPLACE, "msDS-krbTgtLink")
334         ctx.samdb.modify(m)
335
336         ctx.new_krbtgt_dn = "CN=%s,CN=Users,%s" % (ctx.krbtgt_name, ctx.base_dn)
337         print "Renaming %s to %s" % (ctx.krbtgt_dn, ctx.new_krbtgt_dn)
338         ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
339
340     def drsuapi_connect(ctx):
341         '''make a DRSUAPI connection to the naming master'''
342         binding_options = "seal"
343         if int(ctx.lp.get("log level")) >= 4:
344             binding_options += ",print"
345         binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
346         ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
347         (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
348
349     def create_tmp_samdb(ctx):
350         '''create a temporary samdb object for schema queries'''
351         ctx.tmp_schema = Schema(security.dom_sid(ctx.domsid),
352                                 schemadn=ctx.schema_dn)
353         ctx.tmp_samdb = SamDB(session_info=system_session(), url=None, auto_connect=False,
354                               credentials=ctx.creds, lp=ctx.lp, global_schema=False,
355                               am_rodc=False)
356         ctx.tmp_samdb.set_schema(ctx.tmp_schema)
357
358     def build_DsReplicaAttribute(ctx, attrname, attrvalue):
359         '''build a DsReplicaAttributeCtr object'''
360         r = drsuapi.DsReplicaAttribute()
361         r.attid = ctx.tmp_samdb.get_attid_from_lDAPDisplayName(attrname)
362         r.value_ctr = 1
363
364
365     def DsAddEntry(ctx, recs):
366         '''add a record via the DRSUAPI DsAddEntry call'''
367         if ctx.drsuapi is None:
368             ctx.drsuapi_connect()
369         if ctx.tmp_samdb is None:
370             ctx.create_tmp_samdb()
371
372         objects = []
373         for rec in recs:
374             id = drsuapi.DsReplicaObjectIdentifier()
375             id.dn = rec['dn']
376
377             attrs = []
378             for a in rec:
379                 if a == 'dn':
380                     continue
381                 if not isinstance(rec[a], list):
382                     v = [rec[a]]
383                 else:
384                     v = rec[a]
385                 rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
386                 attrs.append(rattr)
387
388             attribute_ctr = drsuapi.DsReplicaAttributeCtr()
389             attribute_ctr.num_attributes = len(attrs)
390             attribute_ctr.attributes = attrs
391
392             object = drsuapi.DsReplicaObject()
393             object.identifier = id
394             object.attribute_ctr = attribute_ctr
395
396             list_object = drsuapi.DsReplicaObjectListItem()
397             list_object.object = object
398             objects.append(list_object)
399
400         req2 = drsuapi.DsAddEntryRequest2()
401         req2.first_object = objects[0]
402         prev = req2.first_object
403         for o in objects[1:]:
404             prev.next_object = o
405             prev = o
406
407         (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
408         if level == 2:
409             if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
410                 print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
411                 raise RuntimeError("DsAddEntry failed")
412             if ctr.extended_err != (0, 'WERR_OK'):
413                 print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
414                 raise RuntimeError("DsAddEntry failed")
415         if level == 3:
416             if ctr.err_ver != 1:
417                 raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
418             if ctr.err_data.status != (0, 'WERR_OK'):
419                 print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status,
420                                                                     ctr.err_data.info.extended_err))
421                 raise RuntimeError("DsAddEntry failed")
422             if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
423                 print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
424                 raise RuntimeError("DsAddEntry failed")
425
426         return ctr.objects
427
428     def join_add_ntdsdsa(ctx):
429         '''add the ntdsdsa object'''
430
431         print "Adding %s" % ctx.ntds_dn
432         rec = {
433             "dn" : ctx.ntds_dn,
434             "objectclass" : "nTDSDSA",
435             "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
436             "dMDLocation" : ctx.schema_dn}
437
438         nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
439
440         if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
441             rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
442
443         if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
444             rec["msDS-HasDomainNCs"] = ctx.base_dn
445
446         if ctx.RODC:
447             rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
448             rec["msDS-HasFullReplicaNCs"] = ctx.nc_list
449             rec["options"] = "37"
450             ctx.samdb.add(rec, ["rodc_join:1:1"])
451         else:
452             rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
453             rec["HasMasterNCs"]      = nc_list
454             if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
455                 rec["msDS-HasMasterNCs"] = ctx.nc_list
456             rec["options"] = "1"
457             rec["invocationId"] = ndr_pack(ctx.invocation_id)
458             ctx.DsAddEntry([rec])
459
460         # find the GUID of our NTDS DN
461         res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
462         ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
463
464     def join_add_objects(ctx):
465         '''add the various objects needed for the join'''
466         if ctx.acct_dn:
467             print "Adding %s" % ctx.acct_dn
468             rec = {
469                 "dn" : ctx.acct_dn,
470                 "objectClass": "computer",
471                 "displayname": ctx.samname,
472                 "samaccountname" : ctx.samname,
473                 "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
474                 "dnshostname" : ctx.dnshostname}
475             if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
476                 rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
477             elif ctx.promote_existing:
478                 rec['msDS-SupportedEncryptionTypes'] = []
479             if ctx.managedby:
480                 rec["managedby"] = ctx.managedby
481             elif ctx.promote_existing:
482                 rec["managedby"] = []
483
484             if ctx.never_reveal_sid:
485                 rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
486             elif ctx.promote_existing:
487                 rec["msDS-NeverRevealGroup"] = []
488
489             if ctx.reveal_sid:
490                 rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
491             elif ctx.promote_existing:
492                 rec["msDS-RevealOnDemandGroup"] = []
493
494             if ctx.promote_existing:
495                 if ctx.promote_from_dn != ctx.acct_dn:
496                     ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
497                 ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
498             else:
499                 ctx.samdb.add(rec)
500
501         if ctx.krbtgt_dn:
502             ctx.add_krbtgt_account()
503
504         print "Adding %s" % ctx.server_dn
505         rec = {
506             "dn": ctx.server_dn,
507             "objectclass" : "server",
508             # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
509             "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
510                                 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
511                                 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
512             # windows seems to add the dnsHostName later
513             "dnsHostName" : ctx.dnshostname}
514
515         if ctx.acct_dn:
516             rec["serverReference"] = ctx.acct_dn
517
518         ctx.samdb.add(rec)
519
520         if ctx.subdomain:
521             # the rest is done after replication
522             ctx.ntds_guid = None
523             return
524
525         ctx.join_add_ntdsdsa()
526
527         if ctx.connection_dn is not None:
528             print "Adding %s" % ctx.connection_dn
529             rec = {
530                 "dn" : ctx.connection_dn,
531                 "objectclass" : "nTDSConnection",
532                 "enabledconnection" : "TRUE",
533                 "options" : "65",
534                 "fromServer" : ctx.dc_ntds_dn}
535             ctx.samdb.add(rec)
536
537         if ctx.acct_dn:
538             print "Adding SPNs to %s" % ctx.acct_dn
539             m = ldb.Message()
540             m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
541             for i in range(len(ctx.SPNs)):
542                 ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
543             m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
544                                                            ldb.FLAG_MOD_REPLACE,
545                                                            "servicePrincipalName")
546             ctx.samdb.modify(m)
547
548             # The account password set operation should normally be done over
549             # LDAP. Windows 2000 DCs however allow this only with SSL
550             # connections which are hard to set up and otherwise refuse with
551             # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
552             # over SAMR.
553             print "Setting account password for %s" % ctx.samname
554             try:
555                 ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
556                                       % ldb.binary_encode(ctx.samname),
557                                       ctx.acct_pass,
558                                       force_change_at_next_login=False,
559                                       username=ctx.samname)
560             except ldb.LdbError, (num, _):
561                 if num != ldb.ERR_UNWILLING_TO_PERFORM:
562                     pass
563                 ctx.net.set_password(account_name=ctx.samname,
564                                      domain_name=ctx.domain_name,
565                                      newpassword=ctx.acct_pass)
566
567             res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
568                                    attrs=["msDS-KeyVersionNumber"])
569             if "msDS-KeyVersionNumber" in res[0]:
570                 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
571             else:
572                 ctx.key_version_number = None
573
574             print("Enabling account")
575             m = ldb.Message()
576             m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
577             m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
578                                                          ldb.FLAG_MOD_REPLACE,
579                                                          "userAccountControl")
580             ctx.samdb.modify(m)
581
582     def join_add_objects2(ctx):
583         """add the various objects needed for the join, for subdomains post replication"""
584
585         print "Adding %s" % ctx.partition_dn
586         # NOTE: windows sends a ntSecurityDescriptor here, we
587         # let it default
588         rec = {
589             "dn" : ctx.partition_dn,
590             "objectclass" : "crossRef",
591             "objectCategory" : "CN=Cross-Ref,%s" % ctx.schema_dn,
592             "nCName" : ctx.base_dn,
593             "nETBIOSName" : ctx.domain_name,
594             "dnsRoot": ctx.dnsdomain,
595             "trustParent" : ctx.parent_partition_dn,
596             "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN)}
597         if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
598             rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
599
600         rec2 = {
601             "dn" : ctx.ntds_dn,
602             "objectclass" : "nTDSDSA",
603             "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
604             "dMDLocation" : ctx.schema_dn}
605
606         nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
607
608         if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
609             rec2["msDS-Behavior-Version"] = str(ctx.behavior_version)
610
611         if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
612             rec2["msDS-HasDomainNCs"] = ctx.base_dn
613
614         rec2["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
615         rec2["HasMasterNCs"]      = nc_list
616         if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
617             rec2["msDS-HasMasterNCs"] = ctx.nc_list
618         rec2["options"] = "1"
619         rec2["invocationId"] = ndr_pack(ctx.invocation_id)
620
621         objects = ctx.DsAddEntry([rec, rec2])
622         if len(objects) != 2:
623             raise DCJoinException("Expected 2 objects from DsAddEntry")
624
625         ctx.ntds_guid = objects[1].guid
626
627         print("Replicating partition DN")
628         ctx.repl.replicate(ctx.partition_dn,
629                            misc.GUID("00000000-0000-0000-0000-000000000000"),
630                            ctx.ntds_guid,
631                            exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
632                            replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
633
634         print("Replicating NTDS DN")
635         ctx.repl.replicate(ctx.ntds_dn,
636                            misc.GUID("00000000-0000-0000-0000-000000000000"),
637                            ctx.ntds_guid,
638                            exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
639                            replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
640
641     def join_provision(ctx):
642         """Provision the local SAM."""
643
644         print "Calling bare provision"
645
646         logger = logging.getLogger("provision")
647         logger.addHandler(logging.StreamHandler(sys.stdout))
648         smbconf = ctx.lp.configfile
649
650         presult = provision(logger, system_session(), None, smbconf=smbconf,
651                 targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
652                 rootdn=ctx.root_dn, domaindn=ctx.base_dn,
653                 schemadn=ctx.schema_dn, configdn=ctx.config_dn,
654                 serverdn=ctx.server_dn, domain=ctx.domain_name,
655                 hostname=ctx.myname, domainsid=ctx.domsid,
656                 machinepass=ctx.acct_pass, serverrole="domain controller",
657                 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
658                 use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend)
659         print "Provision OK for domain DN %s" % presult.domaindn
660         ctx.local_samdb = presult.samdb
661         ctx.lp          = presult.lp
662         ctx.paths       = presult.paths
663         ctx.names       = presult.names
664
665     def join_provision_own_domain(ctx):
666         """Provision the local SAM."""
667
668         # we now operate exclusively on the local database, which
669         # we need to reopen in order to get the newly created schema
670         print("Reconnecting to local samdb")
671         ctx.samdb = SamDB(url=ctx.local_samdb.url,
672                           session_info=system_session(),
673                           lp=ctx.local_samdb.lp,
674                           global_schema=False)
675         ctx.samdb.set_invocation_id(str(ctx.invocation_id))
676         ctx.local_samdb = ctx.samdb
677
678         print("Finding domain GUID from ncName")
679         res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
680                                      controls=["extended_dn:1:1"])
681         domguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID')))
682         print("Got domain GUID %s" % domguid)
683
684         print("Calling own domain provision")
685
686         logger = logging.getLogger("provision")
687         logger.addHandler(logging.StreamHandler(sys.stdout))
688
689         secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
690
691         presult = provision_fill(ctx.local_samdb, secrets_ldb,
692                                  logger, ctx.names, ctx.paths, domainsid=security.dom_sid(ctx.domsid),
693                                  domainguid=domguid,
694                                  targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
695                                  machinepass=ctx.acct_pass, serverrole="domain controller",
696                                  lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
697                                  dns_backend=ctx.dns_backend)
698         print("Provision OK for domain %s" % ctx.names.dnsdomain)
699
700     def join_replicate(ctx):
701         """Replicate the SAM."""
702
703         print "Starting replication"
704         ctx.local_samdb.transaction_start()
705         try:
706             source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
707             if ctx.ntds_guid is None:
708                 print("Using DS_BIND_GUID_W2K3")
709                 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
710             else:
711                 destination_dsa_guid = ctx.ntds_guid
712
713             if ctx.RODC:
714                 repl_creds = Credentials()
715                 repl_creds.guess(ctx.lp)
716                 repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
717                 repl_creds.set_username(ctx.samname)
718                 repl_creds.set_password(ctx.acct_pass)
719             else:
720                 repl_creds = ctx.creds
721
722             binding_options = "seal"
723             if int(ctx.lp.get("log level")) >= 5:
724                 binding_options += ",print"
725             repl = drs_utils.drs_Replicate(
726                 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
727                 ctx.lp, repl_creds, ctx.local_samdb)
728
729             repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
730                     destination_dsa_guid, schema=True, rodc=ctx.RODC,
731                     replica_flags=ctx.replica_flags)
732             repl.replicate(ctx.config_dn, source_dsa_invocation_id,
733                     destination_dsa_guid, rodc=ctx.RODC,
734                     replica_flags=ctx.replica_flags)
735             if not ctx.subdomain:
736                 # Replicate first the critical object for the basedn
737                 if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
738                     print "Replicating critical objects from the base DN of the domain"
739                     ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi.DRSUAPI_DRS_GET_ANC
740                     repl.replicate(ctx.base_dn, source_dsa_invocation_id,
741                                 destination_dsa_guid, rodc=ctx.RODC,
742                                 replica_flags=ctx.domain_replica_flags)
743                     ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi.DRSUAPI_DRS_GET_ANC
744                 else:
745                     ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_GET_ANC
746                 repl.replicate(ctx.base_dn, source_dsa_invocation_id,
747                                destination_dsa_guid, rodc=ctx.RODC,
748                                replica_flags=ctx.domain_replica_flags)
749             print "Done with always replicated NC (base, config, schema)"
750
751             for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
752                 if nc in ctx.nc_list:
753                     print "Replicating %s" % (str(nc))
754                     repl.replicate(nc, source_dsa_invocation_id,
755                                     destination_dsa_guid, rodc=ctx.RODC,
756                                     replica_flags=ctx.replica_flags)
757
758             if 'DC=ForestDnsZones,%s' % ctx.root_dn in ctx.nc_list:
759                 repl.replicate('DC=ForestDnsZones,%s' % ctx.root_dn, source_dsa_invocation_id,
760                                destination_dsa_guid, rodc=ctx.RODC,
761                                replica_flags=ctx.replica_flags)
762             # FIXME At this point we should add an entry in the forestdns and domaindns NC
763             # (those under CN=Partions,DC=...)
764             # in order to indicate that we hold a replica for this NC
765
766             if ctx.RODC:
767                 repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
768                         destination_dsa_guid,
769                         exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
770                 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
771                         destination_dsa_guid,
772                         exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
773             ctx.repl = repl
774             ctx.source_dsa_invocation_id = source_dsa_invocation_id
775             ctx.destination_dsa_guid = destination_dsa_guid
776
777             print "Committing SAM database"
778         except:
779             ctx.local_samdb.transaction_cancel()
780             raise
781         else:
782             ctx.local_samdb.transaction_commit()
783
784     def send_DsReplicaUpdateRefs(ctx, dn):
785         r = drsuapi.DsReplicaUpdateRefsRequest1()
786         r.naming_context = drsuapi.DsReplicaObjectIdentifier()
787         r.naming_context.dn = str(dn)
788         r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
789         r.naming_context.sid = security.dom_sid("S-0-0")
790         r.dest_dsa_guid = ctx.ntds_guid
791         r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
792         r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
793         if not ctx.RODC:
794             r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
795
796         if ctx.drsuapi:
797             ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
798
799     def join_finalise(ctx):
800         """Finalise the join, mark us synchronised and setup secrets db."""
801
802         logger = logging.getLogger("provision")
803         logger.addHandler(logging.StreamHandler(sys.stdout))
804
805         # FIXME we shouldn't do this in all cases
806         # If for some reasons we joined in another site than the one of
807         # DC we just replicated from then we don't need to send the updatereplicateref
808         # as replication between sites is time based and on the initiative of the
809         # requesting DC
810         print "Sending DsReplicateUpdateRefs for all the replicated partitions"
811         for nc in ctx.full_nc_list:
812             ctx.send_DsReplicaUpdateRefs(nc)
813
814         if ctx.RODC:
815             print "Setting RODC invocationId"
816             ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
817             ctx.local_samdb.set_opaque_integer("domainFunctionality",
818                                                ctx.behavior_version)
819             m = ldb.Message()
820             m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
821             m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
822                                                    ldb.FLAG_MOD_REPLACE,
823                                                    "invocationId")
824             ctx.local_samdb.modify(m)
825
826             # Note: as RODC the invocationId is only stored
827             # on the RODC itself, the other DCs never see it.
828             #
829             # Thats is why we fix up the replPropertyMetaData stamp
830             # for the 'invocationId' attribute, we need to change
831             # the 'version' to '0', this is what windows 2008r2 does as RODC
832             #
833             # This means if the object on a RWDC ever gets a invocationId
834             # attribute, it will have version '1' (or higher), which will
835             # will overwrite the RODC local value.
836             ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
837                                                                "invocationId",
838                                                                0)
839
840         print "Setting isSynchronized and dsServiceName"
841         m = ldb.Message()
842         m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
843         m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
844         m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(ctx.ntds_guid),
845                                                 ldb.FLAG_MOD_REPLACE, "dsServiceName")
846         ctx.local_samdb.modify(m)
847
848         if ctx.subdomain:
849             return
850
851         secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
852
853         print "Setting up secrets database"
854         secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
855                             realm=ctx.realm,
856                             dnsdomain=ctx.dnsdomain,
857                             netbiosname=ctx.myname,
858                             domainsid=security.dom_sid(ctx.domsid),
859                             machinepass=ctx.acct_pass,
860                             secure_channel_type=ctx.secure_channel_type,
861                             key_version_number=ctx.key_version_number)
862
863         if ctx.dns_backend.startswith("BIND9_"):
864             dnspass = samba.generate_random_password(128, 255)
865
866             setup_bind9_dns(ctx.local_samdb, secrets_ldb, security.dom_sid(ctx.domsid),
867                             ctx.names, ctx.paths, ctx.lp, logger,
868                             dns_backend=ctx.dns_backend,
869                             dnspass=dnspass, os_level=ctx.behavior_version,
870                             targetdir=ctx.targetdir)
871
872     def join_setup_trusts(ctx):
873         """provision the local SAM."""
874
875         def arcfour_encrypt(key, data):
876             from Crypto.Cipher import ARC4
877             c = ARC4.new(key)
878             return c.encrypt(data)
879
880         def string_to_array(string):
881             blob = [0] * len(string)
882
883             for i in range(len(string)):
884                 blob[i] = ord(string[i])
885
886             return blob
887
888         print "Setup domain trusts with server %s" % ctx.server
889         binding_options = ""  # why doesn't signing work here? w2k8r2 claims no session key
890         lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
891                              ctx.lp, ctx.creds)
892
893         objectAttr = lsa.ObjectAttribute()
894         objectAttr.sec_qos = lsa.QosInfo()
895
896         pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
897                                          objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
898
899         info = lsa.TrustDomainInfoInfoEx()
900         info.domain_name.string = ctx.dnsdomain
901         info.netbios_name.string = ctx.domain_name
902         info.sid = security.dom_sid(ctx.domsid)
903         info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
904         info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
905         info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
906
907         try:
908             oldname = lsa.String()
909             oldname.string = ctx.dnsdomain
910             oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
911                                                            lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
912             print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
913             lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
914         except RuntimeError:
915             pass
916
917         password_blob = string_to_array(ctx.trustdom_pass.encode('utf-16-le'))
918
919         clear_value = drsblobs.AuthInfoClear()
920         clear_value.size = len(password_blob)
921         clear_value.password = password_blob
922
923         clear_authentication_information = drsblobs.AuthenticationInformation()
924         clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
925         clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
926         clear_authentication_information.AuthInfo = clear_value
927
928         authentication_information_array = drsblobs.AuthenticationInformationArray()
929         authentication_information_array.count = 1
930         authentication_information_array.array = [clear_authentication_information]
931
932         outgoing = drsblobs.trustAuthInOutBlob()
933         outgoing.count = 1
934         outgoing.current = authentication_information_array
935
936         trustpass = drsblobs.trustDomainPasswords()
937         confounder = [3] * 512
938
939         for i in range(512):
940             confounder[i] = random.randint(0, 255)
941
942         trustpass.confounder = confounder
943
944         trustpass.outgoing = outgoing
945         trustpass.incoming = outgoing
946
947         trustpass_blob = ndr_pack(trustpass)
948
949         encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
950
951         auth_blob = lsa.DATA_BUF2()
952         auth_blob.size = len(encrypted_trustpass)
953         auth_blob.data = string_to_array(encrypted_trustpass)
954
955         auth_info = lsa.TrustDomainInfoAuthInfoInternal()
956         auth_info.auth_blob = auth_blob
957
958         trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
959                                                          info,
960                                                          auth_info,
961                                                          security.SEC_STD_DELETE)
962
963         rec = {
964             "dn" : "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
965             "objectclass" : "trustedDomain",
966             "trustType" : str(info.trust_type),
967             "trustAttributes" : str(info.trust_attributes),
968             "trustDirection" : str(info.trust_direction),
969             "flatname" : ctx.forest_domain_name,
970             "trustPartner" : ctx.dnsforest,
971             "trustAuthIncoming" : ndr_pack(outgoing),
972             "trustAuthOutgoing" : ndr_pack(outgoing)
973             }
974         ctx.local_samdb.add(rec)
975
976         rec = {
977             "dn" : "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
978             "objectclass" : "user",
979             "userAccountControl" : str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
980             "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le')
981             }
982         ctx.local_samdb.add(rec)
983
984
985     def do_join(ctx):
986         # full_nc_list is the list of naming context (NC) for which we will
987         # send a updateRef command to the partner DC
988         ctx.nc_list = [ ctx.config_dn, ctx.schema_dn ]
989         ctx.full_nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
990
991         if not ctx.subdomain:
992             ctx.nc_list += [ctx.base_dn]
993             if ctx.dns_backend != "NONE":
994                 ctx.nc_list += [ctx.domaindns_zone]
995
996         if ctx.dns_backend != "NONE":
997             ctx.full_nc_list += ['DC=DomainDnsZones,%s' % ctx.base_dn]
998             ctx.full_nc_list += ['DC=ForestDnsZones,%s' % ctx.root_dn]
999             ctx.nc_list += ['DC=ForestDnsZones,%s' % ctx.root_dn]
1000
1001         if ctx.promote_existing:
1002             ctx.promote_possible()
1003         else:
1004             ctx.cleanup_old_join()
1005
1006         try:
1007             ctx.join_add_objects()
1008             ctx.join_provision()
1009             ctx.join_replicate()
1010             if ctx.subdomain:
1011                 ctx.join_add_objects2()
1012                 ctx.join_provision_own_domain()
1013                 ctx.join_setup_trusts()
1014             ctx.join_finalise()
1015         except:
1016             print "Join failed - cleaning up"
1017             ctx.cleanup_old_join()
1018             raise
1019
1020
1021 def join_RODC(server=None, creds=None, lp=None, site=None, netbios_name=None,
1022               targetdir=None, domain=None, domain_critical_only=False,
1023               machinepass=None, use_ntvfs=False, dns_backend=None,
1024               promote_existing=False):
1025     """Join as a RODC."""
1026
1027     ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, domain,
1028                   machinepass, use_ntvfs, dns_backend, promote_existing)
1029
1030     lp.set("workgroup", ctx.domain_name)
1031     print("workgroup is %s" % ctx.domain_name)
1032
1033     lp.set("realm", ctx.realm)
1034     print("realm is %s" % ctx.realm)
1035
1036     ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
1037
1038     # setup some defaults for accounts that should be replicated to this RODC
1039     ctx.never_reveal_sid = [
1040         "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
1041         "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
1042         "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
1043         "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
1044         "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
1045     ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
1046
1047     mysid = ctx.get_mysid()
1048     admin_dn = "<SID=%s>" % mysid
1049     ctx.managedby = admin_dn
1050
1051     ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
1052                               samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
1053                               samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)
1054
1055     ctx.SPNs.extend([ "RestrictedKrbHost/%s" % ctx.myname,
1056                       "RestrictedKrbHost/%s" % ctx.dnshostname ])
1057
1058     ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
1059     ctx.secure_channel_type = misc.SEC_CHAN_RODC
1060     ctx.RODC = True
1061     ctx.replica_flags  =  (drsuapi.DRSUAPI_DRS_INIT_SYNC |
1062                            drsuapi.DRSUAPI_DRS_PER_SYNC |
1063                            drsuapi.DRSUAPI_DRS_GET_ANC |
1064                            drsuapi.DRSUAPI_DRS_NEVER_SYNCED |
1065                            drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
1066                            drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
1067     ctx.domain_replica_flags = ctx.replica_flags
1068     if domain_critical_only:
1069         ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1070
1071     ctx.do_join()
1072
1073     print "Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid)
1074
1075
1076 def join_DC(server=None, creds=None, lp=None, site=None, netbios_name=None,
1077             targetdir=None, domain=None, domain_critical_only=False,
1078             machinepass=None, use_ntvfs=False, dns_backend=None,
1079             promote_existing=False):
1080     """Join as a DC."""
1081     ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, domain,
1082                   machinepass, use_ntvfs, dns_backend, promote_existing)
1083
1084     lp.set("workgroup", ctx.domain_name)
1085     print("workgroup is %s" % ctx.domain_name)
1086
1087     lp.set("realm", ctx.realm)
1088     print("realm is %s" % ctx.realm)
1089
1090     ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1091
1092     ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1093     ctx.secure_channel_type = misc.SEC_CHAN_BDC
1094
1095     ctx.replica_flags = (drsuapi.DRSUAPI_DRS_WRIT_REP |
1096                          drsuapi.DRSUAPI_DRS_INIT_SYNC |
1097                          drsuapi.DRSUAPI_DRS_PER_SYNC |
1098                          drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS |
1099                          drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
1100     ctx.domain_replica_flags = ctx.replica_flags
1101     if domain_critical_only:
1102         ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
1103
1104     ctx.do_join()
1105     print "Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid)
1106
1107 def join_subdomain(server=None, creds=None, lp=None, site=None,
1108         netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
1109         netbios_domain=None, machinepass=None, use_ntvfs=False,
1110         dns_backend=None):
1111     """Join as a DC."""
1112     ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, parent_domain,
1113                   machinepass, use_ntvfs, dns_backend)
1114     ctx.subdomain = True
1115     ctx.parent_domain_name = ctx.domain_name
1116     ctx.domain_name = netbios_domain
1117     ctx.realm = dnsdomain
1118     ctx.parent_dnsdomain = ctx.dnsdomain
1119     ctx.parent_partition_dn = ctx.get_parent_partition_dn()
1120     ctx.dnsdomain = dnsdomain
1121     ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
1122     ctx.naming_master = ctx.get_naming_master()
1123     if ctx.naming_master != ctx.server:
1124         print("Reconnecting to naming master %s" % ctx.naming_master)
1125         ctx.server = ctx.naming_master
1126         ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
1127                           session_info=system_session(),
1128                           credentials=ctx.creds, lp=ctx.lp)
1129
1130     ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
1131     ctx.domsid = str(security.random_sid())
1132     ctx.acct_dn = None
1133     ctx.dnshostname = "%s.%s" % (ctx.myname, ctx.dnsdomain)
1134     ctx.trustdom_pass = samba.generate_random_password(128, 128)
1135
1136     ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
1137
1138     ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
1139     ctx.secure_channel_type = misc.SEC_CHAN_BDC
1140
1141     ctx.replica_flags = (drsuapi.DRSUAPI_DRS_WRIT_REP |
1142                          drsuapi.DRSUAPI_DRS_INIT_SYNC |
1143                          drsuapi.DRSUAPI_DRS_PER_SYNC |
1144                          drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS |
1145                          drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
1146     ctx.domain_replica_flags = ctx.replica_flags
1147
1148     ctx.do_join()
1149     print "Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid)