diff options
Diffstat (limited to 'source4/scripting/python/samba/netcmd/domain.py')
-rw-r--r-- | source4/scripting/python/samba/netcmd/domain.py | 1344 |
1 files changed, 1344 insertions, 0 deletions
diff --git a/source4/scripting/python/samba/netcmd/domain.py b/source4/scripting/python/samba/netcmd/domain.py new file mode 100644 index 0000000000..4ba305c271 --- /dev/null +++ b/source4/scripting/python/samba/netcmd/domain.py @@ -0,0 +1,1344 @@ +# domain management +# +# Copyright Matthias Dieter Wallnoefer 2009 +# Copyright Andrew Kroeger 2009 +# Copyright Jelmer Vernooij 2007-2012 +# Copyright Giampaolo Lauria 2011 +# Copyright Matthieu Patou <mat@matws.net> 2011 +# Copyright Andrew Bartlett 2008 +# Copyright Stefan Metzmacher 2012 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import samba.getopt as options +import ldb +import string +import os +import sys +import tempfile +import logging +from samba.net import Net, LIBNET_JOIN_AUTOMATIC +import samba.ntacls +from samba.join import join_RODC, join_DC, join_subdomain +from samba.auth import system_session +from samba.samdb import SamDB +from samba.dcerpc import drsuapi +from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT +from samba.netcmd import ( + Command, + CommandError, + SuperCommand, + Option + ) +from samba.netcmd.common import netcmd_get_domain_infos_via_cldap +from samba.samba3 import Samba3 +from samba.samba3 import param as s3param +from samba.upgrade import upgrade_from_samba3 +from samba.drs_utils import ( + sendDsReplicaSync, drsuapi_connect, drsException, + sendRemoveDsServer) + + +from samba.dsdb import ( + DS_DOMAIN_FUNCTION_2000, + DS_DOMAIN_FUNCTION_2003, + DS_DOMAIN_FUNCTION_2003_MIXED, + DS_DOMAIN_FUNCTION_2008, + DS_DOMAIN_FUNCTION_2008_R2, + DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL, + DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL, + UF_WORKSTATION_TRUST_ACCOUNT, + UF_SERVER_TRUST_ACCOUNT, + UF_TRUSTED_FOR_DELEGATION + ) + +from samba.credentials import DONT_USE_KERBEROS +from samba.provision import ( + provision, + FILL_FULL, + FILL_NT4SYNC, + FILL_DRS, + ProvisioningError, + ) + +def get_testparm_var(testparm, smbconf, varname): + cmd = "%s -s -l --parameter-name='%s' %s 2>/dev/null" % (testparm, varname, smbconf) + output = os.popen(cmd, 'r').readline() + return output.strip() + +try: + import samba.dckeytab + class cmd_domain_export_keytab(Command): + """Dump Kerberos keys of the domain into a keytab.""" + + synopsis = "%prog <keytab> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + takes_options = [ + Option("--principal", help="extract only this principal", type=str), + ] + + takes_args = ["keytab"] + + def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None): + lp = sambaopts.get_loadparm() + net = Net(None, lp) + net.export_keytab(keytab=keytab, principal=principal) +except: + cmd_domain_export_keytab = None + + +class cmd_domain_info(Command): + """Print basic info about a domain and the DC passed as parameter.""" + + synopsis = "%prog <ip_address> [options]" + + takes_options = [ + ] + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + takes_args = ["address"] + + def run(self, address, credopts=None, sambaopts=None, versionopts=None): + lp = sambaopts.get_loadparm() + try: + res = netcmd_get_domain_infos_via_cldap(lp, None, address) + except RuntimeError: + raise CommandError("Invalid IP address '" + address + "'!") + self.outf.write("Forest : %s\n" % res.forest) + self.outf.write("Domain : %s\n" % res.dns_domain) + self.outf.write("Netbios domain : %s\n" % res.domain_name) + self.outf.write("DC name : %s\n" % res.pdc_dns_name) + self.outf.write("DC netbios name : %s\n" % res.pdc_name) + self.outf.write("Server site : %s\n" % res.server_site) + self.outf.write("Client site : %s\n" % res.client_site) + + +class cmd_domain_provision(Command): + """Provision a domain.""" + + synopsis = "%prog [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("--interactive", help="Ask for names", action="store_true"), + Option("--domain", type="string", metavar="DOMAIN", + help="set domain"), + Option("--domain-guid", type="string", metavar="GUID", + help="set domainguid (otherwise random)"), + Option("--domain-sid", type="string", metavar="SID", + help="set domainsid (otherwise random)"), + Option("--ntds-guid", type="string", metavar="GUID", + help="set NTDS object GUID (otherwise random)"), + Option("--invocationid", type="string", metavar="GUID", + help="set invocationid (otherwise random)"), + Option("--host-name", type="string", metavar="HOSTNAME", + help="set hostname"), + Option("--host-ip", type="string", metavar="IPADDRESS", + help="set IPv4 ipaddress"), + Option("--host-ip6", type="string", metavar="IP6ADDRESS", + help="set IPv6 ipaddress"), + Option("--adminpass", type="string", metavar="PASSWORD", + help="choose admin password (otherwise random)"), + Option("--krbtgtpass", type="string", metavar="PASSWORD", + help="choose krbtgt password (otherwise random)"), + Option("--machinepass", type="string", metavar="PASSWORD", + help="choose machine password (otherwise random)"), + Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND", + choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"], + help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), " + "BIND9_FLATFILE uses bind9 text database to store zone information, " + "BIND9_DLZ uses samba4 AD to store zone information, " + "NONE skips the DNS setup entirely (not recommended)", + default="SAMBA_INTERNAL"), + Option("--dnspass", type="string", metavar="PASSWORD", + help="choose dns password (otherwise random)"), + Option("--ldapadminpass", type="string", metavar="PASSWORD", + help="choose password to set between Samba and it's LDAP backend (otherwise random)"), + Option("--root", type="string", metavar="USERNAME", + help="choose 'root' unix username"), + Option("--nobody", type="string", metavar="USERNAME", + help="choose 'nobody' user"), + Option("--users", type="string", metavar="GROUPNAME", + help="choose 'users' group"), + Option("--quiet", help="Be quiet", action="store_true"), + Option("--blank", action="store_true", + help="do not add users or groups, just the structure"), + Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE", + help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE", + choices=["fedora-ds", "openldap"]), + Option("--server-role", type="choice", metavar="ROLE", + choices=["domain controller", "dc", "member server", "member", "standalone"], + help="The server role (domain controller | dc | member server | member | standalone). Default is dc.", + default="domain controller"), + Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL", + choices=["2000", "2003", "2008", "2008_R2"], + help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2003 Native.", + default="2003"), + Option("--next-rid", type="int", metavar="NEXTRID", default=1000, + help="The initial nextRid value (only needed for upgrades). Default is 1000."), + Option("--partitions-only", + help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"), + Option("--targetdir", type="string", metavar="DIR", + help="Set target directory"), + Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER", + help="List of LDAP-URLS [ ldap://<FQHN>:<PORT>/ (where <PORT> has to be different than 389!) ] separated with comma (\",\") for use with OpenLDAP-MMR (Multi-Master-Replication), e.g.: \"ldap://s4dc1:9000,ldap://s4dc2:9000\""), + Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"], help="Define if we should use the native fs capabilities or a tdb file for storing attributes likes ntacl, auto tries to make an inteligent guess based on the user rights and system capabilities", default="auto"), + Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"), + Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"), + ] + takes_args = [] + + def run(self, sambaopts=None, credopts=None, versionopts=None, + interactive=None, + domain=None, + domain_guid=None, + domain_sid=None, + ntds_guid=None, + invocationid=None, + host_name=None, + host_ip=None, + host_ip6=None, + adminpass=None, + krbtgtpass=None, + machinepass=None, + dns_backend=None, + dns_forwarder=None, + dnspass=None, + ldapadminpass=None, + root=None, + nobody=None, + users=None, + quiet=None, + blank=None, + ldap_backend_type=None, + server_role=None, + function_level=None, + next_rid=None, + partitions_only=None, + targetdir=None, + ol_mmr_urls=None, + use_xattrs=None, + use_ntvfs=None, + use_rfc2307=None): + + self.logger = self.get_logger("provision") + if quiet: + self.logger.setLevel(logging.WARNING) + else: + self.logger.setLevel(logging.INFO) + + lp = sambaopts.get_loadparm() + smbconf = lp.configfile + + creds = credopts.get_credentials(lp) + + creds.set_kerberos_state(DONT_USE_KERBEROS) + + if dns_forwarder is not None: + suggested_forwarder = dns_forwarder + else: + suggested_forwarder = self._get_nameserver_ip() + if suggested_forwarder is None: + suggested_forwarder = "none" + + if len(self.raw_argv) == 1: + interactive = True + + if interactive: + from getpass import getpass + import socket + + def ask(prompt, default=None): + if default is not None: + print "%s [%s]: " % (prompt, default), + else: + print "%s: " % (prompt,), + return sys.stdin.readline().rstrip("\n") or default + + try: + default = socket.getfqdn().split(".", 1)[1].upper() + except IndexError: + default = None + realm = ask("Realm", default) + if realm in (None, ""): + raise CommandError("No realm set!") + + try: + default = realm.split(".")[0] + except IndexError: + default = None + domain = ask("Domain", default) + if domain is None: + raise CommandError("No domain set!") + + server_role = ask("Server Role (dc, member, standalone)", "dc") + + dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL") + if dns_backend in (None, ''): + raise CommandError("No DNS backend set!") + + if dns_backend == "SAMBA_INTERNAL": + dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder) + if dns_forwarder.lower() in (None, 'none'): + suggested_forwarder = None + dns_forwarder = None + + while True: + adminpassplain = getpass("Administrator password: ") + if not adminpassplain: + self.errf.write("Invalid administrator password.\n") + else: + adminpassverify = getpass("Retype password: ") + if not adminpassplain == adminpassverify: + self.errf.write("Sorry, passwords do not match.\n") + else: + adminpass = adminpassplain + break + + else: + realm = sambaopts._lp.get('realm') + if realm is None: + raise CommandError("No realm set!") + if domain is None: + raise CommandError("No domain set!") + + if not adminpass: + self.logger.info("Administrator password will be set randomly!") + + if function_level == "2000": + dom_for_fun_level = DS_DOMAIN_FUNCTION_2000 + elif function_level == "2003": + dom_for_fun_level = DS_DOMAIN_FUNCTION_2003 + elif function_level == "2008": + dom_for_fun_level = DS_DOMAIN_FUNCTION_2008 + elif function_level == "2008_R2": + dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2 + + if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None: + dns_forwarder = suggested_forwarder + + samdb_fill = FILL_FULL + if blank: + samdb_fill = FILL_NT4SYNC + elif partitions_only: + samdb_fill = FILL_DRS + + if targetdir is not None: + if not os.path.isdir(targetdir): + os.mkdir(targetdir) + + eadb = True + + if use_xattrs == "yes": + eadb = False + elif use_xattrs == "auto" and not lp.get("posix:eadb"): + if targetdir: + file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir)) + else: + file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir")))) + try: + try: + samba.ntacls.setntacl(lp, file.name, + "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native") + eadb = False + except Exception: + self.logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. ") + finally: + file.close() + + if eadb: + self.logger.info("not using extended attributes to store ACLs and other metadata. If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.") + + session = system_session() + try: + result = provision(self.logger, + session, creds, smbconf=smbconf, targetdir=targetdir, + samdb_fill=samdb_fill, realm=realm, domain=domain, + domainguid=domain_guid, domainsid=domain_sid, + hostname=host_name, + hostip=host_ip, hostip6=host_ip6, + ntdsguid=ntds_guid, + invocationid=invocationid, adminpass=adminpass, + krbtgtpass=krbtgtpass, machinepass=machinepass, + dns_backend=dns_backend, dns_forwarder=dns_forwarder, + dnspass=dnspass, root=root, nobody=nobody, + users=users, + serverrole=server_role, dom_for_fun_level=dom_for_fun_level, + backend_type=ldap_backend_type, + ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, + useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs, + use_rfc2307=use_rfc2307, skip_sysvolacl=False) + except ProvisioningError, e: + raise CommandError("Provision failed", e) + + result.report_logger(self.logger) + + def _get_nameserver_ip(self): + """Grab the nameserver IP address from /etc/resolv.conf.""" + from os import path + RESOLV_CONF="/etc/resolv.conf" + + if not path.isfile(RESOLV_CONF): + self.logger.warning("Failed to locate %s" % RESOLV_CONF) + return None + + handle = None + try: + handle = open(RESOLV_CONF, 'r') + for line in handle: + if not line.startswith('nameserver'): + continue + # we want the last non-space continuous string of the line + return line.strip().split()[-1] + finally: + if handle is not None: + handle.close() + + self.logger.warning("No nameserver found in %s" % RESOLV_CONF) + + +class cmd_domain_dcpromo(Command): + """Promote an existing domain member or NT4 PDC to an AD DC.""" + + synopsis = "%prog <dnsdomain> [DC|RODC] [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + 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("--domain-critical-only", + help="only replicate critical domain objects", + action="store_true"), + Option("--machinepass", type=str, metavar="PASSWORD", + help="choose machine password (otherwise random)"), + Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)", + action="store_true"), + Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND", + choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"], + help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), " + "BIND9_DLZ uses samba4 AD to store zone information, " + "NONE skips the DNS setup entirely (this DC will not be a DNS server)", + default="SAMBA_INTERNAL") + ] + + takes_args = ["domain", "role?"] + + def run(self, domain, role=None, sambaopts=None, credopts=None, + versionopts=None, server=None, site=None, targetdir=None, + domain_critical_only=False, parent_domain=None, machinepass=None, + use_ntvfs=False, dns_backend=None): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + net = Net(creds, lp, server=credopts.ipaddress) + + if site is None: + site = "Default-First-Site-Name" + + netbios_name = lp.get("netbios name") + + if not role is None: + role = role.upper() + + if role == "DC": + join_DC(server=server, creds=creds, lp=lp, domain=domain, + site=site, netbios_name=netbios_name, targetdir=targetdir, + domain_critical_only=domain_critical_only, + machinepass=machinepass, use_ntvfs=use_ntvfs, + dns_backend=dns_backend, + promote_existing=True) + elif role == "RODC": + join_RODC(server=server, creds=creds, lp=lp, domain=domain, + site=site, netbios_name=netbios_name, targetdir=targetdir, + domain_critical_only=domain_critical_only, + machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend, + promote_existing=True) + else: + raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role) + + +class cmd_domain_join(Command): + """Join domain as either member or backup domain controller.""" + + synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + 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"), + Option("--machinepass", type=str, metavar="PASSWORD", + help="choose machine password (otherwise random)"), + Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)", + action="store_true"), + Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND", + choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"], + help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), " + "BIND9_DLZ uses samba4 AD to store zone information, " + "NONE skips the DNS setup entirely (this DC will not be a DNS server)", + default="SAMBA_INTERNAL") + ] + + takes_args = ["domain", "role?"] + + def run(self, domain, role=None, sambaopts=None, credopts=None, + versionopts=None, server=None, site=None, targetdir=None, + domain_critical_only=False, parent_domain=None, machinepass=None, + use_ntvfs=False, dns_backend=None): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + net = Net(creds, lp, server=credopts.ipaddress) + + if site is None: + site = "Default-First-Site-Name" + + netbios_name = lp.get("netbios name") + + if not role is None: + role = role.upper() + + if role is None or role == "MEMBER": + (join_password, sid, domain_name) = net.join_member( + domain, netbios_name, LIBNET_JOIN_AUTOMATIC, + machinepass=machinepass) + + self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid)) + elif role == "DC": + join_DC(server=server, creds=creds, lp=lp, domain=domain, + site=site, netbios_name=netbios_name, targetdir=targetdir, + domain_critical_only=domain_critical_only, + machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend) + elif role == "RODC": + join_RODC(server=server, creds=creds, lp=lp, domain=domain, + site=site, netbios_name=netbios_name, targetdir=targetdir, + domain_critical_only=domain_critical_only, + machinepass=machinepass, use_ntvfs=use_ntvfs, + dns_backend=dns_backend) + 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, machinepass=machinepass, + use_ntvfs=use_ntvfs, dns_backend=dns_backend) + else: + raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role) + + +class cmd_domain_demote(Command): + """Demote ourselves from the role of Domain Controller.""" + + synopsis = "%prog [options]" + + takes_options = [ + Option("--server", help="DC to force replication before demote", type=str), + Option("--targetdir", help="where provision is stored", type=str), + ] + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + def run(self, sambaopts=None, credopts=None, + versionopts=None, server=None, targetdir=None): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + net = Net(creds, lp, server=credopts.ipaddress) + + netbios_name = lp.get("netbios name") + samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp) + if not server: + res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"]) + if (len(res) == 0): + raise CommandError("Unable to search for servers") + + if (len(res) == 1): + raise CommandError("You are the latest server in the domain") + + server = None + for e in res: + if str(e["name"]).lower() != netbios_name.lower(): + server = e["dnsHostName"] + break + + ntds_guid = samdb.get_ntds_GUID() + msg = samdb.search(base=str(samdb.get_config_basedn()), + scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid, + attrs=['options']) + if len(msg) == 0 or "options" not in msg[0]: + raise CommandError("Failed to find options on %s" % ntds_guid) + + ntds_dn = msg[0].dn + dsa_options = int(str(msg[0]['options'])) + + res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn), + controls=["search_options:1:2"]) + + if len(res) != 0: + raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res)) + + self.errf.write("Using %s as partner server for the demotion\n" % + server) + (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds) + + self.errf.write("Desactivating inbound replication\n") + + nmsg = ldb.Message() + nmsg.dn = msg[0].dn + + dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL + nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") + samdb.modify(nmsg) + + if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc(): + + self.errf.write("Asking partner server %s to synchronize from us\n" + % server) + for part in (samdb.get_schema_basedn(), + samdb.get_config_basedn(), + samdb.get_root_basedn()): + try: + sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP) + except drsException, e: + self.errf.write( + "Error while demoting, " + "re-enabling inbound replication\n") + dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL + nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") + samdb.modify(nmsg) + raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e) + try: + remote_samdb = SamDB(url="ldap://%s" % server, + session_info=system_session(), + credentials=creds, lp=lp) + + self.errf.write("Changing userControl and container\n") + res = remote_samdb.search(base=str(remote_samdb.get_root_basedn()), + expression="(&(objectClass=user)(sAMAccountName=%s$))" % + netbios_name.upper(), + attrs=["userAccountControl"]) + dc_dn = res[0].dn + uac = int(str(res[0]["userAccountControl"])) + + except Exception, e: + self.errf.write( + "Error while demoting, re-enabling inbound replication\n") + dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL + nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") + samdb.modify(nmsg) + raise CommandError("Error while changing account control", e) + + if (len(res) != 1): + self.errf.write( + "Error while demoting, re-enabling inbound replication") + dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL + nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") + samdb.modify(nmsg) + raise CommandError("Unable to find object with samaccountName = %s$" + " in the remote dc" % netbios_name.upper()) + + olduac = uac + + uac ^= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION) + uac |= UF_WORKSTATION_TRUST_ACCOUNT + + msg = ldb.Message() + msg.dn = dc_dn + + msg["userAccountControl"] = ldb.MessageElement("%d" % uac, + ldb.FLAG_MOD_REPLACE, + "userAccountControl") + try: + remote_samdb.modify(msg) + except Exception, e: + self.errf.write( + "Error while demoting, re-enabling inbound replication") + dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL + nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") + samdb.modify(nmsg) + + raise CommandError("Error while changing account control", e) + + parent = msg.dn.parent() + rdn = str(res[0].dn) + rdn = string.replace(rdn, ",%s" % str(parent), "") + # Let's move to the Computer container + i = 0 + newrdn = rdn + + computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.get_root_basedn())) + res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL) + + if (len(res) != 0): + res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i), + scope=ldb.SCOPE_ONELEVEL) + while(len(res) != 0 and i < 100): + i = i + 1 + res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i), + scope=ldb.SCOPE_ONELEVEL) + + if i == 100: + self.errf.write( + "Error while demoting, re-enabling inbound replication\n") + dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL + nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") + samdb.modify(nmsg) + + msg = ldb.Message() + msg.dn = dc_dn + + msg["userAccountControl"] = ldb.MessageElement("%d" % uac, + ldb.FLAG_MOD_REPLACE, + "userAccountControl") + + remote_samdb.modify(msg) + + raise CommandError("Unable to find a slot for renaming %s," + " all names from %s-1 to %s-%d seemed used" % + (str(dc_dn), rdn, rdn, i - 9)) + + newrdn = "%s-%d" % (rdn, i) + + try: + newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn))) + remote_samdb.rename(dc_dn, newdn) + except Exception, e: + self.errf.write( + "Error while demoting, re-enabling inbound replication\n") + dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL + nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") + samdb.modify(nmsg) + + msg = ldb.Message() + msg.dn = dc_dn + + msg["userAccountControl"] = ldb.MessageElement("%d" % uac, + ldb.FLAG_MOD_REPLACE, + "userAccountControl") + + remote_samdb.modify(msg) + raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e) + + + server_dsa_dn = samdb.get_serverName() + domain = remote_samdb.get_root_basedn() + + try: + sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain) + except drsException, e: + self.errf.write( + "Error while demoting, re-enabling inbound replication\n") + dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL + nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") + samdb.modify(nmsg) + + msg = ldb.Message() + msg.dn = newdn + + msg["userAccountControl"] = ldb.MessageElement("%d" % uac, + ldb.FLAG_MOD_REPLACE, + "userAccountControl") + print str(dc_dn) + remote_samdb.modify(msg) + remote_samdb.rename(newdn, dc_dn) + raise CommandError("Error while sending a removeDsServer", e) + + for s in ("CN=Entreprise,CN=Microsoft System Volumes,CN=System,CN=Configuration", + "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp.get("realm"), + "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"): + try: + remote_samdb.delete(ldb.Dn(remote_samdb, + "%s,%s,%s" % (str(rdn), s, str(remote_samdb.get_root_basedn())))) + except ldb.LdbError, l: + pass + + for s in ("CN=Entreprise,CN=NTFRS Subscriptions", + "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"), + "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions", + "CN=NTFRS Subscriptions"): + try: + remote_samdb.delete(ldb.Dn(remote_samdb, + "%s,%s" % (s, str(newdn)))) + except ldb.LdbError, l: + pass + + self.errf.write("Demote successfull\n") + + +class cmd_domain_level(Command): + """Raise domain and forest function levels.""" + + synopsis = "%prog (show|raise <options>) [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", type=str, + metavar="URL", dest="H"), + Option("--quiet", help="Be quiet", action="store_true"), + Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2"], + help="The forest function level (2003 | 2008 | 2008_R2)"), + Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2"], + help="The domain function level (2003 | 2008 | 2008_R2)") + ] + + takes_args = ["subcommand"] + + def run(self, subcommand, H=None, forest_level=None, domain_level=None, + quiet=False, credopts=None, sambaopts=None, versionopts=None): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp, fallback_machine=True) + + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + domain_dn = samdb.domain_dn() + + res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(), + scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"]) + assert len(res_forest) == 1 + + res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE, + attrs=["msDS-Behavior-Version", "nTMixedDomain"]) + assert len(res_domain) == 1 + + res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(), + scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)", + attrs=["msDS-Behavior-Version"]) + assert len(res_dc_s) >= 1 + + try: + level_forest = int(res_forest[0]["msDS-Behavior-Version"][0]) + level_domain = int(res_domain[0]["msDS-Behavior-Version"][0]) + level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0]) + + min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value + for msg in res_dc_s: + if int(msg["msDS-Behavior-Version"][0]) < min_level_dc: + min_level_dc = int(msg["msDS-Behavior-Version"][0]) + + if level_forest < 0 or level_domain < 0: + raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!") + if min_level_dc < 0: + raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!") + if level_forest > level_domain: + raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!") + if level_domain > min_level_dc: + raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!") + + except KeyError: + raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!") + + if subcommand == "show": + self.message("Domain and forest function level for domain '%s'" % domain_dn) + if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: + self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!") + if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: + self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!") + if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: + self.message("\nATTENTION: You run SAMBA 4 on a lowest function level of a DC lower than Windows 2003. This isn't supported! Please step-up or upgrade the concerning DC(s)!") + + self.message("") + + if level_forest == DS_DOMAIN_FUNCTION_2000: + outstr = "2000" + elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED: + outstr = "2003 with mixed domains/interim (NT4 DC support)" + elif level_forest == DS_DOMAIN_FUNCTION_2003: + outstr = "2003" + elif level_forest == DS_DOMAIN_FUNCTION_2008: + outstr = "2008" + elif level_forest == DS_DOMAIN_FUNCTION_2008_R2: + outstr = "2008 R2" + else: + outstr = "higher than 2008 R2" + self.message("Forest function level: (Windows) " + outstr) + + if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: + outstr = "2000 mixed (NT4 DC support)" + elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0: + outstr = "2000" + elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED: + outstr = "2003 with mixed domains/interim (NT4 DC support)" + elif level_domain == DS_DOMAIN_FUNCTION_2003: + outstr = "2003" + elif level_domain == DS_DOMAIN_FUNCTION_2008: + outstr = "2008" + elif level_domain == DS_DOMAIN_FUNCTION_2008_R2: + outstr = "2008 R2" + else: + outstr = "higher than 2008 R2" + self.message("Domain function level: (Windows) " + outstr) + + if min_level_dc == DS_DOMAIN_FUNCTION_2000: + outstr = "2000" + elif min_level_dc == DS_DOMAIN_FUNCTION_2003: + outstr = "2003" + elif min_level_dc == DS_DOMAIN_FUNCTION_2008: + outstr = "2008" + elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2: + outstr = "2008 R2" + else: + outstr = "higher than 2008 R2" + self.message("Lowest function level of a DC: (Windows) " + outstr) + + elif subcommand == "raise": + msgs = [] + + if domain_level is not None: + if domain_level == "2003": + new_level_domain = DS_DOMAIN_FUNCTION_2003 + elif domain_level == "2008": + new_level_domain = DS_DOMAIN_FUNCTION_2008 + elif domain_level == "2008_R2": + new_level_domain = DS_DOMAIN_FUNCTION_2008_R2 + + if new_level_domain <= level_domain and level_domain_mixed == 0: + raise CommandError("Domain function level can't be smaller than or equal to the actual one!") + + if new_level_domain > min_level_dc: + raise CommandError("Domain function level can't be higher than the lowest function level of a DC!") + + # Deactivate mixed/interim domain support + if level_domain_mixed != 0: + # Directly on the base DN + m = ldb.Message() + m.dn = ldb.Dn(samdb, domain_dn) + m["nTMixedDomain"] = ldb.MessageElement("0", + ldb.FLAG_MOD_REPLACE, "nTMixedDomain") + samdb.modify(m) + # Under partitions + m = ldb.Message() + m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn()) + m["nTMixedDomain"] = ldb.MessageElement("0", + ldb.FLAG_MOD_REPLACE, "nTMixedDomain") + try: + samdb.modify(m) + except ldb.LdbError, (enum, emsg): + if enum != ldb.ERR_UNWILLING_TO_PERFORM: + raise + + # Directly on the base DN + m = ldb.Message() + m.dn = ldb.Dn(samdb, domain_dn) + m["msDS-Behavior-Version"]= ldb.MessageElement( + str(new_level_domain), ldb.FLAG_MOD_REPLACE, + "msDS-Behavior-Version") + samdb.modify(m) + # Under partitions + m = ldb.Message() + m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + + ",CN=Partitions,%s" % samdb.get_config_basedn()) + m["msDS-Behavior-Version"]= ldb.MessageElement( + str(new_level_domain), ldb.FLAG_MOD_REPLACE, + "msDS-Behavior-Version") + try: + samdb.modify(m) + except ldb.LdbError, (enum, emsg): + if enum != ldb.ERR_UNWILLING_TO_PERFORM: + raise + + level_domain = new_level_domain + msgs.append("Domain function level changed!") + + if forest_level is not None: + if forest_level == "2003": + new_level_forest = DS_DOMAIN_FUNCTION_2003 + elif forest_level == "2008": + new_level_forest = DS_DOMAIN_FUNCTION_2008 + elif forest_level == "2008_R2": + new_level_forest = DS_DOMAIN_FUNCTION_2008_R2 + if new_level_forest <= level_forest: + raise CommandError("Forest function level can't be smaller than or equal to the actual one!") + if new_level_forest > level_domain: + raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!") + m = ldb.Message() + m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn()) + m["msDS-Behavior-Version"]= ldb.MessageElement( + str(new_level_forest), ldb.FLAG_MOD_REPLACE, + "msDS-Behavior-Version") + samdb.modify(m) + msgs.append("Forest function level changed!") + msgs.append("All changes applied successfully!") + self.message("\n".join(msgs)) + else: + raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand) + + +class cmd_domain_passwordsettings(Command): + """Set password settings. + + Password complexity, history length, minimum password length, the minimum + and maximum password age) on a Samba4 server. + """ + + synopsis = "%prog (show|set <options>) [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", type=str, + metavar="URL", dest="H"), + Option("--quiet", help="Be quiet", action="store_true"), + Option("--complexity", type="choice", choices=["on","off","default"], + help="The password complexity (on | off | default). Default is 'on'"), + Option("--store-plaintext", type="choice", choices=["on","off","default"], + help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"), + Option("--history-length", + help="The password history length (<integer> | default). Default is 24.", type=str), + Option("--min-pwd-length", + help="The minimum password length (<integer> | default). Default is 7.", type=str), + Option("--min-pwd-age", + help="The minimum password age (<integer in days> | default). Default is 1.", type=str), + Option("--max-pwd-age", + help="The maximum password age (<integer in days> | default). Default is 43.", type=str), + ] + + takes_args = ["subcommand"] + + def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None, + quiet=False, complexity=None, store_plaintext=None, history_length=None, + min_pwd_length=None, credopts=None, sambaopts=None, + versionopts=None): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + domain_dn = samdb.domain_dn() + res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE, + attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength", + "minPwdAge", "maxPwdAge"]) + assert(len(res) == 1) + try: + pwd_props = int(res[0]["pwdProperties"][0]) + pwd_hist_len = int(res[0]["pwdHistoryLength"][0]) + cur_min_pwd_len = int(res[0]["minPwdLength"][0]) + # ticks -> days + cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24)) + if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000: + cur_max_pwd_age = 0 + else: + cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24)) + except Exception, e: + raise CommandError("Could not retrieve password properties!", e) + + if subcommand == "show": + self.message("Password informations for domain '%s'" % domain_dn) + self.message("") + if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0: + self.message("Password complexity: on") + else: + self.message("Password complexity: off") + if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0: + self.message("Store plaintext passwords: on") + else: + self.message("Store plaintext passwords: off") + self.message("Password history length: %d" % pwd_hist_len) + self.message("Minimum password length: %d" % cur_min_pwd_len) + self.message("Minimum password age (days): %d" % cur_min_pwd_age) + self.message("Maximum password age (days): %d" % cur_max_pwd_age) + elif subcommand == "set": + msgs = [] + m = ldb.Message() + m.dn = ldb.Dn(samdb, domain_dn) + + if complexity is not None: + if complexity == "on" or complexity == "default": + pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX + msgs.append("Password complexity activated!") + elif complexity == "off": + pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX) + msgs.append("Password complexity deactivated!") + + if store_plaintext is not None: + if store_plaintext == "on" or store_plaintext == "default": + pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT + msgs.append("Plaintext password storage for changed passwords activated!") + elif store_plaintext == "off": + pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT) + msgs.append("Plaintext password storage for changed passwords deactivated!") + + if complexity is not None or store_plaintext is not None: + m["pwdProperties"] = ldb.MessageElement(str(pwd_props), + ldb.FLAG_MOD_REPLACE, "pwdProperties") + + if history_length is not None: + if history_length == "default": + pwd_hist_len = 24 + else: + pwd_hist_len = int(history_length) + + if pwd_hist_len < 0 or pwd_hist_len > 24: + raise CommandError("Password history length must be in the range of 0 to 24!") + + m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len), + ldb.FLAG_MOD_REPLACE, "pwdHistoryLength") + msgs.append("Password history length changed!") + + if min_pwd_length is not None: + if min_pwd_length == "default": + min_pwd_len = 7 + else: + min_pwd_len = int(min_pwd_length) + + if min_pwd_len < 0 or min_pwd_len > 14: + raise CommandError("Minimum password length must be in the range of 0 to 14!") + + m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len), + ldb.FLAG_MOD_REPLACE, "minPwdLength") + msgs.append("Minimum password length changed!") + + if min_pwd_age is not None: + if min_pwd_age == "default": + min_pwd_age = 1 + else: + min_pwd_age = int(min_pwd_age) + + if min_pwd_age < 0 or min_pwd_age > 998: + raise CommandError("Minimum password age must be in the range of 0 to 998!") + + # days -> ticks + min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7)) + + m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks), + ldb.FLAG_MOD_REPLACE, "minPwdAge") + msgs.append("Minimum password age changed!") + + if max_pwd_age is not None: + if max_pwd_age == "default": + max_pwd_age = 43 + else: + max_pwd_age = int(max_pwd_age) + + if max_pwd_age < 0 or max_pwd_age > 999: + raise CommandError("Maximum password age must be in the range of 0 to 999!") + + # days -> ticks + if max_pwd_age == 0: + max_pwd_age_ticks = -0x8000000000000000 + else: + max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7)) + + m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks), + ldb.FLAG_MOD_REPLACE, "maxPwdAge") + msgs.append("Maximum password age changed!") + + if max_pwd_age > 0 and min_pwd_age >= max_pwd_age: + raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age)) + + if len(m) == 0: + raise CommandError("You must specify at least one option to set. Try --help") + samdb.modify(m) + msgs.append("All changes applied successfully!") + self.message("\n".join(msgs)) + else: + raise CommandError("Wrong argument '%s'!" % subcommand) + + +class cmd_domain_classicupgrade(Command): + """Upgrade from Samba classic (NT4-like) database to Samba AD DC database. + + Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or + the testparm utility from your classic installation (with --testparm). + """ + + synopsis = "%prog [options] <classic_smb_conf>" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions + } + + takes_options = [ + Option("--dbdir", type="string", metavar="DIR", + help="Path to samba classic DC database directory"), + Option("--testparm", type="string", metavar="PATH", + help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"), + Option("--targetdir", type="string", metavar="DIR", + help="Path prefix where the new Samba 4.0 AD domain should be initialised"), + Option("--quiet", help="Be quiet", action="store_true"), + Option("--verbose", help="Be verbose", action="store_true"), + Option("--use-xattrs", type="choice", choices=["yes","no","auto"], metavar="[yes|no|auto]", + help="Define if we should use the native fs capabilities or a tdb file for storing attributes likes ntacl, auto tries to make an inteligent guess based on the user rights and system capabilities", default="auto"), + Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)", + action="store_true"), + Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND", + choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"], + help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), " + "BIND9_FLATFILE uses bind9 text database to store zone information, " + "BIND9_DLZ uses samba4 AD to store zone information, " + "NONE skips the DNS setup entirely (this DC will not be a DNS server)", + default="SAMBA_INTERNAL") + ] + + takes_args = ["smbconf"] + + def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None, + quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None, + dns_backend=None, use_ntvfs=False): + + if not os.path.exists(smbconf): + raise CommandError("File %s does not exist" % smbconf) + + if testparm and not os.path.exists(testparm): + raise CommandError("Testparm utility %s does not exist" % testparm) + + if dbdir and not os.path.exists(dbdir): + raise CommandError("Directory %s does not exist" % dbdir) + + if not dbdir and not testparm: + raise CommandError("Please specify either dbdir or testparm") + + logger = self.get_logger() + if verbose: + logger.setLevel(logging.DEBUG) + elif quiet: + logger.setLevel(logging.WARNING) + else: + logger.setLevel(logging.INFO) + + if dbdir and testparm: + logger.warning("both dbdir and testparm specified, ignoring dbdir.") + dbdir = None + + lp = sambaopts.get_loadparm() + + s3conf = s3param.get_context() + + if sambaopts.realm: + s3conf.set("realm", sambaopts.realm) + + if targetdir is not None: + if not os.path.isdir(targetdir): + os.mkdir(targetdir) + + eadb = True + if use_xattrs == "yes": + eadb = False + elif use_xattrs == "auto" and not s3conf.get("posix:eadb"): + if targetdir: + tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir)) + else: + tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir")))) + try: + try: + samba.ntacls.setntacl(lp, tmpfile.name, + "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native") + eadb = False + except Exception: + # FIXME: Don't catch all exceptions here + logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. " + "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.") + finally: + tmpfile.close() + + # Set correct default values from dbdir or testparm + paths = {} + if dbdir: + paths["state directory"] = dbdir + paths["private dir"] = dbdir + paths["lock directory"] = dbdir + paths["smb passwd file"] = dbdir + "/smbpasswd" + else: + paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory") + paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir") + paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file") + paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory") + # "testparm" from Samba 3 < 3.4.x is not aware of the parameter + # "state directory", instead make use of "lock directory" + if len(paths["state directory"]) == 0: + paths["state directory"] = paths["lock directory"] + + for p in paths: + s3conf.set(p, paths[p]) + + # load smb.conf parameters + logger.info("Reading smb.conf") + s3conf.load(smbconf) + samba3 = Samba3(smbconf, s3conf) + + logger.info("Provisioning") + upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(), + useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs) + + +class cmd_domain_samba3upgrade(cmd_domain_classicupgrade): + __doc__ = cmd_domain_classicupgrade.__doc__ + + # This command is present for backwards compatibility only, + # and should not be shown. + + hidden = True + + +class cmd_domain(SuperCommand): + """Domain management.""" + + subcommands = {} + subcommands["demote"] = cmd_domain_demote() + if cmd_domain_export_keytab is not None: + subcommands["exportkeytab"] = cmd_domain_export_keytab() + subcommands["info"] = cmd_domain_info() + subcommands["provision"] = cmd_domain_provision() + subcommands["join"] = cmd_domain_join() + subcommands["dcpromo"] = cmd_domain_dcpromo() + subcommands["level"] = cmd_domain_level() + subcommands["passwordsettings"] = cmd_domain_passwordsettings() + subcommands["classicupgrade"] = cmd_domain_classicupgrade() + subcommands["samba3upgrade"] = cmd_domain_samba3upgrade() |