diff options
Diffstat (limited to 'source4/scripting/python')
-rw-r--r-- | source4/scripting/python/samba/join.py | 172 | ||||
-rw-r--r-- | source4/scripting/python/samba/netcmd/domain.py | 14 | ||||
-rw-r--r-- | source4/scripting/python/samba/provision/__init__.py | 497 |
3 files changed, 406 insertions, 277 deletions
diff --git a/source4/scripting/python/samba/join.py b/source4/scripting/python/samba/join.py index 3d81a296f7..1759990deb 100644 --- a/source4/scripting/python/samba/join.py +++ b/source4/scripting/python/samba/join.py @@ -27,9 +27,10 @@ import ldb, samba, sys, os, uuid from samba.ndr import ndr_pack from samba.dcerpc import security, drsuapi, misc, nbt from samba.credentials import Credentials, DONT_USE_KERBEROS -from samba.provision import secretsdb_self_join, provision, FILL_DRS +from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN from samba.schema import Schema from samba.net import Net +from samba.dcerpc import security import logging import talloc @@ -82,9 +83,6 @@ class dc_join(object): ctx.domsid = ctx.samdb.get_domain_sid() ctx.domain_name = ctx.get_domain_name() - lp.set("workgroup", ctx.domain_name) - print("workgroup is %s" % ctx.domain_name) - ctx.dc_ntds_dn = ctx.get_dsServiceName() ctx.dc_dnsHostName = ctx.get_dnsHostName() ctx.behavior_version = ctx.get_behavior_version() @@ -105,9 +103,6 @@ class dc_join(object): ctx.dnshostname = "%s.%s" % (ctx.myname, ctx.dnsdomain) ctx.realm = ctx.dnsdomain - lp.set("realm", ctx.realm) - - print("realm is %s" % ctx.realm) ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn) @@ -314,23 +309,24 @@ class dc_join(object): def join_add_objects(ctx): '''add the various objects needed for the join''' - print "Adding %s" % ctx.acct_dn - rec = { - "dn" : ctx.acct_dn, - "objectClass": "computer", - "displayname": ctx.samname, - "samaccountname" : ctx.samname, - "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE), - "dnshostname" : ctx.dnshostname} - if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008: - rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES) - if ctx.managedby: - rec["managedby"] = ctx.managedby - if ctx.never_reveal_sid: - rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid - if ctx.reveal_sid: - rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid - ctx.samdb.add(rec) + if ctx.acct_dn: + print "Adding %s" % ctx.acct_dn + rec = { + "dn" : ctx.acct_dn, + "objectClass": "computer", + "displayname": ctx.samname, + "samaccountname" : ctx.samname, + "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE), + "dnshostname" : ctx.dnshostname} + if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008: + rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES) + if ctx.managedby: + rec["managedby"] = ctx.managedby + if ctx.never_reveal_sid: + rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid + if ctx.reveal_sid: + rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid + ctx.samdb.add(rec) if ctx.krbtgt_dn: ctx.add_krbtgt_account() @@ -342,8 +338,11 @@ class dc_join(object): "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME | samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE | samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE), - "serverReference" : ctx.acct_dn, "dnsHostName" : ctx.dnshostname} + + if ctx.acct_dn: + rec["serverReference"] = ctx.acct_dn + ctx.samdb.add(rec) # FIXME: the partition (NC) assignment has to be made dynamic @@ -388,7 +387,7 @@ class dc_join(object): "fromServer" : ctx.dc_ntds_dn} ctx.samdb.add(rec) - if ctx.topology_dn: + if ctx.topology_dn and ctx.acct_dn: print "Adding %s" % ctx.topology_dn rec = { "dn" : ctx.topology_dn, @@ -397,31 +396,32 @@ class dc_join(object): "serverReference" : ctx.ntds_dn} ctx.samdb.add(rec) - print "Adding SPNs to %s" % ctx.acct_dn - m = ldb.Message() - m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn) - for i in range(len(ctx.SPNs)): - ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid)) - m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs, - ldb.FLAG_MOD_ADD, - "servicePrincipalName") - ctx.samdb.modify(m) - - print "Setting account password for %s" % ctx.samname - ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))" % ldb.binary_encode(ctx.samname), - ctx.acct_pass, - force_change_at_next_login=False, - username=ctx.samname) - res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-keyVersionNumber"]) - ctx.key_version_number = int(res[0]["msDS-keyVersionNumber"][0]) - - print("Enabling account") - m = ldb.Message() - m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn) - m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl), - ldb.FLAG_MOD_REPLACE, - "userAccountControl") - ctx.samdb.modify(m) + if ctx.acct_dn: + print "Adding SPNs to %s" % ctx.acct_dn + m = ldb.Message() + m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn) + for i in range(len(ctx.SPNs)): + ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid)) + m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs, + ldb.FLAG_MOD_ADD, + "servicePrincipalName") + ctx.samdb.modify(m) + + print "Setting account password for %s" % ctx.samname + ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))" % ldb.binary_encode(ctx.samname), + ctx.acct_pass, + force_change_at_next_login=False, + username=ctx.samname) + res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-keyVersionNumber"]) + ctx.key_version_number = int(res[0]["msDS-keyVersionNumber"][0]) + + print("Enabling account") + m = ldb.Message() + m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn) + m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl), + ldb.FLAG_MOD_REPLACE, + "userAccountControl") + ctx.samdb.modify(m) def join_provision(ctx): '''provision the local SAM''' @@ -445,6 +445,24 @@ class dc_join(object): ctx.local_samdb = presult.samdb ctx.lp = presult.lp ctx.paths = presult.paths + ctx.names = presult.names + + def join_provision_own_domain(ctx): + '''provision the local SAM''' + + print "Calling bare provision" + + logger = logging.getLogger("provision") + logger.addHandler(logging.StreamHandler(sys.stdout)) + + secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp) + + presult = provision_fill(ctx.local_samdb, secrets_ldb, + logger, ctx.names, ctx.paths, domainsid=security.dom_sid(ctx.domsid), + targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN, + machinepass=ctx.acct_pass, serverrole="domain controller", + lp=ctx.lp) + print "Provision OK for domain %s" % ctx.names.dnsdomain def join_replicate(ctx): @@ -478,9 +496,10 @@ class dc_join(object): repl.replicate(ctx.config_dn, source_dsa_invocation_id, destination_dsa_guid, rodc=ctx.RODC, replica_flags=ctx.replica_flags) - repl.replicate(ctx.base_dn, source_dsa_invocation_id, - destination_dsa_guid, rodc=ctx.RODC, - replica_flags=ctx.domain_replica_flags) + if not ctx.subdomain: + repl.replicate(ctx.base_dn, source_dsa_invocation_id, + destination_dsa_guid, rodc=ctx.RODC, + replica_flags=ctx.domain_replica_flags) if ctx.RODC: repl.replicate(ctx.acct_dn, source_dsa_invocation_id, destination_dsa_guid, @@ -526,7 +545,10 @@ class dc_join(object): ctx.join_add_objects() ctx.join_provision() ctx.join_replicate() - ctx.join_finalise() + if ctx.subdomain: + ctx.join_provision_own_domain() + else: + ctx.join_finalise() except Exception: print "Join failed - cleaning up" ctx.cleanup_old_join() @@ -539,6 +561,12 @@ def join_RODC(server=None, creds=None, lp=None, site=None, netbios_name=None, ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, domain) + lp.set("workgroup", ctx.domain_name) + print("workgroup is %s" % ctx.domain_name) + + lp.set("realm", ctx.realm) + print("realm is %s" % ctx.realm) + ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn) # setup some defaults for accounts that should be replicated to this RODC @@ -584,6 +612,12 @@ def join_DC(server=None, creds=None, lp=None, site=None, netbios_name=None, """join as a DC""" ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, domain) + lp.set("workgroup", ctx.domain_name) + print("workgroup is %s" % ctx.domain_name) + + lp.set("realm", ctx.realm) + print("realm is %s" % ctx.realm) + ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain) @@ -600,3 +634,31 @@ def join_DC(server=None, creds=None, lp=None, site=None, netbios_name=None, ctx.do_join() print "Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid) + +def join_subdomain(server=None, creds=None, lp=None, site=None, netbios_name=None, + targetdir=None, parent_domain=None, dnsdomain=None, netbios_domain=None): + """join as a DC""" + ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, parent_domain) + ctx.subdomain = True + ctx.domain_name = netbios_domain + ctx.realm = dnsdomain + ctx.dnsdomain = dnsdomain + ctx.base_dn = samba.dn_from_dns_name(dnsdomain) + ctx.domsid = str(security.random_sid()) + ctx.acct_dn = None + ctx.dnshostname = "%s.%s" % (ctx.myname, ctx.dnsdomain) + + ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION + + ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain) + ctx.secure_channel_type = misc.SEC_CHAN_BDC + + ctx.replica_flags = (drsuapi.DRSUAPI_DRS_WRIT_REP | + drsuapi.DRSUAPI_DRS_INIT_SYNC | + drsuapi.DRSUAPI_DRS_PER_SYNC | + drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS | + drsuapi.DRSUAPI_DRS_NEVER_SYNCED) + ctx.domain_replica_flags = ctx.replica_flags + + ctx.do_join() + print "Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid) diff --git a/source4/scripting/python/samba/netcmd/domain.py b/source4/scripting/python/samba/netcmd/domain.py index b8f5561552..9490001404 100644 --- a/source4/scripting/python/samba/netcmd/domain.py +++ b/source4/scripting/python/samba/netcmd/domain.py @@ -31,7 +31,7 @@ import logging from samba import Ldb from samba.net import Net, LIBNET_JOIN_AUTOMATIC from samba.dcerpc.misc import SEC_CHAN_WKSTA -from samba.join import join_RODC, join_DC +from samba.join import join_RODC, join_DC, join_subdomain from samba.auth import system_session from samba.samdb import SamDB from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT @@ -76,12 +76,13 @@ class cmd_domain_export_keytab(Command): class cmd_domain_join(Command): """Joins domain as either member or backup domain controller *""" - synopsis = "%prog domain join <dnsdomain> [DC|RODC|MEMBER] [options]" + synopsis = "%prog domain join <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]" takes_options = [ Option("--server", help="DC to join", type=str), Option("--site", help="site to join", type=str), Option("--targetdir", help="where to store provision", type=str), + Option("--parent-domain", help="parent domain to create subdomain under", type=str), Option("--domain-critical-only", help="only replicate critical domain objects", action="store_true"), @@ -91,7 +92,7 @@ class cmd_domain_join(Command): def run(self, domain, role=None, sambaopts=None, credopts=None, versionopts=None, server=None, site=None, targetdir=None, - domain_critical_only=False): + domain_critical_only=False, parent_domain=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) net = Net(creds, lp, server=credopts.ipaddress) @@ -121,6 +122,13 @@ class cmd_domain_join(Command): site=site, netbios_name=netbios_name, targetdir=targetdir, domain_critical_only=domain_critical_only) return + elif role == "SUBDOMAIN": + netbios_domain = lp.get("workgroup") + if parent_domain is None: + parent_domain = ".".join(domain.split(".")[1:]) + join_subdomain(server=server, creds=creds, lp=lp, dnsdomain=domain, parent_domain=parent_domain, + site=site, netbios_name=netbios_name, netbios_domain=netbios_domain, targetdir=targetdir) + return else: raise CommandError("Invalid role %s (possible values: MEMBER, DC, RODC)" % role) diff --git a/source4/scripting/python/samba/provision/__init__.py b/source4/scripting/python/samba/provision/__init__.py index 9f49f44952..130ea72e70 100644 --- a/source4/scripting/python/samba/provision/__init__.py +++ b/source4/scripting/python/samba/provision/__init__.py @@ -438,6 +438,7 @@ class ProvisionResult(object): self.lp = None self.samdb = None self.idmap = None + self.names = None def check_install(lp, session_info, credentials): @@ -761,7 +762,7 @@ def make_smbconf(smbconf, hostname, domain, realm, serverrole, -def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid, +def setup_name_mappings(idmap, sid, root_uid, nobody_uid, users_gid, wheel_gid): """setup reasonable name mappings for sam names to unix names. @@ -1064,7 +1065,7 @@ def setup_samdb_rootdse(samdb, names): }) -def setup_self_join(samdb, names, machinepass, dnspass, +def setup_self_join(samdb, names, fill, machinepass, dnspass, domainsid, next_rid, invocationid, policyguid, policyguid_dc, domainControllerFunctionality, ntdsguid, dc_rid=None): @@ -1100,18 +1101,35 @@ def setup_self_join(samdb, names, machinepass, dnspass, "DNSDOMAIN": names.dnsdomain, "DOMAINDN": names.domaindn}) - # add the NTDSGUID based SPNs - ntds_dn = "CN=NTDS Settings,%s" % names.serverdn - names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID", - expression="", scope=ldb.SCOPE_BASE) - assert isinstance(names.ntdsguid, str) + # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it + if fill == FILL_FULL: + setup_add_ldif(samdb, setup_path("provision_self_join_config.ldif"), { + "CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn, + "DOMAINDN": names.domaindn, + "SERVERDN": names.serverdn, + "INVOCATIONID": invocationid, + "NETBIOSNAME": names.netbiosname, + "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain), + "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')), + "DOMAINSID": str(domainsid), + "DCRID": str(dc_rid), + "SAMBA_VERSION_STRING": version, + "NTDSGUID": ntdsguid_line, + "DOMAIN_CONTROLLER_FUNCTIONALITY": str( + domainControllerFunctionality)}) + + # Setup fSMORoleOwner entries to point at the newly created DC entry + setup_modify_ldif(samdb, setup_path("provision_self_join_modify_config.ldif"), { + "CONFIGDN": names.configdn, + "SCHEMADN": names.schemadn, + "DEFAULTSITE": names.sitename, + "SERVERDN": names.serverdn, + }) # Setup fSMORoleOwner entries to point at the newly created DC entry setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), { "DOMAINDN": names.domaindn, - "CONFIGDN": names.configdn, - "SCHEMADN": names.schemadn, - "DEFAULTSITE": names.sitename, "SERVERDN": names.serverdn, "NETBIOSNAME": names.netbiosname, "RIDALLOCATIONSTART": str(next_rid + 100), @@ -1174,15 +1192,48 @@ def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc): def setup_samdb(path, session_info, provision_backend, lp, names, - logger, domainsid, domainguid, policyguid, policyguid_dc, fill, - adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid, - serverrole, am_rodc=False, dom_for_fun_level=None, schema=None, - next_rid=None, dc_rid=None): + logger, fill, serverrole, + am_rodc=False, schema=None): """Setup a complete SAM Database. :note: This will wipe the main SAM database file! """ + # Also wipes the database + setup_samdb_partitions(path, logger=logger, lp=lp, + provision_backend=provision_backend, session_info=session_info, + names=names, serverrole=serverrole, schema=schema) + + if schema is None: + schema = Schema(domainsid, schemadn=names.schemadn) + + # Load the database, but don's load the global schema and don't connect + # quite yet + samdb = SamDB(session_info=session_info, url=None, auto_connect=False, + credentials=provision_backend.credentials, lp=lp, + global_schema=False, am_rodc=am_rodc) + + logger.info("Pre-loading the Samba 4 and AD schema") + + # Load the schema from the one we computed earlier + samdb.set_schema(schema) + + # Set the NTDS settings DN manually - in order to have it already around + # before the provisioned tree exists and we connect + samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn) + + # And now we can connect to the DB - the schema won't be loaded from the + # DB + samdb.connect(path) + + return samdb + +def fill_samdb(samdb, lp, names, + logger, domainsid, domainguid, policyguid, policyguid_dc, fill, + adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid, + serverrole, am_rodc=False, dom_for_fun_level=None, schema=None, + next_rid=None, dc_rid=None): + if next_rid is None: next_rid = 1000 @@ -1208,36 +1259,10 @@ def setup_samdb(path, session_info, provision_backend, lp, names, domainFunctionality = dom_for_fun_level forestFunctionality = dom_for_fun_level - # Also wipes the database - setup_samdb_partitions(path, logger=logger, lp=lp, - provision_backend=provision_backend, session_info=session_info, - names=names, serverrole=serverrole, schema=schema) - - if schema is None: - schema = Schema(domainsid, schemadn=names.schemadn) - - # Load the database, but don's load the global schema and don't connect - # quite yet - samdb = SamDB(session_info=session_info, url=None, auto_connect=False, - credentials=provision_backend.credentials, lp=lp, - global_schema=False, am_rodc=am_rodc) - - logger.info("Pre-loading the Samba 4 and AD schema") - - # Load the schema from the one we computed earlier - samdb.set_schema(schema) - # Set the NTDS settings DN manually - in order to have it already around # before the provisioned tree exists and we connect samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn) - # And now we can connect to the DB - the schema won't be loaded from the - # DB - samdb.connect(path) - - if fill == FILL_DRS: - return samdb - samdb.transaction_start() try: # Set the domain functionality levels onto the database. @@ -1283,28 +1308,29 @@ def setup_samdb(path, session_info, provision_backend, lp, names, "SAMBA_VERSION_STRING": version }) - logger.info("Adding configuration container") - descr = b64encode(get_config_descriptor(domainsid)) - setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), { - "CONFIGDN": names.configdn, - "DESCRIPTOR": descr, - }) + # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it + if fill == FILL_FULL: + logger.info("Adding configuration container") + descr = b64encode(get_config_descriptor(domainsid)) + setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), { + "CONFIGDN": names.configdn, + "DESCRIPTOR": descr, + }) + + # The LDIF here was created when the Schema object was constructed + logger.info("Setting up sam.ldb schema") + samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"]) + samdb.modify_ldif(schema.schema_dn_modify) + samdb.write_prefixes_from_schema() + samdb.add_ldif(schema.schema_data, controls=["relax:0"]) + setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), + {"SCHEMADN": names.schemadn}) # Now register this container in the root of the forest msg = ldb.Message(ldb.Dn(samdb, names.domaindn)) msg["subRefs"] = ldb.MessageElement(names.configdn , ldb.FLAG_MOD_ADD, "subRefs") - # The LDIF here was created when the Schema object was constructed - logger.info("Setting up sam.ldb schema") - samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"]) - samdb.modify_ldif(schema.schema_dn_modify) - samdb.write_prefixes_from_schema() - samdb.add_ldif(schema.schema_data, controls=["relax:0"]) - setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), - {"SCHEMADN": names.schemadn}) - - logger.info("Reopening sam.ldb with new schema") except Exception: samdb.transaction_cancel() raise @@ -1324,27 +1350,29 @@ def setup_samdb(path, session_info, provision_backend, lp, names, try: samdb.invocation_id = invocationid - logger.info("Setting up sam.ldb configuration data") - setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), { - "CONFIGDN": names.configdn, - "NETBIOSNAME": names.netbiosname, - "DEFAULTSITE": names.sitename, - "DNSDOMAIN": names.dnsdomain, - "DOMAIN": names.domain, - "SCHEMADN": names.schemadn, - "DOMAINDN": names.domaindn, - "SERVERDN": names.serverdn, - "FOREST_FUNCTIONALITY": str(forestFunctionality), - "DOMAIN_FUNCTIONALITY": str(domainFunctionality), - }) + # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it + if fill == FILL_FULL: + logger.info("Setting up sam.ldb configuration data") + setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), { + "CONFIGDN": names.configdn, + "NETBIOSNAME": names.netbiosname, + "DEFAULTSITE": names.sitename, + "DNSDOMAIN": names.dnsdomain, + "DOMAIN": names.domain, + "SCHEMADN": names.schemadn, + "DOMAINDN": names.domaindn, + "SERVERDN": names.serverdn, + "FOREST_FUNCTIONALITY": str(forestFunctionality), + "DOMAIN_FUNCTIONALITY": str(domainFunctionality), + }) - logger.info("Setting up display specifiers") - display_specifiers_ldif = read_ms_ldif( - setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt')) - display_specifiers_ldif = substitute_var(display_specifiers_ldif, - {"CONFIGDN": names.configdn}) - check_all_substituted(display_specifiers_ldif) - samdb.add_ldif(display_specifiers_ldif) + logger.info("Setting up display specifiers") + display_specifiers_ldif = read_ms_ldif( + setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt')) + display_specifiers_ldif = substitute_var(display_specifiers_ldif, + {"CONFIGDN": names.configdn}) + check_all_substituted(display_specifiers_ldif) + samdb.add_ldif(display_specifiers_ldif) logger.info("Adding users container") setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), { @@ -1371,15 +1399,17 @@ def setup_samdb(path, session_info, provision_backend, lp, names, "POLICYGUID_DC": policyguid_dc }) - setup_modify_ldif(samdb, - setup_path("provision_basedn_references.ldif"), { - "DOMAINDN": names.domaindn}) + # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it + if fill == FILL_FULL: + setup_modify_ldif(samdb, + setup_path("provision_basedn_references.ldif"), + {"DOMAINDN": names.domaindn}) setup_modify_ldif(samdb, setup_path("provision_configuration_references.ldif"), { "CONFIGDN": names.configdn, "SCHEMADN": names.schemadn}) - if fill == FILL_FULL: + if fill == FILL_FULL or fill == FILL_SUBDOMAIN: logger.info("Setting up sam.ldb users and groups") setup_add_ldif(samdb, setup_path("provision_users.ldif"), { "DOMAINDN": names.domaindn, @@ -1390,7 +1420,7 @@ def setup_samdb(path, session_info, provision_backend, lp, names, }) logger.info("Setting up self join") - setup_self_join(samdb, names=names, invocationid=invocationid, + setup_self_join(samdb, names=names, fill=fill, invocationid=invocationid, dnspass=dnspass, machinepass=machinepass, domainsid=domainsid, @@ -1414,6 +1444,7 @@ def setup_samdb(path, session_info, provision_backend, lp, names, FILL_FULL = "FULL" +FILL_SUBDOMAIN = "SUBDOMAIN" FILL_NT4SYNC = "NT4SYNC" FILL_DRS = "DRS" SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)" @@ -1512,30 +1543,16 @@ def interface_ips_v6(lp, linklocal=False): return ret -def provision(logger, session_info, credentials, smbconf=None, - targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None, - domaindn=None, schemadn=None, configdn=None, serverdn=None, - domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None, - next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None, krbtgtpass=None, - domainguid=None, policyguid=None, policyguid_dc=None, - invocationid=None, machinepass=None, ntdsguid=None, - dns_backend=None, dnspass=None, - root=None, nobody=None, users=None, wheel=None, backup=None, aci=None, - serverrole=None, dom_for_fun_level=None, ldap_backend_extra_port=None, - ldap_backend_forced_uri=None, backend_type=None, sitename=None, - ol_mmr_urls=None, ol_olc=None, setup_ds_path=None, slapd_path=None, - nosync=False, ldap_dryrun_mode=False, useeadb=False, am_rodc=False, - lp=None): - """Provision samba4 - - :note: caution, this wipes all existing data! - """ - - if domainsid is None: - domainsid = security.random_sid() - else: - domainsid = security.dom_sid(domainsid) - +def provision_fill(samdb, secrets_ldb, logger, names, paths, + domainsid, schema=None, + targetdir=None, samdb_fill=FILL_FULL, + hostip=None, hostip6=None, + next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None, + domainguid=None, policyguid=None, policyguid_dc=None, + invocationid=None, machinepass=None, ntdsguid=None, + dns_backend=None, dnspass=None, + serverrole=None, dom_for_fun_level=None, + am_rodc=False, lp=None): # create/adapt the group policy GUIDs # Default GUID for default policy are described at # "How Core Group Policy Works" @@ -1547,6 +1564,9 @@ def provision(logger, session_info, credentials, smbconf=None, policyguid_dc = DEFAULT_DC_POLICY_GUID policyguid_dc = policyguid_dc.upper() + if invocationid is None: + invocationid = str(uuid.uuid4()) + if adminpass is None: adminpass = samba.generate_random_password(12, 32) if krbtgtpass is None: @@ -1555,6 +1575,135 @@ def provision(logger, session_info, credentials, smbconf=None, machinepass = samba.generate_random_password(128, 255) if dnspass is None: dnspass = samba.generate_random_password(128, 255) + + samdb = fill_samdb(samdb, lp, names, logger=logger, + domainsid=domainsid, schema=schema, domainguid=domainguid, + policyguid=policyguid, policyguid_dc=policyguid_dc, + fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass, + invocationid=invocationid, machinepass=machinepass, + dnspass=dnspass, ntdsguid=ntdsguid, serverrole=serverrole, + dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc, + next_rid=next_rid, dc_rid=dc_rid) + + if serverrole == "domain controller": + # Set up group policies (domain policy and domain controller + # policy) + create_default_gpo(paths.sysvol, names.dnsdomain, policyguid, + policyguid_dc) + setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.wheel_gid, + domainsid, names.dnsdomain, names.domaindn, lp) + + logger.info("Setting up sam.ldb rootDSE marking as synchronized") + setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"), + { 'NTDSGUID' : names.ntdsguid }) + + secretsdb_self_join(secrets_ldb, domain=names.domain, + realm=names.realm, dnsdomain=names.dnsdomain, + netbiosname=names.netbiosname, domainsid=domainsid, + machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC) + + # Now set up the right msDS-SupportedEncryptionTypes into the DB + # In future, this might be determined from some configuration + kerberos_enctypes = str(ENC_ALL_TYPES) + + try: + msg = ldb.Message(ldb.Dn(samdb, + samdb.searchone("distinguishedName", + expression="samAccountName=%s$" % names.netbiosname, + scope=ldb.SCOPE_SUBTREE))) + msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement( + elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE, + name="msDS-SupportedEncryptionTypes") + samdb.modify(msg) + except ldb.LdbError, (enum, estr): + if enum != ldb.ERR_NO_SUCH_ATTRIBUTE: + # It might be that this attribute does not exist in this schema + raise + + if serverrole == "domain controller": + secretsdb_setup_dns(secrets_ldb, names, + paths.private_dir, realm=names.realm, + dnsdomain=names.dnsdomain, + dns_keytab_path=paths.dns_keytab, dnspass=dnspass) + + # Default DNS backend is BIND9 using txt files for zone information + if not dns_backend: + dns_backend = "BIND9" + + setup_ad_dns(samdb, names, logger, hostip=hostip, hostip6=hostip6, + dns_backend=dns_backend, os_level=dom_for_fun_level) + + domainguid = samdb.searchone(basedn=samdb.get_default_basedn(), + attribute="objectGUID") + assert isinstance(domainguid, str) + + create_dns_dir(logger, paths) + + # Only make a zone file on the first DC, it should be + # replicated with DNS replication + if dns_backend == "BIND9": + create_zone_file(lp, logger, paths, targetdir, + dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6, + hostname=names.hostname, realm=names.realm, + domainguid=domainguid, ntdsguid=names.ntdsguid) + + create_named_conf(paths, realm=names.realm, + dnsdomain=names.dnsdomain, dns_backend=dns_backend) + + create_named_txt(paths.namedtxt, + realm=names.realm, dnsdomain=names.dnsdomain, + dnsname = "%s.%s" % (names.hostname, names.dnsdomain), + private_dir=paths.private_dir, + keytab_name=paths.dns_keytab) + logger.info("See %s for an example configuration include file for BIND", paths.namedconf) + logger.info("and %s for further documentation required for secure DNS " + "updates", paths.namedtxt) + + lastProvisionUSNs = get_last_provision_usn(samdb) + maxUSN = get_max_usn(samdb, str(names.rootdn)) + if lastProvisionUSNs is not None: + update_provision_usn(samdb, 0, maxUSN, invocationid, 1) + else: + set_provision_usn(samdb, 0, maxUSN, invocationid) + + # fix any dangling GUIDs from the provision + logger.info("Fixing provision GUIDs") + chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True, quiet=True) + samdb.transaction_start() + # a small number of GUIDs are missing because of ordering issues in the + # provision code + for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']: + chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn), + scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory']) + chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn, + scope=ldb.SCOPE_ONELEVEL, + attrs=['ipsecOwnersReference', + 'ipsecFilterReference', + 'ipsecISAKMPReference', + 'ipsecNegotiationPolicyReference', + 'ipsecNFAReference']) + samdb.transaction_commit() + + +def provision(logger, session_info, credentials, smbconf=None, + targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None, + domaindn=None, schemadn=None, configdn=None, serverdn=None, + domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None, + next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None, krbtgtpass=None, + domainguid=None, policyguid=None, policyguid_dc=None, + dns_backend=None, dnspass=None, + invocationid=None, machinepass=None, ntdsguid=None, + root=None, nobody=None, users=None, wheel=None, backup=None, aci=None, + serverrole=None, dom_for_fun_level=None, ldap_backend_extra_port=None, + ldap_backend_forced_uri=None, backend_type=None, sitename=None, + ol_mmr_urls=None, ol_olc=None, setup_ds_path=None, slapd_path=None, + nosync=False, ldap_dryrun_mode=False, useeadb=False, am_rodc=False, + lp=None): + """Provision samba4 + + :note: caution, this wipes all existing data! + """ + if ldapadminpass is None: # Make a new, random password between Samba and it's LDAP server ldapadminpass=samba.generate_random_password(128, 255) @@ -1562,6 +1711,11 @@ def provision(logger, session_info, credentials, smbconf=None, if backend_type is None: backend_type = "ldb" + if domainsid is None: + domainsid = security.random_sid() + else: + domainsid = security.dom_sid(domainsid) + sid_generator = "internal" if backend_type == "fedora-ds": sid_generator = "backend" @@ -1610,6 +1764,7 @@ def provision(logger, session_info, credentials, smbconf=None, paths = provision_paths_from_lp(lp, names.dnsdomain) paths.bind_gid = bind_gid + paths.wheel_gid = wheel_gid if hostip is None: logger.info("Looking up IPv4 addresses") @@ -1638,8 +1793,6 @@ def provision(logger, session_info, credentials, smbconf=None, serverrole = lp.get("server role") assert serverrole in ("domain controller", "member server", "standalone") - if invocationid is None: - invocationid = str(uuid.uuid4()) if not os.path.exists(paths.private_dir): os.mkdir(paths.private_dir) @@ -1710,16 +1863,15 @@ def provision(logger, session_info, credentials, smbconf=None, idmap = setup_idmapdb(paths.idmapdb, session_info=session_info, lp=lp) + setup_name_mappings(idmap, sid=str(domainsid), + root_uid=root_uid, nobody_uid=nobody_uid, + users_gid=users_gid, wheel_gid=wheel_gid) + logger.info("Setting up SAM db") samdb = setup_samdb(paths.samdb, session_info, - provision_backend, lp, names, logger=logger, - domainsid=domainsid, schema=schema, domainguid=domainguid, - policyguid=policyguid, policyguid_dc=policyguid_dc, - fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass, - invocationid=invocationid, machinepass=machinepass, - dnspass=dnspass, ntdsguid=ntdsguid, serverrole=serverrole, - dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc, - next_rid=next_rid, dc_rid=dc_rid) + provision_backend, lp, names, logger=logger, + serverrole=serverrole, + schema=schema, fill=samdb_fill, am_rodc=am_rodc) if serverrole == "domain controller": if paths.netlogon is None: @@ -1739,90 +1891,16 @@ def provision(logger, session_info, credentials, smbconf=None, os.makedirs(paths.netlogon, 0755) if samdb_fill == FILL_FULL: - setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn, - root_uid=root_uid, nobody_uid=nobody_uid, - users_gid=users_gid, wheel_gid=wheel_gid) - - if serverrole == "domain controller": - # Set up group policies (domain policy and domain controller - # policy) - create_default_gpo(paths.sysvol, names.dnsdomain, policyguid, - policyguid_dc) - setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid, - domainsid, names.dnsdomain, names.domaindn, lp) - - logger.info("Setting up sam.ldb rootDSE marking as synchronized") - setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"), - { 'NTDSGUID' : names.ntdsguid }) - - secretsdb_self_join(secrets_ldb, domain=names.domain, - realm=names.realm, dnsdomain=names.dnsdomain, - netbiosname=names.netbiosname, domainsid=domainsid, - machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC) - - # Now set up the right msDS-SupportedEncryptionTypes into the DB - # In future, this might be determined from some configuration - kerberos_enctypes = str(ENC_ALL_TYPES) - - try: - msg = ldb.Message(ldb.Dn(samdb, - samdb.searchone("distinguishedName", - expression="samAccountName=%s$" % names.netbiosname, - scope=ldb.SCOPE_SUBTREE))) - msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement( - elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE, - name="msDS-SupportedEncryptionTypes") - samdb.modify(msg) - except ldb.LdbError, (enum, estr): - if enum != ldb.ERR_NO_SUCH_ATTRIBUTE: - # It might be that this attribute does not exist in this schema - raise - - if serverrole == "domain controller": - secretsdb_setup_dns(secrets_ldb, names, - paths.private_dir, realm=names.realm, - dnsdomain=names.dnsdomain, - dns_keytab_path=paths.dns_keytab, dnspass=dnspass) - - # Default DNS backend is BIND9 using txt files for zone information - if not dns_backend: - dns_backend = "BIND9" - - setup_ad_dns(samdb, names, logger, hostip=hostip, hostip6=hostip6, - dns_backend=dns_backend, os_level=dom_for_fun_level) - - domainguid = samdb.searchone(basedn=domaindn, - attribute="objectGUID") - assert isinstance(domainguid, str) - - create_dns_dir(logger, paths) - - # Only make a zone file on the first DC, it should be - # replicated with DNS replication - if dns_backend == "BIND9": - create_zone_file(lp, logger, paths, targetdir, - dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6, - hostname=names.hostname, realm=names.realm, - domainguid=domainguid, ntdsguid=names.ntdsguid) - - create_named_conf(paths, realm=names.realm, - dnsdomain=names.dnsdomain, dns_backend=dns_backend) - - create_named_txt(paths.namedtxt, - realm=names.realm, dnsdomain=names.dnsdomain, - dnsname = "%s.%s" % (names.hostname, names.dnsdomain), - private_dir=paths.private_dir, - keytab_name=paths.dns_keytab) - logger.info("See %s for an example configuration include file for BIND", paths.namedconf) - logger.info("and %s for further documentation required for secure DNS " - "updates", paths.namedtxt) - - lastProvisionUSNs = get_last_provision_usn(samdb) - maxUSN = get_max_usn(samdb, str(names.rootdn)) - if lastProvisionUSNs is not None: - update_provision_usn(samdb, 0, maxUSN, invocationid, 1) - else: - set_provision_usn(samdb, 0, maxUSN, invocationid) + provision_fill(samdb, secrets_ldb, logger, + names, paths, schema=schema, targetdir=targetdir, + samdb_fill=samdb_fill, hostip=hostip, hostip6=hostip6, domainsid=domainsid, + next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass, + krbtgtpass=krbtgtpass, domainguid=domainguid, + policyguid=policyguid, policyguid_dc=policyguid_dc, + invocationid=invocationid, machinepass=machinepass, + ntdsguid=ntdsguid, dns_backend=dns_backend, dnspass=dnspass, + serverrole=serverrole, dom_for_fun_level=dom_for_fun_level, + am_rodc=am_rodc, lp=lp) create_krb5_conf(paths.krb5conf, dnsdomain=names.dnsdomain, hostname=names.hostname, @@ -1856,26 +1934,6 @@ def provision(logger, session_info, credentials, smbconf=None, logger.info("Failed to chown %s to bind gid %u", dns_keytab_path, paths.bind_gid) - if samdb_fill != FILL_DRS: - # fix any dangling GUIDs from the provision - logger.info("Fixing provision GUIDs") - chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True, quiet=True) - samdb.transaction_start() - # a small number of GUIDs are missing because of ordering issues in the - # provision code - for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']: - chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn), - scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory']) - chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn, - scope=ldb.SCOPE_ONELEVEL, - attrs=['ipsecOwnersReference', - 'ipsecFilterReference', - 'ipsecISAKMPReference', - 'ipsecNegotiationPolicyReference', - 'ipsecNFAReference']) - samdb.transaction_commit() - - logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php", paths.phpldapadminconfig) @@ -1909,6 +1967,7 @@ def provision(logger, session_info, credentials, smbconf=None, result = ProvisionResult() result.domaindn = domaindn result.paths = paths + result.names = names result.lp = lp result.samdb = samdb result.idmap = idmap |