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