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