diff options
| author | Matthieu Patou <mat@matws.net> | 2011-12-04 23:00:57 +0100 | 
|---|---|---|
| committer | Matthieu Patou <mat@matws.net> | 2011-12-05 18:23:07 +0100 | 
| commit | f44e1a753a9dc489ded1d2e49c83c865124462f1 (patch) | |
| tree | f2f9f2f956eb6e7b6b1fde9553906c57f4a8d37f /source4/scripting/python | |
| parent | fc42b0ab4135295fd23af5ab0cc0b410655bc376 (diff) | |
| download | samba-f44e1a753a9dc489ded1d2e49c83c865124462f1.tar.gz samba-f44e1a753a9dc489ded1d2e49c83c865124462f1.tar.bz2 samba-f44e1a753a9dc489ded1d2e49c83c865124462f1.zip  | |
samba-tool: add a function to cleanly demote a DC
samba-tool domain demote allow the local DC to properly demote against
Microsoft and Samba DC.
Diffstat (limited to 'source4/scripting/python')
| -rw-r--r-- | source4/scripting/python/samba/drs_utils.py | 22 | ||||
| -rw-r--r-- | source4/scripting/python/samba/netcmd/domain.py | 237 | 
2 files changed, 257 insertions, 2 deletions
diff --git a/source4/scripting/python/samba/drs_utils.py b/source4/scripting/python/samba/drs_utils.py index b712932b59..9aacfbc186 100644 --- a/source4/scripting/python/samba/drs_utils.py +++ b/source4/scripting/python/samba/drs_utils.py @@ -53,7 +53,7 @@ def drsuapi_connect(server, lp, creds):          drsuapiBind = drsuapi.drsuapi(binding_string, lp, creds)          (drsuapiHandle, bindSupportedExtensions) = drs_DsBind(drsuapiBind)      except Exception, e: -        raise drsException("DRS connection to %s failed" % server, e) +        raise drsException("DRS connection to %s failed: %s" % (server, e))      return (drsuapiBind, drsuapiHandle, bindSupportedExtensions) @@ -81,6 +81,26 @@ def sendDsReplicaSync(drsuapiBind, drsuapi_handle, source_dsa_guid, naming_conte      except Exception, estr:          raise drsException("DsReplicaSync failed %s" % estr) +def sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain): +    """ +    :param drsuapiBind: a drsuapi Bind object +    :param drsuapi_handle: a drsuapi hanle on the drsuapi connection +    :param server_dsa_dn: a DN object of the server's dsa that we want to demote +    :param domain: a DN object of the server's domain +    :raise drsException: if any error occur while sending and receiving the reply +                         for the DsRemoveDSServer +    """ + +    try: +        req1 = drsuapi.DsRemoveDSServerRequest1() +        req1.server_dn = str(server_dsa_dn) +        req1.domain_dn = str(domain) +        req1.commit = 1 + +        drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1) +    except Exception, estr: +        raise drsException("DsRemoveDSServer failed %s" % estr) +  def drs_DsBind(drs):      '''make a DsBind call, returning the binding handle'''      bind_info = drsuapi.DsBindInfoCtr() diff --git a/source4/scripting/python/samba/netcmd/domain.py b/source4/scripting/python/samba/netcmd/domain.py index bd73b6cab1..a23785f945 100644 --- a/source4/scripting/python/samba/netcmd/domain.py +++ b/source4/scripting/python/samba/netcmd/domain.py @@ -6,6 +6,7 @@  # Copyright Andrew Kroeger 2009  # Copyright Jelmer Vernooij 2009  # Copyright Giampaolo Lauria 2011 +# Copyright Matthieu Patou <mat@matws.net> 2011  #  # 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 @@ -25,15 +26,16 @@  import samba.getopt as options  import ldb +import string  import os  import tempfile  import logging -from samba import Ldb  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, @@ -45,6 +47,10 @@ 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, @@ -52,6 +58,11 @@ from samba.dsdb import (      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      )  def get_testparm_var(testparm, smbconf, varname): @@ -169,6 +180,229 @@ class cmd_domain_join(Command): +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), +        ] + + +    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 + +        print "Using %s as partner server for the demotion" % server +        ntds_guid = samdb.get_ntds_GUID() +        (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds) + + +        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) + +        dsa_options = int(str(msg[0]['options'])) + + +        print "Desactivating inbound replication" + +        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(): + +            print "Asking partner server %s to synchronize from us" % 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: +                    print "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 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) + +            print "Changing userControl and container" +            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: +                print "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) + +        if (len(res) != 1): +            print "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: +            print "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: +                print "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) + +                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: +            print "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) + +            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: +            print "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) + +            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 + +        print "Demote successfull" + + +  class cmd_domain_level(Command):      """Raises domain and forest function levels""" @@ -650,6 +884,7 @@ class cmd_domain(SuperCommand):      """Domain management"""      subcommands = {} +    subcommands["demote"] = cmd_domain_demote()      subcommands["exportkeytab"] = cmd_domain_export_keytab()      subcommands["info"] = cmd_domain_info()      subcommands["join"] = cmd_domain_join()  | 
